Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYann Leboulanger <asterix@lagaule.org>2010-01-19 18:37:14 +0300
committerYann Leboulanger <asterix@lagaule.org>2010-01-19 18:37:14 +0300
commit3a76966c1462bcd65cee534a06dad4c9fc7cc494 (patch)
treeb9b4b257e02f9db2d209ec42ea57b481f1239ef5 /src
parentb38249c4066cf883ceb347b15410d32bd7e3357b (diff)
parentdaf73408d7dbd5324cab1ad7ea4aaec2cb696aaa (diff)
merge from default branch
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am69
-rw-r--r--src/adhoc_commands.py105
-rw-r--r--src/advanced_configuration_window.py10
-rw-r--r--src/atom_window.py18
-rw-r--r--src/chat_control.py723
-rw-r--r--src/command_system/__init__.py2
-rw-r--r--src/command_system/dispatching.py2
-rw-r--r--src/command_system/errors.py9
-rw-r--r--src/command_system/framework.py2
-rw-r--r--src/command_system/implementation/__init__.py2
-rw-r--r--src/command_system/implementation/custom.py2
-rw-r--r--src/command_system/implementation/hosts.py2
-rw-r--r--src/command_system/implementation/middleware.py2
-rw-r--r--src/command_system/implementation/standard.py81
-rw-r--r--src/command_system/mapping.py2
-rw-r--r--src/common/GnuPG.py8
-rw-r--r--src/common/GnuPGInterface.py62
-rw-r--r--src/common/account.py8
-rw-r--r--src/common/atom.py53
-rw-r--r--src/common/caps_cache.py (renamed from src/common/caps.py)269
-rw-r--r--src/common/check_paths.py11
-rw-r--r--src/common/commands.py42
-rw-r--r--src/common/config.py22
-rw-r--r--src/common/configpaths.py14
-rw-r--r--src/common/connection.py1150
-rw-r--r--src/common/connection_handlers.py878
-rw-r--r--src/common/contacts.py349
-rw-r--r--src/common/dataforms.py132
-rw-r--r--src/common/dbus_support.py24
-rw-r--r--src/common/defs.py4
-rw-r--r--src/common/dh.py7
-rw-r--r--src/common/events.py93
-rw-r--r--src/common/exceptions.py70
-rwxr-xr-xsrc/common/fuzzyclock.py6
-rw-r--r--src/common/gajim.py87
-rw-r--r--src/common/helpers.py290
-rw-r--r--src/common/i18n.py11
-rw-r--r--src/common/idle.py5
-rw-r--r--src/common/jingle.py1117
-rw-r--r--src/common/jingle_content.py142
-rw-r--r--src/common/jingle_rtp.py353
-rw-r--r--src/common/jingle_session.py656
-rw-r--r--src/common/jingle_transport.py150
-rw-r--r--src/common/kwalletbinding.py12
-rw-r--r--src/common/latex.py5
-rw-r--r--src/common/location_listener.py137
-rw-r--r--src/common/logger.py210
-rw-r--r--src/common/logging_helpers.py6
-rw-r--r--src/common/multimedia_helpers.py101
-rw-r--r--src/common/optparser.py105
-rw-r--r--src/common/passwords.py2
-rw-r--r--src/common/pep.py827
-rw-r--r--src/common/protocol/__init__.py3
-rw-r--r--src/common/protocol/bytestream.py650
-rw-r--r--src/common/protocol/caps.py111
-rw-r--r--src/common/proxy65_manager.py63
-rw-r--r--src/common/pubsub.py32
-rw-r--r--src/common/resolver.py61
-rw-r--r--src/common/rst_xhtml_generator.py39
-rw-r--r--src/common/sleepy.py8
-rw-r--r--src/common/socks5.py190
-rw-r--r--src/common/stanza_session.py72
-rw-r--r--src/common/xmpp/auth_nb.py106
-rw-r--r--src/common/xmpp/bosh.py68
-rw-r--r--src/common/xmpp/c14n.py5
-rw-r--r--src/common/xmpp/client_nb.py152
-rw-r--r--src/common/xmpp/dispatcher_nb.py195
-rw-r--r--src/common/xmpp/features_nb.py69
-rw-r--r--src/common/xmpp/idlequeue.py172
-rw-r--r--src/common/xmpp/plugin.py31
-rw-r--r--src/common/xmpp/protocol.py1317
-rw-r--r--src/common/xmpp/proxy_connectors.py54
-rw-r--r--src/common/xmpp/roster_nb.py314
-rw-r--r--src/common/xmpp/simplexml.py569
-rw-r--r--src/common/xmpp/stringprepare.py51
-rw-r--r--src/common/xmpp/tls_nb.py50
-rw-r--r--src/common/xmpp/transports_nb.py195
-rw-r--r--src/common/zeroconf/client_zeroconf.py99
-rw-r--r--src/common/zeroconf/connection_handlers_zeroconf.py339
-rw-r--r--src/common/zeroconf/connection_zeroconf.py498
-rw-r--r--src/common/zeroconf/roster_zeroconf.py7
-rw-r--r--src/common/zeroconf/zeroconf_avahi.py4
-rw-r--r--src/common/zeroconf/zeroconf_bonjour.py22
-rw-r--r--src/config.py305
-rw-r--r--src/conversation_textview.py99
-rw-r--r--src/dataforms_widget.py64
-rw-r--r--src/dialogs.py505
-rw-r--r--src/disco.py479
-rw-r--r--src/eggtrayicon.c584
-rw-r--r--src/eggtrayicon.h80
-rw-r--r--src/features_window.py27
-rw-r--r--src/filetransfers_window.py210
-rw-r--r--src/gajim-remote.py47
-rw-r--r--src/gajim.py16
-rw-r--r--src/gajim_themes_window.py31
-rw-r--r--src/groupchat_control.py329
-rw-r--r--src/groups.py13
-rw-r--r--src/gtkexcepthook.py1
-rw-r--r--src/gtkgui_helpers.py285
-rw-r--r--src/gui_interface.py293
-rw-r--r--src/gui_menu_builder.py32
-rw-r--r--src/history_manager.py98
-rw-r--r--src/history_window.py73
-rw-r--r--src/htmltextview.py87
-rw-r--r--src/ipython_view.py147
-rw-r--r--src/message_control.py93
-rw-r--r--src/message_textview.py96
-rw-r--r--src/message_window.py108
-rw-r--r--src/negotiation.py5
-rw-r--r--src/network_manager_listener.py12
-rw-r--r--src/notify.py91
-rw-r--r--src/profile_window.py31
-rw-r--r--src/remote_control.py175
-rw-r--r--src/roster_window.py1227
-rw-r--r--src/search_window.py6
-rw-r--r--src/session.py16
-rw-r--r--src/statusicon.py382
-rw-r--r--src/systray.py459
-rw-r--r--src/tooltips.py202
-rw-r--r--src/trayicon.defs59
-rw-r--r--src/trayicon.override47
-rw-r--r--src/trayiconmodule.c43
-rw-r--r--src/vcard.py31
123 files changed, 11532 insertions, 9351 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 24e7e2234..29333e3f2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,67 +1,16 @@
-CLEANFILES = \
- trayicon.c
INCLUDES = \
$(PYTHON_INCLUDES)
export MACOSX_DEPLOYMENT_TARGET=10.4
-if BUILD_TRAYICON
-trayiconlib_LTLIBRARIES = trayicon.la
-trayiconlibdir = $(pkglibdir)
-trayicon_la_LIBADD = $(PYGTK_LIBS)
-trayicon_la_SOURCES = \
- eggtrayicon.c \
- trayiconmodule.c
-
-nodist_trayicon_la_SOURCES = \
- trayicon.c
-
-trayicon_la_LDFLAGS = \
- -module -avoid-version
-trayicon_la_CFLAGS = $(PYGTK_CFLAGS)
-
-trayicon.c:
- pygtk-codegen-2.0 --prefix trayicon \
- --register $(PYGTK_DEFS)/gdk-types.defs \
- --register $(PYGTK_DEFS)/gtk-types.defs \
- --override $(srcdir)/trayicon.override \
- $(srcdir)/trayicon.defs > $@
-endif
-gajimsrcdir = $(pkgdatadir)/src
-gajimsrc_PYTHON = $(srcdir)/*.py
-
-gajimsrc1dir = $(pkgdatadir)/src/common
-gajimsrc1_PYTHON = \
- $(srcdir)/common/*.py
-
-gajimsrc2dir = $(pkgdatadir)/src/common/xmpp
-gajimsrc2_PYTHON = \
- $(srcdir)/common/xmpp/*.py
-
-gajimsrc3dir = $(pkgdatadir)/src/common/zeroconf
-gajimsrc3_PYTHON = \
- $(srcdir)/common/zeroconf/*.py
-
-gajimsrc4dir = $(pkgdatadir)/src/command_system
-gajimsrc4_PYTHON = \
- $(srcdir)/command_system/*.py
-
-gajimsrc5dir = $(pkgdatadir)/src/command_system/implementation
-gajimsrc5_PYTHON = \
- $(srcdir)/command_system/implementation/*.py
-
-DISTCLEANFILES =
-
-EXTRA_DIST = $(gajimsrc_PYTHON) \
- $(gajimsrc1_PYTHON) \
- $(gajimsrc2_PYTHON) \
- $(gajimsrc3_PYTHON) \
- $(gajimsrc4_PYTHON) \
- $(gajimsrc5_PYTHON) \
- eggtrayicon.c \
- trayiconmodule.c \
- eggtrayicon.h \
- trayicon.defs \
- trayicon.override
+gajimsrcdir = $(gajim_srcdir)
+nobase_dist_gajimsrc_PYTHON = \
+ $(srcdir)/*.py \
+ $(srcdir)/common/*.py \
+ $(srcdir)/common/protocol/*.py \
+ $(srcdir)/common/xmpp/*.py \
+ $(srcdir)/common/zeroconf/*.py \
+ $(srcdir)/command_system/*.py \
+ $(srcdir)/command_system/implementation/*.py
dist-hook:
rm -f $(distdir)/ipython_view.py
diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py
index 9b96f0fee..01d34eee6 100644
--- a/src/adhoc_commands.py
+++ b/src/adhoc_commands.py
@@ -35,17 +35,22 @@ import dialogs
import dataforms_widget
class CommandWindow:
- '''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.
+ """
+ Class for a window for single ad-hoc commands session
- TODO: maybe put this window into MessageWindow? consider this when
- TODO: it will be possible to manage more than one window of one
- TODO: account/jid pair in MessageWindowMgr.
+ Note, that there might be more than one for one account/jid pair in one
+ moment.
- 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.'''
+ """
+ Create new window
+ """
# an account object
self.account = gajim.connections[account]
@@ -89,16 +94,29 @@ class CommandWindow:
self.xml.signal_autoconnect(self)
self.window.show_all()
-# 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_forward_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_adhoc_commands_window_delete_event(self, *anything): assert False
- def do_nothing(self, *anything): return False
+ # 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_forward_button_clicked(self, *anything):
+ assert False
+
+ def stage_execute_button_clicked(self, *anything):
+ assert False
-# widget callbacks
+ def stage_close_button_clicked(self, *anything):
+ assert False
+
+ def stage_adhoc_commands_window_delete_event(self, *anything):
+ assert False
+
+ def do_nothing(self, *anything):
+ return False
+
+ # Widget callbacks...
def on_back_button_clicked(self, *anything):
return self.stage_back_button_clicked(*anything)
@@ -123,8 +141,10 @@ class CommandWindow:
# stage 1: waiting for command list
def stage1(self):
- '''Prepare the first stage. Request command list,
- set appropriate state of widgets.'''
+ """
+ Prepare the first stage. Request command list, set appropriate state of
+ widgets
+ """
# close old stage...
self.stage_finish()
@@ -164,9 +184,12 @@ class CommandWindow:
# stage 2: choosing the command to execute
def stage2(self):
- '''Populate the command list vbox with radiobuttons
- (FIXME: if there is more commands, maybe some kind of list?),
- set widgets' state.'''
+ """
+ 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()
@@ -198,7 +221,9 @@ class CommandWindow:
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.'''
+ """
+ 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)
@@ -247,8 +272,10 @@ class CommandWindow:
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. '''
+ """
+ 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':
@@ -362,7 +389,9 @@ class CommandWindow:
# stage 4: no commands are exposed
def stage4(self):
- '''Display the message. Wait for user to close the window'''
+ """
+ Display the message. Wait for user to close the window
+ """
# close old stage
self.stage_finish()
@@ -387,7 +416,9 @@ class CommandWindow:
# 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'''
+ """
+ Display the error message. Wait for user to close the window
+ """
# FIXME: sending error to responder
# close old stage
self.stage_finish()
@@ -430,8 +461,10 @@ class CommandWindow:
# 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.'''
+ """
+ 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)
@@ -443,14 +476,18 @@ class CommandWindow:
self.pulse_id = gobject.timeout_add(80, callback)
def remove_pulsing(self):
- '''Stop pulsing, useful when especially when removing widget.'''
+ """
+ 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.'''
+ """
+ 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)
@@ -481,7 +518,9 @@ class CommandWindow:
self.account.connection.SendAndCallForResponse(query, callback)
def send_command(self, action='execute'):
- '''Send the command with data form. Wait for reply.'''
+ """
+ Send the command with data form. Wait for reply
+ """
# create the stanza
assert isinstance(self.commandnode, unicode)
assert action in ('execute', 'prev', 'next', 'complete')
@@ -510,7 +549,9 @@ class CommandWindow:
self.account.connection.SendAndCallForResponse(stanza, callback)
def send_cancel(self):
- '''Send the command with action='cancel'. '''
+ """
+ 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.
diff --git a/src/advanced_configuration_window.py b/src/advanced_configuration_window.py
index c56ee27ad..b35461287 100644
--- a/src/advanced_configuration_window.py
+++ b/src/advanced_configuration_window.py
@@ -43,7 +43,9 @@ C_TYPE
GTKGUI_GLADE = 'manage_accounts_window.glade'
def rate_limit(rate):
- ''' call func at most *rate* times per second '''
+ """
+ Call func at most *rate* times per second
+ """
def decorator(func):
timeout = [None]
def f(*args, **kwargs):
@@ -131,8 +133,10 @@ class AdvancedConfigurationWindow(object):
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'''
+ """
+ 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':
diff --git a/src/atom_window.py b/src/atom_window.py
index 56449846f..4712dfc41 100644
--- a/src/atom_window.py
+++ b/src/atom_window.py
@@ -35,7 +35,9 @@ class AtomWindow:
@classmethod
def newAtomEntry(cls, entry):
- ''' Queue new entry, open window if there's no one opened. '''
+ """
+ Queue new entry, open window if there's no one opened
+ """
cls.entries.append(entry)
if cls.window is None:
@@ -48,7 +50,9 @@ class AtomWindow:
cls.window = None
def __init__(self):
- ''' Create new window... only if we have anything to show. '''
+ """
+ Create new window... only if we have anything to show
+ """
assert len(self.__class__.entries)>0
self.entry = None # the entry actually displayed
@@ -69,7 +73,9 @@ class AtomWindow:
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. '''
+ """
+ Get next entry from the queue and display it in the window
+ """
assert len(self.__class__.entries)>0
newentry = self.__class__.entries.pop(0)
@@ -103,8 +109,10 @@ class AtomWindow:
self.entry = newentry
def updateCounter(self):
- ''' We display number of events on the top of window, sometimes it needs to be
- changed...'''
+ """
+ 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(
diff --git a/src/chat_control.py b/src/chat_control.py
index ca4224f81..9ef7fc2b8 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -90,16 +90,22 @@ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
################################################################################
class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
- '''A base class containing a banner, ConversationTextview, MessageTextView
- '''
+ """
+ A base class containing a banner, ConversationTextview, MessageTextView
+ """
def make_href(self, match):
url_color = gajim.config.get('urlmsgcolor')
- return '<a href="%s"><span color="%s">%s</span></a>' % (match.group(),
+ url = match.group()
+ if not '://' in url:
+ url = 'http://' + url
+ return '<a href="%s"><span color="%s">%s</span></a>' % (url,
url_color, match.group())
def get_font_attrs(self):
- ''' get pango font attributes for banner from theme settings '''
+ """
+ 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')
@@ -133,48 +139,60 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
type_]))
def draw_banner(self):
- '''Draw the fat line at the top of the window that
- houses the icon, jid, ...
- '''
+ """
+ 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)
- # Derived types MAY implement this
def draw_banner_text(self):
- pass # Derived types SHOULD implement this
+ """
+ Derived types SHOULD implement this
+ """
+ pass
def update_ui(self):
+ """
+ Derived types SHOULD implement this
+ """
self.draw_banner()
- # Derived types SHOULD implement this
def repaint_themed_widgets(self):
+ """
+ Derived types MAY implement this
+ """
self._paint_banner()
self.draw_banner()
- # Derived classes MAY implement this
def _update_banner_state_image(self):
- pass # Derived types MAY implement this
+ """
+ Derived types MAY implement this
+ """
+ pass
def handle_message_textview_mykey_press(self, widget, event_keyval,
- event_keymod):
- # Derived should implement this rather than connecting to the event
- # itself.
-
+ 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()
+ _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)
+ position = _buffer.get_insert()
+ end = _buffer.get_iter_at_mark(position)
- text = buffer.get_text(start, end, False)
+ text = _buffer.get_text(start, end, False)
text = text.decode('utf8')
splitted = text.split()
@@ -202,8 +220,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.command_hits.append(name)
if self.command_hits:
- buffer.delete(start, end)
- buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ')
+ _buffer.delete(start, end)
+ _buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ')
self.last_key_tabs = True
return True
@@ -214,7 +232,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
helpers.launch_browser_mailer('url', url)
def __init__(self, type_id, parent_win, widget_name, contact, acct,
- resource = None):
+ 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.
@@ -244,21 +262,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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.banner_status_label = self.xml.get_widget('banner_label')
+ id_ = 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()
+ self.handlers[id_] = self.banner_status_label
# Init DND
self.TARGET_TYPE_URI_LIST = 80
@@ -393,7 +400,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
dialogs.AspellDictError(lang)
def on_banner_label_populate_popup(self, label, menu):
- '''We override the default context menu and add our own menutiems'''
+ """
+ Override the default context menu and add our own menutiems
+ """
item = gtk.SeparatorMenuItem()
menu.prepend(item)
@@ -414,8 +423,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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'''
+ """
+ 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:
@@ -459,12 +470,16 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# moved from ChatControl
def _on_banner_eventbox_button_press_event(self, widget, event):
- '''If right-clicked, show popup'''
+ """
+ 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'''
+ """
+ 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.'))
@@ -479,7 +494,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.send_message(message, xhtml=xhtml)
def _paint_banner(self):
- '''Repaint banner with theme color'''
+ """
+ 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')
@@ -526,9 +543,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.handlers[id_] = widget
def _on_style_set_event(self, widget, style, *opts):
- '''set style of widget from style class *.Frame.Eventbox
+ """
+ Set style of widget from style class *.Frame.Eventbox
opts[0] == True -> set fg color
- opts[1] == True -> set bg color'''
+ opts[1] == True -> set bg color
+ """
banner_eventbox = self.xml.get_widget('banner_eventbox')
self.disconnect_style_event(widget)
if opts[1]:
@@ -603,11 +622,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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'''
-
+ 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
@@ -666,8 +685,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
event_keymod)
def _on_drag_data_received(self, widget, context, x, y, selection,
- target_type, timestamp):
- pass # Derived classes SHOULD implement this method
+ 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
@@ -682,10 +704,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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
- '''
+ 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
@@ -725,10 +748,13 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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):
- '''prints 'chat' type messages'''
+ 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
@@ -803,8 +829,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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'''
+ """
+ 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()
@@ -822,12 +850,16 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.msg_textview.grab_focus()
def on_emoticons_button_clicked(self, widget):
- '''popup emoticons menu'''
+ """
+ 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'''
+ """
+ Popup formattings menu
+ """
menu = gtk.Menu()
menuitems = ((_('Bold'), 'bold'),
@@ -889,7 +921,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
def on_actions_button_clicked(self, widget):
- '''popup action menu'''
+ """
+ Popup action menu
+ """
menu = self.prepare_context_menu(hide_buttonbar_items=True)
menu.show_all()
gtkgui_helpers.popup_emoticons_under_button(menu, widget,
@@ -909,7 +943,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
buffer_.delete(start, end)
def _on_history_menuitem_activate(self, widget = None, jid = None):
- '''When history menuitem is pressed: call history window'''
+ """
+ When history menuitem is pressed: call history window
+ """
if not jid:
jid = self.contact.jid
@@ -921,7 +957,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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'''
+ """
+ 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)
@@ -949,7 +987,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
_on_ok(self.contact)
def on_minimize_menuitem_toggled(self, widget):
- '''When a grouchat is minimized, unparent the tab, put it in roster etc'''
+ """
+ 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()
@@ -979,7 +1019,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
def bring_scroll_to_end(self, textview, diff_y = 0):
- ''' scrolls to the end of textview if end is not visible '''
+ """
+ Scroll to the end of textview if end is not visible
+ """
if self.scroll_to_end_id:
# a scroll is already planned
return
@@ -1000,10 +1042,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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'''
+ """
+ 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
@@ -1096,8 +1140,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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 '''
+ """
+ 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()
@@ -1156,7 +1202,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
return color
def widget_set_visible(self, widget, state):
- '''Show or hide a widget. state is bool'''
+ """
+ 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)
@@ -1168,7 +1216,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
widget.show_all()
def chat_buttons_set_visible(self, state):
- '''Toggle chat buttons. state is bool'''
+ """
+ Toggle chat buttons
+ """
MessageControl.chat_buttons_set_visible(self, state)
self.widget_set_visible(self.xml.get_widget('actions_hbox'), state)
@@ -1187,7 +1237,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
################################################################################
class ChatControl(ChatControlBase):
- '''A control for standard 1-1 chat'''
+ """
+ A control for standard 1-1 chat
+ """
(
JINGLE_STATE_NOT_AVAILABLE,
JINGLE_STATE_AVAILABLE,
@@ -1228,25 +1280,19 @@ class ChatControl(ChatControlBase):
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)
+ gtkgui_helpers.add_image_to_button(self._audio_button,
+ 'gajim-mic_inactive')
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)
+ gtkgui_helpers.add_image_to_button(self._video_button,
+ 'gajim-cam_inactive')
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')
+ 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)
@@ -1293,21 +1339,20 @@ class ChatControl(ChatControlBase):
self.video_state = self.JINGLE_STATE_NOT_AVAILABLE
self.update_toolbar()
-
- self._mood_image = self.xml.get_widget('mood_image')
- self._activity_image = self.xml.get_widget('activity_image')
- self._tune_image = self.xml.get_widget('tune_image')
-
- self.update_mood()
- self.update_activity()
- self.update_tune()
+
+ 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._pep_images['location'] = self.xml.get_widget('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(self.contact.resource)
+ self.show_avatar()
# chatstate timers and state
self.reset_kbd_mouse_timeout_vars()
@@ -1337,6 +1382,26 @@ class ChatControl(ChatControlBase):
self.on_avatar_eventbox_button_press_event)
self.handlers[id_] = widget
+ widget = self.xml.get_widget('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_widget(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_widget('mic_hscale')
+ id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed)
+ self.handlers[id_] = widget
+
+ widget = self.xml.get_widget('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
@@ -1444,118 +1509,24 @@ class ChatControl(ChatControlBase):
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_mood(self):
- mood = None
- text = None
-
+ def update_pep(self, pep_type):
if isinstance(self.contact, GC_Contact):
return
-
- if 'mood' in self.contact.mood:
- mood = self.contact.mood['mood'].strip()
- if 'text' in self.contact.mood:
- text = self.contact.mood['text'].strip()
-
- if mood is not None:
- if mood in MOODS:
- self._mood_image.set_from_pixbuf(gtkgui_helpers.load_mood_icon(
- mood).get_pixbuf())
- # Translate standard moods
- mood = MOODS[mood]
- else:
- self._mood_image.set_from_pixbuf(gtkgui_helpers.load_mood_icon(
- 'unknown').get_pixbuf())
-
- mood = gobject.markup_escape_text(mood)
-
- tooltip = '<b>%s</b>' % mood
- if text:
- text = gobject.markup_escape_text(text)
- tooltip += '\n' + text
- self._mood_image.set_tooltip_markup(tooltip)
- self._mood_image.show()
- else:
- self._mood_image.hide()
-
- def update_activity(self):
- activity = None
- subactivity = None
- text = None
-
- if isinstance(self.contact, GC_Contact):
+ if pep_type not in self._pep_images:
return
-
- if 'activity' in self.contact.activity:
- activity = self.contact.activity['activity'].strip()
- if 'subactivity' in self.contact.activity:
- subactivity = self.contact.activity['subactivity'].strip()
- if 'text' in self.contact.activity:
- text = self.contact.activity['text'].strip()
-
- if activity is not None:
- if activity in ACTIVITIES:
- # Translate standard activities
- if subactivity in ACTIVITIES[activity]:
- self._activity_image.set_from_pixbuf(
- gtkgui_helpers.load_activity_icon(activity, subactivity). \
- get_pixbuf())
- subactivity = ACTIVITIES[activity][subactivity]
- else:
- self._activity_image.set_from_pixbuf(
- gtkgui_helpers.load_activity_icon(activity).get_pixbuf())
- activity = ACTIVITIES[activity]['category']
- else:
- self._activity_image.set_from_pixbuf(
- gtkgui_helpers.load_activity_icon('unknown').get_pixbuf())
-
- # Translate standard subactivities
-
- tooltip = '<b>' + gobject.markup_escape_text(activity)
- if subactivity:
- tooltip += ': ' + gobject.markup_escape_text(subactivity)
- tooltip += '</b>'
- if text:
- tooltip += '\n' + gobject.markup_escape_text(text)
- self._activity_image.set_tooltip_markup(tooltip)
-
- self._activity_image.show()
+ 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:
- self._activity_image.hide()
-
- def update_tune(self):
- artist = None
- title = None
- source = None
-
- if isinstance(self.contact, GC_Contact):
- return
-
- if 'artist' in self.contact.tune:
- artist = self.contact.tune['artist'].strip()
- artist = gobject.markup_escape_text(artist)
- if 'title' in self.contact.tune:
- title = self.contact.tune['title'].strip()
- title = gobject.markup_escape_text(title)
- if 'source' in self.contact.tune:
- source = self.contact.tune['source'].strip()
- source = gobject.markup_escape_text(source)
-
- if artist or title:
- if not artist:
- artist = _('Unknown Artist')
- if not title:
- title = _('Unknown Title')
- if not source:
- source = _('Unknown Source')
-
- self._tune_image.set_tooltip_markup(
- _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
- 'from <i>%(source)s</i>') % {'title': title, 'artist': artist,
- 'source': source})
- self._tune_image.show()
- else:
- self._tune_image.hide()
+ img.hide()
# PluginSystem: adding GUI extension point for this ChatControl
# instance object
@@ -1565,27 +1536,44 @@ class ChatControl(ChatControlBase):
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()
+ 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:
- self.__dict__['_' + jingle_type + '_banner_image'].show()
- if self.audio_state == self.JINGLE_STATE_CONNECTING:
- self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock(
+ banner_image.show()
+ if state == self.JINGLE_STATE_CONNECTING:
+ 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(
+ elif state == self.JINGLE_STATE_CONNECTION_RECEIVED:
+ 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(
+ elif state == self.JINGLE_STATE_CONNECTED:
+ 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(
+ 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_widget('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_widget('mic_hscale').set_value(input_vol)
+ self.xml.get_widget('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')
@@ -1621,27 +1609,27 @@ class ChatControl(ChatControlBase):
if state in states:
jingle_state = states[state]
- if self.__dict__[jingle_type + '_state'] == jingle_state:
+ if getattr(self, jingle_type + '_state') == jingle_state:
return
- self.__dict__[jingle_type + '_state'] = jingle_state
+ 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(), self.__dict__[jingle_type + '_sid'])
+ self.contact.get_full_jid(), getattr(self, jingle_type + '_sid'))
if state in ('not_available', 'available', 'stop'):
- self.__dict__[jingle_type + '_sid'] = None
+ setattr(self, jingle_type + '_sid', None)
if state in ('connection_received', 'connecting'):
- self.__dict__[jingle_type + '_sid'] = sid
+ setattr(self, jingle_type + '_sid', sid)
if state in ('connecting', 'connected', 'connection_received'):
- self.__dict__['_' + jingle_type + '_button'].set_active(True)
+ getattr(self, '_' + jingle_type + '_button').set_active(True)
elif state in ('not_available', 'stop'):
- self.__dict__['_' + jingle_type + '_button'].set_active(False)
+ getattr(self, '_' + jingle_type + '_button').set_active(False)
- eval('self.update_' + jingle_type)()
+ 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)
@@ -1649,17 +1637,39 @@ class ChatControl(ChatControlBase):
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):
- '''
- we enter the eventbox area so we under conditions add a timeout
- to show a bigger avatar after 0.5 sec
- '''
+ """
+ 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)
+ avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
if avatar_pixbuf in ('ask', None):
return
avatar_w = avatar_pixbuf.get_width()
@@ -1676,20 +1686,23 @@ class ChatControl(ChatControlBase):
self.show_bigger_avatar, widget)
def on_avatar_eventbox_leave_notify_event(self, widget, event):
- '''we left the eventbox area that holds the avatar img'''
+ """
+ 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 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.contact.jid, self.account, self.contact.get_shown_name())
self.handlers[id_] = menuitem
menu.append(menuitem)
menu.show_all()
@@ -1698,9 +1711,20 @@ class ChatControl(ChatControlBase):
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'''
+ """
+ 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
@@ -1753,9 +1777,10 @@ class ChatControl(ChatControlBase):
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.
- '''
+ """
+ Draw the text in the fat line at the top of the window that houses the
+ name, jid
+ """
contact = self.contact
jid = contact.jid
@@ -1829,14 +1854,11 @@ class ChatControl(ChatControlBase):
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 = '<span %s>%s</span>' % (font_attrs_small, status_text)
- else:
- status_text = '<span %s>%s</span>' % (font_attrs_small, status_escaped)
+ status_text = self.urlfinder.sub(self.make_href, status_escaped)
+ status_text = '<span %s>%s</span>' % (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)
+ self.banner_status_label.show()
else:
status_text = ''
self.banner_status_label.hide()
@@ -1847,45 +1869,39 @@ class ChatControl(ChatControlBase):
banner_name_label.set_markup(label_text)
banner_name_label.set_tooltip_text(label_tooltip)
- def on_audio_button_toggled(self, widget):
+ 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():
- 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)
+ 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:
- 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')
+ 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):
- 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)
+ self.on_jingle_button_toggled(widget, 'video')
def _toggle_gpg(self):
if not self.gpg_is_active and not self.contact.keyID:
@@ -1938,8 +1954,11 @@ class ChatControl(ChatControlBase):
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'''
+ 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
@@ -1948,11 +1967,12 @@ class ChatControl(ChatControlBase):
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'))
+ img_path = gtkgui_helpers.get_icon_path('gajim-security_high')
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'))
+ 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
@@ -1974,7 +1994,9 @@ class ChatControl(ChatControlBase):
def send_message(self, message, keyID='', chatstate=None, xhtml=None,
process_commands=True):
- '''Send a message to contact'''
+ """
+ Send a message to contact
+ """
if message in ('', None, '\n'):
return None
@@ -2038,10 +2060,11 @@ class ChatControl(ChatControlBase):
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 no we go paused if we were previously composing '''
+ """
+ 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
@@ -2065,10 +2088,10 @@ class ChatControl(ChatControlBase):
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 '''
+ """
+ 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
@@ -2098,7 +2121,9 @@ class ChatControl(ChatControlBase):
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
def print_esession_details(self):
- '''print esession settings to textview'''
+ """
+ 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')
@@ -2120,16 +2145,19 @@ class ChatControl(ChatControlBase):
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'''
+ 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':
@@ -2267,12 +2295,12 @@ class ChatControl(ChatControlBase):
return tab_img
def prepare_context_menu(self, hide_buttonbar_items=False):
- '''sets 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
- '''
+ """
+ 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,
@@ -2280,9 +2308,11 @@ class ChatControl(ChatControlBase):
return menu
def send_chatstate(self, state, contact = None):
- ''' sends OUR chatstate as STANDLONE chat state message (eg. no body)
+ """
+ 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'''
+ 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.
@@ -2374,6 +2404,9 @@ class ChatControl(ChatControlBase):
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
@@ -2435,7 +2468,9 @@ class ChatControl(ChatControlBase):
on_yes(self)
def handle_incoming_chatstate(self):
- ''' handle incoming chatstate that jid SENT TO us '''
+ """
+ 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)
@@ -2455,24 +2490,12 @@ class ChatControl(ChatControlBase):
# Re-show the small avatar
self.show_avatar()
- def show_avatar(self, resource = None):
+ def show_avatar(self):
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)
+
+ 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:
@@ -2488,8 +2511,10 @@ class ChatControl(ChatControlBase):
else:
gajim.connections[self.account].request_vcard(jid_with_resource)
return
- if pixbuf is not None:
+ elif pixbuf:
scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat')
+ else:
+ scaled_pixbuf = None
image = self.xml.get_widget('avatar_image')
image.set_from_pixbuf(scaled_pixbuf)
@@ -2617,8 +2642,9 @@ class ChatControl(ChatControlBase):
self.conv_textview.print_empty_line()
def read_queue(self):
- '''read queue and print messages containted in it'''
-
+ """
+ Read queue and print messages containted in it
+ """
jid = self.contact.jid
jid_with_resource = jid
if self.resource:
@@ -2673,16 +2699,15 @@ class ChatControl(ChatControlBase):
control.remove_contact(nick)
def show_bigger_avatar(self, small_avatar):
- '''resizes the avatar, if needed, so it has at max half the screen size
- and shows it'''
+ """
+ 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)
+ self.contact.jid)
if avatar_pixbuf in ('ask', None):
return
# Hide the small avatar
@@ -2741,14 +2766,18 @@ class ChatControl(ChatControlBase):
window.show_all()
def _on_window_avatar_leave_notify_event(self, widget, event):
- '''we just left the popup window that holds avatar'''
+ """
+ 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):
- '''we just moved the mouse so show the cursor'''
+ """
+ Just moved the mouse so show the cursor
+ """
cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
self.bigger_avatar_window.window.set_cursor(cursor)
@@ -2765,7 +2794,9 @@ class ChatControl(ChatControlBase):
self._toggle_gpg()
def _on_convert_to_gc_menuitem_activate(self, widget):
- '''user want to invite some friends to chat'''
+ """
+ User wants to invite some friends to chat
+ """
dialogs.TransformChatToMUC(self.account, [self.contact.jid])
def _on_toggle_e2e_menuitem_activate(self, widget):
@@ -2806,7 +2837,9 @@ class ChatControl(ChatControlBase):
self.draw_banner()
def update_status_display(self, name, uf_show, status):
- '''print the contact's status and update the status/GPG image'''
+ """
+ Print the contact's status and update the status/GPG image
+ """
self.update_ui()
self.parent_win.redraw_tab(self)
diff --git a/src/command_system/__init__.py b/src/command_system/__init__.py
index c0a48f863..ff61c186e 100644
--- a/src/command_system/__init__.py
+++ b/src/command_system/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/command_system/dispatching.py b/src/command_system/dispatching.py
index 2bfd76b96..7f365a915 100644
--- a/src/command_system/dispatching.py
+++ b/src/command_system/dispatching.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/command_system/errors.py b/src/command_system/errors.py
index 992e83ccf..4281e9fd7 100644
--- a/src/command_system/errors.py
+++ b/src/command_system/errors.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
@@ -20,13 +20,18 @@ class BaseError(Exception):
"""
def __init__(self, message, command=None, name=None):
+ self.message = message
+
self.command = command
self.name = name
if command and not name:
self.name = command.first_name
- super(BaseError, self).__init__(message)
+ super(BaseError, self).__init__()
+
+ def __str__(self):
+ return self.message
class DefinitionError(BaseError):
"""
diff --git a/src/command_system/framework.py b/src/command_system/framework.py
index db9eb2e78..30f5cd53c 100644
--- a/src/command_system/framework.py
+++ b/src/command_system/framework.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/command_system/implementation/__init__.py b/src/command_system/implementation/__init__.py
index 66d097f42..c77c23e3f 100644
--- a/src/command_system/implementation/__init__.py
+++ b/src/command_system/implementation/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/command_system/implementation/custom.py b/src/command_system/implementation/custom.py
index 4f54670da..e4aa32dbf 100644
--- a/src/command_system/implementation/custom.py
+++ b/src/command_system/implementation/custom.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/command_system/implementation/hosts.py b/src/command_system/implementation/hosts.py
index b38bb1a35..a90dab464 100644
--- a/src/command_system/implementation/hosts.py
+++ b/src/command_system/implementation/hosts.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/command_system/implementation/middleware.py b/src/command_system/implementation/middleware.py
index 9ef4bea29..2f262f8ed 100644
--- a/src/command_system/implementation/middleware.py
+++ b/src/command_system/implementation/middleware.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py
index 8afa044e5..91bfbc8e9 100644
--- a/src/command_system/implementation/standard.py
+++ b/src/command_system/implementation/standard.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
@@ -17,10 +17,14 @@
Provides an actual implementation for the standard commands.
"""
+from time import localtime, strftime
+from datetime import date
+
import dialogs
from common import gajim
from common import helpers
from common.exceptions import GajimGeneralException
+from common.logger import Constants
from ..errors import CommandError
from ..framework import CommandContainer, command, documentation
@@ -28,6 +32,10 @@ from ..mapping import generate_usage
from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
+# This holds constants fron the logger, which we'll be using in some of our
+# commands.
+lc = Constants()
+
class StandardCommonCommands(CommandContainer):
"""
This command container contains standard commands which are common to all -
@@ -84,6 +92,71 @@ class StandardCommonCommands(CommandContainer):
def me(self, action):
self.send("/me %s" % action)
+ @command('lastlog', overlap=True)
+ @documentation(_("Show logged messages which mention given text"))
+ def grep(self, text, limit=None):
+ results = gajim.logger.get_search_results_for_query(self.contact.jid,
+ text, self.account)
+
+ if not results:
+ raise CommandError(_("%s: Nothing found") % text)
+
+ if limit:
+ try:
+ results = results[len(results) - int(limit):]
+ except ValueError:
+ raise CommandError(_("Limit must be an integer"))
+
+ for row in results:
+ contact, time, kind, show, message, subject = row
+
+ if not contact:
+ if kind == lc.KIND_CHAT_MSG_SENT:
+ contact = gajim.nicks[self.account]
+ else:
+ contact = self.contact.name
+
+ time_obj = localtime(time)
+ date_obj = date.fromtimestamp(time)
+ date_ = strftime('%Y-%m-%d', time_obj)
+ time_ = strftime('%H:%M:%S', time_obj)
+
+ if date_obj == date.today():
+ formatted = "[%s] %s: %s" % (time_, contact, message)
+ else:
+ formatted = "[%s, %s] %s: %s" % (date_, time_, contact, message)
+
+ self.echo(formatted)
+
+ @command(raw=True, empty=True)
+ @documentation(_("""
+ Set current the status
+
+ Status can be given as one of the following values: online, away,
+ chat, xa, dnd.
+ """))
+ def status(self, status, message):
+ if status not in ('online', 'away', 'chat', 'xa', 'dnd'):
+ raise CommandError("Invalid status given")
+ for connection in gajim.connections.itervalues():
+ connection.change_status(status, message)
+
+ @command(raw=True, empty=True)
+ @documentation(_("Set the current status to away"))
+ def away(self, message):
+ if not message:
+ message = _("Away")
+ for connection in gajim.connections.itervalues():
+ connection.change_status('away', message)
+
+ @command('back', raw=True, empty=True)
+ @documentation(_("Set the current status to online"))
+ def online(self, message):
+ if not message:
+ message = _("Available")
+ for connection in gajim.connections.itervalues():
+ connection.change_status('online', message)
+
class StandardChatCommands(CommandContainer):
"""
This command container contains standard command which are unique to a chat.
@@ -98,7 +171,7 @@ class StandardChatCommands(CommandContainer):
raise CommandError(_('Command is not supported for zeroconf accounts'))
gajim.connections[self.account].sendPing(self.contact)
- @command('dtmf')
+ @command
@documentation(_("Sends DTMF events through an open audio session"))
def dtmf(self, events):
if not self.audio_sid:
@@ -114,7 +187,7 @@ class StandardChatCommands(CommandContainer):
else:
raise CommandError(_("No valid DTMF event specified"))
- @command('audio')
+ @command
@documentation(_("Toggle audio session"))
def audio(self):
if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
@@ -125,7 +198,7 @@ class StandardChatCommands(CommandContainer):
state = self._audio_button.get_active()
self._audio_button.set_active(not state)
- @command('video')
+ @command
@documentation(_("Toggle video session"))
def video(self):
if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
diff --git a/src/command_system/mapping.py b/src/command_system/mapping.py
index 707866a20..ecf8f0783 100644
--- a/src/command_system/mapping.py
+++ b/src/command_system/mapping.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent <hell.director@gmail.com>
+# Copyright (C) 2009 Alexander Cherniuk <ts33kr@gmail.com>
#
# 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
diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py
index f6d8e7e2b..64abe75ce 100644
--- a/src/common/GnuPG.py
+++ b/src/common/GnuPG.py
@@ -213,7 +213,9 @@ if gajim.HAVE_GPG:
return self.get_keys(True)
def _stripHeaderFooter(self, data):
- """Remove header and footer from data"""
+ """
+ Remove header and footer from data
+ """
if not data: return ''
lines = data.split('\n')
while lines[0] != '':
@@ -229,7 +231,9 @@ if gajim.HAVE_GPG:
return line
def _addHeaderFooter(self, data, type_):
- """Add header and footer from data"""
+ """
+ Add header and footer from data
+ """
out = "-----BEGIN PGP %s-----\n" % type_
out = out + "Version: PGP\n"
out = out + "\n"
diff --git a/src/common/GnuPGInterface.py b/src/common/GnuPGInterface.py
index d91f776e3..aa4a9eeee 100644
--- a/src/common/GnuPGInterface.py
+++ b/src/common/GnuPGInterface.py
@@ -21,7 +21,8 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-"""Interface to GNU Privacy Guard (GnuPG)
+"""
+Interface to GNU Privacy Guard (GnuPG)
GnuPGInterface is a Python module to interface with GnuPG.
It concentrates on interacting with GnuPG via filehandles,
@@ -249,7 +250,8 @@ _fd_options = { 'passphrase': '--passphrase-fd',
'command': '--command-fd' }
class GnuPG:
- """Class instances represent GnuPG.
+ """
+ Class instances represent GnuPG
Instance attributes of a GnuPG object are:
@@ -275,8 +277,10 @@ class GnuPG:
self.options = Options()
def run(self, gnupg_commands, args=None, create_fhs=None, attach_fhs=None):
- """Calls GnuPG with the list of string commands gnupg_commands,
- complete with prefixing dashes.
+ """
+ Calls GnuPG with the list of string commands gnupg_commands, complete
+ with prefixing dashes
+
For example, gnupg_commands could be
'["--sign", "--encrypt"]'
Returns a GnuPGInterface.Process object.
@@ -336,7 +340,6 @@ class GnuPG:
is a FileObject connected to GnuPG's standard input,
and can be written to.
"""
-
if args is None: args = []
if create_fhs is None: create_fhs = []
if attach_fhs is None: attach_fhs = {}
@@ -367,9 +370,10 @@ class GnuPG:
def _attach_fork_exec(self, gnupg_commands, args, create_fhs, attach_fhs):
- """This is like run(), but without the passphrase-helping
- (note that run() calls this)."""
-
+ """
+ This is like run(), but without the passphrase-helping (note that run()
+ calls this)
+ """
process = Process()
for fh_name in create_fhs + attach_fhs.keys():
@@ -404,7 +408,9 @@ class GnuPG:
def _as_parent(self, process):
- """Stuff run after forking in parent"""
+ """
+ Stuff run after forking in parent
+ """
for k, p in process._pipes.items():
if not p.direct:
os.close(p.child)
@@ -417,7 +423,9 @@ class GnuPG:
def _as_child(self, process, gnupg_commands, args):
- """Stuff run after forking in child"""
+ """
+ Stuff run after forking in child
+ """
# child
for std in _stds:
p = process._pipes[std]
@@ -444,7 +452,10 @@ class GnuPG:
class Pipe:
- """simple struct holding stuff about pipes we use"""
+ """
+ Simple struct holding stuff about pipes we use
+ """
+
def __init__(self, parent, child, direct):
self.parent = parent
self.child = child
@@ -452,7 +463,8 @@ class Pipe:
class Options:
- """Objects of this class encompass options passed to GnuPG.
+ """
+ Objects of this class encompass options passed to GnuPG.
This class is responsible for determining command-line arguments
which are based on options. It can be said that a GnuPG
object has-a Options object in its options attribute.
@@ -522,7 +534,6 @@ class Options:
>>> gnupg.options.get_args()
['--armor', '--recipient', 'Alice', '--recipient', 'Bob', '--no-secmem-warning']
"""
-
def __init__(self):
# booleans
self.armor = 0
@@ -558,12 +569,15 @@ class Options:
self.extra_args = []
def get_args( self ):
- """Generate a list of GnuPG arguments based upon attributes."""
-
+ """
+ Generate a list of GnuPG arguments based upon attributes
+ """
return self.get_meta_args() + self.get_standard_args() + self.extra_args
def get_standard_args( self ):
- """Generate a list of standard, non-meta or extra arguments"""
+ """
+ Generate a list of standard, non-meta or extra arguments
+ """
args = []
if self.homedir is not None:
args.extend( [ '--homedir', self.homedir ] )
@@ -595,7 +609,9 @@ class Options:
return args
def get_meta_args( self ):
- """Get a list of generated meta-arguments"""
+ """
+ Get a list of generated meta-arguments
+ """
args = []
if self.meta_pgp_5_compatible: args.extend( [ '--compress-algo', '1',
@@ -608,8 +624,9 @@ class Options:
class Process:
- """Objects of this class encompass properties of a GnuPG
- process spawned by GnuPG.run().
+ """
+ Objects of this class encompass properties of a GnuPG process spawned by
+ GnuPG.run()
# gnupg is a GnuPG object
process = gnupg.run( [ '--decrypt' ], stdout = 1 )
@@ -637,9 +654,10 @@ class Process:
self._waited = None
def wait(self):
- """Wait on the process to exit, allowing for child cleanup.
- Will raise an IOError if the process exits non-zero."""
-
+ """
+ Wait on the process to exit, allowing for child cleanup. Will raise an
+ IOError if the process exits non-zero
+ """
e = os.waitpid(self.pid, 0)[1]
if e != 0:
raise IOError, "GnuPG exited non-zero, with code %d" % (e << 8)
diff --git a/src/common/account.py b/src/common/account.py
index 1c080cad8..4db660165 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
+ return hash(self.name)
diff --git a/src/common/atom.py b/src/common/atom.py
index 18122e9d1..689df5cee 100644
--- a/src/common/atom.py
+++ b/src/common/atom.py
@@ -20,9 +20,11 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-''' Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports
+"""
+Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports
and services. Very simple. Actually implements only atom:entry. Implement more features
-if you need. '''
+if you need
+"""
# suggestion: rewrite functions that return dates to return standard python time tuples,
# exteneded to contain timezone
@@ -31,8 +33,11 @@ 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)
@@ -60,15 +65,17 @@ class PersonConstruct(xmpp.Node, object):
class Entry(xmpp.Node, object):
def __init__(self, node=None):
- ''' Create new atom entry object. '''
xmpp.Node.__init__(self, 'entry', node=node)
def __repr__(self):
return '<Atom:Entry object of id="%r">' % 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)
@@ -77,8 +84,10 @@ class OldEntry(xmpp.Node, object):
return '<Atom0.3:Entry object of id="%r">' % 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. '''
+ """
+ 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:
@@ -104,7 +113,9 @@ class OldEntry(xmpp.Node, object):
which delivered this entry. ''')
def get_feed_link(self):
- ''' Get source link '''
+ """
+ Get source link
+ """
try:
return self.getTag('feed').getTags('link',{'rel':'alternate'})[1].getData()
except Exception:
@@ -114,15 +125,19 @@ class OldEntry(xmpp.Node, object):
''' Link to main webpage of the feed. ''')
def get_title(self):
- ''' Get an entry's title. '''
+ """
+ Get an entry's title
+ """
return self.getTagData('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). '''
+ """
+ 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:
@@ -135,12 +150,16 @@ class OldEntry(xmpp.Node, object):
''' 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).
+ """
+ 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 <published>
- and <issued> elements. '''
+ and <issued> elements.
+ """
for name in ('updated', 'modified', 'published', 'issued'):
date = self.getTagData(name)
if date is not None: break
diff --git a/src/common/caps.py b/src/common/caps_cache.py
index f34dfcda4..603c348cb 100644
--- a/src/common/caps.py
+++ b/src/common/caps_cache.py
@@ -1,5 +1,5 @@
# -*- coding:utf-8 -*-
-## src/common/caps.py
+## src/common/caps_cache.py
##
## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
## Travis Shirk <travis AT pobox.com>
@@ -23,22 +23,22 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-'''
-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
import base64
import hashlib
-from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES
-from common.xmpp import NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO
-from common.xmpp import NS_JINGLE_RTP_VIDEO
+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)
# 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]
@@ -47,6 +47,7 @@ FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION,
NEW = 0
QUERIED = 1
CACHED = 2 # got the answer
+FAKED = 3 # allow NullClientCaps to behave as it has a cached item
################################################################################
### Public API of this module
@@ -54,7 +55,9 @@ CACHED = 2 # got the answer
capscache = None
def initialize(logger):
- ''' Initializes this module '''
+ """
+ Initialize this module
+ """
global capscache
capscache = CapsCache(logger)
@@ -65,19 +68,34 @@ def client_supports(client_caps, requested_feature):
supported_features = cache_item.features
if requested_feature in supported_features:
return True
- elif supported_features == [] and cache_item.status in (NEW, QUERIED):
+ 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
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'''
-
+ values without a field type list-multi
+ """
def sort_identities_func(i1, i2):
cat1 = i1['category']
cat2 = i2['category']
@@ -98,14 +116,14 @@ def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-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:
@@ -140,108 +158,126 @@ def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
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.
- '''
+ """
+ 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()
-
+ """
+ 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()
-
+ """
+ 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()
+ """
+ To be implemented by subclassess
+ """
+ raise NotImplementedError
class ClientCaps(AbstractClientCaps):
- ''' The current XEP-115 implementation '''
-
+ """
+ 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
+ return computed_hash == self._hash
+
-
class OldClientCaps(AbstractClientCaps):
- ''' Old XEP-115 implemtation. Kept around for background competability. '''
-
+ """
+ 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
+ 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.
-
+ 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
+
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
+ # 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 _is_hash_valid(self, identities, features, dataforms):
- return False
+ 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.
- '''
+ """
+ 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
@@ -255,7 +291,7 @@ class CapsCache(object):
# 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
@@ -274,7 +310,7 @@ class CapsCache(object):
self._features = []
for feature in value:
self._features.append(self.__names.setdefault(feature, feature))
-
+
features = property(_get_features, _set_features)
def _get_identities(self):
@@ -291,7 +327,7 @@ class CapsCache(object):
d['name'] = i[3]
list_.append(d)
return list_
-
+
def _set_identities(self, value):
self._identities = []
for identity in value:
@@ -299,7 +335,7 @@ class CapsCache(object):
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_and_store(self, identities, features):
@@ -308,11 +344,18 @@ class CapsCache(object):
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)
self.__CacheItem = CacheItem
self.logger = logger
@@ -325,9 +368,11 @@ class CapsCache(object):
x.identities = identities
x.features = features
x.status = CACHED
-
+
def _remove_outdated_caps(self):
- '''Removes outdated values from the db'''
+ """
+ Remove outdated values from the db
+ """
self.logger.clean_caps_table()
def __getitem__(self, caps):
@@ -341,96 +386,20 @@ class CapsCache(object):
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.
- '''
+ """
+ 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)
-
+ 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
-
- 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)
-
- capscache.query_client_of_jid_if_unknown(self, jid, client_caps)
- contact.client_caps = client_caps
-
- 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
-
- 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()
+ q.update_last_seen()
# vim: se ts=3:
diff --git a/src/common/check_paths.py b/src/common/check_paths.py
index 0ea19a030..d50158b43 100644
--- a/src/common/check_paths.py
+++ b/src/common/check_paths.py
@@ -27,18 +27,11 @@ import os
import sys
import stat
-import exceptions
from common import gajim
import logger
# DO NOT MOVE ABOVE OF import gajim
-try:
- import sqlite3 as sqlite # python 2.5
-except ImportError:
- try:
- from pysqlite2 import dbapi2 as sqlite
- except ImportError:
- raise exceptions.PysqliteNotAvailable
+import sqlite3 as sqlite
def create_log_db():
print _('creating logs database')
@@ -86,7 +79,7 @@ def create_log_db():
subject TEXT
);
- CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
+ CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC);
CREATE TABLE caps_cache (
hash_method TEXT,
diff --git a/src/common/commands.py b/src/common/commands.py
index 71b0300bc..07c866194 100644
--- a/src/common/commands.py
+++ b/src/common/commands.py
@@ -34,10 +34,13 @@ class AdHocCommand:
@staticmethod
def isVisibleFor(samejid):
- ''' This returns True if that command should be visible and invokable
- for others.
+ """
+ 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.'''
+ jid.
+ """
return True
def __init__(self, conn, jid, sessionid):
@@ -80,7 +83,9 @@ class ChangeStatusCommand(AdHocCommand):
@staticmethod
def isVisibleFor(samejid):
- ''' Change status is visible only if the entity has the same bare jid. '''
+ """
+ Change status is visible only if the entity has the same bare jid
+ """
return samejid
def execute(self, request):
@@ -177,7 +182,9 @@ class LeaveGroupchatsCommand(AdHocCommand):
@staticmethod
def isVisibleFor(samejid):
- ''' Change status is visible only if the entity has the same bare jid. '''
+ """
+ Change status is visible only if the entity has the same bare jid
+ """
return samejid
def execute(self, request):
@@ -259,7 +266,9 @@ class ForwardMessagesCommand(AdHocCommand):
@staticmethod
def isVisibleFor(samejid):
- ''' Change status is visible only if the entity has the same bare jid. '''
+ """
+ Change status is visible only if the entity has the same bare jid
+ """
return samejid
def execute(self, request):
@@ -282,7 +291,10 @@ class ForwardMessagesCommand(AdHocCommand):
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 = {}
@@ -297,7 +309,9 @@ class ConnectionCommands:
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. '''
+ """
+ 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):
@@ -318,8 +332,10 @@ class ConnectionCommands:
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. '''
+ """
+ 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')
@@ -342,8 +358,10 @@ class ConnectionCommands:
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. '''
+ """
+ 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')
diff --git a/src/common/config.py b/src/common/config.py
index b73b23c66..a3c59b129 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -225,6 +225,7 @@ class Config:
'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.')],
@@ -272,6 +273,15 @@ class Config:
'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 = {
@@ -344,13 +354,16 @@ class Config:
'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, ''],
}, {}),
'statusmsg': ({
@@ -515,7 +528,9 @@ class Config:
cb(data, opt3, [opt, opt2], dict_[opt2][opt3])
def get_children(self, node=None):
- ''' Tree-like interface '''
+ """
+ Tree-like interface
+ """
if node is None:
for child, option in self.__options.iteritems():
yield (child, ), option
@@ -701,8 +716,9 @@ class Config:
return False
def should_log(self, account, jid):
- '''should conversations between a local account and a remote jid be
- logged?'''
+ """
+ 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:
diff --git a/src/common/configpaths.py b/src/common/configpaths.py
index ac21e42c6..25c0c8e2d 100644
--- a/src/common/configpaths.py
+++ b/src/common/configpaths.py
@@ -25,6 +25,7 @@
import os
import sys
import tempfile
+import defs
# Note on path and filename encodings:
#
@@ -46,7 +47,9 @@ import tempfile
# not displayed to the user, Unicode is not really necessary here.
def fse(s):
- '''Convert from filesystem encoding if not already Unicode'''
+ """
+ Convert from filesystem encoding if not already Unicode
+ """
return unicode(s, sys.getfilesystemencoding())
def windowsify(s):
@@ -114,12 +117,9 @@ class ConfigPaths:
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')))
+ basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir))
+ self.add('DATA', os.path.join(basedir, windowsify(u'data')))
+ self.add('ICONS', os.path.join(basedir, windowsify(u'icons')))
self.add('HOME', fse(os.path.expanduser('~')))
try:
self.add('TMP', fse(tempfile.gettempdir()))
diff --git a/src/common/connection.py b/src/common/connection.py
index 1e7002d8a..b8b8cc65e 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -98,107 +98,599 @@ ssl_error = {
32: _("Key usage does not include certificate signing"),
50: _("Application verification failure")
}
-class Connection(ConnectionHandlers):
- '''Connection class'''
+
+class CommonConnection:
+ """
+ Common connection class, can be derivated for normal connection or zeroconf
+ connection
+ """
+
def __init__(self, name):
- ConnectionHandlers.__init__(self)
self.name = name
# self.connected:
# 0=>offline,
# 1=>connection in progress,
- # 2=>authorised
+ # 2=>online
+ # 3=>free for chat
+ # ...
self.connected = 0
self.connection = None # xmpppy ClientCommon instance
- # 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]
+ 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.priority = gajim.get_priority(name, 'offline')
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.interface.dispatch(event, self.name, data)
+ 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 <body>
+ # 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.time_to_reconnect = None
self.last_time_to_reconnect = None
self.new_account_info = None
self.new_account_form = None
- self.bookmarks = []
self.annotations = {}
- self.on_purpose = False
self.last_io = gajim.idlequeue.current_time()
self.last_sent = []
self.last_history_time = {}
self.password = passwords.get_password(name)
- self.server_resource = gajim.config.get_per('accounts', name, 'resource')
- # All valid resource substitution strings should be added to this hash.
- if self.server_resource:
- self.server_resource = Template(self.server_resource).safe_substitute({
- 'hostname': socket.gethostname()
- })
- 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
- self.privacy_rules_supported = False
- # Used to ask privacy only once at connection
- self.privacy_rules_requested = False
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_all = False
+
self.music_track_info = 0
+ self.location_info = {}
self.pubsub_supported = False
self.pubsub_publish_options_supported = False
- self.pep_supported = False
- self.mood = {}
- self.tune = {}
- self.activity = {}
- # Do we continue connection when we get roster (send presence,get vcard..)
- self.continue_connect_info = None
# Do we auto accept insecure connection
self.connection_auto_accepted = False
- # 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.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.muc_jid = {} # jid of muc server for each transport type
self.available_transports = {} # list of available transports on this
# server {'icq': ['icq.server.com', 'icq2.server.com'], }
- self.vcard_supported = False
self.private_storage_supported = True
self.streamError = ''
self.secret_hmac = str(random.random())[2:]
# END __init__
- def dispatch(self, event, data):
- '''always passes account name as first param'''
- #gajim.interface.dispatch(event, self.name, data)
- gajim.ged.raise_event(event, self.name, data)
+ 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
@@ -225,12 +717,16 @@ class Connection(ConnectionHandlers):
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'''
+ """
+ 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
@@ -280,6 +776,7 @@ class Connection(ConnectionHandlers):
_('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)
@@ -385,18 +882,13 @@ class Connection(ConnectionHandlers):
elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
# data is (dict)
self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
- elif 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 _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.'''
-
+ """
+ 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:
@@ -418,10 +910,13 @@ class Connection(ConnectionHandlers):
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)'''
+ """
+ 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, ''
@@ -803,10 +1298,6 @@ class Connection(ConnectionHandlers):
self.on_connect_auth = None
# END connect
- def quit(self, kill_core):
- if kill_core and gajim.account_is_connected(self.name):
- self.disconnect(on_purpose=True)
-
def add_lang(self, stanza):
if self.lang:
stanza.setAttr('xml:lang', self.lang)
@@ -821,9 +1312,16 @@ class Connection(ConnectionHandlers):
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.'''
+ """
+ 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()
@@ -847,7 +1345,7 @@ class Connection(ConnectionHandlers):
timePing = time_time()
self.connection.SendAndCallForResponse(iq, _on_response)
else:
- self.connection.send(iq)
+ 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'))
@@ -891,7 +1389,9 @@ class Connection(ConnectionHandlers):
common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
def build_privacy_rule(self, name, action, order=1):
- '''Build a Privacy rule stanza for invisibility'''
+ """
+ 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)})
@@ -924,7 +1424,9 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
def activate_privacy_rule(self, name):
- '''activate a privacy rule'''
+ """
+ Activate a privacy rule
+ """
if not self.connection:
return
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
@@ -986,45 +1488,11 @@ class Connection(ConnectionHandlers):
#Inform GUI we just signed in
self.dispatch('SIGNED_IN', ())
- 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_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 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 connect_and_auth(self):
self.on_connect_success = self._connect_success
self.on_connect_failure = self._connect_failure
@@ -1045,6 +1513,13 @@ class Connection(ConnectionHandlers):
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 = '')
@@ -1078,92 +1553,30 @@ class Connection(ConnectionHandlers):
p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
self.connection.send(p)
- def change_status(self, show, msg, auto = False):
- if not show in gajim.SHOW_LIST:
- return -1
- sshow = helpers.get_xmpp_show(show)
- 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 = gajim.config.get_per('accounts', self.name,
- 'resource')
- # All valid resource substitution strings should be added to this hash.
- if self.server_resource:
- self.server_resource = Template(self.server_resource).\
- safe_substitute({
- 'hostname': socket.gethostname()
- })
- 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_to_invisible(self, msg):
+ signed = self.get_signed_presence(msg)
+ self.send_invisible_presence(msg, signed)
- elif show == 'offline':
- self.connected = 0
- if self.connection:
- self.terminate_sessions()
-
- 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.time_to_reconnect = None
-
- self.connection.RegisterDisconnectHandler(self._on_disconnected)
- self.connection.send(p, now=True)
- self.connection.start_disconnect()
- #self.connection.start_disconnect(p, self._on_disconnected)
- else:
- self.time_to_reconnect = None
- 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':
- signed = self.get_signed_presence(msg)
- self.send_invisible_presence(msg, signed)
- return
- was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
- self.connected = gajim.SHOW_LIST.index(show)
- if was_invisible and self.privacy_rules_supported:
- iq = self.build_privacy_rule('visible', 'allow')
- self.connection.send(iq)
- self.activate_privacy_rule('visible')
- priority = unicode(gajim.get_priority(self.name, sshow))
- p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
- 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 _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 _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:
@@ -1177,205 +1590,28 @@ class Connection(ConnectionHandlers):
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=[]):
- if not self.connection or self.connected < 2:
- return 1
- try:
- jid = helpers.parse_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._on_message_encrypted(output, type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml,
- subject, chatstate, composing_xep, forward_from,
- delayed, session, form_node, user_nick, keyID,
- callback, callback_args)
- self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust)
- else:
- self._on_message_encrypted(output, type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml, subject,
- chatstate, composing_xep, forward_from, delayed, session,
- form_node, user_nick, keyID, callback, callback_args)
- gajim.thread_interface(encrypt_thread, [msg, keyID, False],
- _on_encrypted, [])
- return
-
- self._on_message_encrypted(('', error), type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml, subject, chatstate,
- composing_xep, forward_from, delayed, session, form_node, user_nick,
- keyID, callback, callback_args)
-
- 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,
- callback_args)
-
- def _on_message_encrypted(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, callback_args):
- 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, callback_args)
- 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,
- callback_args):
- 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 <body>
- # 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)
+ 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)
- if session:
- # XEP-0201
- session.last_send = time.time()
- msg_iq.setThread(session.thread_id)
+ self.log_message(jid, msg, forward_from, session, original_message,
+ subject, type_)
- # XEP-0200
- if session.enable_encryption:
- msg_iq = session.encrypt_stanza(msg_iq)
-
- msg_id = self.connection.send(msg_iq)
- 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': 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
- self.dispatch('MSGSENT', (jid, msg, keyID))
-
- if callback:
- callback(msg_id, *callback_args)
+ 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)'''
+ """
+ Send contacts with RosterX (Xep-0144)
+ """
if not self.connection:
return
if len(contacts) == 1:
@@ -1394,7 +1630,9 @@ class Connection(ConnectionHandlers):
self.connection.send(msg_iq)
def send_stanza(self, stanza):
- ''' send a stanza untouched '''
+ """
+ Send a stanza untouched
+ """
if not self.connection:
return
self.connection.send(stanza)
@@ -1413,8 +1651,8 @@ class Connection(ConnectionHandlers):
p = common.xmpp.Presence(jid, 'unsubscribe')
self.connection.send(p)
- def request_subscription(self, jid, msg = '', name = '', groups = [],
- auto_auth = False, user_nick = ''):
+ 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)
@@ -1426,7 +1664,7 @@ class Connection(ConnectionHandlers):
infos['name'] = name
iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER)
q = iq.getTag('query')
- item = q.addChild('item', attrs = infos)
+ item = q.addChild('item', attrs=infos)
for g in groups:
item.addChild('group').setData(g)
self.connection.send(iq)
@@ -1477,17 +1715,6 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
self.connection.getRoster().delItem(agent)
- def update_contact(self, jid, name, groups):
- '''update roster item on jabber server'''
- if self.connection:
- self.connection.getRoster().setItem(jid = jid, name = name,
- groups = groups)
-
- def update_contacts(self, contacts):
- '''update multiple roster items on jabber server'''
- if self.connection:
- self.connection.getRoster().setItemMulti(contacts)
-
def send_new_account_infos(self, form, is_form):
if is_form:
# Get username and password and put them in new_account_info
@@ -1505,7 +1732,7 @@ class Connection(ConnectionHandlers):
self.new_account_form = form
self.new_account(self.name, self.new_account_info)
- def new_account(self, name, config, sync = False):
+ def new_account(self, name, config, sync=False):
# If a connection already exist we cannot create a new account
if self.connection:
return
@@ -1516,7 +1743,7 @@ class Connection(ConnectionHandlers):
self.on_connect_failure = self._on_new_account
self.connect(config)
- def _on_new_account(self, con = None, con_type = None):
+ 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
@@ -1528,12 +1755,11 @@ class Connection(ConnectionHandlers):
self.connection = con
common.xmpp.features_nb.getRegInfo(con, self._hostname)
- def account_changed(self, new_name):
- self.name = new_name
-
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'''
+ """
+ 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
@@ -1549,8 +1775,10 @@ class Connection(ConnectionHandlers):
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'''
+ """
+ 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
@@ -1570,8 +1798,10 @@ class Connection(ConnectionHandlers):
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'''
+ """
+ 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
@@ -1591,7 +1821,9 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
def get_settings(self):
- ''' Get Gajim settings as described in XEP 0049 '''
+ """
+ Get Gajim settings as described in XEP 0049
+ """
if not self.connection:
return
iq = common.xmpp.Iq(typ='get')
@@ -1612,9 +1844,12 @@ class Connection(ConnectionHandlers):
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'''
+ """
+ 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':
@@ -1626,9 +1861,12 @@ class Connection(ConnectionHandlers):
self._request_bookmarks_xml()
def store_bookmarks(self, storage_type=None):
- ''' Send bookmarks to the storage namespace or PubSub if supported
+ """
+ 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'''
+ else it will be stored on both
+ """
if not self.connection:
return
iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'})
@@ -1670,7 +1908,9 @@ class Connection(ConnectionHandlers):
self.connection.send(iqA)
def get_annotations(self):
- '''Get Annonations from storage as described in XEP 0048, and XEP 0145'''
+ """
+ Get Annonations from storage as described in XEP 0048, and XEP 0145
+ """
self.annotations = {}
if not self.connection:
return
@@ -1680,7 +1920,9 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
def store_annotations(self):
- '''Set Annonations in private storage as described in XEP 0048, and XEP 0145'''
+ """
+ Set Annonations in private storage as described in XEP 0048, and XEP 0145
+ """
if not self.connection:
return
iq = common.xmpp.Iq(typ='set')
@@ -1695,7 +1937,9 @@ class Connection(ConnectionHandlers):
def get_metacontacts(self):
- '''Get metacontacts list from storage as described in XEP 0049'''
+ """
+ Get metacontacts list from storage as described in XEP 0049
+ """
if not self.connection:
return
iq = common.xmpp.Iq(typ='get')
@@ -1707,7 +1951,9 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
def store_metacontacts(self, tags_list):
- ''' Send meta contacts to the storage namespace '''
+ """
+ Send meta contacts to the storage namespace
+ """
if not self.connection:
return
iq = common.xmpp.Iq(typ='set')
@@ -1787,7 +2033,7 @@ class Connection(ConnectionHandlers):
last_date = time.time() - gajim.config.get(
'muc_restore_timeout') * 60
else:
- last_time = min(last_date, time.time() - gajim.config.get(
+ 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(
@@ -1855,16 +2101,19 @@ class Connection(ConnectionHandlers):
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)
+ """
+ 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'''
+ """
+ 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 =\
@@ -1877,7 +2126,9 @@ class Connection(ConnectionHandlers):
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'''
+ """
+ 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 =\
@@ -1921,26 +2172,6 @@ class Connection(ConnectionHandlers):
query.addChild(node = form)
self.connection.send(iq)
- 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 change_password(self, password):
if not self.connection:
return
@@ -1992,7 +2223,9 @@ class Connection(ConnectionHandlers):
_on_unregister_account_connect(self.connection)
def send_invite(self, room, to, reason='', continue_tag=False):
- '''sends invitation'''
+ """
+ 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})
@@ -2005,6 +2238,7 @@ class Connection(ConnectionHandlers):
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):
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 11eefcd76..0cf0ab6b2 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -30,7 +30,6 @@
import os
import base64
-import socket
import sys
import operator
import hashlib
@@ -41,17 +40,17 @@ from time import (altzone, daylight, gmtime, localtime, mktime, strftime,
from calendar import timegm
import datetime
-import socks5
import common.xmpp
from common import helpers
from common import gajim
-from common import atom
-from common import pep
from common import exceptions
from common.commands import ConnectionCommands
from common.pubsub import ConnectionPubSub
-from common.caps import ConnectionCaps
+from common.pep import ConnectionPEP
+from common.protocol.caps import ConnectionCaps
+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
@@ -80,600 +79,32 @@ PRIVACY_ARRIVED = 'privacy_arrived'
PEP_CONFIG = 'pep_config'
HAS_IDLE = True
try:
- import idle
+# import idle
+ import common.sleepy
except Exception:
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 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 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_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'])
-
- 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'])
-
- 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
-
- 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'])
-
- # 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_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_request(self, file_props):
- ''' send iq for new FT request '''
- if not self.connection or self.connected < 2:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- resource = self.server_resource
- frm = our_jid + '/' + resource
- file_props['sender'] = frm
- fjid = file_props['receiver'].jid + '/' + file_props['receiver'].resource
- 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 _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)
-
- # 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 _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': helpers.get_full_jid_from_iq(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
-
- 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 = helpers.get_full_jid_from_iq(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 _bytestreamResultCB(self, con, iq_obj):
- log.debug('_bytestreamResultCB')
- frm = helpers.get_full_jid_from_iq(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 = helpers.parse_jid(streamhost.getAttr('jid'))
- 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
-
- 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
-
- 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
-
- 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'] = helpers.get_full_jid_from_iq(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 = helpers.get_jid_from_iq(iq_obj)
- file_props = {'type': 'r'}
- file_props['sender'] = helpers.get_full_jid_from_iq(iq_obj)
- 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'] = our_jid + '/' + resource
- 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 = helpers.get_jid_from_iq(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'''
+ """
+ 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.'''
+ """
+ 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:
+ """
+ According to XEP-0030:
For identity: category, type is mandatory, name is optional.
- For feature: var is mandatory'''
+ For feature: var is mandatory.
+ """
self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix)
def request_register_agent_info(self, agent):
@@ -693,6 +124,12 @@ class ConnectionDisco:
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') % {
@@ -703,20 +140,23 @@ class ConnectionDisco:
if not self.connection or self.connected < 2:
return
if is_form:
- iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
+ iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=agent)
query = iq.getTag('query')
info.setAttr('type', 'submit')
- query.addChild(node = info)
+ 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)
+ 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):
+ 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)
+ iq = common.xmpp.Iq(typ='get', to=jid, queryNS=ns)
if id_prefix:
id_ = self.connection.getAnID()
iq.setID('%s%s' % (id_prefix, id_))
@@ -729,18 +169,19 @@ class ConnectionDisco:
self._IqCB(con, resp)
def _discoGetCB(self, con, iq_obj):
- ''' get disco info '''
+ """
+ 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 = 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])
+ 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')
@@ -781,8 +222,7 @@ class ConnectionDisco:
continue
items.append(attr)
jid = helpers.get_full_jid_from_iq(iq_obj)
- hostname = gajim.config.get_per('accounts', self.name,
- 'hostname')
+ hostname = gajim.config.get_per('accounts', self.name, 'hostname')
id_ = iq_obj.getID()
if jid == hostname and id_[:6] == 'Gajim_':
for item in items:
@@ -945,10 +385,10 @@ class ConnectionVcard:
if self.vcard_sha is not None:
c.setTagData('photo', self.vcard_sha)
if send_caps:
- return self.add_caps(p)
+ return self._add_caps(p)
return p
- def add_caps(self, 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')
@@ -956,7 +396,7 @@ class ConnectionVcard:
c.setAttr('ver', gajim.caps_hash[self.name])
return p
- def node_to_dict(self, node):
+ def _node_to_dict(self, node):
dict_ = {}
for info in node.getChildren():
name = info.getName()
@@ -974,7 +414,7 @@ class ConnectionVcard:
dict_[name][c.getName()] = c.getData()
return dict_
- def save_vcard_to_hd(self, full_jid, card):
+ 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)
@@ -999,9 +439,11 @@ class ConnectionVcard:
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'''
+ """
+ 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:
@@ -1021,7 +463,7 @@ class ConnectionVcard:
# We are unable to parse it. Remove it
os.remove(path_to_file)
return None
- vcard = self.node_to_dict(card)
+ vcard = self._node_to_dict(card)
if 'PHOTO' in vcard:
if not isinstance(vcard['PHOTO'], dict):
del vcard['PHOTO']
@@ -1035,10 +477,14 @@ class ConnectionVcard:
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'''
+ 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')
@@ -1126,7 +572,7 @@ class ConnectionVcard:
# Save it to file
our_jid = gajim.get_jid_from_account(self.name)
- self.save_vcard_to_hd(our_jid, vcard_iq)
+ 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] !=\
@@ -1155,7 +601,7 @@ class ConnectionVcard:
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, '')
+ 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:
@@ -1233,8 +679,9 @@ class ConnectionVcard:
del self.awaiting_answers[id_]
def _vCardCB(self, con, vc):
- '''Called when we receive a vCard
- Parse the vCard and send it to plugins'''
+ """
+ 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:
@@ -1253,7 +700,7 @@ class ConnectionVcard:
else:
who = frm = our_jid
card = vc.getChildren()[0]
- vcard = self.node_to_dict(card)
+ vcard = self._node_to_dict(card)
photo_decoded = None
if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
'BINVAL' in vcard['PHOTO']:
@@ -1270,7 +717,7 @@ class ConnectionVcard:
card.getTag('PHOTO').setTagData('SHA', avatar_sha)
# Save it to file
- self.save_vcard_to_hd(who, card)
+ 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
@@ -1340,8 +787,9 @@ class ConnectionHandlersBase:
self.sessions = {}
def get_sessions(self, jid):
- '''get all sessions for the given full 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)
@@ -1351,9 +799,10 @@ class ConnectionHandlersBase:
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.'''
-
+ """
+ Return an existing session between this connection and 'jid', returns a
+ new one if none exist
+ """
pm = True
jid = fjid
@@ -1381,7 +830,9 @@ class ConnectionHandlersBase:
return None
def terminate_sessions(self, send_termination=False):
- '''send termination messages and delete all active sessions'''
+ """
+ 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)
@@ -1400,10 +851,11 @@ class ConnectionHandlersBase:
del self.sessions[jid]
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.'''
-
+ """
+ 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
@@ -1420,8 +872,9 @@ sent a message to.'''
return None
def find_controlless_session(self, jid, resource=None):
- '''find an active session that doesn't have a control attached'''
-
+ """
+ Find an active session that doesn't have a control attached
+ """
try:
sessions = self.sessions[jid].values()
@@ -1439,8 +892,12 @@ sent a message to.'''
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'.'''
+ """
+ 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
@@ -1458,12 +915,20 @@ sent a message to.'''
return sess
-class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
+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
@@ -1482,9 +947,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
self.continue_connect_info = None
try:
- idle.init()
+ self.sleeper = common.sleepy.Sleepy()
+# idle.init()
+ HAS_IDLE = True
except Exception:
- global HAS_IDLE
HAS_IDLE = False
self.gmail_last_tid = None
@@ -1530,16 +996,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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')
@@ -1566,8 +1030,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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'''
+ """
+ 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
@@ -1650,15 +1115,19 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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
- iq_obj = iq_obj.buildReply('result')
- qp = iq_obj.getTag('query')
- if not HAS_IDLE:
- qp.attrs['seconds'] = '0'
+ 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:
- qp.attrs['seconds'] = idle.getIdleSec()
+ 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
@@ -1741,7 +1210,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
# wrong answer
return
tzo = qp.getTag('tzo').getData()
- if tzo == 'Z':
+ if tzo.lower() == 'z':
tzo = '0:0'
tzoh, tzom = tzo.split(':')
utc_time = qp.getTag('utc').getData()
@@ -1781,7 +1250,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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'''
+ """
+ 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'):
@@ -1803,7 +1274,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
raise common.xmpp.NodeProcessed
def _gMailQueryCB(self, con, gm):
- '''Called when we receive results from Querying the server for mail messages in gmail account'''
+ """
+ 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')
@@ -1849,7 +1323,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
raise common.xmpp.NodeProcessed
def _rosterItemExchangeCB(self, con, msg):
- ''' XEP-0144 Roster Item Echange '''
+ """
+ XEP-0144 Roster Item Echange
+ """
exchange_items_list = {}
jid_from = helpers.get_full_jid_from_iq(msg)
items_list = msg.getTag('x').getChildren()
@@ -1866,9 +1342,23 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
continue
name = item.getAttr('name')
- groups=[]
+ 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)
@@ -1877,7 +1367,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
raise common.xmpp.NodeProcessed
def _messageCB(self, con, msg):
- '''Called when we receive a message'''
+ """
+ Called when we receive a message
+ """
log.debug('MessageCB')
gajim.nec.push_incoming_event(NetworkEvent('raw-message-received',
@@ -1887,13 +1379,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
mtype = msg.getType()
- # check if the message is pubsub#event
- if msg.getTag('event') is not None:
- if mtype == 'groupchat':
- return
- if msg.getTag('error') is None:
- self._pubsubEventCB(con, msg)
- return
# check if the message is a roster item exchange (XEP-0144)
if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX):
@@ -1911,6 +1396,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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)
@@ -2133,6 +1619,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
# 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:
@@ -2159,47 +1650,12 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
self.dispatch('GC_INVITATION',(frm, jid_from, reason, password,
is_continued))
- def _pubsubEventCB(self, con, msg):
- ''' Called when we receive <message/> with pubsub event. '''
- # TODO: Logging? (actually services where logging would be useful, should
- # TODO: allow to access archives remotely...)
- jid = helpers.get_full_jid_from_iq(msg)
- event = msg.getTag('event')
-
- # XEP-0107: User Mood
- items = event.getTag('items', {'node': common.xmpp.NS_MOOD})
- if items: pep.user_mood(items, self.name, jid)
- # XEP-0118: User Tune
- items = event.getTag('items', {'node': common.xmpp.NS_TUNE})
- if items: pep.user_tune(items, self.name, jid)
- # XEP-0080: User Geolocation
- items = event.getTag('items', {'node': common.xmpp.NS_GEOLOC})
- if items: pep.user_geoloc(items, self.name, jid)
- # XEP-0108: User Activity
- items = event.getTag('items', {'node': common.xmpp.NS_ACTIVITY})
- if items: pep.user_activity(items, self.name, jid)
- # XEP-0172: User Nickname
- items = event.getTag('items', {'node': common.xmpp.NS_NICK})
- if items: pep.user_nickname(items, self.name, jid)
-
- items = event.getTag('items')
- if items is None: return
-
- for item in items.getTags('item'):
- entry = item.getTag('entry')
- if entry is not None:
- # for each entry in feed (there shouldn't be more than one,
- # but to be sure...
- self.dispatch('ATOM_ENTRY', (atom.OldEntry(node=entry),))
- continue
- # unknown type... probably user has another client who understands that event
- raise common.xmpp.NodeProcessed
-
def _presenceCB(self, con, prs):
- '''Called when we receive a presence'''
+ """
+ Called when we receive a presence
+ """
gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received',
- conn = con,
- xmpp_pres = prs))
+ conn=con, xmpp_pres=prs))
ptype = prs.getType()
if ptype == 'available':
ptype = None
@@ -2301,7 +1757,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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.
@@ -2407,6 +1863,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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:
@@ -2486,9 +1947,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
# 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)
+ 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]
@@ -2582,11 +2043,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
raise common.xmpp.NodeProcessed
def _PrivacySetCB(self, con, iq_obj):
- '''
+ """
Privacy lists (XEP 016)
- A list has been set
- '''
+ A list has been set.
+ """
log.debug('PrivacySetCB')
if not self.connection or self.connected < 2:
return
@@ -2686,6 +2147,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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)
@@ -2765,6 +2228,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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',
diff --git a/src/common/contacts.py b/src/common/contacts.py
index 86175567f..439e5f5c0 100644
--- a/src/common/contacts.py
+++ b/src/common/contacts.py
@@ -28,31 +28,34 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
+
+from common import caps_cache
+from common.account import Account
import common.gajim
-import caps
-from account import Account
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
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)
-
+
self.show = show
self.status = status
self.name = name
-
- self.client_caps = client_caps or caps.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'
@@ -67,18 +70,18 @@ class CommonContact(XMPPEntity):
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
+ """
+ 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
@@ -86,33 +89,34 @@ class CommonContact(XMPPEntity):
# return caps for a contact that has no resources left.
return False
else:
- return caps.client_supports(self.client_caps, requested_feature)
+ return caps_cache.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, mood={}, tune={}, activity={}):
-
- CommonContact.__init__(self, jid, account, resource, show, status, name,
+ """
+ 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)
-
+
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.priority = priority
self.keyID = keyID
self.msg_id = msg_id
self.last_status_time = last_status_time
-
- self.mood = mood.copy()
- self.tune = tune.copy()
- self.activity = activity.copy()
+ self.last_activity_time = last_activity_time
+
+ self.pep = {}
def get_full_jid(self):
if self.resource:
@@ -139,7 +143,9 @@ class Contact(CommonContact):
return self.groups
def is_hidden_from_roster(self):
- '''if contact should not be visible in roster'''
+ """
+ If contact should not be visible in roster
+ """
# XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
if self.is_transport():
return False
@@ -169,54 +175,68 @@ class Contact(CommonContact):
def is_transport(self):
# if not '@' or '@' starts the jid then contact is transport
- if self.jid.find('@') <= 0:
- return True
- return False
+ 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):
-
+ 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'''
+ """
+ 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)
-
+ 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):
+
+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 = {}
-
- def change_account_name(self, old_name, new_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]
-
+
self._metacontact_manager.change_account_name(old_name, new_name)
def add_account(self, account_name):
- self._accounts[account_name] = Account(account_name,
- Contacts_New(), GC_Contacts())
+ self._accounts[account_name] = Account(account_name, Contacts(),
+ GC_Contacts())
self._metacontact_manager.add_account(account_name)
def get_accounts(self):
@@ -226,27 +246,31 @@ class Contacts:
del self._accounts[account]
self._metacontact_manager.remove_account(account)
- 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, mood={}, tune={}, activity={}):
- account = self._accounts.get(account, account) # Use Account object if available
+ 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, mood=mood, tune=tune, activity=activity)
-
- def create_self_contact(self, jid, account, resource, show, status, priority, keyID=''):
+ 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 create_self_contact(self, jid, account, resource, show, status, priority,
+ name='', keyID=''):
conn = common.gajim.connections[account]
- nick = common.gajim.nicks[account]
+ nick = name or common.gajim.nicks[account]
account = self._accounts.get(account, account) # Use Account object if available
- return self.create_contact(jid=jid, account=account,
+ 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, mood=conn.mood, tune=conn.tune,
- activity=conn.activity)
-
+ 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,
@@ -254,12 +278,15 @@ class Contacts:
status='', sub='none', keyID=keyID)
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)
+ 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 add_contact(self, account, contact):
if account not in self._accounts:
@@ -275,7 +302,7 @@ class Contacts:
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)
@@ -288,13 +315,13 @@ class Contacts:
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 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()
@@ -318,10 +345,11 @@ class Contacts:
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'''
+ """
+ Return the number of online contacts and the total number of contacts
+ """
if accounts == []:
accounts = self.get_accounts()
nbr_online = 0
@@ -357,25 +385,13 @@ class Contacts:
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
@@ -402,15 +418,18 @@ class Contacts:
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():
-
+
+
+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 = {}
-
- def add_contact(self, contact):
+
+ def add_contact(self, contact):
if contact.jid not in self._contacts:
self._contacts[contact.jid] = [contact]
return
@@ -426,7 +445,7 @@ class Contacts_New():
self.remove_contact(c)
break
contacts.append(contact)
-
+
def remove_contact(self, contact):
if contact.jid not in self._contacts:
return
@@ -434,56 +453,60 @@ class Contacts_New():
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.'''
+ """
+ Remove all contacts for a given jid
+ """
if jid in self._contacts:
- return self._contacts[jid]
- else:
- return []
-
+ 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_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'''
+ """
+ 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
- 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'''
+ """
+ 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'''
+ """
+ Return all contacts in the given group
+ """
group_contacts = []
for jid in self._contacts:
contacts = self.get_contacts(jid)
@@ -502,11 +525,11 @@ class Contacts_New():
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}
@@ -524,9 +547,8 @@ class GC_Contacts():
del self._rooms[gc_contact.room_jid]
def remove_room(self, room_jid):
- if room_jid not in self._rooms:
- return
- del self._rooms[room_jid]
+ if room_jid in self._rooms:
+ del self._rooms[room_jid]
def get_gc_list(self):
return self._rooms.keys()
@@ -544,8 +566,10 @@ class GC_Contacts():
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'''
+ """
+ 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
@@ -554,25 +578,25 @@ class GC_Contacts():
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
@@ -582,13 +606,15 @@ class MetacontactManager():
#FIXME: can this append ?
assert False
- def iter_metacontacts_families(self, account):
+ 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):
- '''Returns the tag of a jid'''
+ """
+ Return the tag of a jid
+ """
if not account in self._metacontacts_tags:
return None
for tag in self._metacontacts_tags[account]:
@@ -625,8 +651,8 @@ class MetacontactManager():
def remove_metacontact(self, account, jid):
if not account in self._metacontacts_tags:
- return None
-
+ return
+
found = None
for tag in self._metacontacts_tags[account]:
for data in self._metacontacts_tags[account][tag]:
@@ -657,7 +683,9 @@ class MetacontactManager():
return False
def _get_metacontacts_jids(self, tag, accounts):
- '''Returns all jid for the given tag in the form {acct: [jid1, jid2],.}'''
+ """
+ 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]:
@@ -669,9 +697,10 @@ class MetacontactManager():
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'''
+ """
+ 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)
@@ -687,9 +716,12 @@ class MetacontactManager():
return answers
def _compare_metacontacts(self, data1, data2):
- '''compare 2 metacontacts.
- Data is {'jid': jid, 'account': account, 'order': order}
- order is optional'''
+ """
+ Compare 2 metacontacts
+
+ Data is {'jid': jid, 'account': account, 'order': order} order is
+ optional
+ """
jid1 = data1['jid']
jid2 = data2['jid']
account1 = data1['account']
@@ -765,16 +797,17 @@ class MetacontactManager():
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
+ """
+ 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
@@ -789,9 +822,11 @@ class MetacontactManager():
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 ?'''
+ """
+ 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:
diff --git a/src/common/dataforms.py b/src/common/dataforms.py
index bd9c3e15f..048077a74 100644
--- a/src/common/dataforms.py
+++ b/src/common/dataforms.py
@@ -21,8 +21,10 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-""" This module contains wrappers for different parts of data forms (JEP 0004).
-For information how to use them, read documentation. """
+"""
+This module contains wrappers for different parts of data forms (JEP 0004). For
+information how to use them, read documentation
+"""
import xmpp
@@ -72,7 +74,9 @@ def Field(typ, **attrs):
return f
def ExtendField(node):
- ''' Helper function to extend a node to field of appropriate type. '''
+ """
+ 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...
@@ -94,19 +98,23 @@ def ExtendField(node):
return f[typ](extend=node)
def ExtendForm(node):
- ''' Helper function to extend a node to form of appropriate type. '''
+ """
+ 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):
+ required=False, options=None, extend=None):
if extend is None:
ExtendedNode.__init__(self, 'field')
@@ -124,10 +132,12 @@ class DataField(ExtendedNode):
@nested_property
def type():
- '''Type of field. Recognized values are: 'boolean', 'fixed', 'hidden',
+ """
+ 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.'''
+ DataField will store given name, but treat all data as text-single
+ """
def fget(self):
t = self.getAttr('type')
if t is None:
@@ -142,7 +152,9 @@ class DataField(ExtendedNode):
@nested_property
def var():
- '''Field identifier.'''
+ """
+ Field identifier
+ """
def fget(self):
return self.getAttr('var')
@@ -157,7 +169,9 @@ class DataField(ExtendedNode):
@nested_property
def label():
- '''Human-readable field name.'''
+ """
+ Human-readable field name
+ """
def fget(self):
l = self.getAttr('label')
if not l:
@@ -176,7 +190,9 @@ class DataField(ExtendedNode):
@nested_property
def description():
- '''Human-readable description of field meaning.'''
+ """
+ Human-readable description of field meaning
+ """
def fget(self):
return self.getTagData('desc') or u''
@@ -196,7 +212,9 @@ class DataField(ExtendedNode):
@nested_property
def required():
- '''Controls whether this field required to fill. Boolean.'''
+ """
+ Controls whether this field required to fill. Boolean
+ """
def fget(self):
return bool(self.getTag('required'))
@@ -212,7 +230,9 @@ class DataField(ExtendedNode):
class BooleanField(DataField):
@nested_property
def value():
- '''Value of field. May contain True, False or None.'''
+ """
+ Value of field. May contain True, False or None
+ """
def fget(self):
v = self.getTagData('value')
if v in ('0', 'false'):
@@ -234,10 +254,15 @@ class BooleanField(DataField):
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.'''
+ """
+ Value of field. May be any unicode string
+ """
def fget(self):
return self.getTagData('value') or u''
@@ -256,10 +281,15 @@ class StringField(DataField):
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.'''
+ """
+ Options
+ """
def fget(self):
options = []
for element in self.getTags('option'):
@@ -294,14 +324,21 @@ class ListField(DataField):
yield (v, l)
class ListSingleField(ListField, StringField):
- '''Covers list-single and jid-single fields.'''
+ """
+ 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.'''
+ """
+ Values held in field
+ """
def fget(self):
values = []
for element in self.getTags('value'):
@@ -326,7 +363,9 @@ class ListMultiField(ListField):
class TextMultiField(DataField):
@nested_property
def value():
- '''Value held in field.'''
+ """
+ Value held in field
+ """
def fget(self):
value = u''
for element in self.iterTags('value'):
@@ -347,8 +386,10 @@ class TextMultiField(DataField):
return locals()
class DataRecord(ExtendedNode):
- '''The container for data fields - an xml element which has DataField
- elements as children.'''
+ """
+ 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 = {}
@@ -373,7 +414,9 @@ class DataRecord(ExtendedNode):
@nested_property
def fields():
- '''List of fields in this record.'''
+ """
+ List of fields in this record
+ """
def fget(self):
return self.getTags('field')
@@ -391,14 +434,17 @@ class DataRecord(ExtendedNode):
return locals()
def iter_fields(self):
- ''' Iterate over fields in this record. Do not take associated
- into account. '''
+ """
+ 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. '''
+ """
+ Iterate over associated, yielding both our field and associated one
+ together
+ """
for field in self.associated.iter_fields():
yield self[field.var], field
@@ -420,9 +466,11 @@ class DataForm(ExtendedNode):
@nested_property
def type():
- ''' Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
+ """
+ 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)...'''
+ filledform = DataForm(replyto=thisform)
+ """
def fget(self):
return self.getAttr('type')
@@ -434,7 +482,11 @@ class DataForm(ExtendedNode):
@nested_property
def title():
- ''' Title of the form. Human-readable, should not contain any \\r\\n.'''
+ """
+ Title of the form
+
+ Human-readable, should not contain any \\r\\n.
+ """
def fget(self):
return self.getTagData('title')
@@ -451,7 +503,11 @@ class DataForm(ExtendedNode):
@nested_property
def instructions():
- ''' Instructions for this form. Human-readable, may contain \\r\\n. '''
+ """
+ Instructions for this form
+
+ Human-readable, may contain \\r\\n.
+ """
# TODO: the same code is in TextMultiField. join them
def fget(self):
value = u''
@@ -520,7 +576,9 @@ class MultipleDataForm(DataForm):
@nested_property
def items():
- ''' A list of all records. '''
+ """
+ A list of all records
+ """
def fget(self):
return list(self.iter_records())
@@ -543,7 +601,9 @@ class MultipleDataForm(DataForm):
# @nested_property
# def reported():
-# ''' DataRecord that contains descriptions of fields in records.'''
+# """
+# DataRecord that contains descriptions of fields in records
+# """
# def fget(self):
# return self.getTag('reported')
# def fset(self, record):
diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py
index ae2016603..4762b474d 100644
--- a/src/common/dbus_support.py
+++ b/src/common/dbus_support.py
@@ -51,7 +51,10 @@ else:
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
@@ -60,7 +63,7 @@ class SystemBus:
raise exceptions.DbusNotSupported
if not self.present():
- raise exceptions.SystemBusNotPresent
+ raise exceptions.SystemBusNotPresent
return self.system_bus
def bus(self):
@@ -84,7 +87,10 @@ class SystemBus:
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
@@ -115,8 +121,10 @@ class SessionBus:
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.'''
+ """
+ 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():
@@ -144,9 +152,11 @@ def get_interface(interface, path, start_service=True):
def get_notifications_interface(notif=None):
- '''Returns the notifications interface.
+ """
+ Get the notifications interface
- :param notif: DesktopNotification instance'''
+ :param notif: DesktopNotification instance
+ """
# try to see if KDE notifications are available
iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications',
start_service=False)
diff --git a/src/common/defs.py b/src/common/defs.py
index 6640c3b32..11a8d23a6 100644
--- a/src/common/defs.py
+++ b/src/common/defs.py
@@ -24,10 +24,10 @@
##
docdir = '../'
-datadir = '../'
+basedir = '../'
localedir = '../po'
-version = '0.13.0.1-dev'
+version = '0.13.10.1-dev'
import sys, os.path
for base in ('.', 'common'):
diff --git a/src/common/dh.py b/src/common/dh.py
index 4b038ea93..b13cdefc0 100644
--- a/src/common/dh.py
+++ b/src/common/dh.py
@@ -19,12 +19,13 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-'''
+"""
This module defines a number of constants; specifically, large primes suitable
-for use with the Diffie-Hellman key exchange.
+for use with the Diffie-Hellman key exchange
These constants have been obtained from RFC2409 and RFC3526.
-'''
+"""
+
import string
generators = [ None, # one to get the right offset
diff --git a/src/common/events.py b/src/common/events.py
index 4e5068652..ccf5744f1 100644
--- a/src/common/events.py
+++ b/src/common/events.py
@@ -27,13 +27,18 @@
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,
+ 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]
@@ -47,7 +52,7 @@ class Event:
subscription_request: [text, nick]
unsubscribed: contact
jingle-incoming: (fulljid, sessionid, content_types)
- '''
+ """
self.type_ = type_
self.time_ = time_
self.parameters = parameters
@@ -58,29 +63,40 @@ class Event:
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 event_added_subscribe(self, listener):
- '''Add a listener when an event is added to the queue'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ Remove a listener when an event is removed from the queue
+ """
if listener in self._event_removed_listeners:
self._event_removed_listeners.remove(listener)
@@ -125,9 +141,10 @@ class Events:
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 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]:
@@ -177,10 +194,11 @@ class Events:
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'''
+ """
+ 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:
@@ -202,7 +220,9 @@ class Events:
return events_list
def get_first_event(self, account, jid = None, type_ = None):
- '''Return the first event of type type_ if given'''
+ """
+ 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
@@ -213,9 +233,11 @@ class Events:
first_event = event
return first_event
- def _get_nb_events(self, account = None, jid = None, attribute = None,
- types = []):
- '''return the number of pending events'''
+ def _get_nb_events(self, account = None, jid = None, attribute = None, types
+ = []):
+ """
+ Return the number of pending events
+ """
nb = 0
if account:
accounts = [account]
@@ -241,7 +263,9 @@ class Events:
return nb
def _get_some_events(self, attribute):
- '''attribute in systray, roster'''
+ """
+ Attribute in systray, roster
+ """
events = {}
for account in self._events:
events[account] = {}
@@ -258,8 +282,11 @@ class Events:
return events
def _get_first_event_with_attribute(self, events):
- '''get the first event
- events is in the form {account1: {jid1: [ev1, ev2], },. }'''
+ """
+ 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
@@ -276,12 +303,16 @@ class Events:
return first_account, first_jid, first_event
def get_nb_systray_events(self, types = []):
- '''returns the number of events displayed in roster'''
+ """
+ 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 all events that must be displayed in systray:
+ {account1: {jid1: [ev1, ev2], },. }
+ """
return self._get_some_events('systray')
def get_first_systray_event(self):
@@ -289,13 +320,17 @@ class 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 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 all events that must be displayed in roster:
+ {account1: {jid1: [ev1, ev2], },. }
+ """
return self._get_some_events('roster')
# vim: se ts=3:
diff --git a/src/common/exceptions.py b/src/common/exceptions.py
index 9f20e1d97..090255768 100644
--- a/src/common/exceptions.py
+++ b/src/common/exceptions.py
@@ -21,16 +21,11 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-class PysqliteNotAvailable(Exception):
- '''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...')
-
class PysqliteOperationalError(Exception):
- '''sqlite2 raised pysqlite2.dbapi2.OperationalError'''
+ """
+ Sqlite2 raised pysqlite2.dbapi2.OperationalError
+ """
+
def __init__(self, text=''):
Exception.__init__(self)
self.text = text
@@ -39,7 +34,10 @@ class PysqliteOperationalError(Exception):
return self.text
class DatabaseMalformed(Exception):
- '''The databas can't be read'''
+ """
+ The databas can't be read
+ """
+
def __init__(self):
Exception.__init__(self)
@@ -47,7 +45,10 @@ class DatabaseMalformed(Exception):
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)
@@ -55,7 +56,10 @@ class ServiceNotAvailable(Exception):
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)
@@ -63,27 +67,52 @@ class DbusNotSupported(Exception):
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 __str__(self):
- return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus')
+ 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
+ """
+
+ 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'}
class NegotiationError(Exception):
- '''A session negotiation failed'''
+ """
+ A session negotiation failed
+ """
pass
class DecryptionError(Exception):
- '''A message couldn't be decrypted into usable XML'''
+ """
+ A message couldn't be decrypted into usable XML
+ """
pass
class Cancelled(Exception):
- '''The user cancelled an operation'''
+ """
+ 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
@@ -92,7 +121,10 @@ class LatexError(Exception):
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
diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py
index fc285fd8f..3adad66d1 100755
--- a/src/common/fuzzyclock.py
+++ b/src/common/fuzzyclock.py
@@ -21,7 +21,7 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-'''
+"""
Python class to show a "fuzzy clock".
Homepage of the original: http://home.gna.org/fuzzyclock/
Project Page of the original: http://gna.org/projects/fuzzyclock
@@ -30,7 +30,7 @@ The class is based on a port from PHP code by
Henrique Recidive <henrique at recidive.com> which was
in turn based on the Fuzzy Clock Applet of Frerich Raabe (KDE).
So most of the credit goes to this guys, thanks :-)
-'''
+"""
import time
@@ -46,7 +46,7 @@ class FuzzyClock:
_('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'),
+ FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'),
_('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'),
_('Late evening'), _('Night') ]
diff --git a/src/common/gajim.py b/src/common/gajim.py
index caf4c440e..59c9b40aa 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -86,9 +86,10 @@ MY_EMOTS_PATH = gajimpaths['MY_EMOTS']
MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS']
MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS']
MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS']
-MY_CACERTS = gajimpaths['MY_CACERTS']
+MY_CACERTS = gajimpaths['MY_CACERTS']
TMP = gajimpaths['TMP']
DATA_DIR = gajimpaths['DATA']
+ICONS_DIR = gajimpaths['ICONS']
HOME_DIR = gajimpaths['HOME']
PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
gajimpaths['PLUGINS_USER']]
@@ -106,7 +107,7 @@ else:
os_info = None # used to cache os information
-from contacts import Contacts
+from contacts import LegacyContactsAPI
from events import Events
gmail_domains = ['gmail.com', 'googlemail.com']
@@ -117,7 +118,7 @@ last_message_time = {} # list of time of the latest incomming message
# {acct1: {jid1: time1, jid2: time2}, }
encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
-contacts = Contacts()
+contacts = LegacyContactsAPI()
gc_connected = {} # tell if we are connected to the room or not {acct: {room_jid: True}}
gc_passwords = {} # list of the pass required to enter a room {room_jid: password}
automatic_rooms = {} # list of rooms that must be automaticaly configured and for which we have a list of invities {account: {room_jid: {'invities': []}}}
@@ -172,12 +173,6 @@ try:
except ImportError:
HAVE_PYCRYPTO = False
-HAVE_PYSEXY = True
-try:
- import sexy
-except ImportError:
- HAVE_PYSEXY = False
-
HAVE_GPG = True
try:
import GnuPGInterface
@@ -188,8 +183,9 @@ else:
if system('gpg -h >/dev/null 2>&1'):
HAVE_GPG = False
-import latex
-HAVE_LATEX = latex.check_for_latex_support()
+# Depends on use_latex option. Will be correctly set after we config options are
+# read.
+HAVE_LATEX = False
HAVE_INDICATOR = True
try:
@@ -217,8 +213,8 @@ gajim_optional_features = {}
# Capabilities hash per account
caps_hash = {}
-import caps
-caps.initialize(logger)
+import caps_cache
+caps_cache.initialize(logger)
def get_nick_from_jid(jid):
pos = jid.find('@')
@@ -228,11 +224,6 @@ def get_server_from_jid(jid):
pos = jid.find('@') + 1 # after @
return jid[pos:]
-def get_resource_from_jid(jid):
- tokens = jid.split('/', 1)
- if len(tokens) > 1:
- return tokens[1]
-
def get_name_and_server_from_jid(jid):
name = get_nick_from_jid(jid)
server = get_server_from_jid(jid)
@@ -248,8 +239,9 @@ def get_room_and_nick_from_fjid(jid):
return l
def get_real_jid_from_fjid(account, fjid):
- '''returns real jid or returns None
- if we don't know the 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
@@ -274,7 +266,9 @@ def get_jid_without_resource(jid):
return jid.split('/')[0]
def construct_fjid(room_jid, nick):
- ''' nick is in utf8 (taken from treeview); room_jid is in unicode'''
+ """
+ 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):
@@ -288,19 +282,17 @@ def get_resource_from_jid(jid):
else:
return ''
-# [15:34:28] <asterix> we should add contact.fake_jid I think
-# [15:34:46] <asterix> so if we know real jid, it wil be in contact.jid, or we look in contact.fake_jid
-# [15:32:54] <asterix> they can have resource if we know the real jid
-# [15:33:07] <asterix> and that resource is in contact.resource
-
def get_number_of_accounts():
- '''returns the number of ALL accounts'''
+ """
+ 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
- you can optionally pass an accounts_list
- and if you do those will be checked, else all will be checked'''
+ """
+ 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()
@@ -327,7 +319,9 @@ def zeroconf_is_connected():
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'''
+ """
+ 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):
@@ -342,8 +336,11 @@ def account_is_securely_connected(account):
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'''
+ """
+ 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?
@@ -379,21 +376,27 @@ def jid_is_transport(jid):
return False
def get_jid_from_account(account_name):
- '''return the jid we use in the given account'''
+ """
+ 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'''
+ """
+ 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)'''
+ """
+ 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'):
@@ -401,7 +404,9 @@ def get_hostname_from_account(account_name, use_srv = False):
return config.get_per('accounts', account_name, 'hostname')
def get_notification_image_prefix(jid):
- '''returns the prefix for the notification images'''
+ """
+ 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
@@ -410,7 +415,9 @@ def get_notification_image_prefix(jid):
return prefix
def get_name_from_jid(account, jid):
- '''returns from JID's shown name and if no contact returns jids'''
+ """
+ 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()
@@ -419,7 +426,9 @@ def get_name_from_jid(account, jid):
return actor
def get_priority(account, show):
- '''return the priority an account must have'''
+ """
+ Return the priority an account must have
+ """
if not show:
show = 'online'
diff --git a/src/common/helpers.py b/src/common/helpers.py
index 70bdfc9fe..15dc0f7ee 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -38,7 +38,7 @@ import errno
import select
import base64
import hashlib
-import caps
+import caps_cache
from encodings.punycode import punycode_encode
@@ -92,15 +92,19 @@ def decompose_jid(jidstring):
return user, server, resource
def parse_jid(jidstring):
- '''Perform stringprep on all JID fragments from a string
- and return the full jid'''
+ """
+ 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))
def idn_to_ascii(host):
- '''convert IDN (Internationalized Domain Names) to ACE
- (ASCII-compatible encoding)'''
+ """
+ Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible
+ encoding)
+ """
from encodings import idna
labels = idna.dots.split(host)
converted_labels = []
@@ -109,8 +113,10 @@ def idn_to_ascii(host):
return ".".join(converted_labels)
def ascii_to_idn(host):
- '''convert ACE (ASCII-compatible encoding) to IDN
- (Internationalized Domain Names)'''
+ """
+ Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain
+ Names)
+ """
from encodings import idna
labels = idna.dots.split(host)
converted_labels = []
@@ -119,7 +125,9 @@ def ascii_to_idn(host):
return ".".join(converted_labels)
def parse_resource(resource):
- '''Perform stringprep on resource and return it'''
+ """
+ Perform stringprep on resource and return it
+ """
if resource:
try:
from xmpp.stringprepare import resourceprep
@@ -128,10 +136,11 @@ def parse_resource(resource):
raise InvalidFormat, 'Invalid character in resource.'
def prep(user, server, resource):
- '''Perform stringprep on all JID fragments and return the full jid'''
+ """
+ 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
@@ -186,10 +195,13 @@ def temp_failure_retry(func, *args, **kwargs):
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'''
+ """
+ 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')
@@ -324,7 +336,9 @@ def from_one_line(msg):
return msg
def get_uf_chatstate(chatstate):
- '''removes chatstate jargon and returns user friendly messages'''
+ """
+ Remove chatstate jargon and returns user friendly messages
+ """
if chatstate == 'active':
return _('is paying attention to the conversation')
elif chatstate == 'inactive':
@@ -339,10 +353,11 @@ def get_uf_chatstate(chatstate):
return ''
def is_in_path(command, return_abs_path=False):
- '''Returns True if 'command' is found in one of the directories in the
- user's path. If 'return_abs_path' is True, returns the absolute path of
- the first found command instead. Returns False otherwise and on errors.'''
-
+ """
+ Return True if 'command' is found in one of the directories in the user's
+ path. If 'return_abs_path' is True, return the absolute path of the first
+ found command instead. Return False otherwise and on errors
+ """
for directory in os.getenv('PATH').split(os.pathsep):
try:
if command in os.listdir(directory):
@@ -405,7 +420,9 @@ def get_output_of_command(command):
return output
def decode_string(string):
- '''try to decode (to make it Unicode instance) given string'''
+ """
+ Try to decode (to make it Unicode instance) given string
+ """
if isinstance(string, unicode):
return string
# by the time we go to iso15 it better be the one else we show bad characters
@@ -420,7 +437,9 @@ def decode_string(string):
return string
def ensure_utf8_string(string):
- '''make sure string is in UTF-8'''
+ """
+ Make sure string is in UTF-8
+ """
try:
string = decode_string(string).encode('utf-8')
except Exception:
@@ -428,28 +447,28 @@ def ensure_utf8_string(string):
return string
def get_windows_reg_env(varname, default=''):
- '''asks for paths commonly used but not exposed as ENVs
- in english Windows 2003 those are:
- 'AppData' = %USERPROFILE%\Application Data (also an ENV)
- 'Desktop' = %USERPROFILE%\Desktop
- 'Favorites' = %USERPROFILE%\Favorites
- 'NetHood' = %USERPROFILE%\NetHood
- 'Personal' = D:\My Documents (PATH TO MY DOCUMENTS)
- 'PrintHood' = %USERPROFILE%\PrintHood
- 'Programs' = %USERPROFILE%\Start Menu\Programs
- 'Recent' = %USERPROFILE%\Recent
- 'SendTo' = %USERPROFILE%\SendTo
- 'Start Menu' = %USERPROFILE%\Start Menu
- 'Startup' = %USERPROFILE%\Start Menu\Programs\Startup
- 'Templates' = %USERPROFILE%\Templates
- 'My Pictures' = D:\My Documents\My Pictures
- 'Local Settings' = %USERPROFILE%\Local Settings
- 'Local AppData' = %USERPROFILE%\Local Settings\Application Data
- 'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files
- 'Cookies' = %USERPROFILE%\Cookies
- 'History' = %USERPROFILE%\Local Settings\History
- '''
-
+ """
+ Ask for paths commonly used but not exposed as ENVs in english Windows 2003
+ those are:
+ 'AppData' = %USERPROFILE%\Application Data (also an ENV)
+ 'Desktop' = %USERPROFILE%\Desktop
+ 'Favorites' = %USERPROFILE%\Favorites
+ 'NetHood' = %USERPROFILE%\NetHood
+ 'Personal' = D:\My Documents (PATH TO MY DOCUMENTS)
+ 'PrintHood' = %USERPROFILE%\PrintHood
+ 'Programs' = %USERPROFILE%\Start Menu\Programs
+ 'Recent' = %USERPROFILE%\Recent
+ 'SendTo' = %USERPROFILE%\SendTo
+ 'Start Menu' = %USERPROFILE%\Start Menu
+ 'Startup' = %USERPROFILE%\Start Menu\Programs\Startup
+ 'Templates' = %USERPROFILE%\Templates
+ 'My Pictures' = D:\My Documents\My Pictures
+ 'Local Settings' = %USERPROFILE%\Local Settings
+ 'Local AppData' = %USERPROFILE%\Local Settings\Application Data
+ 'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files
+ 'Cookies' = %USERPROFILE%\Cookies
+ 'History' = %USERPROFILE%\Local Settings\History
+ """
if os.name != 'nt':
return ''
@@ -467,7 +486,9 @@ r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders')
return val
def get_my_pictures_path():
- '''windows-only atm. [Unix lives in the past]'''
+ """
+ Windows-only atm
+ """
return get_windows_reg_env('My Pictures')
def get_desktop_path():
@@ -485,8 +506,10 @@ def get_documents_path():
return path
def sanitize_filename(filename):
- '''makes sure the filename we will write does contain only acceptable and
- latin characters, and is not too long (in that case hash it)'''
+ """
+ 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)
@@ -502,11 +525,13 @@ def sanitize_filename(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] + '...'
@@ -535,10 +560,11 @@ def get_account_status(account):
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".'''
+ """
+ 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_
@@ -552,13 +578,16 @@ def get_avatar_path(prefix):
return None
def datetime_tuple(timestamp):
- '''Converts 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.'''
+ the above format.
+ """
timestamp = timestamp.split('.')[0]
timestamp = timestamp.replace('-', '')
timestamp = timestamp.replace('z', '')
@@ -569,8 +598,6 @@ def datetime_tuple(timestamp):
# import gajim only when needed (after decode_string is defined) see #4764
import gajim
-import pep
-
def convert_bytes(string):
suffix = ''
@@ -611,8 +638,11 @@ def convert_bytes(string):
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'''
+ """
+ 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,
@@ -694,13 +724,15 @@ def play_sound(event):
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.
+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.'''
+ :return the path to file or None if it doesn't exists.
+ """
if not file:
return None
elif os.path.exists(file):
@@ -712,16 +744,17 @@ def check_soundfile_path(file,
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:
+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
@@ -777,56 +810,11 @@ def get_global_status():
status = gajim.connections[account].status
return status
-def get_pep_dict(account):
- pep_dict = {}
- con = gajim.connections[account]
- # activity
- if 'activity' in con.activity and con.activity['activity'] in pep.ACTIVITIES:
- activity = con.activity['activity']
- if 'subactivity' in con.activity and con.activity['subactivity'] in \
- pep.ACTIVITIES[activity]:
- subactivity = con.activity['subactivity']
- else:
- subactivity = 'other'
- else:
- activity = ''
- subactivity = ''
- if 'text' in con.activity:
- text = con.activity['text']
- else:
- text = ''
- pep_dict['activity'] = activity
- pep_dict['subactivity'] = subactivity
- pep_dict['activity_text'] = text
-
- # mood
- if 'mood' in con.mood and con.mood['mood'] in pep.MOODS:
- mood = con.mood['mood']
- else:
- mood = ''
- if 'text' in con.mood:
- text = con.mood['text']
- else:
- text = ''
- pep_dict['mood'] = mood
- pep_dict['mood_text'] = text
- return pep_dict
-
-def get_global_pep():
- maxi = 0
- pep_dict = {'activity': '', 'mood': ''}
- 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
- pep_dict = get_pep_dict(account)
- return pep_dict
def statuses_unified():
- '''testing if all statuses are the same.'''
+ """
+ Test if all statuses are the same
+ """
reference = None
for account in gajim.connections:
if not gajim.config.get_per('accounts', account,
@@ -839,7 +827,9 @@ def statuses_unified():
return True
def get_icon_name_to_show(contact, account = None):
- '''Get the icon name to show in online, away, requested, ...'''
+ """
+ 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,
@@ -868,16 +858,22 @@ def get_icon_name_to_show(contact, account = None):
return 'not in roster'
def get_full_jid_from_iq(iq_obj):
- '''return the full jid (with resource) from an iq as unicode'''
+ """
+ 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'''
+ """
+ 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 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):
@@ -910,7 +906,9 @@ distro_info = {
}
def get_random_string_16():
- ''' create random string of length 16'''
+ """
+ Create random string of length 16
+ """
rng = range(65, 90)
rng.extend(range(48, 57))
char_sequence = [chr(e) for e in rng]
@@ -921,7 +919,8 @@ def get_os_info():
if gajim.os_info:
return gajim.os_info
if os.name == 'nt':
- ver = os.sys.getwindowsversion()
+ # 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',
@@ -932,6 +931,7 @@ def get_os_info():
(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]
@@ -995,11 +995,14 @@ def get_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'''
+ 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')
@@ -1016,7 +1019,9 @@ advanced_notif_num = None, is_first_message = True):
return False
def allow_popup_window(account, advanced_notif_num = None):
- '''is it allowed to popup windows?'''
+ """
+ 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')
@@ -1067,8 +1072,10 @@ def get_chat_control(account, contact):
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]}'''
+ """
+ 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
@@ -1180,7 +1187,9 @@ def get_notification_icon_tooltip_text():
return text
def get_accounts_info():
- '''helper for notification icon tooltip'''
+ """
+ Helper for notification icon tooltip
+ """
accounts = []
accounts_list = sorted(gajim.contacts.get_accounts())
for account in accounts_list:
@@ -1231,9 +1240,14 @@ def get_transport_path(transport):
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'''
+ """
+ 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:]
@@ -1276,10 +1290,14 @@ def update_optional_features(account = None):
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'):
@@ -1295,7 +1313,7 @@ def update_optional_features(account = None):
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.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
diff --git a/src/common/i18n.py b/src/common/i18n.py
index 3c47d3da3..7b9e8dab3 100644
--- a/src/common/i18n.py
+++ b/src/common/i18n.py
@@ -32,7 +32,7 @@ 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:
@@ -84,11 +84,12 @@ def Q_(s):
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..
- '''
+ 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
diff --git a/src/common/idle.py b/src/common/idle.py
index eb7e1be6a..d3ff4dff7 100644
--- a/src/common/idle.py
+++ b/src/common/idle.py
@@ -73,7 +73,10 @@ except OSError, e:
xss_available = False
def getIdleSec():
- """Returns the idle time in seconds"""
+ 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:
diff --git a/src/common/jingle.py b/src/common/jingle.py
index 6549ee10c..73745592a 100644
--- a/src/common/jingle.py
+++ b/src/common/jingle.py
@@ -10,20 +10,11 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
-''' Handles the jingle signalling protocol. '''
+"""
+Handles the jingle signalling protocol
+"""
#TODO:
-# * things in XEP 0166, including:
-# - 'senders' attribute of 'content' element
-# - security preconditions
-# * actions:
-# - content-modify
-# - description-info, session-info
-# - security-info
-# - transport-accept, transport-reject
-# * sid/content related:
-# - tiebreaking
-# - if there already is a session, use it
# * things in XEP 0176, including:
# - http://xmpp.org/extensions/xep-0176.html#protocol-restarts
# - http://xmpp.org/extensions/xep-0176.html#fallback
@@ -34,1070 +25,23 @@
# - video integration
# * config:
# - codecs
-# - STUN
-# * DONE: figure out why it doesn't work with pidgin:
-# That's a bug in pidgin: http://xmpp.org/extensions/xep-0176.html#protocol-checks
+# * figure out why it doesn't work with pidgin:
+# That's maybe a bug in pidgin:
+# http://xmpp.org/extensions/xep-0176.html#protocol-checks
-# * timeout
-
-# * split this file in several modules
-# For example, a file dedicated for XEP0166, one for XEP0176,
-# and one for XEP0167
-
-# * handle different kinds of sink and src elements
-
-import gobject
-
-import gajim
import xmpp
import helpers
-import farsight, gst
-
-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()
-
-#FIXME: Move it to JingleSession.States?
-class JingleStates(object):
- ''' States in which jingle session may exist. '''
- ended = 0
- pending = 1
- active = 2
-
-#FIXME: Move it to JingleTransport.Type?
-class TransportType(object):
- ''' Possible types of a JingleTransport '''
- datagram = 1
- streaming = 2
-
-class OutOfOrder(Exception):
- ''' 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. '''
-
-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
- 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 == 'audio':
- cls = JingleVoIP
- elif media == 'video':
- cls = JingleVideo
- #elif media == None:
- # cls = JingleContent
- else:
- return None
-
- for content in self.contents.values():
- if isinstance(content, cls):
- 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 <service-unavailable/>
- # 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'):
- desc = element.getTag('description')
- desc_ns = desc.getNamespace()
- tran_ns = element.getTag('transport').getNamespace()
- if desc_ns == xmpp.NS_JINGLE_RTP and desc['media'] in ('audio', 'video'):
- contents_ok = True
- #TODO: Everything here should be moved somewhere else
- if tran_ns == xmpp.NS_JINGLE_ICE_UDP:
- if desc['media'] == 'audio':
- self.add_content(element['name'], JingleVoIP(self), 'peer')
- else:
- self.add_content(element['name'], JingleVideo(self), 'peer')
- contents.append((desc['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 <content/> element to <jingle/> element,
- with (full=True) or without (full=False) <content/>
- children. '''
- jingle.addChild('content',
- attrs={'name': content.name, 'creator': content.creator})
-
- def __append_contents(self, jingle):
- ''' Append all <content/> elements to <jingle/>.'''
- # 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))
-
-#TODO:
-#class JingleTransport(object):
-# ''' An abstraction of a transport in Jingle sessions. '''
-# def __init__(self):
-# pass
-
-
-class JingleContent(object):
- ''' An abstraction of content in Jingle sessions. '''
- def __init__(self, session, node=None):
- self.session = session
- # 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.candidates = [] # Local transport candidates
- self.remote_candidates = [] # Remote transport candidates
-
- 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': [],
- }
-
- def is_ready(self):
- #print '[%s] %s, %s' % (self.media, self.candidates_ready,
- # self.p2psession.get_property('codecs-ready'))
- return (self.accepted and self.candidates_ready and not self.sent
- and self.p2psession.get_property('codecs-ready'))
-
- 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 = []
- transport = content.getTag('transport')
- 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
-
- 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)
- #FIXME: connectivity should not be etablished yet
- # Instead, it should be etablished after session-accept!
- if len(candidates) > 0:
- if self.sent:
- self.p2pstream.set_remote_candidates(candidates)
- else:
- self.remote_candidates.extend(candidates)
- #self.p2pstream.set_remote_candidates(candidates)
- #print self.media, self.creator, self.name, 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 __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 iter_candidates(self):
- for candidate in self.candidates:
- yield self.__candidate(candidate)
-
- def send_candidate(self, candidate):
- content = self.__content()
- transport = content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport')
-
- if candidate.username and candidate.password:
- transport['ufrag'] = candidate.username
- transport['pwd'] = candidate.password
-
- transport.addChild(node=self.__candidate(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
-
- if self.candidates and self.candidates[0].username and \
- self.candidates[0].password:
- attrs = {'ufrag': self.candidates[0].username,
- 'pwd': self.candidates[0].password}
- else:
- attrs = {}
- content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport', attrs=attrs,
- payload=self.iter_candidates())
-
- def destroy(self):
- self.callbacks = None
- del self.session.contents[(self.creator, self.name)]
-
-
-class JingleRTPContent(JingleContent):
- def __init__(self, session, media, node=None):
- JingleContent.__init__(self, session, node)
- 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.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)
-
- # 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)
-
- 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)
-
- 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 _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 _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 _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.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 len(self.remote_candidates) > 0:
- self.p2pstream.set_remote_candidates(self.remote_candidates)
- self.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
-
- 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
-
- 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 __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)
-
-
-class JingleVoIP(JingleRTPContent):
- ''' Jingle VoIP sessions consist of audio content transported
- over an ICE UDP protocol. '''
- def __init__(self, session, node=None):
- JingleRTPContent.__init__(self, session, 'audio', node)
- self.setup_stream()
-
-
- ''' 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
-
- 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)
-
- # 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)
-
- # 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)
-
- # The following is needed for farsight to process ICE requests:
- self.pipeline.set_state(gst.STATE_PLAYING)
-
-
-class JingleVideo(JingleRTPContent):
- def __init__(self, session, node=None):
- JingleRTPContent.__init__(self, session, 'video', node)
- 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')
-
- 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)
-
- 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)
+from jingle_session import JingleSession
+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 = {}
@@ -1107,13 +51,17 @@ class ConnectionJingle(object):
self.__iq_responses = {}
def add_jingle(self, jingle):
- ''' Add a jingle session to a jingle stanza dispatcher
+ """
+ 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 '''
+ """
+ Remove a jingle session from a jingle stanza dispatcher
+ """
key = (peerjid, sid)
if key in self.__sessions:
#FIXME: Move this elsewhere?
@@ -1123,18 +71,21 @@ class ConnectionJingle(object):
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.'''
+ """
+ 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()
if (jid, id) in self.__iq_responses.keys():
- self.__iq_responses[(jid, id)].stanzaCB(stanza)
+ self.__iq_responses[(jid, id)].on_stanza(stanza)
del self.__iq_responses[(jid, id)]
raise xmpp.NodeProcessed
@@ -1149,24 +100,24 @@ class ConnectionJingle(object):
self.add_jingle(newjingle)
# we already have such session in dispatcher...
- self.__sessions[(jid, sid)].stanzaCB(stanza)
+ self.__sessions[(jid, sid)].on_stanza(stanza)
raise xmpp.NodeProcessed
- def startVoIP(self, jid):
+ 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', JingleVoIP(jingle))
+ jingle.add_content('voice', JingleAudio(jingle))
else:
jingle = JingleSession(self, weinitiate=True, jid=jid)
self.add_jingle(jingle)
- jingle.add_content('voice', JingleVoIP(jingle))
+ jingle.add_content('voice', JingleAudio(jingle))
jingle.start_session()
return jingle.sid
- def startVideoIP(self, jid):
+ 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')
@@ -1193,3 +144,5 @@ class ConnectionJingle(object):
return session
return None
+
+# vim: se ts=3: \ No newline at end of file
diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py
new file mode 100644
index 000000000..7b0e3f8fe
--- /dev/null
+++ b/src/common/jingle_content.py
@@ -0,0 +1,142 @@
+##
+## Copyright (C) 2006 Gajim Team
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+"""
+Handles Jingle contents (XEP 0166)
+"""
+
+import xmpp
+
+contents = {}
+
+def get_jingle_content(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.
+ """
+
+
+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
+ self.negotiated = False
+
+ self.media = None
+
+ 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': [],
+ }
+
+ 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 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_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 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 destroy(self):
+ self.callbacks = None
+ del self.session.contents[(self.creator, self.name)]
+
+# vim: se ts=3:
diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py
new file mode 100644
index 000000000..ce37370f4
--- /dev/null
+++ b/src/common/jingle_rtp.py
@@ -0,0 +1,353 @@
+##
+## Copyright (C) 2006 Gajim Team
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+"""
+Handles Jingle RTP sessions (XEP 0167)
+"""
+
+import gobject
+import socket
+
+import xmpp
+import farsight, gst
+from glib import GError
+
+import gajim
+
+from jingle_transport import JingleTransportICEUDP
+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]
+
+ 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]
+
+ 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
+
+ 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
+
+ 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 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 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 _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 _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 _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_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
+ """
+
+ 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)
+
+ 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 __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)
+
+
+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()
+
+ 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 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
+
+ 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"))
+
+ 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')
+
+ # 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)
+
+ # 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 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'))
+
+ 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)
+
+ 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)
+
+
+def get_content(desc):
+ 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
new file mode 100644
index 000000000..3e2392739
--- /dev/null
+++ b/src/common/jingle_session.py
@@ -0,0 +1,656 @@
+##
+## Copyright (C) 2006 Gajim Team
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+"""
+Handles Jingle sessions (XEP 0166)
+"""
+
+#TODO:
+# * 'senders' attribute of 'content' element
+# * security preconditions
+# * actions:
+# - content-modify
+# - session-info
+# - security-info
+# - transport-accept, transport-reject
+# - Tie-breaking
+# * timeout
+
+import gajim #Get rid of that?
+import xmpp
+from jingle_transport import get_jingle_transport
+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
+
+class OutOfOrder(Exception):
+ """
+ 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
+ """
+
+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 <service-unavailable/>
+ # 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 <content/> element to <jingle/> element, with (full=True) or
+ without (full=False) <content/> children
+ """
+ jingle.addChild('content',
+ attrs={'name': content.name, 'creator': content.creator})
+
+ def __append_contents(self, jingle):
+ """
+ Append all <content/> elements to <jingle/>
+ """
+ # 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:
diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py
new file mode 100644
index 000000000..82a0cb987
--- /dev/null
+++ b/src/common/jingle_transport.py
@@ -0,0 +1,150 @@
+##
+## Copyright (C) 2006 Gajim Team
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+"""
+Handles Jingle Transports (currently only ICE-UDP)
+"""
+
+import xmpp
+
+transports = {}
+
+def get_jingle_transport(node):
+ namespace = node.getNamespace()
+ if namespace in transports:
+ return transports[namespace]()
+
+
+class TransportType(object):
+ """
+ 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 = []
+
+ 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_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 []
+
+
+import farsight
+
+class JingleTransportICEUDP(JingleTransport):
+ 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_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'])
+
+ 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'])
+
+ #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 0de030bd8..842aff8b9 100644
--- a/src/common/kwalletbinding.py
+++ b/src/common/kwalletbinding.py
@@ -25,7 +25,9 @@ import subprocess
def kwallet_available():
- """Return True if kwalletcli can be run, False otherwise."""
+ """
+ Return True if kwalletcli can be run, False otherwise
+ """
try:
p = subprocess.Popen(["kwalletcli", "-qV"])
except Exception:
@@ -37,7 +39,8 @@ def kwallet_available():
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)
@@ -45,7 +48,6 @@ def kwallet_get(folder, entry):
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)
@@ -60,7 +62,8 @@ def kwallet_get(folder, entry):
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)
@@ -68,7 +71,6 @@ def kwallet_put(folder, entry, passphrase):
• 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)
diff --git a/src/common/latex.py b/src/common/latex.py
index 777577d1c..492f04c39 100644
--- a/src/common/latex.py
+++ b/src/common/latex.py
@@ -86,8 +86,9 @@ def popen_nt_friendly(command):
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 if latex is available and if it can create a picture
+ """
try:
filename = latex_to_image("test")
if filename:
diff --git a/src/common/location_listener.py b/src/common/location_listener.py
new file mode 100644
index 000000000..f1ba5b714
--- /dev/null
+++ b/src/common/location_listener.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+## src/common/location_listener.py
+##
+## Copyright (C) 2009 Yann Leboulanger <asterix AT lagaule.org>
+##
+## This file is part of Gajim.
+##
+## Gajim is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## Gajim is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+##
+
+from common import gajim
+from common import pep
+from common import dbus_support
+if dbus_support.supported:
+ import dbus
+ import dbus.glib
+
+class LocationListener:
+ _instance = None
+ @classmethod
+ def get(cls):
+ if cls._instance is None:
+ cls._instance = cls()
+ return cls._instance
+
+ def __init__(self):
+ self._data = {}
+
+ 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_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 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_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 enable():
+ listener = LocationListener.get()
+ listener.start()
+
+def disable():
+ listener = LocationListener.get()
+ listener.shut_down()
diff --git a/src/common/logger.py b/src/common/logger.py
index c96282743..507b29cfa 100644
--- a/src/common/logger.py
+++ b/src/common/logger.py
@@ -24,7 +24,9 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-''' This module allows to access the on-disk database of logs. '''
+"""
+This module allows to access the on-disk database of logs
+"""
import os
import sys
@@ -36,13 +38,7 @@ from cStringIO import StringIO
import exceptions
import gajim
-try:
- import sqlite3 as sqlite # python 2.5
-except ImportError:
- try:
- from pysqlite2 import dbapi2 as sqlite
- except ImportError:
- raise exceptions.PysqliteNotAvailable
+import sqlite3 as sqlite
import configpaths
LOG_DB_PATH = configpaths.gajimpaths['LOG_DB']
@@ -150,7 +146,9 @@ class Logger:
self.get_jids_already_in_db()
def simple_commit(self, sql_to_commit):
- '''helper to commit'''
+ """
+ Helper to commit
+ """
self.cur.execute(sql_to_commit)
try:
self.con.commit()
@@ -177,14 +175,14 @@ class Logger:
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 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)
@@ -202,13 +200,14 @@ class Logger:
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
- '''
+ """
+ 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
@@ -238,7 +237,9 @@ class Logger:
return jid_id
def convert_human_values_to_db_api_values(self, kind, show):
- '''coverts from string style to constant ints for db'''
+ """
+ Convert from string style to constant ints for db
+ """
if kind == 'status':
kind_col = constants.KIND_STATUS
elif kind == 'gcstatus':
@@ -277,7 +278,9 @@ class Logger:
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'''
+ """
+ Convert from string style to constant ints for db
+ """
if type_ == 'aim':
return constants.TYPE_AIM
if type_ == 'gadu-gadu':
@@ -309,7 +312,9 @@ class Logger:
return None
def convert_api_values_to_human_transport_type(self, type_id):
- '''converts from constant ints for db to string style'''
+ """
+ Convert from constant ints for db to string style
+ """
if type_id == constants.TYPE_AIM:
return 'aim'
if type_id == constants.TYPE_GG:
@@ -340,7 +345,9 @@ class Logger:
return 'mrim'
def convert_human_subscription_values_to_db_api_values(self, sub):
- '''converts from string style to constant ints for db'''
+ """
+ Convert from string style to constant ints for db
+ """
if sub == 'none':
return constants.SUBSCRIPTION_NONE
if sub == 'to':
@@ -351,7 +358,9 @@ class Logger:
return constants.SUBSCRIPTION_BOTH
def convert_db_api_values_to_human_subscription_values(self, sub):
- '''converts from constant ints for db to string style'''
+ """
+ Convert from constant ints for db to string style
+ """
if sub == constants.SUBSCRIPTION_NONE:
return 'none'
if sub == constants.SUBSCRIPTION_TO:
@@ -382,30 +391,40 @@ class Logger:
return message_id
def insert_unread_events(self, message_id, jid_id):
- ''' add unread message with id: message_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'''
+ """
+ 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 '''
+ """
+ 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 '''
+ """
+ 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 '''
+ """
+ Get all unread messages
+ """
all_messages = []
try:
self.cur.execute(
@@ -435,15 +454,18 @@ class Logger:
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
+ """
+ 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()
+ 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.'''
+ 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()
@@ -516,12 +538,14 @@ class Logger:
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'''
+ 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:
@@ -561,9 +585,12 @@ class Logger:
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'''
+ """
+ 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:
@@ -586,9 +613,12 @@ class Logger:
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'''
+ """
+ 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:
@@ -615,7 +645,9 @@ class Logger:
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)'''
+ """
+ Return the list of days that have logs (not status messages)
+ """
try:
self.get_jid_id(jid)
except exceptions.PysqliteOperationalError, e:
@@ -650,8 +682,10 @@ class Logger:
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)'''
+ """
+ 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)
@@ -676,8 +710,10 @@ class Logger:
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'''
+ """
+ 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:
@@ -697,8 +733,10 @@ class Logger:
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'''
+ """
+ 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)' % \
@@ -706,8 +744,9 @@ class Logger:
self.simple_commit(sql)
def _build_contact_where(self, account, jid):
- '''build the where clause for a jid, including metacontacts
- jid(s) if any'''
+ """
+ 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
@@ -727,7 +766,9 @@ class Logger:
return where_sql
def save_transport_type(self, jid, type_):
- '''save the type of the transport in DB'''
+ """
+ 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
@@ -747,7 +788,9 @@ class Logger:
self.simple_commit(sql)
def get_transports_type(self):
- '''return all the type of the transports in DB'''
+ """
+ Return all the type of the transports in DB
+ """
self.cur.execute(
'SELECT * from transports_cache')
results = self.cur.fetchall()
@@ -767,11 +810,13 @@ class Logger:
# (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.
+ """
+ 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. '''
-
+ 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
@@ -854,16 +899,21 @@ class Logger:
self.simple_commit(sql)
def clean_caps_table(self):
- '''Remove caps which was not seen for 3 months'''
+ """
+ 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 '''
+ """
+ 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.
@@ -887,7 +937,9 @@ class Logger:
roster_version)
def del_contact(self, account_jid, jid):
- ''' Remove jid from account_jid roster. '''
+ """
+ Remove jid from account_jid roster
+ """
try:
account_jid_id = self.get_jid_id(account_jid)
jid_id = self.get_jid_id(jid)
@@ -902,7 +954,9 @@ class Logger:
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. '''
+ """
+ Add or update a contact from account_jid roster
+ """
if sub == 'remove':
self.del_contact(account_jid, jid)
return
@@ -933,7 +987,9 @@ class Logger:
self.con.commit()
def get_roster(self, account_jid):
- ''' Return the accound_jid roster in NonBlockingRoster format. '''
+ """
+ Return the accound_jid roster in NonBlockingRoster format
+ """
data = {}
account_jid_id = self.get_jid_id(account_jid)
@@ -972,7 +1028,9 @@ class Logger:
return data
def remove_roster(self, account_jid):
- ''' Remove all entry from account_jid roster. '''
+ """
+ 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=?',
diff --git a/src/common/logging_helpers.py b/src/common/logging_helpers.py
index ae5d1da3e..733958b2e 100644
--- a/src/common/logging_helpers.py
+++ b/src/common/logging_helpers.py
@@ -23,7 +23,7 @@ import i18n
def parseLogLevel(arg):
"""
- eiter numeric value or level name from logging module
+ Eiter numeric value or level name from logging module
"""
if arg.isdigit():
return int(arg)
@@ -98,7 +98,7 @@ def colorize(text, color):
class FancyFormatter(logging.Formatter):
"""
- A Eye-candy formatter with colors
+ An eye-candy formatter with colors
"""
colors_mapping = {
'DEBUG': colors.BLUE,
@@ -134,7 +134,7 @@ class FancyFormatter(logging.Formatter):
def init(use_color=False):
"""
- initialize the logging system
+ Iinitialize the logging system
"""
consoleloghandler = logging.StreamHandler()
consoleloghandler.setFormatter(
diff --git a/src/common/multimedia_helpers.py b/src/common/multimedia_helpers.py
new file mode 100644
index 000000000..4a2a4fb2e
--- /dev/null
+++ b/src/common/multimedia_helpers.py
@@ -0,0 +1,101 @@
+##
+## Copyright (C) 2009 Thibaut GIRKA <thib AT sitedethib.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+import gst
+
+
+class DeviceManager(object):
+ def __init__(self):
+ self.devices = {}
+
+ def detect(self):
+ 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
+
+
+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')
+
+
+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')
+
+
+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'
+
+
+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)'))
+
+
diff --git a/src/common/optparser.py b/src/common/optparser.py
index f9f738d5e..7b7fe911d 100644
--- a/src/common/optparser.py
+++ b/src/common/optparser.py
@@ -32,16 +32,9 @@ import re
from time import time
from common import gajim
from common import helpers
-from common import caps
-
-import exceptions
-try:
- import sqlite3 as sqlite # python 2.5
-except ImportError:
- try:
- from pysqlite2 import dbapi2 as sqlite
- except ImportError:
- raise exceptions.PysqliteNotAvailable
+from common import caps_cache
+
+import sqlite3 as sqlite
import logger
class OptionsParser:
@@ -219,16 +212,20 @@ class OptionsParser:
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, 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.config.set('version', new_version)
- caps.capscache.initialize_from_db()
+ caps_cache.capscache.initialize_from_db()
def assert_unread_msgs_table_exists(self):
- '''create table unread_messages if there is no such table'''
+ """
+ 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)
@@ -346,8 +343,10 @@ class OptionsParser:
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
+ """
+ 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)
@@ -369,9 +368,11 @@ class OptionsParser:
gajim.config.set('version', '0.10.1.3')
def update_config_to_01014(self):
- '''apply indeces to the logs database'''
+ """
+ Apply indeces to the logs database
+ """
print _('migrating logs database to indices')
- #FIXME see #2812
+ # FIXME see #2812
back = os.getcwd()
os.chdir(logger.LOG_DB_FOLDER)
con = sqlite.connect(logger.LOG_DB_FILE)
@@ -393,7 +394,9 @@ class OptionsParser:
gajim.config.set('version', '0.10.1.4')
def update_config_to_01015(self):
- '''clean show values in logs database'''
+ """
+ Clean show values in logs database
+ """
#FIXME see #2812
back = os.getcwd()
os.chdir(logger.LOG_DB_FOLDER)
@@ -412,8 +415,10 @@ class OptionsParser:
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.'''
+ """
+ #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'):
@@ -422,36 +427,45 @@ class OptionsParser:
gajim.config.set('version', '0.10.1.6')
def update_config_to_01017(self):
- '''trayicon_notification_on_new_messages ->
- trayicon_notification_on_events '''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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 \
@@ -459,7 +473,9 @@ class OptionsParser:
gajim.config.set('version', '0.11.1.1')
def update_config_to_01112(self):
- '''gtk+ theme is renamed to default'''
+ """
+ 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'))
@@ -568,7 +584,9 @@ class OptionsParser:
gajim.config.set('version', '0.11.4.1')
def update_config_to_01142(self):
- '''next_message_received sound event is splittedin 2 events'''
+ """
+ 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'):
@@ -681,7 +699,9 @@ class OptionsParser:
gajim.config.set('version', '0.12.1.4')
def update_config_to_01215(self):
- '''Remove hardcoded ../data/sounds from config'''
+ """
+ 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')
@@ -820,7 +840,7 @@ class OptionsParser:
'proxy.jabber.ru', 'proxy.jabbim.cz'])
gajim.config.set('version', '0.12.5.8')
- def update_config_to_01301(self):
+ def update_config_to_013100(self):
back = os.getcwd()
os.chdir(logger.LOG_DB_FOLDER)
con = sqlite.connect(logger.LOG_DB_FILE)
@@ -837,6 +857,27 @@ class OptionsParser:
except sqlite.OperationalError:
pass
con.close()
- gajim.config.set('version', '0.13.0.1')
+ 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:
diff --git a/src/common/passwords.py b/src/common/passwords.py
index 98a7f61d9..9000085c4 100644
--- a/src/common/passwords.py
+++ b/src/common/passwords.py
@@ -122,6 +122,8 @@ class GnomePasswordStorage(PasswordStorage):
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,
diff --git a/src/common/pep.py b/src/common/pep.py
index 54012514f..e85e7021e 100644
--- a/src/common/pep.py
+++ b/src/common/pep.py
@@ -23,9 +23,6 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-import common.gajim
-from common import xmpp
-
MOODS = {
'afraid': _('Afraid'),
'amazed': _('Amazed'),
@@ -192,390 +189,444 @@ ACTIVITIES = {
'studying': _('Studying'),
'writing': _('Writing')}}
-def user_mood(items, name, jid):
- has_child = False
- retract = False
- mood = None
- text = None
- for item in items.getTags('item'):
- child = item.getTag('mood')
- if child is not None:
- has_child = True
- for ch in child.getChildren():
- if ch.getName() != 'text':
- mood = ch.getName()
- else:
- text = ch.getData()
- if items.getTag('retract') is not None:
- retract = True
-
- if jid == common.gajim.get_jid_from_account(name):
- acc = common.gajim.connections[name]
- if has_child:
- if 'mood' in acc.mood:
- del acc.mood['mood']
- if 'text' in acc.mood:
- del acc.mood['text']
- if mood is not None:
- acc.mood['mood'] = mood
- if text is not None:
- acc.mood['text'] = text
- elif retract:
- if 'mood' in acc.mood:
- del acc.mood['mood']
- if 'text' in acc.mood:
- del acc.mood['text']
-
- (user, resource) = common.gajim.get_room_and_nick_from_fjid(jid)
- for contact in common.gajim.contacts.get_contacts(name, user):
- if has_child:
- if 'mood' in contact.mood:
- del contact.mood['mood']
- if 'text' in contact.mood:
- del contact.mood['text']
- if mood is not None:
- contact.mood['mood'] = mood
- if text is not None:
- contact.mood['text'] = text
- elif retract:
- if 'mood' in contact.mood:
- del contact.mood['mood']
- if 'text' in contact.mood:
- del contact.mood['text']
-
- if jid == common.gajim.get_jid_from_account(name):
- common.gajim.interface.roster.draw_account(name)
- common.gajim.interface.roster.draw_mood(user, name)
- ctrl = common.gajim.interface.msg_win_mgr.get_control(user, name)
- if ctrl:
- ctrl.update_mood()
-
-def user_tune(items, name, jid):
- has_child = False
- retract = False
- artist = None
- title = None
- source = None
- track = None
- length = None
-
- for item in items.getTags('item'):
- child = item.getTag('tune')
- if child is not None:
- has_child = True
- for ch in child.getChildren():
- if ch.getName() == 'artist':
- artist = ch.getData()
- elif ch.getName() == 'title':
- title = ch.getData()
- elif ch.getName() == 'source':
- source = ch.getData()
- elif ch.getName() == 'track':
- track = ch.getData()
- elif ch.getName() == 'length':
- length = ch.getData()
- if items.getTag('retract') is not None:
- retract = True
-
- if jid == common.gajim.get_jid_from_account(name):
- acc = common.gajim.connections[name]
- if has_child:
- if 'artist' in acc.tune:
- del acc.tune['artist']
- if 'title' in acc.tune:
- del acc.tune['title']
- if 'source' in acc.tune:
- del acc.tune['source']
- if 'track' in acc.tune:
- del acc.tune['track']
- if 'length' in acc.tune:
- del acc.tune['length']
- if artist is not None:
- acc.tune['artist'] = artist
- if title is not None:
- acc.tune['title'] = title
- if source is not None:
- acc.tune['source'] = source
- if track is not None:
- acc.tune['track'] = track
- if length is not None:
- acc.tune['length'] = length
- elif retract:
- if 'artist' in acc.tune:
- del acc.tune['artist']
- if 'title' in acc.tune:
- del acc.tune['title']
- if 'source' in acc.tune:
- del acc.tune['source']
- if 'track' in acc.tune:
- del acc.tune['track']
- if 'length' in acc.tune:
- del acc.tune['length']
-
- user = common.gajim.get_room_and_nick_from_fjid(jid)[0]
- for contact in common.gajim.contacts.get_contacts(name, user):
- if has_child:
- if 'artist' in contact.tune:
- del contact.tune['artist']
- if 'title' in contact.tune:
- del contact.tune['title']
- if 'source' in contact.tune:
- del contact.tune['source']
- if 'track' in contact.tune:
- del contact.tune['track']
- if 'length' in contact.tune:
- del contact.tune['length']
- if artist is not None:
- contact.tune['artist'] = artist
- if title is not None:
- contact.tune['title'] = title
- if source is not None:
- contact.tune['source'] = source
- if track is not None:
- contact.tune['track'] = track
- if length is not None:
- contact.tune['length'] = length
- elif retract:
- if 'artist' in contact.tune:
- del contact.tune['artist']
- if 'title' in contact.tune:
- del contact.tune['title']
- if 'source' in contact.tune:
- del contact.tune['source']
- if 'track' in contact.tune:
- del contact.tune['track']
- if 'length' in contact.tune:
- del contact.tune['length']
-
- if jid == common.gajim.get_jid_from_account(name):
- common.gajim.interface.roster.draw_account(name)
- common.gajim.interface.roster.draw_tune(user, name)
- ctrl = common.gajim.interface.msg_win_mgr.get_control(user, name)
- if ctrl:
- ctrl.update_tune()
-
-def user_geoloc(items, name, jid):
- pass
-
-def user_activity(items, name, jid):
- has_child = False
- retract = False
- activity = None
- subactivity = None
- text = None
-
- for item in items.getTags('item'):
- child = item.getTag('activity')
- if child is not None:
- has_child = True
- for ch in child.getChildren():
- if ch.getName() != 'text':
- activity = ch.getName()
- for chi in ch.getChildren():
- subactivity = chi.getName()
- else:
- text = ch.getData()
- if items.getTag('retract') is not None:
- retract = True
-
- if jid == common.gajim.get_jid_from_account(name):
- acc = common.gajim.connections[name]
- if has_child:
- if 'activity' in acc.activity:
- del acc.activity['activity']
- if 'subactivity' in acc.activity:
- del acc.activity['subactivity']
- if 'text' in acc.activity:
- del acc.activity['text']
- if activity is not None:
- acc.activity['activity'] = activity
- if subactivity is not None and subactivity != 'other':
- acc.activity['subactivity'] = subactivity
- if text is not None:
- acc.activity['text'] = text
- elif retract:
- if 'activity' in acc.activity:
- del acc.activity['activity']
- if 'subactivity' in acc.activity:
- del acc.activity['subactivity']
- if 'text' in acc.activity:
- del acc.activity['text']
-
- user = common.gajim.get_room_and_nick_from_fjid(jid)[0]
- for contact in common.gajim.contacts.get_contacts(name, user):
- if has_child:
- if 'activity' in contact.activity:
- del contact.activity['activity']
- if 'subactivity' in contact.activity:
- del contact.activity['subactivity']
- if 'text' in contact.activity:
- del contact.activity['text']
- if activity is not None:
- contact.activity['activity'] = activity
- if subactivity is not None and subactivity != 'other':
- contact.activity['subactivity'] = subactivity
- if text is not None:
- contact.activity['text'] = text
- elif retract:
- if 'activity' in contact.activity:
- del contact.activity['activity']
- if 'subactivity' in contact.activity:
- del contact.activity['subactivity']
- if 'text' in contact.activity:
- del contact.activity['text']
-
- if jid == common.gajim.get_jid_from_account(name):
- common.gajim.interface.roster.draw_account(name)
- common.gajim.interface.roster.draw_activity(user, name)
- ctrl = common.gajim.interface.msg_win_mgr.get_control(user, name)
- if ctrl:
- ctrl.update_activity()
-
-def user_nickname(items, name, jid):
- has_child = False
- retract = False
- nick = None
-
- for item in items.getTags('item'):
- child = item.getTag('nick')
- if child is not None:
- has_child = True
- nick = child.getData()
- break
-
- if items.getTag('retract') is not None:
- retract = True
-
- if jid == common.gajim.get_jid_from_account(name):
- if has_child:
- common.gajim.nicks[name] = nick
- if retract:
- common.gajim.nicks[name] = common.gajim.config.get_per('accounts',
- name, 'name')
-
- user = common.gajim.get_room_and_nick_from_fjid(jid)[0]
- if has_child:
- if nick is not None:
- for contact in common.gajim.contacts.get_contacts(name, user):
- contact.contact_name = nick
- common.gajim.interface.roster.draw_contact(user, name)
-
- ctrl = common.gajim.interface.msg_win_mgr.get_control(user, name)
- if ctrl:
- ctrl.update_ui()
- win = ctrl.parent_win
- win.redraw_tab(ctrl)
- win.show_title()
- elif retract:
- contact.contact_name = ''
-
-def user_send_mood(account, mood, message=''):
- if not common.gajim.connections[account].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)
-
- common.gajim.connections[account].send_pb_publish('', xmpp.NS_MOOD, item,
- '0')
-
-def user_send_activity(account, activity, subactivity='', message=''):
- if not common.gajim.connections[account].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)
-
- common.gajim.connections[account].send_pb_publish('', xmpp.NS_ACTIVITY, item,
- '0')
-
-def user_send_tune(account, artist='', title='', source='', track=0, length=0,
-items=None):
- if not (common.gajim.config.get_per('accounts', account, 'publish_tune') and\
- common.gajim.connections[account].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 != 0:
- i = item.addChild('track')
- i.addData(track)
- if length != 0:
- i = item.addChild('length')
- i.addData(length)
- if items is not None:
- item.addChild(payload=items)
-
- common.gajim.connections[account].send_pb_publish('', xmpp.NS_TUNE, item,
- '0')
-
-def user_send_nickname(account, nick):
- if not common.gajim.connections[account].pep_supported:
- return
- item = xmpp.Node('nick', {'xmlns': xmpp.NS_NICK})
- item.addData(nick)
-
- common.gajim.connections[account].send_pb_publish('', xmpp.NS_NICK, item,
- '0')
-
-def user_retract_mood(account):
- common.gajim.connections[account].send_pb_retract('', xmpp.NS_MOOD, '0')
-
-def user_retract_activity(account):
- common.gajim.connections[account].send_pb_retract('', xmpp.NS_ACTIVITY, '0')
-
-def user_retract_tune(account):
- common.gajim.connections[account].send_pb_retract('', xmpp.NS_TUNE, '0')
-
-def user_retract_nickname(account):
- common.gajim.connections[account].send_pb_retract('', xmpp.NS_NICK, '0')
-
-def delete_pep(jid, name):
- user = common.gajim.get_room_and_nick_from_fjid(jid)[0]
-
- if jid == common.gajim.get_jid_from_account(name):
- acc = common.gajim.connections[name]
- del acc.activity
- acc.activity = {}
- user_send_tune(name)
- del acc.tune
- acc.tune = {}
- del acc.mood
- acc.mood = {}
-
- for contact in common.gajim.contacts.get_contacts(name, user):
- del contact.activity
- contact.activity = {}
- del contact.tune
- contact.tune = {}
- del contact.mood
- contact.mood = {}
-
- if jid == common.gajim.get_jid_from_account(name):
- common.gajim.interface.roster.draw_account(name)
-
- common.gajim.interface.roster.draw_activity(user, name)
- common.gajim.interface.roster.draw_tune(user, name)
- common.gajim.interface.roster.draw_mood(user, name)
- ctrl = common.gajim.interface.msg_win_mgr.get_control(user, name)
- if ctrl:
- ctrl.update_activity()
- ctrl.update_tune()
- ctrl.update_mood()
+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']
+
+import gobject
+import gtk
+
+import logging
+log = logging.getLogger('gajim.c.pep')
+
+from common import helpers
+from common import atom
+from common import xmpp
+from common import gajim
+
+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 = '<b>%s</b>' % 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
+
+ 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 = _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
+ 'from <i>%(source)s</i>') % {'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
+
+ if activity in ACTIVITIES:
+ # Translate standard activities
+ if subactivity in ACTIVITIES[activity]:
+ subactivity = ACTIVITIES[activity][subactivity]
+ activity = ACTIVITIES[activity]['category']
+
+ markuptext = '<b>' + gobject.markup_escape_text(activity)
+ if subactivity:
+ markuptext += ': ' + gobject.markup_escape_text(subactivity)
+ markuptext += '</b>'
+ 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
+
+
+class UserLocationPEP(AbstractPEP):
+ '''XEP-0080: User Location'''
+
+ type = 'location'
+ namespace = xmpp.NS_LOCATION
+
+ 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
+
+ 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 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 = ''
+
+ for entry in location.keys():
+ text = location[entry]
+ text = gobject.markup_escape_text(text)
+ location_string += '\n<b>%(tag)s</b>: %(text)s' % \
+ {'tag': entry.capitalize(), 'text': text}
+
+ return location_string.strip()
+
+
+SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP,
+ UserNicknamePEP, UserLocationPEP]
+
+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 <message /> 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')
+
+ 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
+ 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 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 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
+ 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 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 send_location(self, info):
+ 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:
diff --git a/src/common/protocol/__init__.py b/src/common/protocol/__init__.py
new file mode 100644
index 000000000..f50b2cd01
--- /dev/null
+++ b/src/common/protocol/__init__.py
@@ -0,0 +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
new file mode 100644
index 000000000..ce0086b19
--- /dev/null
+++ b/src/common/protocol/bytestream.py
@@ -0,0 +1,650 @@
+# -*- coding:utf-8 -*-
+## src/common/connection_handlers.py
+##
+## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
+## Junglecow J <junglecow AT gmail.com>
+## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
+## Travis Shirk <travis AT pobox.com>
+## Nikos Kouremenos <kourem AT gmail.com>
+## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org>
+## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
+## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
+## Jean-Marie Traissard <jim AT lapin.org>
+## Stephan Erb <steve-e AT h3c.de>
+## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
+##
+## This file is part of Gajim.
+##
+## Gajim is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## Gajim is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+##
+
+import socket
+
+from common import xmpp
+from common import gajim
+from common import helpers
+from common import dataforms
+
+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']
+
+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']
+
+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
+
+
+class ConnectionBytestream:
+
+ 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 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_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'])
+
+ 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'])
+
+ 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
+
+ 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.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_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')))
+
+ 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
+
+ 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)
+
+ 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
+
+ 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 _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 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 _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)
+
+ # 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 _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
+
+ 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 _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
+
+ 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
+
+ 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)
+
+ 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()
+
+ 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
+
+
+class ConnectionBytestreamZeroconf(ConnectionBytestream):
+
+ 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_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:
diff --git a/src/common/protocol/caps.py b/src/common/protocol/caps.py
new file mode 100644
index 000000000..3fd6c1386
--- /dev/null
+++ b/src/common/protocol/caps.py
@@ -0,0 +1,111 @@
+# -*- coding:utf-8 -*-
+## src/common/protocol/caps.py
+##
+## Copyright (C) 2009 Stephan Erb <steve-e AT h3c.de>
+##
+## This file is part of Gajim.
+##
+## Gajim is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## Gajim is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+##
+
+"""
+Module containing the network portion of XEP-115 (Entity Capabilities)
+"""
+
+import logging
+log = logging.getLogger('gajim.c.p.caps')
+
+from common.xmpp import NS_CAPS
+from common import gajim
+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 _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)
+
+ 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 _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 _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)
+
+ 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())
+
+ self._dispatch_event('CAPS_RECEIVED', (jid,))
+
+# vim: se ts=3:
diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py
index b1bbea79d..5c4420839 100644
--- a/src/common/proxy65_manager.py
+++ b/src/common/proxy65_manager.py
@@ -41,10 +41,13 @@ 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
@@ -53,7 +56,9 @@ class Proxy65Manager:
self.default_proxies = {}
def resolve(self, proxy, connection, sender_jid, default=None):
- ''' start '''
+ """
+ Start
+ """
if proxy in self.proxies:
resolver = self.proxies[proxy]
else:
@@ -102,7 +107,9 @@ class Proxy65Manager:
class ProxyResolver:
def resolve_result(self, host, port, jid):
- ''' test if host has a real proxy65 listening on port '''
+ """
+ Test if host has a real proxy65 listening on port
+ """
self.host = str(host)
self.port = int(port)
self.jid = unicode(jid)
@@ -175,19 +182,25 @@ class ProxyResolver:
self.try_next_connection()
def try_next_connection(self):
- ''' try to resolve proxy with the next possible connection '''
+ """
+ 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 '''
+ """
+ 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 '''
+ """
+ Request network address from proxy
+ """
self.state = S_STARTED
self.active_connection = connection
iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get')
@@ -209,10 +222,16 @@ class ProxyResolver:
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)
- call on_success, or on_failure according to the result'''
+ """
+ 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
@@ -226,7 +245,9 @@ class HostTester(Socks5, IdleObject):
self.sid = sid
def connect(self):
- ''' create the socket and plug it to the idlequeue '''
+ """
+ Create the socket and plug it to the idlequeue
+ """
if self.host is None:
self.on_failure()
return None
@@ -320,10 +341,16 @@ class HostTester(Socks5, IdleObject):
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)
- call on_success, or on_failure according to the result'''
+ """
+ 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
@@ -337,7 +364,9 @@ class ReceiverTester(Socks5, IdleObject):
self.sid = sid
def connect(self):
- ''' create the socket and plug it to the idlequeue '''
+ """
+ Create the socket and plug it to the idlequeue
+ """
if self.host is None:
self.on_failure()
return None
diff --git a/src/common/pubsub.py b/src/common/pubsub.py
index 8f65ff1a8..d2dbf88e1 100644
--- a/src/common/pubsub.py
+++ b/src/common/pubsub.py
@@ -67,7 +67,9 @@ class ConnectionPubSub:
self.__callbacks[id_]=(cb, args, kwargs)
def send_pb_publish(self, jid, node, item, id_, options=None):
- '''Publish item to a node.'''
+ """
+ Publish item to a node
+ """
if not self.connection or self.connected < 2:
return
query = xmpp.Iq('set', to=jid)
@@ -80,20 +82,24 @@ class ConnectionPubSub:
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})
+ 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)
def send_pb_retract(self, jid, node, id_):
- '''Delete item from a node'''
+ """
+ Delete item from a node
+ """
if not self.connection or self.connected < 2:
return
query = xmpp.Iq('set', to=jid)
@@ -104,7 +110,9 @@ class ConnectionPubSub:
self.connection.send(query)
def send_pb_delete(self, jid, node):
- '''Deletes node.'''
+ """
+ Delete node
+ """
if not self.connection or self.connected < 2:
return
query = xmpp.Iq('set', to=jid)
@@ -122,7 +130,9 @@ class ConnectionPubSub:
'node': node})
def send_pb_create(self, jid, node, configure = False, configure_form = None):
- '''Creates new node.'''
+ """
+ Create a new node
+ """
if not self.connection or self.connected < 2:
return
query = xmpp.Iq('set', to=jid)
diff --git a/src/common/resolver.py b/src/common/resolver.py
index b11abcd98..3e696e110 100644
--- a/src/common/resolver.py
+++ b/src/common/resolver.py
@@ -23,8 +23,14 @@ import re
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)
+
from common import helpers
-from xmpp.idlequeue import IdleCommand
+from common.xmpp.idlequeue import IdleCommand
# it is good to check validity of arguments, when calling system commands
ns_type_pattern = re.compile('^[a-z]+$')
@@ -56,6 +62,7 @@ class CommonResolver():
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
@@ -63,19 +70,23 @@ class CommonResolver():
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):
@@ -88,11 +99,12 @@ class CommonResolver():
# 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.
- '''
+ 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)
@@ -125,18 +137,23 @@ class LibAsyncNSResolver(CommonResolver):
while resq is not None:
try:
rl = resq.get_done()
- except:
+ 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']
- self._on_ready(
- host = resq.userdata['host'],
- type = resq.userdata['type'],
- result_list = rl)
+ hosts.append(r)
+ self._on_ready(host=requested_host, type=requested_type,
+ result_list=hosts)
try:
resq = self.asyncns.get_next()
- except:
+ except Exception:
resq = None
elif type(resq) == libasyncns.AddrInfoQuery:
# getaddrinfo result (A or AAAA)
@@ -146,20 +163,22 @@ class LibAsyncNSResolver(CommonResolver):
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.
- '''
+ 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 '''
+ """
+ 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':
@@ -260,7 +279,9 @@ class NSLookupResolver(CommonResolver):
CommonResolver._on_ready(self, host, type, result_list)
def start_resolve(self, host, type):
- ''' spawn new nslookup process and start waiting for results '''
+ """
+ Spawn new nslookup process and start waiting for results
+ """
ns = NsLookup(self._on_ready, host, type)
ns.set_idlequeue(self.idlequeue)
ns.commandtimeout = 10
@@ -319,6 +340,8 @@ if __name__ == '__main__':
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:
diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py
index 8f57bb698..7211e8a1d 100644
--- a/src/common/rst_xhtml_generator.py
+++ b/src/common/rst_xhtml_generator.py
@@ -33,17 +33,19 @@ except ImportError:
return None
else:
def pos_int_validator(text):
- """Validates that text can be evaluated as a positive integer."""
+ """
+ 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):
+ """
+ Create and register a uri based "interpreted role"
Those are similar to the RFC, and PEP ones, and take
role_name:
@@ -58,7 +60,7 @@ else:
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:
@@ -94,15 +96,15 @@ else:
pos_int_validator)
class HTMLGenerator:
- '''Really simple HTMLGenerator starting from publish_parts.
+ """
+ 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'):
+ """
+ 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,
@@ -124,13 +126,12 @@ else:
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.'''
+ 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)
diff --git a/src/common/sleepy.py b/src/common/sleepy.py
index 507452ea9..0116faefb 100644
--- a/src/common/sleepy.py
+++ b/src/common/sleepy.py
@@ -66,7 +66,9 @@ class SleepyWindows:
return idleDelta
def poll(self):
- '''checks to see if we should change state'''
+ """
+ Check to see if we should change state
+ """
if not SUPPORTED:
return False
@@ -113,7 +115,9 @@ class SleepyUnix:
return idle.getIdleSec()
def poll(self):
- '''checks to see if we should change state'''
+ """
+ Check to see if we should change state
+ """
if not SUPPORTED:
return False
diff --git a/src/common/socks5.py b/src/common/socks5.py
index e1c1099ad..f3c1a5b5e 100644
--- a/src/common/socks5.py
+++ b/src/common/socks5.py
@@ -33,6 +33,7 @@ from errno import ENOBUFS
from errno import EINTR
from errno import EISCONN
from errno import EINPROGRESS
+from errno import EAFNOSUPPORT
from xmpp.idlequeue import IdleObject
MAX_BUFF_LEN = 65536
@@ -52,9 +53,12 @@ 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):
+ progress_transfer_cb=None, error_cb=None):
self.connected = 0
self.readers = {}
self.files_props = {}
@@ -72,9 +76,10 @@ class SocksQueue:
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
- '''
+ """
+ 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)
@@ -121,8 +126,10 @@ class SocksQueue:
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 '''
+ """
+ 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:
@@ -137,10 +144,11 @@ class SocksQueue:
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.
- '''
+ """
+ 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
@@ -173,7 +181,9 @@ class SocksQueue:
self.process_result(-1, receiver)
def _connection_refused(self, streamhost, file_props, idx):
- ''' cb, called when we loose connection during transfer'''
+ """
+ Called when we loose connection during transfer
+ """
if file_props is None:
return
streamhost['state'] = -1
@@ -189,7 +199,9 @@ class SocksQueue:
del(file_props['failure_cb'])
def add_receiver(self, account, sock5_receiver):
- ''' add new file request '''
+ """
+ Add new file request
+ """
self.readers[self.idx] = sock5_receiver
sock5_receiver.queue_idx = self.idx
sock5_receiver.queue = self
@@ -259,9 +271,10 @@ class SocksQueue:
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
- '''
+ """
+ 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']
@@ -279,7 +292,9 @@ class SocksQueue:
self.connected = 0
def get_file_props(self, account, sid):
- ''' get fil_prop by account name and session id '''
+ """
+ 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:
@@ -294,11 +309,12 @@ class SocksQueue:
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
- '''
+ """
+ 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:
@@ -310,8 +326,10 @@ class SocksQueue:
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'''
+ """
+ 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]
@@ -325,8 +343,10 @@ class SocksQueue:
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'''
+ """
+ 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:
@@ -386,9 +406,10 @@ class Socks5:
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.
- '''
+ """
+ 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:
@@ -413,8 +434,10 @@ class Socks5:
pass
def receive(self):
- ''' Reads small chunks of data.
- Calls owner's disconnected() method if appropriate.'''
+ """
+ Read small chunks of data. Call owner's disconnected() method if
+ appropriate
+ """
received = ''
try:
add = self._recv(64)
@@ -426,7 +449,9 @@ class Socks5:
return add
def send_raw(self,raw_data):
- ''' Writes raw outgoing data. '''
+ """
+ Write raw outgoing data
+ """
try:
self._send(raw_data)
except Exception:
@@ -483,7 +508,9 @@ class Socks5:
return -1
def get_file_contents(self, timeout):
- ''' read file contents from socket and write them to file '''
+ """
+ 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
@@ -557,7 +584,9 @@ class Socks5:
return None
def disconnect(self):
- ''' Closes open descriptors and remover socket descr. from idleque '''
+ """
+ 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)
@@ -573,13 +602,15 @@ class Socks5:
self.state = -1
def _get_auth_buff(self):
- ''' Message, that we support 1 one auth mechanism:
- the 'no auth' mechanism. '''
+ """
+ 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 '''
+ """
+ Parse the initial message and create a list of auth mechanisms
+ """
auth_mechanisms = []
try:
num_auth = struct.unpack('!xB', buff[:2])[0]
@@ -591,9 +622,9 @@ class Socks5:
return auth_mechanisms
def _get_auth_response(self):
- ''' socks version(5), number of extra auth methods (we send
- 0x00 - no auth
- ) '''
+ """
+ Socks version(5), number of extra auth methods (we send 0x00 - no auth)
+ """
return struct.pack('!BB', 0x05, 0x00)
def _get_connect_buff(self):
@@ -604,8 +635,10 @@ class Socks5:
return buff
def _get_request_buff(self, msg, command = 0x01):
- ''' Connect request by domain name,
- sid sha, instead of domain name (jep 0096) '''
+ """
+ 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
@@ -634,7 +667,9 @@ class Socks5:
return (req_type, host, port)
def read_connect(self):
- ''' connect responce: version, auth method '''
+ """
+ Connect response: version, auth method
+ """
buff = self._recv()
try:
version, method = struct.unpack('!BB', buff)
@@ -652,7 +687,9 @@ class Socks5:
self.idlequeue.plug_idle(self, True, False)
def _get_sha1_auth(self):
- ''' get sha of sid + Initiator jid + Target jid '''
+ """
+ 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,
@@ -662,9 +699,12 @@ class Socks5:
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):
+ port=None):
self.queue_idx = sock_hash
self.queue = parent
Socks5.__init__(self, idlequeue, host, port, None, None, None)
@@ -745,7 +785,9 @@ class Socks5Sender(Socks5, IdleObject):
self.disconnect()
def send_file(self):
- ''' start sending the file over verified connection '''
+ """
+ Start sending the file over verified connection
+ """
if self.file_props['started']:
return
self.file_props['error'] = 0
@@ -766,7 +808,9 @@ class Socks5Sender(Socks5, IdleObject):
return self.write_next() # initial for nl byte
def main(self):
- ''' initial requests for verifying the connection '''
+ """
+ Initial requests for verifying the connection
+ """
if self.state == 1: # initial read
buff = self.receive()
if not self.connected:
@@ -785,7 +829,9 @@ class Socks5Sender(Socks5, IdleObject):
return None
def disconnect(self, cb=True):
- ''' Closes the socket. '''
+ """
+ Close the socket
+ """
# close connection and remove us from the queue
Socks5.disconnect(self)
if self.file_props is not None:
@@ -796,10 +842,12 @@ class Socks5Sender(Socks5, IdleObject):
class Socks5Listener(IdleObject):
def __init__(self, idlequeue, port):
- ''' handle all incomming connections on (0.0.0.0, 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)
@@ -813,8 +861,14 @@ class Socks5Listener(IdleObject):
def bind(self):
for ai in self.ais:
- #try the different possibilities (ipv6, ipv4, etc.)
- self._serv = socket.socket(*ai[:3])
+ # try the different possibilities (ipv6, ipv4, etc.)
+ try:
+ self._serv = socket.socket(*ai[:3])
+ except socket.error, e:
+ if e.errno == 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)
@@ -843,16 +897,22 @@ class Socks5Listener(IdleObject):
self.started = True
def pollend(self):
- ''' called when we stop listening on (host, port) '''
+ """
+ Called when we stop listening on (host, port)
+ """
self.disconnect()
def pollin(self):
- ''' accept a new incomming connection and notify queue'''
+ """
+ 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 '''
+ """
+ Free all resources, we are not listening anymore
+ """
self.idlequeue.remove_timeout(self.fd)
self.idlequeue.unplug_idle(self.fd)
self.fd = -1
@@ -864,7 +924,9 @@ class Socks5Listener(IdleObject):
pass
def accept_conn(self):
- ''' accepts a new incomming connection '''
+ """
+ Accept a new incomming connection
+ """
_sock = self._serv.accept()
_sock[0].setblocking(False)
return _sock
@@ -909,7 +971,9 @@ class Socks5Receiver(Socks5, IdleObject):
self.queue.reconnect_receiver(self, self.streamhost)
def connect(self):
- ''' create the socket and plug it to the idlequeue '''
+ """
+ Create the socket and plug it to the idlequeue
+ """
if self.ais is None:
return None
@@ -1018,7 +1082,9 @@ class Socks5Receiver(Socks5, IdleObject):
return 1 # we are connected
def main(self, timeout=0):
- ''' begin negotiation. on success 'address' != 0 '''
+ """
+ Begin negotiation. on success 'address' != 0
+ """
result = 1
buff = self.receive()
if buff == '':
@@ -1087,7 +1153,9 @@ class Socks5Receiver(Socks5, IdleObject):
return None
def disconnect(self, cb=True):
- ''' Closes the socket. Remove self from queue if cb is True'''
+ """
+ Close the socket. Remove self from queue if cb is True
+ """
# close connection
Socks5.disconnect(self)
if cb is True:
diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py
index 3cb1a13cf..a85155efd 100644
--- a/src/common/stanza_session.py
+++ b/src/common/stanza_session.py
@@ -84,10 +84,11 @@ class StanzaSession(object):
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)
- '''
+ """
+ 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()):
@@ -140,10 +141,10 @@ class StanzaSession(object):
self.cancelled_negotiation()
def cancelled_negotiation(self):
- '''
+ """
A negotiation has been cancelled, so reset this session to its default
- state.
- '''
+ state
+ """
if self.control:
self.control.on_cancel_session_negotiation()
@@ -175,7 +176,7 @@ class StanzaSession(object):
class EncryptedStanzaSession(StanzaSession):
- '''
+ """
An encrypted stanza negotiation has several states. They arerepresented as
the following values in the 'status' attribute of the session object:
@@ -198,7 +199,8 @@ class EncryptedStanzaSession(StanzaSession):
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')
@@ -228,9 +230,9 @@ class EncryptedStanzaSession(StanzaSession):
return True
def set_kc_s(self, value):
- '''
- keep the encrypter updated with my latest cipher key
- '''
+ """
+ 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)
@@ -239,9 +241,9 @@ class EncryptedStanzaSession(StanzaSession):
return self._kc_s
def set_kc_o(self, value):
- '''
- keep the decrypter updated with the other party's latest cipher key
- '''
+ """
+ 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)
@@ -335,7 +337,9 @@ class EncryptedStanzaSession(StanzaSession):
return self.encrypter.encrypt(padded)
def decrypt_stanza(self, stanza):
- ''' delete the unencrypted explanation body, if it exists '''
+ """
+ Delete the unencrypted explanation body, if it exists
+ """
orig_body = stanza.getTag('body')
if orig_body:
stanza.delChild(orig_body)
@@ -584,7 +588,9 @@ class EncryptedStanzaSession(StanzaSession):
self.send(request)
def verify_options_bob(self, form):
- ''' 4.3 esession response (bob) '''
+ """
+ 4.3 esession response (bob)
+ """
negotiated = {'recv_pubkey': None, 'send_pubkey': None}
not_acceptable = []
ask_user = {}
@@ -653,7 +659,9 @@ class EncryptedStanzaSession(StanzaSession):
return (negotiated, not_acceptable, ask_user)
def respond_e2e_bob(self, form, negotiated, not_acceptable):
- ''' 4.3 esession response (bob) '''
+ """
+ 4.3 esession response (bob)
+ """
response = xmpp.Message()
feature = response.NT.feature
feature.setNamespace(xmpp.NS_FEATURE)
@@ -728,7 +736,9 @@ class EncryptedStanzaSession(StanzaSession):
self.send(response)
def verify_options_alice(self, form):
- ''' 'Alice Accepts' '''
+ """
+ 'Alice Accepts'
+ """
negotiated = {}
ask_user = {}
not_acceptable = []
@@ -756,11 +766,13 @@ class EncryptedStanzaSession(StanzaSession):
return (negotiated, not_acceptable, ask_user)
def accept_e2e_alice(self, form, negotiated):
- ''' 'Alice Accepts', continued '''
+ """
+ 'Alice Accepts', continued
+ """
self.encryptable_stanzas = ['message']
self.sas_algs = 'sas28x5'
self.cipher = AES
- self.hash_alg = sha256
+ self.hash_alg = sha256
self.compression = None
self.negotiated = negotiated
@@ -828,7 +840,9 @@ class EncryptedStanzaSession(StanzaSession):
self.status = 'identified-alice'
def accept_e2e_bob(self, form):
- ''' 4.5 esession accept (bob) '''
+ """
+ 4.5 esession accept (bob)
+ """
response = xmpp.Message()
init = response.NT.init
@@ -948,11 +962,11 @@ class EncryptedStanzaSession(StanzaSession):
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.
- '''
+ been verified
+ """
new_srs = self.hmac(k, 'New Retained Secret')
self.srs = new_srs
@@ -1017,12 +1031,12 @@ class EncryptedStanzaSession(StanzaSession):
self.enable_encryption = False
def fail_bad_negotiation(self, reason, fields=None):
- '''
- Sends an error and cancels everything.
+ """
+ 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
- '''
+ 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)
diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py
index e5e51f6b8..9872f651b 100644
--- a/src/common/xmpp/auth_nb.py
+++ b/src/common/xmpp/auth_nb.py
@@ -13,12 +13,14 @@
## 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.
-'''
+
+"""
Provides plugs for SASL and NON-SASL authentication mechanisms.
-Can be used both for client and transport authentication.
+Can be used both for client and transport authentication
See client_nb.py
-'''
+"""
+
from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH
from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID
from plugin import PlugIn
@@ -49,7 +51,8 @@ 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",\
@@ -59,7 +62,7 @@ def challenge_splitter(data):
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 = '', ''
@@ -112,16 +115,17 @@ def challenge_splitter(data):
class SASL(PlugIn):
- '''
+ """
Implements SASL authentication. Can be plugged into NonBlockingClient
- to start authentication.
- '''
+ 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
@@ -141,7 +145,9 @@ class SASL(PlugIn):
self.startsasl = None
def plugout(self):
- ''' Remove SASL handlers from owner's dispatcher. Used internally. '''
+ """
+ Remove SASL handlers from owner's dispatcher. Used internally
+ """
if 'features' in self._owner.__dict__:
self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
@@ -156,13 +162,13 @@ class SASL(PlugIn):
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.
+ 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:
@@ -176,7 +182,9 @@ class SASL(PlugIn):
self.FeaturesHandler, xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
- ''' Used to determine if server supports SASL auth. Used internally. '''
+ """
+ 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')
@@ -199,6 +207,14 @@ class SASL(PlugIn):
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:
@@ -229,13 +245,16 @@ class SASL(PlugIn):
self.startsasl = SASL_IN_PROCESS
raise NodeProcessed
self.startsasl = SASL_FAILURE
- log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.')
+ 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. '''
+ """
+ Perform next SASL auth step. Used internally
+ """
if challenge.getNamespace() != NS_SASL:
return
### Handle Auth result
@@ -332,8 +351,17 @@ class SASL(PlugIn):
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']])
+ 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)]))
@@ -359,12 +387,15 @@ class SASL(PlugIn):
class NonBlockingNonSASL(PlugIn):
- '''
+ """
Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
- transport authentication.
- '''
+ transport authentication
+ """
+
def __init__(self, user, password, resource, on_auth):
- ''' Caches username, password and resource for auth. '''
+ """
+ Caches username, password and resource for auth
+ """
PlugIn.__init__(self)
self.user = user
if password is None:
@@ -375,10 +406,10 @@ class NonBlockingNonSASL(PlugIn):
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.
- '''
+ Returns used method name on success. Used internally
+ """
log.info('Querying server about possible auth methods')
self.owner = owner
@@ -438,10 +469,11 @@ class NonBlockingNonSASL(PlugIn):
class NonBlockingBind(PlugIn):
- '''
+ """
Bind some JID to the current connection to allow router know of our
- location. Must be plugged after successful SASL auth.
- '''
+ location. Must be plugged after successful SASL auth
+ """
+
def __init__(self):
PlugIn.__init__(self)
self.bound = None
@@ -459,10 +491,10 @@ class NonBlockingBind(PlugIn):
xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
- '''
+ """
Determine if server supports resource binding and set some internal
- attributes accordingly.
- '''
+ attributes accordingly
+ """
if not feats.getTag('bind', namespace=NS_BIND):
log.error('Server does not requested binding.')
# we try to bind resource anyway
@@ -476,14 +508,16 @@ class NonBlockingBind(PlugIn):
self.bound = []
def plugout(self):
- ''' Remove Bind handler from owner's dispatcher. Used internally. '''
+ """
+ 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).
- '''
+ """
+ Perform binding. Use provided resource name or random (if not provided).
+ """
self.on_bound = on_bound
self._resource = resource
if self._resource:
diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py
index bb6ea4dd8..2658668ba 100644
--- a/src/common/xmpp/bosh.py
+++ b/src/common/xmpp/bosh.py
@@ -125,11 +125,12 @@ class NonBlockingBOSH(NonBlockingTransport):
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.
+ 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
@@ -149,14 +150,18 @@ class NonBlockingBOSH(NonBlockingTransport):
def get_socket_in(self, state):
- ''' gets sockets in desired 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):
- ''' Selects and returns socket eligible for sending a data to.'''
+ """
+ Select and returns socket eligible for sending a data to
+ """
if self.http_pipelining:
return self.get_socket_in(CONNECTED)
else:
@@ -176,10 +181,10 @@ class NonBlockingBOSH(NonBlockingTransport):
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
@@ -236,15 +241,16 @@ class NonBlockingBOSH(NonBlockingTransport):
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.
+ """
+ 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.
- '''
+ 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:
@@ -285,10 +291,11 @@ class NonBlockingBOSH(NonBlockingTransport):
self.wait_cb_time)
def on_persistent_fallback(self, socket):
- '''
- Called from underlying transport when server closes TCP connection.
+ """
+ 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
@@ -302,9 +309,10 @@ class NonBlockingBOSH(NonBlockingTransport):
def handle_body_attrs(self, stanza_attrs):
- '''
- Called for each incoming body stanza from dispatcher. Checks body attributes.
- '''
+ """
+ Called for each incoming body stanza from dispatcher. Checks body
+ attributes.
+ """
self.remove_bosh_wait_timeout()
if self.after_init:
@@ -345,7 +353,9 @@ class NonBlockingBOSH(NonBlockingTransport):
def append_stanza(self, stanza):
- ''' appends stanza to a buffer to send '''
+ """
+ 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
@@ -378,7 +388,9 @@ class NonBlockingBOSH(NonBlockingTransport):
def boshify_stanzas(self, stanzas=[], body_attrs=None):
- ''' wraps zero to many stanzas by body tag with xmlns and sid '''
+ """
+ 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)
@@ -470,10 +482,10 @@ def get_rand_number():
class AckChecker():
- '''
+ """
Class for generating rids and generating and checking acknowledgements in
- BOSH messages.
- '''
+ BOSH messages
+ """
def __init__(self):
self.rid = get_rand_number()
self.ack = 1
@@ -516,9 +528,9 @@ class AckChecker():
class KeyStack():
- '''
+ """
Class implementing key sequences for BOSH messages
- '''
+ """
def __init__(self, count):
self.count = count
self.keys = []
diff --git a/src/common/xmpp/c14n.py b/src/common/xmpp/c14n.py
index bccce8155..521f3144b 100644
--- a/src/common/xmpp/c14n.py
+++ b/src/common/xmpp/c14n.py
@@ -18,7 +18,10 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-''' XML canonicalisation methods (for XEP-0116) '''
+"""
+XML canonicalisation methods (for XEP-0116)
+"""
+
from simplexml import ustr
def c14n(node, is_buggy):
diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py
index 489885f76..b2b1d3b35 100644
--- a/src/common/xmpp/client_nb.py
+++ b/src/common/xmpp/client_nb.py
@@ -16,9 +16,10 @@
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
-'''
+"""
Client class establishs connection to XMPP Server and handles authentication
-'''
+"""
+
import socket
import transports_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh
from protocol import NS_TLS
@@ -28,21 +29,22 @@ 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.
- '''
+ handling, whereas underlying modules take care of feature-specific logic
+ """
+
def __init__(self, domain, idlequeue, caller=None):
- '''
- Caches connection data:
+ """
+ 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
@@ -69,10 +71,10 @@ class NonBlockingClient:
self.protocol_type = 'XMPP'
def disconnect(self, message=''):
- '''
- Called on disconnection - disconnect callback is picked based on state
- of the client.
- '''
+ """
+ Called on disconnection - disconnect callback is picked based on state of
+ the client.
+ """
# to avoid recursive calls
if self.disconnecting: return
@@ -112,7 +114,7 @@ class NonBlockingClient:
log.debug('calling on_proxy_failure cb')
self.on_proxy_failure(reason=message)
else:
- log.debug('ccalling on_connect_failure cb')
+ log.debug('calling on_connect_failure cb')
self.on_connect_failure()
else:
# we are connected to XMPP server
@@ -132,9 +134,10 @@ class NonBlockingClient:
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).
+ 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
@@ -150,7 +153,7 @@ class NonBlockingClient:
'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
@@ -223,7 +226,11 @@ class NonBlockingClient:
on_success=self._try_next_ip)
def _resolve_hostname(self, hostname, port, on_success):
- ''' wrapper for getaddinfo call. FIXME: getaddinfo blocks'''
+ """
+ Wrapper for getaddinfo call
+
+ FIXME: getaddinfo blocks
+ """
try:
self.ip_addresses = socket.getaddrinfo(hostname, port,
socket.AF_UNSPEC, socket.SOCK_STREAM)
@@ -234,7 +241,9 @@ class NonBlockingClient:
on_success()
def _try_next_ip(self, err_message=None):
- '''Iterates over IP addresses tries to connect to it'''
+ """
+ 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 == []:
@@ -249,18 +258,20 @@ class NonBlockingClient:
on_connect_failure=self._try_next_ip)
def incoming_stream_version(self):
- ''' gets version of xml stream'''
+ """
+ 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):
- '''
- Starts XMPP connecting process - opens the XML stream. Is called after TCP
+ """
+ Start XMPP connecting process - open the XML stream. Is called after TCP
connection is established or after switch to TLS when successfully
negotiated with <starttls>.
- '''
+ """
# socket_type contains info which transport connection was established
if not socket_type:
if self.Connection.ssl_lib:
@@ -273,19 +284,19 @@ class NonBlockingClient:
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.
- '''
+ """
+ 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
+ """
+ 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
@@ -346,10 +357,10 @@ class NonBlockingClient:
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)
@@ -381,7 +392,9 @@ class NonBlockingClient:
assert False, 'Stream opened for unsupported connection'
def _tls_negotiation_handler(self, con=None, tag=None):
- ''' takes care of TLS negotioation with <starttls> '''
+ """
+ Take care of TLS negotioation with <starttls>
+ """
log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag)
if not con and not tag:
# starting state when we send the <starttls>
@@ -407,15 +420,17 @@ class NonBlockingClient:
on_fail = lambda: self.disconnect('error while etabilishing TLS'))
def _on_connect(self):
- ''' preceeds call of on_connect callback '''
+ """
+ Preceed 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
+ """
+ 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)
@@ -425,9 +440,9 @@ class NonBlockingClient:
###############################################################################
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.
+ random one or library name used
:param user: XMPP username
:param password: XMPP password
@@ -435,7 +450,7 @@ class NonBlockingClient:
: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
@@ -443,7 +458,9 @@ class NonBlockingClient:
return
def _on_old_auth(self, res):
- ''' Callback used by NON-SASL auth. On auth failure, res is None. '''
+ """
+ Callback used by NON-SASL auth. On auth failure, res is None
+ """
if res:
self.connected += '+old_auth'
self.on_auth(self, 'old_auth')
@@ -451,7 +468,9 @@ class NonBlockingClient:
self.on_auth(self, None)
def _on_sasl_auth(self, res):
- ''' Used internally. On auth failure, res is None. '''
+ """
+ Used internally. On auth failure, res is None
+ """
self.onreceive(None)
if res:
self.connected += '+sasl'
@@ -460,7 +479,9 @@ class NonBlockingClient:
self.on_auth(self, None)
def _on_doc_attrs(self):
- ''' Plug authentication objects and start auth. '''
+ """
+ Plug authentication objects and start auth
+ """
if self._sasl:
auth_nb.SASL.get_instance(self._User, self._Password,
self._on_start_sasl).PlugIn(self)
@@ -474,7 +495,9 @@ class NonBlockingClient:
return True
def _on_start_sasl(self, data=None):
- ''' Callback used by SASL, called on each auth step.'''
+ """
+ Callback used by SASL, called on each auth step
+ """
if data:
self.Dispatcher.ProcessNonBlocking(data)
if not 'SASL' in self.__dict__:
@@ -504,20 +527,26 @@ class NonBlockingClient:
return True
def initRoster(self, version=''):
- ''' Plug in the roster. '''
+ """
+ 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. '''
+ """
+ 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.'''
+ """
+ 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)
@@ -528,31 +557,38 @@ class NonBlockingClient:
###############################################################################
def RegisterDisconnectHandler(self,handler):
- ''' Register handler that will be called on disconnect.'''
+ """
+ Register handler that will be called on disconnect
+ """
self.disconnect_handlers.append(handler)
def UnregisterDisconnectHandler(self,handler):
- ''' Unregister handler that is called on disconnect.'''
+ """
+ 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 get_connect_type(self):
- ''' Returns connection state. F.e.: None / 'tls' / 'plain+non_sasl'. '''
+ """
+ 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).
+ 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 8f4746f68..469c47f4a 100644
--- a/src/common/xmpp/dispatcher_nb.py
+++ b/src/common/xmpp/dispatcher_nb.py
@@ -15,10 +15,10 @@
## GNU General Public License for more details.
-'''
+"""
Main xmpp decision making logic. Provides library with methods to assign
-different handlers to different XMPP stanzas and namespaces.
-'''
+different handlers to different XMPP stanzas and namespaces
+"""
import simplexml, sys, locale
from xml.parsers.expat import ExpatError
@@ -36,7 +36,7 @@ XML_DECLARATION = '<?xml version=\'1.0\'?>'
# 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
@@ -46,7 +46,8 @@ class Dispatcher():
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)
@@ -57,22 +58,23 @@ class Dispatcher():
@classmethod
def get_instance(cls, *args, **kwargs):
- '''
- Factory Method for object creation.
+ """
+ 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)
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 = {}
@@ -94,25 +96,24 @@ class XMPPDispatcher(PlugIn):
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 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.
- '''
+ """
+ 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):
- '''
- Registers default namespaces/protocols/handlers. Used internally.
- '''
+ """
+ Register default namespaces/protocols/handlers. Used internally
+ """
# FIXME: inject dependencies, do not rely that they are defined by our
# owner
self.RegisterNamespace('unknown')
@@ -126,10 +127,10 @@ class XMPPDispatcher(PlugIn):
self.on_responses = {}
def plugin(self, owner):
- '''
- Plug the Dispatcher instance into Client class instance and send
- initial stream header. Used internally.
- '''
+ """
+ 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
@@ -140,7 +141,9 @@ class XMPPDispatcher(PlugIn):
self.StreamInit()
def plugout(self):
- ''' Prepares instance to be destructed. '''
+ """
+ Prepare instance to be destructed
+ """
self.Stream.dispatch = None
self.Stream.features = None
self.Stream.destroy()
@@ -148,7 +151,9 @@ class XMPPDispatcher(PlugIn):
self.Stream = None
def StreamInit(self):
- ''' Send an initial stream header. '''
+ """
+ Send an initial stream header
+ """
self.Stream = simplexml.NodeBuilder()
self.Stream.dispatch = self.dispatch
self.Stream._dispatch_depth = 2
@@ -170,15 +175,15 @@ class XMPPDispatcher(PlugIn):
% (tag, ns))
def ProcessNonBlocking(self, data):
- '''
- Check incoming stream for data waiting.
+ """
+ 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.
@@ -211,61 +216,60 @@ class XMPPDispatcher(PlugIn):
return len(data)
def RegisterNamespace(self, xmlns, order='info'):
- '''
- Creates internal structures for newly registered namespace.
+ """
+ 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)
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.
+ """
+ Used to declare some top-level stanza name to dispatcher
- Iq, message and presence protocols are registered by 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.
- '''
+ 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.
+ """
+ Register user callback as stanzas handler of declared type
- Callback must take (if chained, see later) arguments:
+ Callback arguments:
dispatcher instance (for replying), incoming return of previous handlers.
The callback must raise xmpp.NodeProcessed just before return if it wants
- other callbacks to be called with the same stanza as argument _and_, more
- importantly library from returning stanza to sender with error set.
+ 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 chained: chain together output of several handlers.
- :param makefirst: insert handler in the beginning of handlers list instea
+ :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.
- '''
- # FIXME: What does chain mean and where is it handled?
+ """
if not xmlns:
xmlns=self._owner.defaultNamespace
log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' %
@@ -286,18 +290,20 @@ class XMPPDispatcher(PlugIn):
'system':system})
def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None,
- makefirst=0, system=0):
- ''' Unregister handler after first call (not implemented yet). '''
+ 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:
@@ -316,58 +322,59 @@ class XMPPDispatcher(PlugIn):
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 RegisterEventHandler(self, handler):
- '''
- Register handler that will process events. F.e.
- "FILERECEIVED" event. See common/connection: _event_dispatcher()
- '''
+ """
+ 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 <feature-not-implemented/> error set
- '''
+ """
+ Return stanza back to the sender with <feature-not-implemented/> 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.
- '''
+ """
+ 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 Event(self, realm, event, data):
- '''
- Raise some event.
+ """
+ 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)
def dispatch(self, stanza, session=None, direct=0):
- '''
+ """
Main procedure that performs XMPP stanza recognition and calling
- apppropriate handlers for it. Called by simplexml.
- '''
+ apppropriate handlers for it. Called by simplexml
+ """
# FIXME: Where do we set session and direct. Why? What are those intended
# to do?
@@ -452,9 +459,9 @@ class XMPPDispatcher(PlugIn):
self._defaultHandler(session, stanza)
def _WaitForData(self, data):
- '''
+ """
Internal wrapper around ProcessNonBlocking. Will check for
- '''
+ """
if data is None:
return
res = self.ProcessNonBlocking(data)
@@ -483,12 +490,12 @@ class XMPPDispatcher(PlugIn):
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.
+ 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)
@@ -501,15 +508,17 @@ class XMPPDispatcher(PlugIn):
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. '''
+ """
+ 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.
- '''
+ """
+ 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):
@@ -531,7 +540,9 @@ class BOSHDispatcher(XMPPDispatcher):
XMPPDispatcher.PlugIn(self, owner)
def StreamInit(self):
- ''' Send an initial stream header. '''
+ """
+ Send an initial stream header
+ """
self.Stream = simplexml.NodeBuilder()
self.Stream.dispatch = self.dispatch
self.Stream._dispatch_depth = 2
@@ -551,7 +562,9 @@ class BOSHDispatcher(XMPPDispatcher):
self._owner.Connection.send_init(after_SASL=self.after_SASL)
def StreamTerminate(self):
- ''' Send a stream terminator. '''
+ """
+ Send a stream terminator
+ """
self._owner.Connection.send_terminator()
def ProcessNonBlocking(self, data=None):
diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py
index b713e38b2..a2eb669ca 100644
--- a/src/common/xmpp/features_nb.py
+++ b/src/common/xmpp/features_nb.py
@@ -15,10 +15,10 @@
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
-'''
+"""
Different stuff that wasn't worth separating it into modules
(Registration, Privacy Lists, ...)
-'''
+"""
from protocol import NS_REGISTER, NS_PRIVACY, NS_DATA, Iq, isResultNode, Node
@@ -38,13 +38,13 @@ 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
+ """
+ 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])
@@ -76,32 +76,32 @@ def _ReceivedRegInfo(con, resp, agent):
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.
+def register(disp, host, info, cb, args=None):
+ """
+ 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)
+ 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)
def changePasswordTo(disp, newpassword, host=None, cb = None):
- '''
- Changes password on specified or current (if not specified) server.
- Returns true on success.
- '''
+ """
+ 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',
@@ -123,10 +123,10 @@ 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.
- '''
+ """
+ Request privacy lists from connected server. Returns dictionary of existing
+ lists on success.
+ """
iq = Iq('get', NS_PRIVACY)
def _on_response(resp):
dict_ = {'lists': []}
@@ -157,10 +157,10 @@ def getActiveAndDefaultPrivacyLists(disp):
disp.SendAndCallForResponse(iq, _on_response)
def getPrivacyList(disp, listname):
- '''
- Requests specific privacy list listname. Returns list of XML nodes (rules)
+ """
+ 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))
@@ -170,10 +170,10 @@ def getPrivacyList(disp, 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.
- '''
+ """
+ Switch privacy list 'listname' to specified type. By default the type is
+ 'active'. Returns true on success.
+ """
if listname:
attrs={'name':listname}
else:
@@ -182,15 +182,20 @@ def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
_on_default_response(disp, iq, cb)
def setDefaultPrivacyList(disp, listname=None):
- ''' Sets the default privacy list as 'listname'. Returns true on success. '''
+ """
+ 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.
- '''
+ Returns true on success.
+ """
iq = Iq('set', NS_PRIVACY, xmlns = '')
list_query = iq.getTag('query').setTag('list', {'name': listname})
for item in tags:
diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py
index 63cb26bf6..8d61159da 100644
--- a/src/common/xmpp/idlequeue.py
+++ b/src/common/xmpp/idlequeue.py
@@ -12,10 +12,12 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
-'''
-Idlequeues are Gajim's network heartbeat. Transports can be plugged as
-idle objects and be informed about possible IO.
-'''
+
+"""
+Idlequeues are Gajim's network heartbeat. Transports can be plugged as idle
+objects and be informed about possible IO
+"""
+
import os
import select
import logging
@@ -45,7 +47,9 @@ IS_CLOSED = 16 # channel closed
def get_idlequeue():
- ''' Get an appropriate idlequeue '''
+ """
+ Get an appropriate idlequeue
+ """
if os.name == 'nt':
# gobject.io_add_watch does not work on windows
return SelectIdleQueue()
@@ -59,34 +63,44 @@ def get_idlequeue():
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 pollend(self):
- ''' called on stream failure '''
+ """
+ Called on stream failure
+ """
pass
def pollin(self):
- ''' called on new read event '''
+ """
+ Called on new read event
+ """
pass
def pollout(self):
- ''' called on new write event (connect in sockets is a pollout) '''
+ """
+ Called on new write event (connect in sockets is a pollout)
+ """
pass
def read_timeout(self):
- ''' called when timeout happened '''
+ """
+ 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 )
@@ -111,7 +125,9 @@ class IdleCommand(IdleObject):
return ['echo', 'da']
def _compose_command_line(self):
- ''' return one line representation of command and its arguments '''
+ """
+ Return one line representation of command and its arguments
+ """
return reduce(lambda left, right: left + ' ' + right,
self._compose_command_args())
@@ -187,7 +203,7 @@ class IdleCommand(IdleObject):
class IdleQueue:
- '''
+ """
IdleQueue provide three distinct time based features. Uses select.poll()
1. Alarm timeout: Execute a callback after foo seconds
@@ -195,7 +211,8 @@ class IdleQueue:
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)
@@ -215,13 +232,15 @@ class IdleQueue:
self._init_idle()
def _init_idle(self):
- ''' Hook method for subclassed. Will be called by __init__. '''
+ """
+ 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.
- '''
+ """
+ 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:
@@ -231,10 +250,10 @@ class IdleQueue:
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
- '''
+ """
+ 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
@@ -251,7 +270,9 @@ class IdleQueue:
return False
def remove_timeout(self, fd, timeout=None):
- ''' Removes the read timeout '''
+ """
+ Remove the read timeout
+ """
log.info('read timeout removed for fd %s' % fd)
if fd in self.read_timeouts:
if timeout:
@@ -263,12 +284,12 @@ class IdleQueue:
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.
+ """
+ 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)
@@ -280,11 +301,10 @@ class IdleQueue:
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')
+ for plugged objects if specified time has ellapsed
+ """
current_time = self.current_time()
for fd, timeouts in self.read_timeouts.items():
@@ -313,13 +333,13 @@ class IdleQueue:
del(self.alarms[alarm_time])
def plug_idle(self, obj, writable=True, readable=True):
- '''
- Plug an IdleObject into idlequeue. Filedescriptor fd must be set.
+ """
+ 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:
@@ -339,11 +359,15 @@ class IdleQueue:
self._add_idle(obj.fd, flags)
def _add_idle(self, fd, flags):
- ''' Hook method for subclasses, called by plug_idle '''
+ """
+ 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. '''
+ """
+ Remove plugged IdleObject, specified by filedescriptor fd
+ """
if fd in self.queue:
del(self.queue[fd])
self._remove_idle(fd)
@@ -353,7 +377,9 @@ class IdleQueue:
return time()
def _remove_idle(self, fd):
- ''' Hook method for subclassed, called by unplug_idle '''
+ """
+ Hook method for subclassed, called by unplug_idle
+ """
self.selector.unregister(fd)
def _process_events(self, fd, flags):
@@ -379,13 +405,13 @@ class IdleQueue:
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.
+ """
+ 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()
@@ -403,24 +429,26 @@ class IdleQueue:
class SelectIdleQueue(IdleQueue):
- '''
+ """
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):
- '''
- Creates a dict, which maps file/pipe/sock descriptor to glib event id
- '''
+ """
+ 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
- '''
+ """
+ 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:
@@ -428,9 +456,10 @@ class SelectIdleQueue(IdleQueue):
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
- '''
+ """
+ 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:
@@ -466,27 +495,29 @@ class SelectIdleQueue(IdleQueue):
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)
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
- '''
+ """
+ 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
@@ -501,9 +532,10 @@ class GlibIdleQueue(IdleQueue):
raise
def _remove_idle(self, fd):
- ''' this method is called when we unplug a new idle object.
- Stop listening for events from 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])
diff --git a/src/common/xmpp/plugin.py b/src/common/xmpp/plugin.py
index 7f5d91ee1..2e4368eb2 100644
--- a/src/common/xmpp/plugin.py
+++ b/src/common/xmpp/plugin.py
@@ -14,33 +14,34 @@
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
-'''
-Provides PlugIn class functionality to develop extentions for xmpppy.
-'''
+"""
+Provides PlugIn class functionality to develop extentions for xmpppy
+"""
import logging
log = logging.getLogger('gajim.c.x.plugin')
class PlugIn:
- '''
+ """
Abstract xmpppy plugin infrastructure code, providing plugging in/out and
- debugging functionality.
+ debugging functionality
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=[]
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.
- '''
+ code after plugging
+ """
self._owner=owner
log.info('Plugging %s __INTO__ %s' % (self, self._owner))
if self.__class__.__name__ in owner.__dict__:
@@ -63,11 +64,11 @@ class 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.
- '''
+ hook code
+ """
log.info('Plugging %s __OUT__ of %s.' % (self, self._owner))
for method in self._exported_methods:
del self._owner.__dict__[method.__name__]
@@ -85,13 +86,13 @@ class PlugIn:
@classmethod
def get_instance(cls, *args, **kwargs):
- '''
- Factory Method for object creation.
+ """
+ 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:
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 619771409..16b7ff058 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -14,14 +14,15 @@
# $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $
-'''
+"""
Protocol module contains tools that are needed for processing of xmpp-related
-data structures, including jabber-objects like JID or different stanzas and sub-
-stanzas) handling routines.
-'''
+data structures, including jabber-objects like JID or different stanzas and
+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'
@@ -71,6 +72,7 @@ NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-01
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'
@@ -88,6 +90,7 @@ 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'
@@ -125,7 +128,7 @@ 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'
-xmpp_stream_error_conditions='''
+xmpp_stream_error_conditions = '''
bad-format -- -- -- The entity has sent XML that cannot be processed.
bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.
conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.
@@ -150,7 +153,8 @@ unsupported-encoding -- -- -- The initiating entity has encoded the stream in
unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server.
unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server.
xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.'''
-xmpp_stanza_error_conditions='''
+
+xmpp_stanza_error_conditions = '''
bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.
conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.
feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed.
@@ -173,7 +177,8 @@ service-unavailable -- 503 -- cancel -- The server or recipient does not current
subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.
undefined-condition -- 500 -- -- Undefined Condition
unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).'''
-sasl_error_conditions='''
+
+sasl_error_conditions = '''
aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.
incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data.
invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data.
@@ -182,53 +187,115 @@ 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 <response/> element or an <auth/> 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 <auth/> element or <response/> element.'''
-ERRORS,_errorcodes={},{}
-for ns,errname,errpool in ((NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions),
+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
+ 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'
+ """
+ Return 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'
+ """
+ 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. '''
+ """
+ Exception that should be raised by handler when the handling should be
+ stopped
+ """
+ pass
+
class StreamError(Exception):
- ''' Base exception class for stream errors.'''
-class BadFormat(StreamError): pass
-class BadNamespacePrefix(StreamError): pass
-class Conflict(StreamError): pass
-class ConnectionTimeout(StreamError): pass
-class HostGone(StreamError): pass
-class HostUnknown(StreamError): pass
-class ImproperAddressing(StreamError): pass
-class InternalServerError(StreamError): pass
-class InvalidFrom(StreamError): pass
-class InvalidID(StreamError): pass
-class InvalidNamespace(StreamError): pass
-class InvalidXML(StreamError): pass
-class NotAuthorized(StreamError): pass
-class PolicyViolation(StreamError): pass
-class RemoteConnectionFailed(StreamError): pass
-class ResourceConstraint(StreamError): pass
-class RestrictedXML(StreamError): pass
-class SeeOtherHost(StreamError): pass
-class SystemShutdown(StreamError): pass
-class UndefinedCondition(StreamError): pass
-class UnsupportedEncoding(StreamError): pass
-class UnsupportedStanzaType(StreamError): pass
-class UnsupportedVersion(StreamError): pass
-class XMLNotWellFormed(StreamError): pass
+ """
+ Base exception class for stream errors
+ """
+ pass
+
+class BadFormat(StreamError):
+ pass
+
+class BadNamespacePrefix(StreamError):
+ pass
+
+class Conflict(StreamError):
+ pass
+
+class ConnectionTimeout(StreamError):
+ pass
+
+class HostGone(StreamError):
+ pass
+
+class HostUnknown(StreamError):
+ pass
+
+class ImproperAddressing(StreamError):
+ pass
+
+class InternalServerError(StreamError):
+ pass
+
+class InvalidFrom(StreamError):
+ pass
+
+class InvalidID(StreamError):
+ pass
+
+class InvalidNamespace(StreamError):
+ pass
+
+class InvalidXML(StreamError):
+ pass
+
+class NotAuthorized(StreamError):
+ pass
+
+class PolicyViolation(StreamError):
+ pass
+
+class RemoteConnectionFailed(StreamError):
+ pass
+
+class ResourceConstraint(StreamError):
+ pass
+
+class RestrictedXML(StreamError):
+ pass
+
+class SeeOtherHost(StreamError):
+ pass
+
+class SystemShutdown(StreamError):
+ pass
+
+class UndefinedCondition(StreamError):
+ pass
+
+class UnsupportedEncoding(StreamError):
+ pass
+
+class UnsupportedStanzaType(StreamError):
+ pass
+
+class UnsupportedVersion(StreamError):
+ pass
+
+class XMLNotWellFormed(StreamError):
+ pass
stream_exceptions = {'bad-format': BadFormat,
'bad-namespace-prefix': BadNamespacePrefix,
@@ -256,250 +323,430 @@ stream_exceptions = {'bad-format': BadFormat,
'xml-not-well-formed': XMLNotWellFormed}
class JID:
- ''' JID object. 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=''):
- ''' 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
+ """
+ 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,''
+ 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 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 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 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 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)
+ """
+ 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. '''
+ """
+ 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. '''
+ """
+ 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
+
+ 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__())
+ """
+ Produce hash of the JID, Allows to use JID objects as keys of the dictionary
+ """
+ return hash(str(self))
class BOSHBody(Node):
- '''
+ """
<body> 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
+ """
+ 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):
+ 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):
+ 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=''
+ 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
+ """
+ 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
+ """
+ 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 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())
+ """
+ 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 the value of the 'id' attribute
+ """
return self.getAttr('id')
- def setTo(self,val):
- ''' Set the value of the 'to' attribute. '''
+
+ 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 the value of the 'type' attribute
+ """
return self.getAttr('type')
- def setFrom(self,val):
- ''' Set the value of the 'from' attribute. '''
+
+ 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. '''
+
+ 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. '''
+
+ 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')
+ """
+ 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()
+ 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')
+ """
+ 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()
+ if tag.getName() == 'text':
+ return tag.getData()
return self.getError()
+
def getErrorCode(self):
- ''' Return the error code. Obsolete. '''
+ """
+ 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. '''
+ """
+ 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)
+ 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())
+
+ 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)
+ 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=[]
+ """
+ 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)
+ 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.'''
- 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)
+ """
+ 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.
+
+ 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 text of the message
+ """
return self.getTagData('body')
+
def getXHTML(self, xmllang=None):
- ''' Returns serialized xhtml-im element text of the message.
+ """
+ Return serialized xhtml-im element text of the message
- TODO: Returning a DOM could make rendering faster.'''
+ TODO: Returning a DOM could make rendering faster.
+ """
xhtml = self.getTag('html')
if xhtml:
if xmllang:
- body = xhtml.getTag('body', attrs={'xml:lang':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 subject of the message
+ """
return self.getTagData('subject')
+
def getThread(self):
- ''' Returns thread of the message. '''
+ """
+ Return 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.'''
+ 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('<body xmlns="'+NS_XHTML+'" xml:lang="'+xmllang+'" >' + val + '</body>').getDom()
+ dom = NodeBuilder('<body xmlns="%s" xml:lang="%s">%s</body>' % (NS_XHTML, xmllang, val)).getDom()
else:
- dom = NodeBuilder('<body xmlns="'+NS_XHTML+'">'+val+'</body>',0).getDom()
+ dom = NodeBuilder('<body xmlns="%s">%s</body>, 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)
+ 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)
+ # 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 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 getStatusCode(self):
- '''Returns the status code of the message (for groupchat config
- change)'''
+ """
+ Return the status code of the message (for groupchat config change)
+ """
attrs = []
for xtag in self.getTags('x'):
for child in xtag.getTags('status'):
@@ -507,68 +754,117 @@ class Message(Protocol):
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 __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):
- ''' Returns the priority of the message. '''
+ """
+ Return the priority of the message
+ """
return self.getTagData('priority')
+
def getShow(self):
- ''' Returns the show value of the message. '''
+ """
+ Return the show value of the message
+ """
return self.getTagData('show')
+
def getStatus(self):
- ''' Returns the status string of the message. '''
+ """
+ Return 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):
+
+ 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 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_getSubTagDataAttr(self,tag,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
+ return cchild.getData(), cchild.getAttr(attr)
+ return None, None
+
def getRole(self):
- '''Returns the presence role (for groupchat)'''
- return self._muc_getItemAttr('item','role')
+ """
+ Return 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')
+ """
+ Return 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')
+ """
+ Return 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')
+ """
+ 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]
+ """
+ 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]
+ """
+ Return 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)'''
+ """
+ Return the status code of the presence (for groupchat)
+ """
attrs = []
for xtag in self.getTags('x'):
for child in xtag.getTags('status'):
@@ -576,244 +872,435 @@ class Presence(Protocol):
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. '''
+ """
+ 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.
+
+ 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)
+ 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()
+ """
+ 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')
+ """
+ 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()
+ """
+ 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.'''
+ """
+ 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.'''
+
+ 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())
+
+ 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
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.'''
+ """
+ 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):
+ """
+ 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)
+ 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)
+ """
+ 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.
- '''
+ 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')
+ """
+ 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
+ try:
+ self.delChild('required')
+ except ValueError:
+ return
+
def isRequired(self):
- ''' Returns in this field a required one. '''
+ """
+ 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 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 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 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 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 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 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. '''
+ """
+ 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. '''
+ """
+ Get 'var' attribute value of this field
+ """
return self.getAttr('var')
- def setVar(self,val):
- ''' Set 'var' attribute value of this field. '''
+
+ 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.'''
+ """
+ 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)
+ """
+ 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=[]
+ 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)
+ 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 title:
+ self.setTitle(title)
if isinstance(data, dict):
- newdata=[]
- for name in data.keys(): newdata.append(DataField(name,data[name]))
- data=newdata
+ 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))
+ 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 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 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 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
+
+ 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={}
+ """
+ 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()
+ 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()
+ 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()
+
+ 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.'''
+
+ 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:
diff --git a/src/common/xmpp/proxy_connectors.py b/src/common/xmpp/proxy_connectors.py
index e4fb4dbd8..b4e7acfcd 100644
--- a/src/common/xmpp/proxy_connectors.py
+++ b/src/common/xmpp/proxy_connectors.py
@@ -14,27 +14,30 @@
## 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.
-'''
-Module containing classes for proxy connecting. So far its HTTP CONNECT
-and SOCKS5 proxy.
+
+"""
+Module containing classes for proxy connecting. So far its HTTP CONNECT and
+SOCKS5 proxy
+
Authentication to NTLM (Microsoft implementation) proxies can be next.
-'''
+"""
import struct, socket, base64
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.
- '''
+ 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)):
- '''
+ on_failure, xmpp_server, proxy_creds=(None,None)):
+ """
Creates proxy connector, starts connecting immediately and gives control
- back to transport afterwards.
+ back to transport afterwards
:param send_method: transport send method
:param onreceive: method to set on_receive callbacks
@@ -44,7 +47,7 @@ class ProxyConnector:
: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
@@ -58,12 +61,12 @@ class ProxyConnector:
@classmethod
def get_instance(cls, *args, **kwargs):
- '''
- Factory Method for object creation.
+ """
+ Factory Method for object creation
- Use this instead of directly initializing the class in order to make
- unit testing much easier.
- '''
+ Use this instead of directly initializing the class in order to make unit
+ testing much easier.
+ """
return cls(*args, **kwargs)
def start_connecting(self):
@@ -75,11 +78,11 @@ class ProxyConnector:
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.
- '''
+ """
+ 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',
@@ -115,10 +118,11 @@ class HTTPCONNECTConnector(ProxyConnector):
class SOCKS5Connector(ProxyConnector):
- '''
+ """
SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with
- (optionally) simple authentication (only USERNAME/PASSWORD auth).
- '''
+ (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:
diff --git a/src/common/xmpp/roster_nb.py b/src/common/xmpp/roster_nb.py
index 715476b9e..f343c8341 100644
--- a/src/common/xmpp/roster_nb.py
+++ b/src/common/xmpp/roster_nb.py
@@ -16,10 +16,11 @@
# $Id: roster.py,v 1.17 2005/05/02 08:38:49 snakeru Exp $
-'''
+
+"""
Simple roster implementation. Can be used though for different tasks like
mass-renaming of contacts.
-'''
+"""
from protocol import JID, Iq, Presence, Node, NodeProcessed, NS_MUC_USER, NS_ROSTER
from plugin import PlugIn
@@ -29,15 +30,18 @@ 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. '''
+ """
+ Init internal variables
+ """
PlugIn.__init__(self)
self.version = version
self._data = {}
@@ -45,11 +49,15 @@ class NonBlockingRoster(PlugIn):
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)
@@ -59,9 +67,11 @@ class NonBlockingRoster(PlugIn):
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. '''
+ 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):
@@ -85,15 +95,19 @@ class NonBlockingRoster(PlugIn):
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())
+ 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 <iq type='error'/>
- def PresenceHandler(self,dis,pres):
- ''' Presence tracker. Used internally for setting items' resources state in
- internal roster representation. '''
+ 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()
@@ -118,112 +132,204 @@ class NonBlockingRoster(PlugIn):
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('/')]
+ 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]
+
+ 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
+ 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'])
+ 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 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 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 getPriority(self, jid):
+ """
+ Return 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 roster representation in internal format
+ """
return self._data
- def getRawItem(self,jid):
- ''' Returns roster item 'jid' representation in internal format. '''
+
+ def getRawItem(self, jid):
+ """
+ Return 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')
+ """
+ Return '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 '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 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=[]):
- ''' 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]))
+
+ 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):
- ''' Renames multiple contacts and sets their group lists.'''
- iq=Iq('set',NS_ROSTER)
- query=iq.getTag('query')
+
+ 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]))
+ 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 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.'''
+ """
+ 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.'''
+
+ 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'))
+ """
+ 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'))
+ """
+ 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 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 the internal data representation of the roster
+ """
return self._data
+
def setRaw(self, data):
- '''Returns the internal data representation of the roster.'''
+ """
+ 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
- # copypasted methods for roster.py from constructor to here
-
+ 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.'''
+ """
+ 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)
@@ -242,7 +348,9 @@ class NonBlockingRoster(PlugIn):
return True
def getRoster(self, on_ready=None, force=False):
- ''' Requests roster from server if neccessary and returns self. '''
+ """
+ Request roster from server if neccessary and returns self
+ """
return_self = True
if not self.set:
self.on_ready = on_ready
diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py
index f47142c46..2ace6657a 100644
--- a/src/common/xmpp/simplexml.py
+++ b/src/common/xmpp/simplexml.py
@@ -14,64 +14,96 @@
# $Id: simplexml.py,v 1.27 2005/04/30 07:20:27 snakeru Exp $
-'''Simplexml module provides xmpppy library with all needed tools to handle XML nodes and XML streams.
-I'm personally using it in many other separate projects. It is designed to be as standalone as possible.'''
+"""
+Simplexml module provides xmpppy library with all needed tools to handle XML
+nodes and XML streams. I'm personally using it in many other separate
+projects. It is designed to be as standalone as possible
+"""
import xml.parsers.expat
import logging
log = logging.getLogger('gajim.c.x.simplexml')
def XMLescape(txt):
- '''Returns provided string with symbols & < > " replaced by their respective XML entities.'''
+ """
+ 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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").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)
+ """
+ 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 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.'''
+ """
+ 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)
+ node = str(node)
if not isinstance(node, Node):
- node=NodeBuilder(node,self)
+ 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,{}
+ 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 = {}
@@ -94,10 +126,12 @@ class Node(object):
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))
+ if isinstance(i, Node):
+ self.addChild(node=i)
+ else:
+ self.data.append(ustr(i))
- def lookup_nsp(self,pfx=''):
+ def lookup_nsp(self, pfx=''):
ns = self.nsd.get(pfx,None)
if ns is None:
ns = self.nsp_cache.get(pfx,None)
@@ -109,9 +143,11 @@ class Node(object):
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.'''
+ 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:
@@ -142,9 +178,12 @@ class Node(object):
s = s + "</" + self.name + ">"
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 "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:
@@ -155,196 +194,338 @@ class Node(object):
newnode.setNamespace(namespace)
self.kids.append(newnode)
return newnode
+
def addData(self, data):
- ''' Adds some CDATA to node. '''
+ """
+ Add some CDATA to node
+ """
self.data.append(ustr(data))
+
def clearData(self):
- ''' Removes all CDATA from the node. '''
- self.data=[]
+ """
+ Remove all CDATA from the node
+ """
+ self.data = []
+
def delAttr(self, key):
- ''' Deletes an attribute "key" '''
+ """
+ Delete 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)
+ """
+ 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):
- ''' Returns all node's attributes as dictionary. '''
+ """
+ Return all node's attributes as dictionary
+ """
return self.attrs
+
def getAttr(self, key):
- ''' Returns value of specified attribute. '''
+ """
+ Return value of specified attribute
+ """
return self.attrs.get(key)
+
def getChildren(self):
- ''' Returns all node's child nodes as list. '''
+ """
+ Return all node's child nodes as list
+ """
return self.kids
+
def getData(self):
- ''' Returns all node CDATA as string (concatenated). '''
+ """
+ Return all node CDATA as string (concatenated)
+ """
return ''.join(self.data)
+
def getName(self):
- ''' Returns the name of node '''
+ """
+ Return the name of node
+ """
return self.name
+
def getNamespace(self):
- ''' Returns the namespace of node '''
+ """
+ Return the namespace of node
+ """
return self.namespace
+
def getParent(self):
- ''' Returns the parent of node (if present). '''
+ """
+ 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 "<node>text1<nodea/><nodeb/> text2</node>" will be returned list:
- ['text1', <nodea instance>, <nodeb instance>, ' text2']. '''
- ret=[]
+ """
+ Return the payload of node i.e. list of child nodes and CDATA entries.
+ F.e. for "<node>text1<nodea/><nodeb/> text2</node>" will be returned
+ list: ['text1', <nodea instance>, <nodeb instance>, ' 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
+ 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. '''
+ """
+ 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):
- ''' Returns attribute value of the child with specified name (or None if no such attribute).'''
+
+ 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):
- ''' Returns cocatenated CDATA of the child with specified name.'''
+ """
+ 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):
- ''' Filters all child nodes using specified arguments as filter.
- Returns the list of nodes found. '''
- nodes=[]
+ """
+ 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 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
+ 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. '''
+ """
+ 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 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
+ 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
+ """
+ Set 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. '''
+ """
+ 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 setNamespace(self, namespace):
- ''' Changes the node namespace. '''
- self.namespace=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. '''
+ """
+ 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):
- ''' 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 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 setTagAttr(self,tag,attr,val):
- ''' Creates new node (if not already present) with name "tag"
- and sets it's attribute "attr" to value "val". '''
+ """
+ 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
+ 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". '''
+ 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".'''
+ self.addChild(tag,attrs,payload = [ustr(val)])
+
+ def has_attr(self, key):
+ """
+ Check if node have attribute "key"
+ """
return key in self.attrs
- def __getitem__(self,item):
- ''' Returns node's attribute "item" value. '''
+
+ def __getitem__(self, item):
+ """
+ Return 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". '''
+
+ 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 __contains__(self,item):
- """ Checks if node has attribute "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)
+
+ 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)
+ 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.
- '''
+ """
+ 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):
+ """
+ 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.StartElementHandler = self.starttag
+ self._parser.EndElementHandler = self.endtag
self._parser.StartNamespaceDeclHandler = self.handle_namespace_start
- self._parser.CharacterDataHandler = self.handle_cdata
+ self._parser.CharacterDataHandler = self.handle_cdata
self._parser.buffer_text = True
self.Parse = self._parser.Parse
@@ -369,15 +550,19 @@ class NodeBuilder:
self.data_buffer = None
def destroy(self):
- ''' Method used to allow class instance to be garbage-collected. '''
+ """
+ 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.StartElementHandler = None
+ self._parser.EndElementHandler = None
+ self._parser.CharacterDataHandler = None
self._parser.StartNamespaceDeclHandler = None
def starttag(self, tag, attrs):
- '''XML Parser callback. Used internally'''
+ """
+ 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`))
@@ -410,8 +595,11 @@ class NodeBuilder:
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'''
+ """
+ XML Parser callback. Used internally
+ """
log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag))
self.check_data_buffer()
if self.__depth == self._dispatch_depth:
@@ -439,25 +627,42 @@ class NodeBuilder:
self.last_is_data = 1
def handle_namespace_start(self, prefix, uri):
- '''XML Parser callback. Used internally'''
+ """
+ XML Parser callback. Used internally
+ """
self.check_data_buffer()
def getDom(self):
- ''' Returns just built Node. '''
+ """
+ Return 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. '''
+
+ 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_footer_received(self):
- ''' Method called when stream just closed. '''
+ """
+ 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 True if at least one end tag was seen (at level)
+ """
return self.__depth <= level and self.__max_depth > level
def _inc_depth(self):
@@ -470,14 +675,20 @@ class NodeBuilder:
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. '''
+ """
+ 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):
- ''' Converts supplied textual string into XML node. Survives if xml data is cutted half way round.
- I.e. "<html>some text <br>some more text". Will raise xml.parser.expat.parsererror on misplaced
- tags though. F.e. "<b>some text <br>some more text</b>" will not work.'''
+ """
+ Convert supplied textual string into XML node. Survives if xml data is
+ cutted half way round. I.e. "<html>some text <br>some more text". Will raise
+ xml.parser.expat.parsererror on misplaced tags though. F.e. "<b>some text
+ <br>some more text</b>" will not work
+ """
return NodeBuilder(xml).getDom()
# vim: se ts=3:
diff --git a/src/common/xmpp/stringprepare.py b/src/common/xmpp/stringprepare.py
index 47b1a2d1e..39759a513 100644
--- a/src/common/xmpp/stringprepare.py
+++ b/src/common/xmpp/stringprepare.py
@@ -26,16 +26,26 @@ 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. """
+ """
+ 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. """
+ """
+ Return mapping for character
+ """
+ pass
class LookupTableFromFunction:
@@ -75,8 +85,8 @@ class EmptyMappingTable:
return c
class Profile:
- def __init__(self, mappings=[], normalize=True, prohibiteds=[],
- check_unassigneds=True, check_bidi=True):
+ def __init__(self, mappings=[], normalize=True, prohibiteds=[],
+ check_unassigneds=True, check_bidi=True):
self.mappings = mappings
self.normalize = normalize
self.prohibiteds = prohibiteds
@@ -140,24 +150,25 @@ class Profile:
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.
diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py
index 5ed10728b..8706e7fb7 100644
--- a/src/common/xmpp/tls_nb.py
+++ b/src/common/xmpp/tls_nb.py
@@ -54,13 +54,17 @@ def gattr(obj, attr, default=None):
class SSLWrapper:
- '''
+ """
Abstract SSLWrapper base class
- '''
+ """
+
class Error(IOError):
- ''' Generic SSL Error Wrapper '''
+ """
+ Generic SSL Error Wrapper
+ """
+
def __init__(self, sock=None, exc=None, errno=None, strerror=None,
- peer=None):
+ peer=None):
self.parent = IOError
errno = errno or gattr(exc, 'errno') or exc[0]
@@ -122,7 +126,7 @@ class SSLWrapper:
log.debug("%s.__init__ called with %s", self.__class__, sslobj)
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
@@ -130,16 +134,20 @@ class SSLWrapper:
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 '''
+ """
+ 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
@@ -202,7 +210,9 @@ class PyOpenSSLWrapper(SSLWrapper):
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
@@ -230,18 +240,18 @@ class StdlibSSLWrapper(SSLWrapper):
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.
- '''
+ """
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
@@ -254,10 +264,10 @@ class NonBlockingTLS(PlugIn):
"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.
- '''
+ """
+ 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()
@@ -289,7 +299,9 @@ class NonBlockingTLS(PlugIn):
"Unknown"), pkey.type())
def _startSSL(self):
- ''' Immediatedly switch socket to TLS mode. Used internally.'''
+ """
+ Immediatedly switch socket to TLS mode. Used internally
+ """
log.debug("_startSSL called")
if USE_PYOPENSSL:
diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py
index 704cad170..dfe9870c6 100644
--- a/src/common/xmpp/transports_nb.py
+++ b/src/common/xmpp/transports_nb.py
@@ -15,14 +15,14 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
-'''
+"""
Transports are objects responsible for connecting to XMPP server and putting
data to wrapped sockets in in desired form (SSL, TLS, TCP, for HTTP proxy,
for SOCKS5 proxy...)
Transports are not aware of XMPP stanzas and only responsible for low-level
connection handling.
-'''
+"""
from simplexml import ustr
from plugin import PlugIn
@@ -41,12 +41,12 @@ 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
- '''
+ 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:
@@ -72,7 +72,7 @@ def get_proxy_data_from_dict(proxy):
# 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['useauth']:
+ if proxy.get('useauth', False):
proxy_user, proxy_pass = proxy['user'], proxy['pass']
return tcp_host, tcp_port, proxy_user, proxy_pass
@@ -92,6 +92,7 @@ RECV_BUFSIZE = 32768 # 2x maximum size of ssl packet, should be plenty
DATA_RECEIVED = 'DATA RECEIVED'
DATA_SENT = 'DATA SENT'
+DATA_ERROR = 'DATA ERROR'
DISCONNECTED = 'DISCONNECTED'
DISCONNECTING = 'DISCONNECTING'
@@ -101,17 +102,18 @@ 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):
- '''
+ certs):
+ """
Each trasport class can have different constructor but it has to have at
- least all the arguments of NonBlockingTransport constructor.
+ 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
@@ -120,7 +122,7 @@ class NonBlockingTransport(PlugIn):
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
@@ -156,14 +158,14 @@ class NonBlockingTransport(PlugIn):
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.
+ 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]
@@ -177,14 +179,18 @@ class NonBlockingTransport(PlugIn):
return self.state
def _on_connect(self):
- ''' preceeds call of on_connect callback '''
+ """
+ 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 '''
+ """
+ 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
@@ -203,15 +209,16 @@ class NonBlockingTransport(PlugIn):
self.on_disconnect()
def onreceive(self, recv_handler):
- '''
- Sets the on_receive callback.
+ """
+ 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.
- Do not confuse it with on_receive() method, which is the callback itself.
- '''
+ 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
@@ -225,13 +232,17 @@ class NonBlockingTransport(PlugIn):
self.set_state(CONNECTING)
def read_timeout(self):
- ''' called when there's no response from server in defined timeout '''
+ """
+ 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 '''
+ """
+ called when there's no response from server in defined timeout
+ """
if self.on_timeout2:
self.on_timeout2()
self.renew_send_timeout2()
@@ -276,17 +287,17 @@ class NonBlockingTransport(PlugIn):
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):
- '''
+ 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)
@@ -370,10 +381,10 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
proxy_creds=self.proxy_dict['credentials'])
def _on_connect(self):
- '''
- Preceeds invoking of on_connect callback. TCP connection is already
- estabilished by this time.
- '''
+ """
+ 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),
@@ -383,10 +394,10 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
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:
@@ -395,12 +406,16 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
on_fail()
def pollin(self):
- '''called by idlequeu when receive on plugged socket is possible '''
+ """
+ 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'''
+ """
+ Called by idlequeu when send to plugged socket is possible
+ """
log.info('pollout called, state == %s' % self.get_state())
if self.get_state() == CONNECTING:
@@ -416,7 +431,9 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
self._do_send()
def pollend(self):
- '''called by idlequeue on TCP connection errors'''
+ """
+ Called by idlequeue on TCP connection errors
+ """
log.info('pollend called, state == %s' % self.get_state())
if self.get_state() == CONNECTING:
@@ -464,10 +481,10 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
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.
- '''
+ """
+ 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)
@@ -481,7 +498,9 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
self._plug_idle(writable=True, readable=True)
def encode_stanza(self, stanza):
- ''' Encode str or unicode to utf-8 '''
+ """
+ Encode str or unicode to utf-8
+ """
if isinstance(stanza, unicode):
stanza = stanza.encode('utf-8')
elif not isinstance(stanza, str):
@@ -489,8 +508,8 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
return stanza
def _plug_idle(self, writable, readable):
- '''
- Plugs file descriptor of socket to Idlequeue.
+ """
+ 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
@@ -498,15 +517,15 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
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.
- '''
+ sendqueue will be sent
+ """
if not self.sendbuff:
if not self.sendqueue:
log.warn('calling send on empty buffer and queue')
@@ -529,10 +548,10 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
self.disconnect()
def _do_receive(self):
- '''
+ """
Reads all pending incoming data. Will call owner's disconnected() method
- if appropriate.
- '''
+ if appropriate
+ """
received = None
errnum = 0
errstr = 'No Error Set'
@@ -587,27 +606,29 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
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.'''
+ """
+ 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):
- '''
+ 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)
@@ -638,10 +659,10 @@ class NonBlockingHTTP(NonBlockingTCP):
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
@@ -650,7 +671,7 @@ class NonBlockingHTTP(NonBlockingTCP):
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
@@ -662,12 +683,12 @@ class NonBlockingHTTP(NonBlockingTCP):
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, %d got' %
- (self.expected_length, len(self.recvbuff)))
+ 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
@@ -685,10 +706,10 @@ class NonBlockingHTTP(NonBlockingTCP):
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.
- '''
+ """
+ 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),
@@ -710,15 +731,16 @@ class NonBlockingHTTP(NonBlockingTCP):
return('%s%s' % (headers, httpbody))
def parse_http_message(self, message):
- '''
- splits http message to tuple:
+ """
+ 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
@@ -726,9 +748,6 @@ class NonBlockingHTTP(NonBlockingTCP):
return ('', '', '', buffer_rest)
else:
(header, httpbody) = splitted[:2]
- if httpbody.endswith('\n'):
- httpbody = httpbody[:-1]
- buffer_rest = "\n\n".join(splitted[2:])
header = header.split('\n')
statusline = header[0].split(' ', 2)
header = header[1:]
@@ -736,14 +755,20 @@ class NonBlockingHTTP(NonBlockingTCP):
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
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
index 730eb3ec3..96cbd7436 100644
--- a/src/common/zeroconf/client_zeroconf.py
+++ b/src/common/zeroconf/client_zeroconf.py
@@ -23,7 +23,7 @@ from common.xmpp.idlequeue import IdleObject
from common.xmpp import dispatcher_nb, simplexml
from common.xmpp.plugin import *
from common.xmpp.simplexml import ustr
-from common.xmpp.transports_nb import DATA_RECEIVED, DATA_SENT
+from common.xmpp.transports_nb import DATA_RECEIVED, DATA_SENT, DATA_ERROR
from common.zeroconf import zeroconf
from common.xmpp.protocol import *
@@ -47,7 +47,9 @@ ACTIVITY_TIMEOUT_SECONDS = 30
class ZeroconfListener(IdleObject):
def __init__(self, port, conn_holder):
- ''' handle all incomming connections on ('0.0.0.0', port)'''
+ """
+ Handle all incomming connections on ('0.0.0.0', port)
+ """
self.port = port
self.queue_idx = -1
#~ self.queue = None
@@ -80,11 +82,15 @@ class ZeroconfListener(IdleObject):
self.started = True
def pollend(self):
- ''' called when we stop listening on (host, port) '''
+ """
+ Called when we stop listening on (host, port)
+ """
self.disconnect()
def pollin(self):
- ''' accept a new incomming connection and notify queue'''
+ """
+ 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
@@ -97,7 +103,9 @@ class ZeroconfListener(IdleObject):
P2PClient(sock[0], ipaddr, sock[1][1], self.conn_holder, [], from_jid)
def disconnect(self, message=''):
- ''' free all resources, we are not listening anymore '''
+ """
+ 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)
@@ -110,14 +118,16 @@ class ZeroconfListener(IdleObject):
self.conn_holder.kill_all_connections()
def accept_conn(self):
- ''' accepts a new incoming connection '''
+ """
+ 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):
+ on_ok=None, on_not_ok=None):
self._owner = self
self.Namespace = 'jabber:client'
self.protocol_type = 'XMPP'
@@ -207,7 +217,9 @@ class P2PClient(IdleObject):
self._register_handlers()
def StreamInit(self):
- ''' Send an initial stream header. '''
+ """
+ Send an initial stream header
+ """
self.Dispatcher.Stream = simplexml.NodeBuilder()
self.Dispatcher.Stream._dispatch_depth = 2
self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
@@ -288,6 +300,7 @@ class P2PClient(IdleObject):
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',
@@ -373,8 +386,10 @@ class P2PConnection(IdleObject, PlugIn):
return True
def plugout(self):
- '''Disconnect from the remote server and unregister self.disconnected method from
- the owner's dispatcher.'''
+ """
+ Disconnect from the remote server and unregister self.disconnected method
+ from the owner's dispatcher
+ """
self.disconnect()
self._owner = None
@@ -391,10 +406,13 @@ class P2PConnection(IdleObject, PlugIn):
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.
- '''
+ """
+ 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
@@ -416,8 +434,11 @@ class P2PConnection(IdleObject, PlugIn):
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]:
- self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to,
- thread_id))
+ 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()
@@ -454,7 +475,10 @@ class P2PConnection(IdleObject, PlugIn):
self.disconnect()
def pollin(self):
- ''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
+ """
+ Reads all pending incoming data. Call owner's disconnected() method if
+ appropriate
+ """
received = ''
errnum = 0
try:
@@ -495,7 +519,9 @@ class P2PConnection(IdleObject, PlugIn):
return True
def disconnect(self, message=''):
- ''' Closes the socket. '''
+ """
+ Close the socket
+ """
gajim.idlequeue.remove_timeout(self.fd)
gajim.idlequeue.unplug_idle(self.fd)
try:
@@ -578,6 +604,8 @@ class ClientZeroconf:
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)
@@ -632,6 +660,9 @@ class ClientZeroconf:
self.last_msg = msg
def disconnect(self):
+ # to avoid recursive calls
+ if self.disconnecting:
+ return
if self.listener:
self.listener.disconnect()
self.listener = None
@@ -642,6 +673,14 @@ class ClientZeroconf:
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 kill_all_connections(self):
for connection in self.connections.values():
@@ -720,13 +759,25 @@ class ClientZeroconf:
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 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.
+ 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):
@@ -747,8 +798,10 @@ class ClientZeroconf:
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. '''
+ """
+ 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:
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
index 09818be47..0d45379c3 100644
--- a/src/common/zeroconf/connection_handlers_zeroconf.py
+++ b/src/common/zeroconf/connection_handlers_zeroconf.py
@@ -28,13 +28,14 @@ import socket
from calendar import timegm
-from common import socks5
import common.xmpp
from common import helpers
from common import gajim
from common.zeroconf import zeroconf
from common.commands import ConnectionCommands
+from common.pep import ConnectionPEP
+from common.protocol.bytestream import ConnectionBytestreamZeroconf
import logging
log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
@@ -57,10 +58,10 @@ from session import ChatControlSession
class ConnectionVcard(connection_handlers.ConnectionVcard):
def add_sha(self, p, send_caps = True):
- pass
+ return p
def add_caps(self, p):
- pass
+ return p
def request_vcard(self, jid = None, is_fake_jid = False):
pass
@@ -68,318 +69,12 @@ class ConnectionVcard(connection_handlers.ConnectionVcard):
def send_vcard(self, vcard):
pass
-class ConnectionBytestream(connection_handlers.ConnectionBytestream):
- def send_socks5_info(self, file_props, fast = True, receiver = None,
- sender = None):
- ''' send iq for the present streamhosts and proxies '''
- 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')
- if receiver is None:
- receiver = file_props['receiver']
- if sender is None:
- sender = file_props['sender']
- 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:
- try:
- ft_host = socket.gethostbyname(ft_host)
- ft_add_hosts.append(ft_host)
- except socket.gaierror:
- self.dispatch('ERROR', (_('Wrong host'), _('The host %s you configured as the ft_add_hosts_to_send advanced option is not valid, so ignored.') % 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', 'tcp')
- 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)
- for thehost in self.peerhost:
- thehost = self.peerhost[0]
- streamhost = common.xmpp.Node(tag = 'streamhost') # My IP
- query.addChild(node = streamhost)
- streamhost.setAttr('port', unicode(port))
- streamhost.setAttr('host', thehost)
- streamhost.setAttr('jid', sender)
- 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
- our_jid = gajim.get_jid_from_account(self.name)
- frm = our_jid
- file_props['sender'] = frm
- fjid = file_props['receiver'].jid
- 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 _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': unicode(iq_obj.getFrom())
- }
- 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':
- 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
-
- def _ResultCB(self, con, iq_obj):
- log.debug('_ResultCB')
- # if we want to respect jep-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 = unicode(iq_obj.getFrom())
- 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 _bytestreamResultCB(self, con, iq_obj):
- log.debug('_bytestreamResultCB')
- frm = unicode(iq_obj.getFrom())
- 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_'):
- id_ = real_id[3:]
- 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 = streamhost.getAttr('jid')
- if 'streamhost-used' in file_props and \
- file_props['streamhost-used'] is True:
- raise common.xmpp.NodeProcessed
-
- if real_id.startswith('au_'):
- 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
-
- 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:
- 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
-
- def _siResultCB(self, con, iq_obj):
- log.debug('_siResultCB')
- self.peerhost = con._owner.Connection._sock.getsockname()
- 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'] = unicode(iq_obj.getFrom())
- 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 = unicode(iq_obj.getFrom())
- si = iq_obj.getTag('si')
- profile = si.getAttr('profile')
- mime_type = si.getAttr('mime-type')
- if profile != common.xmpp.NS_FILE:
- return
- file_tag = si.getTag('file')
- file_props = {'type': 'r'}
- 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)
- file_props['receiver'] = our_jid
- file_props['sender'] = unicode(iq_obj.getFrom())
- file_props['request-id'] = unicode(iq_obj.getAttr('id'))
- 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 = unicode(iq_obj.getFrom())
- file_props['error'] = -3
- self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
- raise common.xmpp.NodeProcessed
-class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream,
-ConnectionCommands, connection_handlers.ConnectionHandlersBase):
+class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestreamZeroconf,
+ConnectionCommands, ConnectionPEP, connection_handlers.ConnectionHandlersBase):
def __init__(self):
ConnectionVcard.__init__(self)
- ConnectionBytestream.__init__(self)
+ ConnectionBytestreamZeroconf.__init__(self)
ConnectionCommands.__init__(self)
connection_handlers.ConnectionHandlersBase.__init__(self)
@@ -390,8 +85,9 @@ ConnectionCommands, connection_handlers.ConnectionHandlersBase):
HAS_IDLE = False
def _messageCB(self, ip, con, msg):
- '''Called when we receive a message'''
-
+ """
+ Called when we receive a message
+ """
log.debug('Zeroconf MessageCB')
frm = msg.getFrom()
@@ -470,22 +166,13 @@ ConnectionCommands, connection_handlers.ConnectionHandlersBase):
# END messageCB
def store_metacontacts(self, tags):
- ''' fake empty method '''
+ """
+ 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_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 _DiscoverItemsGetCB(self, con, iq_obj):
log.debug('DiscoverItemsGetCB')
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
index 26c7fbf63..2059e9b82 100644
--- a/src/common/zeroconf/connection_zeroconf.py
+++ b/src/common/zeroconf/connection_zeroconf.py
@@ -5,7 +5,7 @@
## - Nikos Kouremenos <nkour@jabber.org>
## - Dimitur Kirov <dkirov@gmail.com>
## - Travis Shirk <travis@pobox.com>
-## - Stefan Bethge <stefan@lanpartei.de>
+## - Stefan Bethge <stefan@lanpartei.de>
##
## Copyright (C) 2003-2004 Yann Leboulanger <asterix@lagaule.org>
## Vincent Hanquez <tab@snarc.org>
@@ -43,147 +43,92 @@ if os.name != 'nt':
import getpass
import gobject
+from common.connection import CommonConnection
from common import gajim
from common import GnuPG
from common.zeroconf import client_zeroconf
from common.zeroconf import zeroconf
from connection_handlers_zeroconf import *
-class ConnectionZeroconf(ConnectionHandlersZeroconf):
- '''Connection class'''
+class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
def __init__(self, name):
ConnectionHandlersZeroconf.__init__(self)
# system username
self.username = None
- self.name = name
self.server_resource = '' # zeroconf has no resource, fake an empty one
- self.connected = 0 # offline
- self.connection = None
- 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.is_zeroconf = True
- self.privacy_rules_supported = False
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_all = False
- self.status = ''
- self.old_show = ''
- self.priority = 0
-
self.call_resolve_timeout = False
-
- self.time_to_reconnect = None
- #self.new_account_info = None
- self.bookmarks = []
-
- #we don't need a password, but must be non-empty
+ # we don't need a password, but must be non-empty
self.password = 'zeroconf'
-
self.autoconnect = False
- self.sync_with_global_status = True
- self.no_log_for = False
-
- self.pep_supported = False
- self.mood = {}
- self.tune = {}
- self.activity = {}
- # Do we continue connection when we get roster (send presence,get vcard...)
- self.continue_connect_info = None
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
- self.get_config_values_or_default()
-
- self.muc_jid = {} # jid of muc server for each transport type
- self.vcard_supported = False
- self.private_storage_supported = False
+ 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'''
-
+ """
+ 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,
+ '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.no_log_for = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for')
- 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, '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)
+ 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')
+ self.username = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'name')
# END __init__
- def dispatch(self, event, data):
- #gajim.interface.dispatch(event, self.name, data)
- gajim.ged.raise_event(event, self.name, data)
+ 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')
-# signed = self.get_signed_msg(self.status)
self.disconnect()
self.change_status(self.old_show, self.status)
- def quit(self, kill_core):
- if kill_core and self.connected > 1:
- self.disconnect()
-
def disable_account(self):
self.disconnect()
- def test_gpg_passphrase(self, password):
- self.gpg.passphrase = password
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- signed = self.gpg.sign('test', keyID)
- self.gpg.password = None
- return signed != 'BAD_PASSPHRASE'
-
- def get_signed_msg(self, msg):
- 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.connected < 2 and self.gpg.passphrase is None and \
- not use_gpg_agent:
- # We didn't set a passphrase
- self.dispatch('ERROR', (_('OpenPGP passphrase was not given'),
- #%s is the account name here
- _('You will be connected to %s without OpenPGP.') % self.name))
- self.USE_GPG = False
- elif 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 = ''
- if self.connected < 2:
- self.dispatch('BAD_PASSPHRASE', ())
- return signed
-
def _on_resolve_timeout(self):
if self.connected:
self.connection.resolve_all()
@@ -200,8 +145,10 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
# 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))
+ 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)
@@ -209,18 +156,11 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
# keyID, timestamp, contact_nickname))
self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None))
- def _on_disconnected(self):
- self.disconnect()
- self.dispatch('STATUS', 'offline')
- self.dispatch('CONNECTION_LOST',
- (_('Connection with account "%s" has been lost') % self.name,
- _('To continue sending and receiving messages, you will need to reconnect.')))
- self.status = 'offline'
- self.disconnect()
-
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'''
+ """
+ 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
@@ -237,9 +177,10 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
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))
+ self.dispatch('ERROR', (_('Avahi error'),
+ _('%s\nLink-local messaging might not work properly.') % message))
- def connect(self, show = 'online', msg = ''):
+ def connect(self, show='online', msg=''):
self.get_config_values_or_default()
if not self.connection:
self.connection = client_zeroconf.ClientZeroconf(self)
@@ -270,10 +211,12 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
self.roster = self.connection.getRoster()
self.dispatch('ROSTER', self.roster)
- #display contacts already detected and resolved
+ # 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.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)
@@ -282,7 +225,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
gobject.timeout_add_seconds(5, self._on_resolve_timeout)
return True
- def disconnect(self, on_purpose = False):
+ def disconnect(self, on_purpose=False):
self.connected = 0
self.time_to_reconnect = None
if self.connection:
@@ -294,15 +237,20 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
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')
+ 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')
+ 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
@@ -314,41 +262,18 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
else:
self.reannounce()
- def change_status(self, show, msg, sync = False, auto = False):
- if not show in STATUS_LIST:
- return -1
- self.status = show
-
- check = True #to check for errors from zeroconf
- # 'connect'
- if show != 'offline' and not self.connected:
- 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', ())
-
- # 'disconnect'
- elif show == 'offline' and self.connected:
- self.disconnect()
- self.time_to_reconnect = None
-
- # update status
- elif show != 'offline' and self.connected:
- was_invisible = self.connected == STATUS_LIST.index('invisible')
+ 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)
- if show == 'invisible':
- check = check and self.connection.remove_announce()
- elif was_invisible:
- if not self.connected:
- check = check and self.connect(show, msg)
- check = check and self.connection.announce()
- if self.connection and not show == 'invisible':
- check = check and self.connection.set_show_msg(show, msg)
-
- #stay offline when zeroconf does something wrong
+ self.dispatch('SIGNED_IN', ())
+
+ # stay offline when zeroconf does something wrong
if check:
self.dispatch('STATUS', show)
else:
@@ -359,136 +284,63 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
(_('Could not change status of account "%s"') % self.name,
_('Please check if avahi-daemon is running.')))
- def get_status(self):
- return STATUS_LIST[self.connected]
+ 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 _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=[]):
- fjid = jid
-
- 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 self.connection:
- return
- if not msg and chatstate is None:
- return
-
- if self.status in ('invisible', 'offline'):
- self.dispatch('MSGERROR', [unicode(jid), -1,
- _('You are not connected or not visible to others. Your message '
- 'could not be sent.'), None, None, session])
- return
-
- msgtxt = msg
- msgenc = ''
- if keyID and self.USE_GPG:
- 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:
- # encrypt
- msgenc, error = self.gpg.encrypt(msg, [keyID])
- if msgenc and not error:
- msgtxt = '[This message is encrypted]'
- lang = os.getenv('LANG')
- if lang is not None or lang != 'en': # we're not english
- msgtxt = _('[This message is encrypted]') +\
- ' ([This message is encrypted])' # one in locale and one en
- else:
- # Encryption failed, do not send message
- tim = time.localtime()
- self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
- return
-
- 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)
-
- # chatstates - if peer supports jep85 or jep22, send chatstates
- # please note that the only valid tag inside a message containing a <body>
- # tag is the active event
- if chatstate is not None:
- if composing_xep == 'XEP-0085' or not composing_xep:
- # JEP-0085
- msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
- if composing_xep == 'XEP-0022' or not composing_xep:
- # JEP-0022
- chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
- if not msgtxt: # when no <body>, add <id>
- if not msg_id: # avoid putting 'None' in <id> tag
- msg_id = ''
- chatstate_node.setTagData('id', msg_id)
- # when msgtxt, requests JEP-0022 composing notification
- 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%TZ', time.gmtime(delayed))
- msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2,
- attrs={'from': our_jid, 'stamp': timestamp})
-
- if session:
- session.last_send = time.time()
- msg_iq.setThread(session.thread_id)
-
- if session.enable_encryption:
- msg_iq = session.encrypt_stanza(msg_iq)
-
- def on_send_ok(id):
- no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
- ji = gajim.get_jid_without_resource(jid)
- if session.is_loggable() and self.name not in no_log_for and\
- ji not in no_log_for:
- log_msg = msg
- if subject:
- log_msg = _('Subject: %(subject)s\n%(message)s') % \
- {'subject': subject, 'message': msg}
- if log_msg:
- if type_ == 'chat':
- kind = 'chat_msg_sent'
- else:
- kind = 'single_msg_sent'
- gajim.logger.write(kind, jid, log_msg)
+ def on_send_ok(msg_id):
self.dispatch('MSGSENT', (jid, msg, keyID))
-
if callback:
- callback(id, *callback_args)
+ callback(msg_id, *callback_args)
+
+ 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])
- 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])
+ 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])
+
+ 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
@@ -498,95 +350,10 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
stanza = common.xmpp.Protocol(node=stanza)
self.connection.send(stanza)
- def ack_subscribed(self, jid):
- gajim.log.debug('This should not happen (ack_subscribed)')
-
- def ack_unsubscribed(self, jid):
- gajim.log.debug('This should not happen (ack_unsubscribed)')
-
- def request_subscription(self, jid, msg = '', name = '', groups = [],
- auto_auth = False):
- gajim.log.debug('This should not happen (request_subscription)')
-
- def send_authorization(self, jid):
- gajim.log.debug('This should not happen (send_authorization)')
-
- def refuse_authorization(self, jid):
- gajim.log.debug('This should not happen (refuse_authorization)')
-
- def unsubscribe(self, jid, remove_auth = True):
- gajim.log.debug('This should not happen (unsubscribe)')
-
- def unsubscribe_agent(self, agent):
- gajim.log.debug('This should not happen (unsubscribe_agent)')
-
- 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):
- gajim.log.debug('This should not happen (new_account)')
-
- def _on_new_account(self, con = None, con_type = None):
- gajim.log.debug('This should not happen (_on_new_account)')
-
- def account_changed(self, new_name):
- self.name = new_name
-
- def request_last_status_time(self, jid, resource):
- gajim.log.debug('This should not happen (request_last_status_time)')
-
- def request_os_info(self, jid, resource):
- gajim.log.debug('This should not happen (request_os_info)')
-
- def get_settings(self):
- gajim.log.debug('This should not happen (get_settings)')
-
- def get_bookmarks(self):
- gajim.log.debug('This should not happen (get_bookmarks)')
-
- def store_bookmarks(self):
- gajim.log.debug('This should not happen (store_bookmarks)')
-
- def get_metacontacts(self):
- gajim.log.debug('This should not happen (get_metacontacts)')
-
- def send_agent_status(self, agent, ptype):
- gajim.log.debug('This should not happen (send_agent_status)')
-
- 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 _event_dispatcher(self, realm, event, data):
+ CommonConnection._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))
- elif event == common.xmpp.transports.DATA_ERROR:
+ 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)
@@ -594,9 +361,6 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
_('Connection to host could not be established: Timeout while '
'sending data.'), None, None, session])
- def load_roster_from_db(self):
- return
-
# END ConnectionZeroconf
# vim: se ts=3:
diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py
index 42fe0edd6..4fe05aee1 100644
--- a/src/common/zeroconf/roster_zeroconf.py
+++ b/src/common/zeroconf/roster_zeroconf.py
@@ -37,9 +37,10 @@ class 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 '''
-
+ """
+ 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()
diff --git a/src/common/zeroconf/zeroconf_avahi.py b/src/common/zeroconf/zeroconf_avahi.py
index baa373880..1db21adb4 100644
--- a/src/common/zeroconf/zeroconf_avahi.py
+++ b/src/common/zeroconf/zeroconf_avahi.py
@@ -77,7 +77,7 @@ class Zeroconf:
log.debug('Found service %s in domain %s on %i.%i.' % (name, domain,
interface, protocol))
if not self.connected:
- return
+ return
# synchronous resolving
self.server.ResolveService( int(interface), int(protocol), name, stype,
@@ -90,7 +90,7 @@ class Zeroconf:
log.debug('Service %s in domain %s on %i.%i disappeared.' % (name,
domain, interface, protocol))
if not self.connected:
- return
+ return
if name != self.name:
for key in self.contacts.keys():
if self.contacts[key][C_BARE_NAME] == name:
diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py
index 10568a7a7..772c9b2f0 100644
--- a/src/common/zeroconf/zeroconf_bonjour.py
+++ b/src/common/zeroconf/zeroconf_bonjour.py
@@ -10,11 +10,11 @@
##
## 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
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
from common import gajim
@@ -28,14 +28,14 @@ except ImportError, e:
pass
-resolve_timeout = 1
+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.domain = None # specific domain to browse
self.stype = '_presence._tcp'
- self.port = port # listening port that gets announced
+ self.port = port # listening port that gets announced
self.username = name
self.host = host
self.txt = pybonjour.TXTRecord() # service data
@@ -48,7 +48,7 @@ class Zeroconf:
self.disconnected_CB = disconnected_CB
self.error_CB = error_CB
- self.contacts = {} # all current local contacts with data
+ self.contacts = {} # all current local contacts with data
self.connected = False
self.announced = False
self.invalid_self_contact = {}
@@ -58,7 +58,7 @@ class Zeroconf:
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
+ return
if errorCode != pybonjour.kDNSServiceErr_NoError:
return
if not (flags & pybonjour.kDNSServiceFlagsAdd):
@@ -83,7 +83,7 @@ class Zeroconf:
def remove_service_callback(self, name):
gajim.log.debug('Service %s disappeared.' % name)
if not self.connected:
- return
+ return
if name != self.name:
for key in self.contacts.keys():
if self.contacts[key][C_BARE_NAME] == name:
@@ -103,7 +103,7 @@ class Zeroconf:
def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname,
hosttarget, port, txtRecord):
- # TODO: do proper decoding...
+ # TODO: do proper decoding...
escaping= {
r'\.': '.',
r'\032': ' ',
@@ -211,7 +211,7 @@ class Zeroconf:
txt['version'] = 1
txt['txtvers'] = 1
- # replace gajim's show messages with compatible ones
+ # replace gajim's show messages with compatible ones
if 'status' in self.txt:
txt['status'] = self.replace_show(self.txt['status'])
else:
@@ -221,7 +221,7 @@ class Zeroconf:
try:
sdRef = pybonjour.DNSServiceRegister(name = self.name,
- regtype = self.stype, port = self.port, txtRecord = self.txt,
+ regtype = self.stype, port = self.port, txtRecord = self.txt,
callBack = self.service_added_callback)
self.service_sdRef = sdRef
except pybonjour.BonjourError, e:
diff --git a/src/config.py b/src/config.py
index a8e3b0902..e75d5eee8 100644
--- a/src/config.py
+++ b/src/config.py
@@ -59,21 +59,34 @@ from common.zeroconf import connection_zeroconf
from common import dataforms
from common import GnuPG
+try:
+ from common.multimedia_helpers import AudioInputManager, AudioOutputManager
+ from common.multimedia_helpers import VideoInputManager, VideoOutputManager
+ HAS_GST = True
+except ImportError:
+ HAS_GST = False
+
from common.exceptions import GajimGeneralException
#---------- PreferencesWindow class -------------#
class PreferencesWindow:
- '''Class for Preferences window'''
+ """
+ Class for Preferences window
+ """
def on_preferences_window_destroy(self, widget):
- '''close window'''
+ """
+ Close window
+ """
del gajim.interface.instances['preferences']
def on_close_button_clicked(self, widget):
self.window.destroy()
def __init__(self):
- '''Initialize Preferences window'''
+ """
+ Initialize Preferences window
+ """
self.xml = gtkgui_helpers.get_glade('preferences_window.glade')
self.window = self.xml.get_widget('preferences_window')
self.window.set_transient_for(gajim.interface.roster.window)
@@ -124,6 +137,11 @@ class PreferencesWindow:
self.xml.get_widget('show_tunes_in_roster_checkbutton'). \
set_active(st)
+ # Display location in roster
+ st = gajim.config.get('show_location_in_roster')
+ self.xml.get_widget('show_location_in_roster_checkbutton'). \
+ set_active(st)
+
# Sort contacts by show
st = gajim.config.get('sort_by_show_in_roster')
self.xml.get_widget('sort_by_show_in_roster_checkbutton').set_active(st)
@@ -297,8 +315,6 @@ class PreferencesWindow:
systray_combobox.set_active(1)
else:
systray_combobox.set_active(2)
- if not gajim.interface.systray_capabilities:
- systray_combobox.set_sensitive(False)
# sounds
if gajim.config.get('sounds_on'):
@@ -366,7 +382,8 @@ class PreferencesWindow:
# Default Status messages
self.default_msg_tree = self.xml.get_widget('default_msg_treeview')
- col2 = self.default_msg_tree.rc_get_style().bg[gtk.STATE_ACTIVE].to_string()
+ col2 = self.default_msg_tree.rc_get_style().bg[gtk.STATE_ACTIVE].\
+ to_string()
# (status, translated_status, message, enabled)
model = gtk.ListStore(str, str, str, bool)
self.default_msg_tree.set_model(model)
@@ -410,6 +427,41 @@ class PreferencesWindow:
buf = self.xml.get_widget('msg_textview').get_buffer()
buf.connect('changed', self.on_msg_textview_changed)
+ ### Audio / Video tab ###
+ def create_av_combobox(opt_name, device_dict):
+ combobox = self.xml.get_widget(opt_name + '_combobox')
+ cell = gtk.CellRendererText()
+ combobox.pack_start(cell, True)
+ combobox.add_attribute(cell, 'text', 0)
+ model = gtk.ListStore(str, str)
+ combobox.set_model(model)
+
+ for index, (name, value) in enumerate(sorted(device_dict.iteritems())):
+ model.append((name, value))
+ if gajim.config.get(opt_name + '_device') == value:
+ combobox.set_active(index)
+
+ if HAS_GST:
+ create_av_combobox('audio_input', AudioInputManager().get_devices())
+ create_av_combobox('audio_output', AudioOutputManager().get_devices())
+ create_av_combobox('video_input', VideoInputManager().get_devices())
+ create_av_combobox('video_output', VideoOutputManager().get_devices())
+ else:
+ for opt_name in ('audio_input', 'audio_output', 'video_input',
+ 'video_output'):
+ combobox = self.xml.get_widget(opt_name + '_combobox')
+ combobox.set_sensitive(False)
+
+ # STUN
+ cb = self.xml.get_widget('stun_checkbutton')
+ st = gajim.config.get('use_stun_server')
+ cb.set_active(st)
+
+ entry = self.xml.get_widget('stun_server_entry')
+ entry.set_text(gajim.config.get('stun_server'))
+ if not st:
+ entry.set_sensitive(False)
+
### Advanced tab ###
# open links with
if os.name == 'nt':
@@ -463,6 +515,14 @@ class PreferencesWindow:
else:
w.set_active(st)
+ # send idle time
+ w = self.xml.get_widget('send_idle_time_checkbutton')
+ st = self.get_per_account_option('send_idle_time')
+ if st == 'mixed':
+ w.set_inconsistent(True)
+ else:
+ w.set_active(st)
+
# check if gajm is default
st = gajim.config.get('check_if_gajim_is_default')
self.xml.get_widget('check_default_client_checkbutton').set_active(st)
@@ -497,8 +557,10 @@ class PreferencesWindow:
self.window.hide()
def get_per_account_option(self, opt):
- '''Return the value of the option opt if it's the same in all accounts
- else returns "mixed"'''
+ """
+ Return the value of the option opt if it's the same in all accounts else
+ returns "mixed"
+ """
if len(gajim.connections) == 0:
# a non existant key return default value
return gajim.config.get_per('accounts', '__default__', opt)
@@ -529,37 +591,43 @@ class PreferencesWindow:
w.set_sensitive(widget.get_active())
gajim.interface.save_config()
+ def _get_all_controls(self):
+ for ctrl in gajim.interface.msg_win_mgr.get_controls():
+ yield ctrl
+ for account in gajim.connections:
+ for ctrl in gajim.interface.minimized_controls[account].values():
+ yield ctrl
+
+ def _get_all_muc_controls(self):
+ for ctrl in gajim.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC):
+ yield ctrl
+ for account in gajim.connections:
+ for ctrl in gajim.interface.minimized_controls[account].values():
+ yield ctrl
+
def on_sort_by_show_in_roster_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'sort_by_show_in_roster')
gajim.interface.roster.setup_and_draw_roster()
def on_sort_by_show_in_muc_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'sort_by_show_in_muc')
- # Redraw connected groupchats
- for account in gajim.connections:
- if gajim.connections[account].connected:
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + \
- gajim.interface.minimized_controls[account].values():
- gc_control.draw_roster()
+ # Redraw groupchats
+ for ctrl in self._get_all_muc_controls():
+ ctrl.draw_roster()
def on_show_avatars_in_roster_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'show_avatars_in_roster')
gajim.interface.roster.setup_and_draw_roster()
- # Redraw connected groupchats (in an ugly way)
- for account in gajim.connections:
- if gajim.connections[account].connected:
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + \
- gajim.interface.minimized_controls[account].values():
- gc_control.draw_roster()
+ # Redraw groupchats (in an ugly way)
+ for ctrl in self._get_all_muc_controls():
+ ctrl.draw_roster()
def on_show_status_msgs_in_roster_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'show_status_msgs_in_roster')
gajim.interface.roster.setup_and_draw_roster()
- for ctl in gajim.interface.msg_win_mgr.controls():
- if ctl.type_id == message_control.TYPE_GC:
- ctl.update_ui()
+ for ctrl in self._get_all_muc_controls():
+ ctrl.update_ui()
def on_show_mood_in_roster_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'show_mood_in_roster')
@@ -573,6 +641,10 @@ class PreferencesWindow:
self.on_checkbutton_toggled(widget, 'show_tunes_in_roster')
gajim.interface.roster.setup_and_draw_roster()
+ def on_show_location_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'show_location_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+
def on_emoticons_combobox_changed(self, widget):
active = widget.get_active()
model = widget.get_model()
@@ -587,9 +659,11 @@ class PreferencesWindow:
self.toggle_emoticons()
def toggle_emoticons(self):
- '''Update emoticons state in Opened Chat Windows'''
- for win in gajim.interface.msg_win_mgr.windows():
- win.toggle_emoticons()
+ """
+ Update emoticons state in Opened Chat Windows
+ """
+ for ctrl in self._get_all_controls():
+ ctrl.toggle_emoticons()
def on_one_window_type_combo_changed(self, widget):
active = widget.get_active()
@@ -600,8 +674,8 @@ class PreferencesWindow:
def on_compact_view_checkbutton_toggled(self, widget):
active = widget.get_active()
- for ctl in gajim.interface.msg_win_mgr.controls():
- ctl.chat_buttons_set_visible(active)
+ for ctrl in self._get_all_controls():
+ ctrl.chat_buttons_set_visible(active)
gajim.config.set('compact_view', active)
gajim.interface.save_config()
@@ -610,7 +684,7 @@ class PreferencesWindow:
helpers.update_optional_features()
def apply_speller(self):
- for ctrl in gajim.interface.msg_win_mgr.controls():
+ for ctrl in self._get_all_controls():
if isinstance(ctrl, chat_control.ChatControlBase):
try:
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
@@ -621,7 +695,7 @@ class PreferencesWindow:
ctrl.set_speller()
def remove_speller(self):
- for ctrl in gajim.interface.msg_win_mgr.controls():
+ for ctrl in self._get_all_controls():
if isinstance(ctrl, chat_control.ChatControlBase):
try:
spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
@@ -781,9 +855,11 @@ class PreferencesWindow:
self.sounds_preferences.window.present()
def update_text_tags(self):
- '''Update color tags in Opened Chat Windows'''
- for win in gajim.interface.msg_win_mgr.windows():
- win.update_tags()
+ """
+ Update color tags in opened chat windows
+ """
+ for ctrl in self._get_all_controls():
+ ctrl.update_tags()
def on_preference_widget_color_set(self, widget, text):
color = widget.get_color()
@@ -802,9 +878,11 @@ class PreferencesWindow:
gajim.interface.save_config()
def update_text_font(self):
- '''Update text font in Opened Chat Windows'''
- for win in gajim.interface.msg_win_mgr.windows():
- win.update_font()
+ """
+ Update text font in opened chat windows
+ """
+ for ctrl in self._get_all_controls():
+ ctrl.update_font()
def on_incoming_nick_colorbutton_color_set(self, widget):
self.on_preference_widget_color_set(widget, 'inmsgcolor')
@@ -835,7 +913,7 @@ class PreferencesWindow:
else:
font_widget.set_sensitive(True)
self.on_preference_widget_font_set(font_widget, 'conversation_font')
-
+
def draw_color_widgets(self):
col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton',
'outmsgcolor': 'outgoing_nick_colorbutton',
@@ -881,7 +959,9 @@ class PreferencesWindow:
gajim.interface.save_config()
def _set_color(self, state, widget_name, option):
- ''' set color value in prefs and update the UI '''
+ """
+ Set color value in prefs and update the UI
+ """
if state:
color = self.xml.get_widget(widget_name).get_color()
color_string = gtkgui_helpers.make_color_string(color)
@@ -993,6 +1073,31 @@ class PreferencesWindow:
def on_msg_treemodel_row_deleted(self, model, path):
self.save_status_messages(model)
+ def on_av_combobox_changed(self, combobox, opt_name):
+ model = combobox.get_model()
+ active = combobox.get_active()
+ device = model[active][1].decode('utf-8')
+ gajim.config.set(opt_name + '_device', device)
+
+ def on_audio_input_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'audio_input')
+
+ def on_audio_output_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'audio_output')
+
+ def on_video_input_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'video_input')
+
+ def on_video_output_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'video_output')
+
+ def on_stun_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'use_stun_server',
+ [self.xml.get_widget('stun_server_entry')])
+
+ def stun_server_entry_changed(self, widget):
+ gajim.config.set('stun_server', widget.get_text().decode('utf-8'))
+
def on_applications_combobox_changed(self, widget):
gajim.config.set('autodetect_browser_mailer', False)
if widget.get_active() == 4:
@@ -1033,6 +1138,10 @@ class PreferencesWindow:
widget.set_inconsistent(False)
self.on_per_account_checkbutton_toggled(widget, 'send_os_info')
+ def on_send_idle_time_checkbutton_toggled(self, widget):
+ widget.set_inconsistent(False)
+ self.on_per_account_checkbutton_toggled(widget, 'send_idle_time')
+
def on_check_default_client_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'check_if_gajim_is_default')
@@ -1348,7 +1457,10 @@ class ManageProxiesWindow:
#---------- AccountsWindow class -------------#
class AccountsWindow:
- '''Class for accounts window: list of accounts'''
+ """
+ Class for accounts window: list of accounts
+ """
+
def on_accounts_window_destroy(self, widget):
del gajim.interface.instances['accounts']
@@ -1363,8 +1475,7 @@ class AccountsWindow:
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')
+ path_to_kbd_input_img = gtkgui_helpers.get_icon_path('gajim-kbd_input')
img = self.xml.get_widget('rename_image')
img.set_from_file(path_to_kbd_input_img)
self.notebook = self.xml.get_widget('notebook')
@@ -1416,7 +1527,9 @@ class AccountsWindow:
iter_ = model.iter_next(iter_)
def init_accounts(self):
- '''initialize listStore with existing accounts'''
+ """
+ Initialize listStore with existing accounts
+ """
self.remove_button.set_sensitive(False)
self.rename_button.set_sensitive(False)
self.current_account = None
@@ -1442,7 +1555,9 @@ class AccountsWindow:
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'''
+ """
+ Login with previous status
+ """
# first make sure connection is really closed,
# 0.5 may not be enough
gajim.connections[account].disconnect(True)
@@ -1475,7 +1590,9 @@ class AccountsWindow:
self.resend_presence = False
def on_accounts_treeview_cursor_changed(self, widget):
- '''Activate modify buttons when a row is selected, update accounts info'''
+ """
+ Activate modify buttons when a row is selected, update accounts info
+ """
sel = self.accounts_treeview.get_selection()
(model, iter_) = sel.get_selected()
if iter_:
@@ -1741,7 +1858,9 @@ class AccountsWindow:
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'''
+ """
+ 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:
@@ -1749,8 +1868,10 @@ class AccountsWindow:
AccountCreationWizardWindow()
def on_remove_button_clicked(self, widget):
- '''When delete button is clicked:
- Remove an account from the listStore and from the config file'''
+ """
+ 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
@@ -2313,7 +2434,7 @@ class AccountsWindow:
# normal account
if self.ignore_events:
return
- if gajim.account_is_connected(self.current_account):
+ if gajim.connections[self.current_account].connected > 0:
self.ignore_events = True
self.xml.get_widget('enable_zeroconf_checkbutton2').set_active(True)
self.ignore_events = False
@@ -2347,7 +2468,9 @@ class AccountsWindow:
def on_enable_checkbutton1_toggled(self, widget):
if self.ignore_events:
return
- if gajim.account_is_connected(self.current_account):
+ 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_widget('enable_checkbutton1').set_active(True)
self.ignore_events = False
@@ -2411,8 +2534,11 @@ class AccountsWindow:
'zeroconf_email', email)
class FakeDataForm(gtk.Table, object):
- '''Class for forms that are in XML format <entry1>value1</entry1>
- infos in a table {entry1: value1, }'''
+ """
+ Class for forms that are in XML format <entry1>value1</entry1> infos in a
+ table {entry1: value1}
+ """
+
def __init__(self, infos):
gtk.Table.__init__(self)
self.infos = infos
@@ -2420,7 +2546,9 @@ class FakeDataForm(gtk.Table, object):
self._draw_table()
def _draw_table(self):
- '''Draw the table'''
+ """
+ Draw the table
+ """
nbrow = 0
if 'instructions' in self.infos:
nbrow = 1
@@ -2454,9 +2582,11 @@ class FakeDataForm(gtk.Table, object):
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'''
+ """
+ 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
@@ -2503,7 +2633,7 @@ class ServiceRegistrationWindow:
self.window.destroy()
class GroupchatConfigWindow:
- '''GroupchatConfigWindow class'''
+
def __init__(self, account, room_jid, form = None):
self.account = account
self.room_jid = room_jid
@@ -2656,7 +2786,9 @@ class GroupchatConfigWindow:
self.remove_button[affiliation].set_sensitive(True)
def affiliation_list_received(self, users_dict):
- '''Fill the affiliation treeview'''
+ """
+ Fill the affiliation treeview
+ """
for jid in users_dict:
affiliation = users_dict[jid]['affiliation']
if affiliation not in self.affiliation_labels.keys():
@@ -2705,8 +2837,10 @@ class GroupchatConfigWindow:
#---------- 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:
@@ -2906,7 +3040,9 @@ class ManageBookmarksWindow:
del gajim.interface.instances['manage_bookmarks']
def on_add_bookmark_button_clicked(self, widget):
- '''Add a new bookmark.'''
+ """
+ Add a new bookmark
+ """
# Get the account that is currently used
# (the parent of the currently selected item)
(model, iter_) = self.selection.get_selected()
@@ -2931,9 +3067,9 @@ class ManageBookmarksWindow:
self.view.set_cursor(model.get_path(iter_))
def on_remove_bookmark_button_clicked(self, widget):
- '''
- Remove selected bookmark.
- '''
+ """
+ Remove selected bookmark
+ """
(model, iter_) = self.selection.get_selected()
if not iter_: # Nothing selected
return
@@ -2946,9 +3082,9 @@ class ManageBookmarksWindow:
self.clear_fields()
def check_valid_bookmark(self):
- '''
- Check if all neccessary fields are entered correctly.
- '''
+ """
+ Check if all neccessary fields are entered correctly
+ """
(model, iter_) = self.selection.get_selected()
if not model.iter_parent(iter_):
@@ -2965,10 +3101,10 @@ class ManageBookmarksWindow:
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.
- '''
+ """
+ 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
@@ -2999,9 +3135,9 @@ class ManageBookmarksWindow:
self.window.destroy()
def bookmark_selected(self, selection):
- '''
+ """
Fill in the bookmark's data into the fields.
- '''
+ """
(model, iter_) = selection.get_selected()
if not iter_:
@@ -3148,6 +3284,7 @@ class AccountCreationWizardWindow:
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
@@ -3274,7 +3411,7 @@ class AccountCreationWizardWindow:
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')
+ 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
@@ -3445,8 +3582,10 @@ class AccountCreationWizardWindow:
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.'''
+ 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()
@@ -3480,7 +3619,9 @@ class AccountCreationWizardWindow:
self.notebook.set_current_page(4) # show form page
def new_acc_not_connected(self, reason):
- '''Account creation failed: connection to server failed'''
+ """
+ Account creation failed: connection to server failed
+ """
if self.account not in gajim.connections:
return
if self.update_progressbar_timeout_id is not None:
@@ -3500,7 +3641,9 @@ class AccountCreationWizardWindow:
self.notebook.set_current_page(6) # show finish page
def acc_is_ok(self, config):
- '''Account creation succeeded'''
+ """
+ Account creation succeeded
+ """
self.create_vars(config)
self.show_finish_page()
@@ -3508,7 +3651,9 @@ class AccountCreationWizardWindow:
gobject.source_remove(self.update_progressbar_timeout_id)
def acc_is_not_ok(self, reason):
- '''Account creation failed'''
+ """
+ Account creation failed
+ """
self.back_button.show()
self.cancel_button.show()
self.go_online_checkbutton.hide()
diff --git a/src/conversation_textview.py b/src/conversation_textview.py
index 837e2a0ce..02f949376 100644
--- a/src/conversation_textview.py
+++ b/src/conversation_textview.py
@@ -67,13 +67,15 @@ def has_focus(widget):
class TextViewImage(gtk.Image):
- def __init__(self, anchor):
+ 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()
@@ -156,8 +158,10 @@ class TextViewImage(gtk.Image):
class ConversationTextview(gobject.GObject):
- '''Class for the conversation textview (where user reads already said
- messages) for chat/groupchat windows'''
+ """
+ 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
@@ -165,18 +169,19 @@ class ConversationTextview(gobject.GObject):
)
)
- 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'))
+ 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'''
+ """
+ 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
@@ -640,8 +645,9 @@ class ConversationTextview(gobject.GObject):
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'''
+ """
+ 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)
@@ -686,7 +692,9 @@ class ConversationTextview(gobject.GObject):
self.change_cursor = True
def clear(self, tv = None):
- '''clear text in the textview'''
+ """
+ Clear text in the textview
+ """
buffer_ = self.tv.get_buffer()
start, end = buffer_.get_bounds()
buffer_.delete(start, end)
@@ -696,15 +704,18 @@ class ConversationTextview(gobject.GObject):
self.focus_out_end_mark = None
def visit_url_from_menuitem(self, widget, link):
- '''basically it filters out the widget instance'''
+ """
+ Basically it filters out the widget instance
+ """
helpers.launch_browser_mailer('url', link)
def on_textview_populate_popup(self, textview, menu):
- '''we 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)'''
-
+ """
+ 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()
@@ -897,16 +908,7 @@ class ConversationTextview(gobject.GObject):
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]
@@ -969,13 +971,13 @@ class ConversationTextview(gobject.GObject):
def detect_and_print_special_text(self, otext, other_tags, graphics=True):
- '''detects special text (emots & links & formatting)
- prints normal text before any special text it founts,
- then print special text (that happens many times until
- last special text is printed) and then returns the index
+ """
+ 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()'''
-
+ print_conversation_line()
+ """
buffer_ = self.tv.get_buffer()
insert_tags_func = buffer_.insert_with_tags_by_name
@@ -1021,8 +1023,10 @@ class ConversationTextview(gobject.GObject):
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)'''
+ """
+ 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
@@ -1043,7 +1047,7 @@ class ConversationTextview(gobject.GObject):
emot_ascii = possible_emot_ascii_caps
end_iter = buffer_.get_end_iter()
anchor = buffer_.create_child_anchor(end_iter)
- img = TextViewImage(anchor)
+ img = TextViewImage(anchor, special_text)
animations = gajim.interface.emoticons_animations
if not emot_ascii in animations:
animations[emot_ascii] = gtk.gdk.PixbufAnimation(
@@ -1125,7 +1129,7 @@ class ConversationTextview(gobject.GObject):
end_iter = buffer_.get_end_iter()
if imagepath is not None:
anchor = buffer_.create_child_anchor(end_iter)
- img = gtk.Image()
+ img = TextViewImage(anchor, special_text)
img.set_from_file(imagepath)
img.show()
# add
@@ -1161,9 +1165,12 @@ class ConversationTextview(gobject.GObject):
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):
- '''prints 'chat' type messages'''
+ 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():
@@ -1261,8 +1268,10 @@ class ConversationTextview(gobject.GObject):
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'''
+ """
+ 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) -
@@ -1315,8 +1324,10 @@ class ConversationTextview(gobject.GObject):
self.print_empty_line()
def print_real_text(self, text, text_tags=[], name=None, xhtml=None,
- graphics=True):
- '''this adds normal and special text. call this to add text'''
+ 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')):
diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py
index 18195dccd..4a9681dab 100644
--- a/src/dataforms_widget.py
+++ b/src/dataforms_widget.py
@@ -38,7 +38,10 @@ 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)
@@ -65,7 +68,9 @@ class DataFormWidget(gtk.Alignment, object):
selection.set_mode(gtk.SELECTION_MULTIPLE)
def set_data_form(self, dataform):
- ''' Set the data form (xmpp.DataForm) displayed in widget. '''
+ """
+ Set the data form (xmpp.DataForm) displayed in widget
+ """
assert isinstance(dataform, dataforms.DataForm)
self.del_data_form()
@@ -84,7 +89,9 @@ class DataFormWidget(gtk.Alignment, object):
gtkgui_helpers.label_set_autowrap(self.instructions_label)
def get_data_form(self):
- ''' Data form displayed in the widget or None if no form. '''
+ """
+ Data form displayed in the widget or None if no form
+ """
return self._data_form
def del_data_form(self):
@@ -95,8 +102,10 @@ class DataFormWidget(gtk.Alignment, object):
'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. '''
+ """
+ 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
@@ -117,9 +126,11 @@ class DataFormWidget(gtk.Alignment, object):
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.'''
+ """
+ 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):
@@ -138,14 +149,18 @@ class DataFormWidget(gtk.Alignment, object):
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.'''
+ """
+ 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.'''
+ """
+ Invoked when new multiple form is to be created
+ """
assert isinstance(self._data_form, dataforms.MultipleDataForm)
self.clean_data_form()
@@ -196,13 +211,17 @@ class DataFormWidget(gtk.Alignment, object):
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.'''
+ """
+ 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.'''
+ """
+ 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()
@@ -273,9 +292,12 @@ class DataFormWidget(gtk.Alignment, object):
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)
@@ -284,9 +306,11 @@ class SingleForm(gtk.Table, object):
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. '''
+ """
+ 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()
diff --git a/src/dialogs.py b/src/dialogs.py
index 1e4e6266b..4f1799a33 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -32,7 +32,6 @@
import gtk
import gobject
import os
-from weakref import WeakValueDictionary
import gtkgui_helpers
import vcard
@@ -61,9 +60,14 @@ 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'''
+ """
+ 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)
@@ -96,7 +100,9 @@ class EditGroupsDialog:
self.dialog.destroy()
def remove_group(self, group):
- '''remove group group from all contacts and all their brothers'''
+ """
+ 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])
@@ -104,7 +110,9 @@ class EditGroupsDialog:
gajim.interface.roster.draw_group(_('General'), account)
def add_group(self, group):
- '''add group group to all contacts and all their brothers'''
+ """
+ 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])
@@ -199,7 +207,9 @@ class EditGroupsDialog:
column.set_attributes(renderer, active=1, inconsistent=2)
class PassphraseDialog:
- '''Class for Passphrase dialog'''
+ """
+ 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')
@@ -258,7 +268,10 @@ class PassphraseDialog:
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, ...}'''
@@ -428,9 +441,9 @@ class ChangeActivityDialog:
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'))
@@ -524,10 +537,10 @@ class ChangeMoodDialog:
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
@@ -540,7 +553,9 @@ class TimeoutDialog:
gobject.timeout_add_seconds(1, self.countdown)
def on_timeout():
- '''To be implemented in derivated classes'''
+ """
+ To be implemented in derivated classes
+ """
pass
def countdown(self):
@@ -638,7 +653,9 @@ class ChangeStatusMessageDialog(TimeoutDialog):
self.dialog.show_all()
def draw_activity(self):
- '''Set activity button'''
+ """
+ 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 \
@@ -661,7 +678,9 @@ class ChangeStatusMessageDialog(TimeoutDialog):
label.set_text('')
def draw_mood(self):
- '''Set mood button'''
+ """
+ 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:
@@ -803,13 +822,17 @@ class ChangeStatusMessageDialog(TimeoutDialog):
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:')}
+
def __init__(self, account=None, jid=None, user_nick=None, group=None):
self.account = account
if account is None:
@@ -983,11 +1006,15 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
self.window.destroy()
def on_cancel_button_clicked(self, widget):
- '''When Cancel button is clicked'''
+ """
+ When Cancel button is clicked
+ """
self.window.destroy()
def on_add_button_clicked(self, widget):
- '''When Subscribe button is clicked'''
+ """
+ When Subscribe button is clicked
+ """
jid = self.uid_entry.get_text().decode('utf-8').strip()
if not jid:
return
@@ -1111,7 +1138,10 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
self.add_button.set_sensitive(False)
class AboutDialog:
- '''Class for about dialog'''
+ """
+ Class for about dialog
+ """
+
def __init__(self):
dlg = gtk.AboutDialog()
dlg.set_transient_for(gajim.interface.roster.window)
@@ -1159,8 +1189,7 @@ class AboutDialog:
dlg.props.wrap_license = True
- pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(
- gajim.DATA_DIR, 'pixmaps', 'gajim_about.png'))
+ pixbuf = gtkgui_helpers.get_icon_pixmap('gajim-about', 128)
dlg.set_logo(pixbuf)
#here you write your name in the form Name FamilyName <someone@somewhere>
@@ -1184,7 +1213,9 @@ class AboutDialog:
return str_[0:-1] # remove latest .
def get_path(self, filename):
- '''where can we find this Credits file ?'''
+ """
+ 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):
@@ -1273,14 +1304,18 @@ class HigDialog(gtk.MessageDialog):
self.destroy()
def popup(self):
- '''show dialog'''
+ """
+ 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'''
+ """
+ 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):
@@ -1332,7 +1367,10 @@ class AspellDictError:
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
@@ -1359,7 +1397,10 @@ class ConfirmationDialog(HigDialog):
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
@@ -1386,8 +1427,11 @@ class NonModalConfirmationDialog(HigDialog):
self.destroy()
class WarningDialog(HigDialog):
+ """
+ HIG compliant warning dialog
+ """
+
def __init__(self, pritext, sectext=''):
- '''HIG compliant warning dialog.'''
HigDialog.__init__( self, None,
gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext)
self.set_modal(False)
@@ -1396,8 +1440,11 @@ class WarningDialog(HigDialog):
self.popup()
class InformationDialog(HigDialog):
+ """
+ HIG compliant info dialog
+ """
+
def __init__(self, pritext, sectext=''):
- '''HIG compliant info dialog.'''
HigDialog.__init__(self, None,
gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext)
self.set_modal(False)
@@ -1405,16 +1452,22 @@ class InformationDialog(HigDialog):
self.popup()
class ErrorDialog(HigDialog):
+ """
+ HIG compliant error dialog
+ """
+
def __init__(self, pritext, sectext=''):
- '''HIG compliant error dialog.'''
HigDialog.__init__( self, None,
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext)
self.popup()
class YesNoDialog(HigDialog):
+ """
+ HIG compliant YesNo dialog
+ """
+
def __init__(self, pritext, sectext='', checktext='', on_response_yes=None,
- on_response_no=None):
- '''HIG compliant YesNo dialog.'''
+ on_response_no=None):
self.user_response_yes = on_response_yes
self.user_response_no = on_response_no
HigDialog.__init__( self, None,
@@ -1448,15 +1501,20 @@ class YesNoDialog(HigDialog):
self.destroy()
def is_checked(self):
- ''' Get active state of the checkbutton '''
+ """
+ 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.'''
- def __init__(self, pritext, sectext='', checktext='',
- on_response_ok=None, on_response_cancel=None, is_modal=True):
+ """
+ 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
@@ -1495,11 +1553,16 @@ class ConfirmationDialogCheck(ConfirmationDialog):
self.destroy()
def is_checked(self):
- ''' Get active state of the checkbutton '''
+ """
+ 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
@@ -1560,7 +1623,10 @@ class ConfirmationDialogDubbleCheck(ConfirmationDialog):
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,
@@ -1597,7 +1663,10 @@ class FTOverwriteConfirmationDialog(ConfirmationDialog):
self.destroy()
class CommonInputDialog:
- '''Common Class for Input dialogs'''
+ """
+ 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')
@@ -1633,9 +1702,12 @@ class CommonInputDialog:
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):
+ 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)
@@ -1651,9 +1723,12 @@ class InputDialog(CommonInputDialog):
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):
+ 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,
@@ -1683,7 +1758,9 @@ class InputDialogCheck(InputDialog):
return self.input_entry.get_text().decode('utf-8')
def is_checked(self):
- ''' Get active state of the checkbutton '''
+ """
+ Get active state of the checkbutton
+ """
try:
return self.checkbutton.get_active()
except Exception:
@@ -1691,7 +1768,10 @@ class InputDialogCheck(InputDialog):
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)
@@ -1773,7 +1853,10 @@ class ChangeNickDialog(InputDialogCheck):
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')
@@ -1790,7 +1873,10 @@ class InputTextDialog(CommonInputDialog):
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')
@@ -1876,7 +1962,9 @@ class SubscriptionRequestWindow:
self.window.destroy()
def on_authorize_button_clicked(self, widget):
- '''accept the request'''
+ """
+ Accept the request
+ """
gajim.connections[self.account].send_authorization(self.jid)
self.window.destroy()
contact = gajim.contacts.get_contact(self.account, self.jid)
@@ -1884,7 +1972,9 @@ class SubscriptionRequestWindow:
AddNewContactWindow(self.account, self.jid, self.user_nick)
def on_contact_info_activate(self, widget):
- '''ask vcard'''
+ """
+ Ask vcard
+ """
if self.jid in gajim.interface.instances[self.account]['infos']:
gajim.interface.instances[self.account]['infos'][self.jid].window.present()
else:
@@ -1896,11 +1986,15 @@ class SubscriptionRequestWindow:
get_widget('information_notebook').remove_page(0)
def on_start_chat_activate(self, widget):
- '''open chat'''
+ """
+ Open chat
+ """
gajim.interface.new_chat_from_jid(self.account, self.jid)
def on_deny_button_clicked(self, widget):
- '''refuse the request'''
+ """
+ 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():
@@ -1908,7 +2002,9 @@ class SubscriptionRequestWindow:
self.window.destroy()
def on_actions_button_clicked(self, widget):
- '''popup action menu'''
+ """
+ Popup action menu
+ """
menu = self.prepare_popup_menu()
menu.show_all()
gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window)
@@ -1916,11 +2012,12 @@ class SubscriptionRequestWindow:
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'''
-
+ 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]:
@@ -2007,10 +2104,13 @@ class JoinGroupchatWindow:
self.window.show_all()
def on_join_groupchat_window_destroy(self, widget):
- '''close window'''
+ """
+ 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_join_groupchat_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape: # ESCAPE
@@ -2039,7 +2139,9 @@ class JoinGroupchatWindow:
self._room_jid_entry.set_text(room_jid)
def on_cancel_button_clicked(self, widget):
- '''When Cancel button is clicked'''
+ """
+ When Cancel button is clicked
+ """
self.window.destroy()
def on_bookmark_checkbutton_toggled(self, widget):
@@ -2050,7 +2152,9 @@ class JoinGroupchatWindow:
auto_join_checkbutton.set_sensitive(False)
def on_join_button_clicked(self, widget):
- '''When Join button is clicked'''
+ """
+ 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 '
@@ -2138,7 +2242,9 @@ class SynchroniseSelectAccountDialog:
self.window.destroy()
def init_accounts(self):
- '''initialize listStore with existing accounts'''
+ """
+ Initialize listStore with existing accounts
+ """
model = self.accounts_treeview.get_model()
model.clear()
for remote_account in gajim.connections:
@@ -2204,7 +2310,9 @@ class SynchroniseSelectContactsDialog:
self.window.destroy()
def init_contacts(self):
- '''initialize listStore with existing accounts'''
+ """
+ Initialize listStore with existing accounts
+ """
model = self.contacts_treeview.get_model()
model.clear()
@@ -2267,7 +2375,9 @@ class NewChatDialog(InputDialog):
self.dialog.show_all()
def new_chat_response(self, jid):
- ''' called when ok button is clicked '''
+ """
+ Called when ok button is clicked
+ """
if gajim.connections[self.account].connected <= 1:
#if offline or connecting
ErrorDialog(_('Connection not available'),
@@ -2351,9 +2461,7 @@ class PopupNotificationWindow:
# 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
+ path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
if event_type == _('Contact Signed In'):
bg_color = 'limegreen'
@@ -2433,10 +2541,10 @@ class PopupNotificationWindow:
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'.
- '''
+ """
+ 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='',
@@ -2739,14 +2847,23 @@ class XMLConsoleWindow:
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.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.tagOutComment = buffer_.create_tag('out_comment')
- self.tagOutComment.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
@@ -2774,6 +2891,35 @@ class XMLConsoleWindow:
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_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_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 scroll_to_end(self, ):
parent = self.stanzas_log_textview.get_parent()
buffer_ = self.stanzas_log_textview.get_buffer()
@@ -2800,15 +2946,29 @@ class XMLConsoleWindow:
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'
+
+ if type_:
+ type_ = kind + '_' + type_
+ else:
+ type_ = kind # 'incoming' or 'outgoing'
+
if kind == 'incoming':
buffer.insert_with_tags_by_name(end_iter, '<!-- In -->\n',
- 'in_comment')
+ type_)
elif kind == 'outgoing':
buffer.insert_with_tags_by_name(end_iter, '<!-- Out -->\n',
- 'out_comment')
+ type_)
end_iter = buffer.get_end_iter()
- buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') + \
- '\n\n', kind)
+ 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)
@@ -2843,10 +3003,16 @@ class XMLConsoleWindow:
# 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')}
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):
+ message_body=None):
self.account = account
self.action = action
self.exchange_list = exchange_list
@@ -2866,12 +3032,13 @@ class RosterItemExchangeWindow:
# Set labels
# self.action can be 'add', 'modify' or 'remove'
- self.type_label.set_label(\
- _('<b>%s</b> would like you to <b>%s</b> some contacts in your '
- 'roster.') % (jid_from, _(self.action)))
+ self.type_label.set_label(
+ _('<b>%(jid)s</b> would like you to <b>%(action)s</b> 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)
+ buffer_ = self.body_textview.get_buffer()
+ buffer_.set_text(self.message_body)
else:
self.body_scrolledwindow.hide()
# Treeview
@@ -2924,8 +3091,8 @@ class RosterItemExchangeWindow:
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)
+ 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'))
@@ -2955,8 +3122,8 @@ class RosterItemExchangeWindow:
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)
+ 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'))
@@ -2979,8 +3146,8 @@ class RosterItemExchangeWindow:
groups = groups + group + ', '
if is_in_roster:
show_dialog = True
- iter = model.append()
- model.set(iter, 0, True, 1, jid, 2, name, 3, groups)
+ 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'))
@@ -2991,49 +3158,49 @@ class RosterItemExchangeWindow:
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()
+ 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()
+ iter_ = model.get_iter_root()
if self.action == 'add':
a = 0
- while iter:
- if model[iter][0]:
+ while iter_:
+ if model[iter_][0]:
a+=1
# it is selected
- #remote_jid = model[iter][1].decode('utf-8')
+ #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(', ')
+ groups = model[iter_][3].split(', ')
if groups == ['']:
groups = []
- jid = model[iter][1].decode('utf-8')
+ 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],
+ self.account, groups=groups, nickname=model[iter_][2],
auto_auth=True)
- iter = model.iter_next(iter)
- InformationDialog('Added %s contacts' % str(a))
+ iter_ = model.iter_next(iter_)
+ InformationDialog(_('Added %s contacts') % str(a))
elif self.action == 'modify':
a = 0
- while iter:
- if model[iter][0]:
+ while iter_:
+ if model[iter_][0]:
a+=1
# it is selected
- jid = model[iter][1].decode('utf-8')
+ jid = model[iter_][1].decode('utf-8')
# keep same groups and same nickname
- groups = model[iter][3].split(', ')
+ groups = model[iter_][3].split(', ')
if groups == ['']:
groups = []
for u in gajim.contacts.get_contact(self.account, jid):
- u.name = model[iter][2]
+ u.name = model[iter_][2]
gajim.connections[self.account].update_contact(jid,
- model[iter][2], groups)
+ model[iter_][2], groups)
self.draw_contact(jid, account)
# Update opened chat
ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account)
@@ -3043,19 +3210,19 @@ class RosterItemExchangeWindow:
self.account)
win.redraw_tab(ctrl)
win.show_title()
- iter = model.iter_next(iter)
+ iter_ = model.iter_next(iter_)
elif self.action == 'delete':
a = 0
- while iter:
- if model[iter][0]:
+ while iter_:
+ if model[iter_][0]:
a+=1
# it is selected
- jid = model[iter][1].decode('utf-8')
+ 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))
+ iter_ = model.iter_next(iter_)
+ InformationDialog(_('Removed %s contacts') % str(a))
self.window.destroy()
def on_cancel_button_clicked(self, widget):
@@ -3063,8 +3230,10 @@ class RosterItemExchangeWindow:
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'''
@@ -3076,6 +3245,8 @@ class PrivacyListWindow:
self.global_rules = {}
self.list_of_groups = {}
+ self.max_order = 0
+
# Default Edit Values
self.edit_rule_type = 'jid'
self.allow_deny = 'allow'
@@ -3171,6 +3342,8 @@ class PrivacyListWindow:
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:
@@ -3318,7 +3491,7 @@ class PrivacyListWindow:
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(1)
+ 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(
@@ -3361,6 +3534,8 @@ class PrivacyListWindow:
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)
@@ -3396,9 +3571,10 @@ class PrivacyListWindow:
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'''
+ """
+ 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 = []
@@ -3555,9 +3731,10 @@ class InvitationReceivedDialog:
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'''
+ """
+ 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')
@@ -3584,10 +3761,14 @@ class ProgressDialog:
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'''
+ 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'''
+ """
+ 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]
@@ -3623,8 +3804,10 @@ class SoundChooserDialog(FileChooserDialog):
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'''
+ 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()
@@ -3715,8 +3898,10 @@ class AvatarChooserDialog(ImageChooserDialog):
class AddSpecialNotificationDialog:
def __init__(self, jid):
- '''jid is the jid for which we want to add special notification
- (sound and notification popups)'''
+ """
+ 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')
@@ -3836,7 +4021,9 @@ class AdvancedNotificationsWindow:
self.window.show_all()
def initiate_rule_state(self):
- '''Set values for all widgets'''
+ """
+ Set values for all widgets
+ """
if self.active_num < 0:
return
# event
@@ -4227,8 +4414,10 @@ 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.'''
+ """
+ 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
@@ -4379,7 +4568,9 @@ class DataFormWindow(Dialog):
self.destroy()
class ESessionInfoWindow:
- '''Class for displaying information about a XEP-0116 encrypted session'''
+ """
+ Class for displaying information about a XEP-0116 encrypted session
+ """
def __init__(self, session):
self.session = session
@@ -4397,43 +4588,37 @@ class ESessionInfoWindow:
def update_info(self):
labeltext = _('''Your chat session with <b>%(jid)s</b> is encrypted.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'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'
+ security_image = 'gajim-security_high'
if self.session.control:
self.session.control._show_lock_image(True, 'E2E', True,
- self.session.is_loggable(), 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(
- '<b><span size="x-large">' +
- verification_status +
- '</span></b>')
+ '<b><span size="x-large">%s</span></b>' % 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)
+ self.session.is_loggable(), False)
labeltext += '\n\n' + _('''To be certain that <b>only</b> 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'
+ security_image = 'gajim-security_low'
verification_status = _('''Contact's identity NOT verified''')
self.window.set_title(verification_status)
self.xml.get_widget('verification_status_label').set_markup(
- '<b><span size="x-large">' +
- verification_status +
- '</span></b>')
+ '<b><span size="x-large">%s</span></b>' % verification_status)
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 = gtkgui_helpers.get_icon_path(security_image, 32)
+ self.security_image.set_from_file(path)
self.xml.get_widget('info_display').set_markup(labeltext)
@@ -4458,7 +4643,9 @@ class ESessionInfoWindow:
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'''
+ """
+ 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')
@@ -4477,13 +4664,13 @@ class GPGInfoWindow:
verification_status = _('''Contact's identity NOT verified''')
info = _('The contact\'s key (%s) <b>does not match</b> the key '
'assigned in Gajim.') % keyID[:8]
- image = 'security-low-big.png'
+ 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 = 'security-low-big.png'
+ image = 'gajim-security_low'
else:
error = gajim.connections[account].gpg.encrypt('test', [keyID])[1]
if error:
@@ -4491,21 +4678,19 @@ class GPGInfoWindow:
info = _('GPG key is assigned to this contact, but <b>you do not '
'trust his key</b>, so message <b>cannot</b> be encrypted. Use '
'your GPG client to trust this key.')
- image = 'security-low-big.png'
+ 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 = 'security-high-big.png'
+ image = 'gajim-security_high'
status_label.set_markup('<b><span size="x-large">%s</span></b>' % \
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)
+ path = gtkgui_helpers.get_icon_path(image, 32)
+ security_image.set_from_file(path)
xml.signal_autoconnect(self)
self.window.show_all()
@@ -4606,11 +4791,13 @@ class VoIPCallReceivedDialog(object):
contact = gajim.contacts.get_contact(self.account, jid)
if not contact:
return
- ctrl = gajim.interface.new_chat(contact, self.account)
+ ctrl = gajim.interface.new_chat(contact, self.account, resource)
# Chat control opened, update content's status
- if session.get_content('audio'):
+ audio = session.get_content('audio')
+ video = session.get_content('video')
+ if audio and not audio.negotiated:
ctrl.set_audio_state('connecting', self.sid)
- if session.get_content('video'):
+ 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
diff --git a/src/disco.py b/src/disco.py
index 0d3621f13..1526ecd16 100644
--- a/src/disco.py
+++ b/src/disco.py
@@ -73,44 +73,44 @@ def _gen_agent_type_info():
(0, 0): (None, None),
# Jabber server
- ('server', 'im'): (ToplevelAgentBrowser, 'jabber.png'),
- ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber.png'),
- ('hierarchy', 'branch'): (AgentBrowser, 'jabber.png'),
+ ('server', 'im'): (ToplevelAgentBrowser, 'jabber'),
+ ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber'),
+ ('hierarchy', 'branch'): (AgentBrowser, 'jabber'),
# 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'),
+ ('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.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'),
+ ('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
@@ -124,16 +124,21 @@ _cat_to_descr = {
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 = {}
class CacheItem:
- '''An object to store cache items and their timeouts.'''
+ """
+ An object to store cache items and their timeouts
+ """
def __init__(self, value):
self.value = value
self.source = None
@@ -149,13 +154,17 @@ class CacheDictionary:
del self.cache[key]
def _expire_timeout(self, key):
- '''The timeout has expired, remove the object.'''
+ """
+ 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.'''
+ """
+ The object was accessed, refresh the timeout
+ """
item = self.cache[key]
if item.source:
gobject.source_remove(item.source)
@@ -187,20 +196,25 @@ class CacheDictionary:
_icon_cache = CacheDictionary(15)
def get_agent_address(jid, node = None):
- '''Returns an agent's address for displaying in the GUI.'''
+ """
+ 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.
- 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
@@ -229,8 +243,11 @@ class Closure(object):
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)
@@ -256,7 +273,9 @@ class ServicesCache:
del self._cbs[cbkey]
def get_icon(self, identities = []):
- '''Return the icon for an agent.'''
+ """
+ Return the icon for an agent
+ """
# Grab the first identity with an icon
for identity in identities:
try:
@@ -272,19 +291,20 @@ class ServicesCache:
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'
+ filename = 'jabber'
# 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)
+ pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-' + filename, size=32)
# Store in cache
_icon_cache[filename] = pix
return pix
def get_browser(self, identities=[], features=[]):
- '''Return the browser class for an agent.'''
+ """
+ Return the browser class for an agent
+ """
# First pass, we try to find a ToplevelAgentBrowser
for identity in identities:
try:
@@ -316,7 +336,9 @@ class ServicesCache:
return None
def get_info(self, jid, node, cb, force = False, nofetch = False, args = ()):
- '''Get info for an agent.'''
+ """
+ Get info for an agent
+ """
addr = get_agent_address(jid, node)
# Check the cache
if addr in self._info:
@@ -338,7 +360,9 @@ class ServicesCache:
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.'''
+ """
+ Get a list of items in an agent
+ """
addr = get_agent_address(jid, node)
# Check the cache
if addr in self._items:
@@ -360,7 +384,9 @@ class ServicesCache:
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.'''
+ """
+ Callback for when we receive an agent's info
+ """
addr = get_agent_address(jid, node)
# Store in cache
@@ -376,7 +402,9 @@ class ServicesCache:
del self._cbs[cbkey]
def agent_items(self, jid, node, items):
- '''Callback for when we receive an agent's items.'''
+ """
+ Callback for when we receive an agent's items
+ """
addr = get_agent_address(jid, node)
# Store in cache
@@ -392,8 +420,10 @@ class ServicesCache:
del self._cbs[cbkey]
def agent_info_error(self, jid):
- '''Callback for when a query fails. (even after the browse and agents
- namespaces)'''
+ """
+ Callback for when a query fails. Even after the browse and agents
+ namespaces
+ """
addr = get_agent_address(jid)
# Call callbacks
@@ -406,8 +436,10 @@ class ServicesCache:
del self._cbs[cbkey]
def agent_items_error(self, jid):
- '''Callback for when a query fails. (even after the browse and agents
- namespaces)'''
+ """
+ Callback for when a query fails. Even after the browse and agents
+ namespaces
+ """
addr = get_agent_address(jid)
# Call callbacks
@@ -421,7 +453,10 @@ class ServicesCache:
# 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
@@ -510,8 +545,10 @@ _('Without a connection, you can not browse available services'))
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.'''
+ """
+ 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)
@@ -550,7 +587,9 @@ _('Without a connection, you can not browse available services'))
self.banner.set_markup(markup)
def paint_banner(self):
- '''Repaint the banner with theme color'''
+ """
+ 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')
@@ -584,10 +623,11 @@ _('Without a connection, you can not browse available services'))
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
+ """
+ Set style of widget from style class *.Frame.Eventbox
opts[0] == True -> set fg color
- opts[1] == True -> set bg color '''
-
+ opts[1] == True -> set bg color
+ """
self.disconnect_style_event()
if opts[1]:
bg_color = widget.style.bg[gtk.STATE_SELECTED]
@@ -599,9 +639,11 @@ _('Without a connection, you can not browse available services'))
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.'''
+ """
+ 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
@@ -632,7 +674,9 @@ _('Without a connection, you can not browse available services'))
self.cache.cleanup()
def travel(self, jid, node):
- '''Travel to an agent within the current services window.'''
+ """
+ Travel to an agent within the current services window
+ """
if self.browser:
self.browser.cleanup()
self.browser = None
@@ -649,7 +693,9 @@ _('Without a connection, you can not browse available services'))
self.cache.get_info(jid, node, self._travel)
def _travel(self, jid, node, identities, features, data):
- '''Continuation of travel.'''
+ """
+ Continuation of travel
+ """
if self.dying or jid != self.jid or node != self.node:
return
if not identities:
@@ -671,7 +717,9 @@ _('This type of service does not contain any items to browse.'))
self.browser.browse()
def open(self, jid, node):
- '''Open an agent. By default, this happens in a new window.'''
+ """
+ Open an agent. By default, this happens in a new window
+ """
try:
win = gajim.interface.instances[self.account]['disco']\
[get_agent_address(jid, node)]
@@ -737,10 +785,13 @@ _('This type of service does not contain any items to browse.'))
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
@@ -751,20 +802,26 @@ class AgentBrowser:
self.active = False
def _get_agent_address(self):
- '''Returns the agent's address for displaying in the GUI.'''
+ """
+ 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.'''
+ """
+ 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.'''
+ """
+ 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)
@@ -792,8 +849,10 @@ class AgentBrowser:
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.'''
+ """
+ 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'))
@@ -807,13 +866,17 @@ class AgentBrowser:
self.browse_button.show_all()
def _clean_actions(self):
- '''Remove the action buttons specific to this browser.'''
+ """
+ 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 window title based on agent info
+ """
# Set the banner and window title
if 'name' in identities[0]:
name = identities[0]['name']
@@ -830,8 +893,10 @@ class AgentBrowser:
pass
def prepare_window(self, window):
- '''Prepare the service discovery window. Called when a browser is hooked
- up with a ServiceDiscoveryWindow instance.'''
+ """
+ Prepare the service discovery window. Called when a browser is hooked up
+ with a ServiceDiscoveryWindow instance
+ """
self.window = window
self.cache = window.cache
@@ -852,7 +917,9 @@ class AgentBrowser:
self.cache.get_info(self.jid, self.node, self._set_title)
def cleanup(self):
- '''Cleanup when the window intends to switch browsers.'''
+ """
+ Cleanup when the window intends to switch browsers
+ """
self.active = False
self._clean_actions()
@@ -862,12 +929,16 @@ class AgentBrowser:
self.window._initial_state()
def update_theme(self):
- '''Called when the default theme is changed.'''
+ """
+ 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.'''
+ """
+ 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
@@ -877,8 +948,9 @@ class AgentBrowser:
self.window.open(jid, node)
def update_actions(self):
- '''When we select a row:
- activate action buttons based on the agent's info.'''
+ """
+ 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()
@@ -890,7 +962,9 @@ class AgentBrowser:
self.cache.get_info(jid, node, self._update_actions, nofetch = True)
def _update_actions(self, jid, node, identities, features, data):
- '''Continuation of update_actions.'''
+ """
+ Continuation of update_actions
+ """
if not identities or not self.browse_button:
return
klass = self.cache.get_browser(identities, features)
@@ -898,8 +972,10 @@ class AgentBrowser:
self.browse_button.set_sensitive(True)
def default_action(self):
- '''When we double-click a row:
- perform the default action on the selected item.'''
+ """
+ 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
@@ -909,7 +985,9 @@ class AgentBrowser:
self.cache.get_info(jid, node, self._default_action, nofetch = True)
def _default_action(self, jid, node, identities, features, data):
- '''Continuation of default_action.'''
+ """
+ Continuation of default_action
+ """
if self.cache.get_browser(identities, features):
# Browse if we can
self.on_browse_button_clicked()
@@ -917,7 +995,9 @@ class AgentBrowser:
return False
def browse(self, force = False):
- '''Fill the treeview with agents, fetching the info if necessary.'''
+ """
+ Fill the treeview with agents, fetching the info if necessary
+ """
self.model.clear()
self._total_items = self._progress = 0
self.window.progressbar.show()
@@ -926,15 +1006,19 @@ class AgentBrowser:
force = force, args = (force,))
def _pulse_timeout_cb(self, *args):
- '''Simple callback to keep the progressbar pulsing.'''
+ """
+ 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.'''
+ """
+ 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')
@@ -947,7 +1031,9 @@ class AgentBrowser:
return None
def _agent_items(self, jid, node, items, force):
- '''Callback for when we receive a list of agent items.'''
+ """
+ Callback for when we receive a list of agent items
+ """
self.model.clear()
self._total_items = 0
gobject.source_remove(self._pulse_timeout)
@@ -973,7 +1059,9 @@ _('This service does not contain any items to browse.'))
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.'''
+ """
+ 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
@@ -987,21 +1075,27 @@ _('This service does not contain any items to browse.'))
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.'''
+ """
+ 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. (seldom)'''
+ """
+ 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.'''
+ """
+ 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
@@ -1012,8 +1106,11 @@ _('This service does not contain any items to browse.'))
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
@@ -1029,7 +1126,9 @@ class ToplevelAgentBrowser(AgentBrowser):
self._scroll_signal = None
def _pixbuf_renderer_data_func(self, col, cell, model, iter_):
- '''Callback for setting the pixbuf renderer's properties.'''
+ """
+ Callback for setting the pixbuf renderer's properties
+ """
jid = model.get_value(iter_, 0)
if jid:
pix = model.get_value(iter_, 2)
@@ -1039,7 +1138,9 @@ class ToplevelAgentBrowser(AgentBrowser):
cell.set_property('visible', False)
def _text_renderer_data_func(self, col, cell, model, iter_):
- '''Callback for setting the text renderer's properties.'''
+ """
+ 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)
@@ -1060,7 +1161,9 @@ class ToplevelAgentBrowser(AgentBrowser):
cell.set_property('foreground_set', False)
def _treemodel_sort_func(self, model, iter1, iter2):
- '''Sort function for our treemodel.'''
+ """
+ Sort function for our treemode
+ """
# Compare state
statecmp = cmp(model.get_value(iter1, 4), model.get_value(iter2, 4))
if statecmp == 0:
@@ -1120,8 +1223,9 @@ class ToplevelAgentBrowser(AgentBrowser):
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 '''
+ """
+ This happens on scroll_event, key_press_event and button_press_event
+ """
self.tooltip.hide_tooltip()
def _create_treemodel(self):
@@ -1236,8 +1340,9 @@ class ToplevelAgentBrowser(AgentBrowser):
AgentBrowser._clean_actions(self)
def on_search_button_clicked(self, widget = None):
- '''When we want to search something:
- open search window'''
+ """
+ When we want to search something: open search window
+ """
model, iter_ = self.window.services_treeview.get_selection().get_selected()
if not iter_:
return
@@ -1261,8 +1366,9 @@ class ToplevelAgentBrowser(AgentBrowser):
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'''
+ """
+ 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
@@ -1271,9 +1377,10 @@ class ToplevelAgentBrowser(AgentBrowser):
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.'''
+ """
+ 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
@@ -1283,8 +1390,10 @@ class ToplevelAgentBrowser(AgentBrowser):
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.'''
+ """
+ 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
@@ -1375,7 +1484,9 @@ class ToplevelAgentBrowser(AgentBrowser):
AgentBrowser.browse(self, force = force)
def _expand_all(self):
- '''Expand all items in the treeview'''
+ """
+ Expand all items in the treeview
+ """
# GTK apparently screws up here occasionally. :/
#def expand_all(*args):
# self.window.services_treeview.expand_all()
@@ -1386,7 +1497,9 @@ class ToplevelAgentBrowser(AgentBrowser):
self.window.services_treeview.expand_all()
def _update_progressbar(self):
- '''Update the progressbar.'''
+ """
+ Update the progressbar
+ """
# Refresh this every update
if self._progressbar_sourceid:
gobject.source_remove(self._progressbar_sourceid)
@@ -1408,13 +1521,17 @@ class ToplevelAgentBrowser(AgentBrowser):
self.window.progressbar.set_fraction(fraction)
def _hide_progressbar_cb(self, *args):
- '''Simple callback to hide the progressbar a second after we finish.'''
+ """
+ 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.'''
+ """
+ Get the friendly category name and priority
+ """
cat = None
if type_:
# Try type-specific override
@@ -1430,12 +1547,16 @@ class ToplevelAgentBrowser(AgentBrowser):
return cat, prio
def _create_category(self, cat, type_=None):
- '''Creates a category row.'''
+ """
+ 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.'''
+ """
+ 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_:
@@ -1670,8 +1791,10 @@ class MucBrowser(AgentBrowser):
_('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'''
+ """
+ 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
@@ -1697,18 +1820,24 @@ class MucBrowser(AgentBrowser):
self.on_join_button_clicked()
def _start_info_query(self):
- '''Idle callback to start checking for visible rows.'''
+ """
+ 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.'''
+ """
+ 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.'''
+ """
+ Query the next visible row for info
+ """
if self._fetch_source:
# We're already fetching
return
@@ -1751,9 +1880,10 @@ class MucBrowser(AgentBrowser):
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.'''
+ """
+ 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
@@ -1816,15 +1946,20 @@ class MucBrowser(AgentBrowser):
self.cache.get_items(jid, node, self._channel_altinfo)
def PubSubBrowser(account, jid, node):
- ''' Returns an AgentBrowser subclass that will display service discovery
- for particular pubsub service. Different pubsub services may need to
- present different data during browsing. '''
+ """
+ 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)
@@ -1837,10 +1972,12 @@ class DiscussionGroupsBrowser(AgentBrowser):
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._on_pep_subscriptions)
def _create_treemodel(self):
- ''' Create treemodel for the window. '''
+ """
+ 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
@@ -1891,8 +2028,10 @@ class DiscussionGroupsBrowser(AgentBrowser):
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. '''
+ """
+ Called when we got basic information about new node from query. Show the
+ item
+ """
name = item.get('name', '')
if self.subscriptions is not None:
@@ -1962,8 +2101,10 @@ class DiscussionGroupsBrowser(AgentBrowser):
self.unsubscribe_button = None
def update_actions(self):
- '''Called when user selected a row. Make subscribe/unsubscribe buttons
- sensitive appropriatelly.'''
+ """
+ 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
@@ -1980,7 +2121,9 @@ class DiscussionGroupsBrowser(AgentBrowser):
self.unsubscribe_button.set_sensitive(subscribed)
def on_post_button_clicked(self, widget):
- '''Called when 'post' button is pressed. Open window to create post'''
+ """
+ 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
@@ -1989,26 +2132,32 @@ class DiscussionGroupsBrowser(AgentBrowser):
groups.GroupsPostWindow(self.account, self.jid, groupnode)
def on_subscribe_button_clicked(self, widget):
- '''Called when 'subscribe' button is pressed. Send subscribtion request.'''
+ """
+ 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
- gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._subscribeCB, 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.'''
+ """
+ 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
- gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._unsubscribeCB, groupnode)
+ gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._on_pep_unsubscribe, 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. '''
+ 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:
@@ -2027,30 +2176,34 @@ class DiscussionGroupsBrowser(AgentBrowser):
# 3 = insensitive checkbox for subscribed
# 4 = subscribed?
groupnode = row[1]
- row[3]=False
- row[4]=groupnode in groups
+ row[3] = False
+ row[4] = groupnode in groups
# we now know subscriptions, update button states
self.update_actions()
raise xmpp.NodeProcessed
- def _subscribeCB(self, conn, request, groupnode):
- '''We have just subscribed to a node. Update UI'''
+ 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
+ row[4] = True
break
self.update_actions()
raise xmpp.NodeProcessed
- def _unsubscribeCB(self, conn, request, groupnode):
- '''We have just unsubscribed from a node. Update UI'''
+ 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()
diff --git a/src/eggtrayicon.c b/src/eggtrayicon.c
deleted file mode 100644
index 56b3a0fb9..000000000
--- a/src/eggtrayicon.c
+++ /dev/null
@@ -1,584 +0,0 @@
-/* eggtrayicon.c
- * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-#include <string.h>
-#include <glib/gi18n.h>
-#include <libintl.h>
-
-#include "eggtrayicon.h"
-
-#include <gdkconfig.h>
-#if defined (GDK_WINDOWING_X11)
-#include <gdk/gdkx.h>
-#include <X11/Xatom.h>
-#elif defined (GDK_WINDOWING_WIN32)
-#include <gdk/gdkwin32.h>
-#endif
-
-#ifndef EGG_COMPILATION
-#ifndef _
-#define _(x) dgettext (GETTEXT_PACKAGE, x)
-#define N_(x) x
-#endif
-#else
-#define _(x) x
-#define N_(x) x
-#endif
-
-#define SYSTEM_TRAY_REQUEST_DOCK 0
-#define SYSTEM_TRAY_BEGIN_MESSAGE 1
-#define SYSTEM_TRAY_CANCEL_MESSAGE 2
-
-#define SYSTEM_TRAY_ORIENTATION_HORZ 0
-#define SYSTEM_TRAY_ORIENTATION_VERT 1
-
-enum {
- PROP_0,
- PROP_ORIENTATION
-};
-
-static GtkPlugClass *parent_class = NULL;
-
-static void egg_tray_icon_init (EggTrayIcon *icon);
-static void egg_tray_icon_class_init (EggTrayIconClass *klass);
-
-static void egg_tray_icon_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec);
-
-static void egg_tray_icon_add (GtkContainer *container, GtkWidget *widget);
-
-static void egg_tray_icon_realize (GtkWidget *widget);
-static void egg_tray_icon_unrealize (GtkWidget *widget);
-
-#ifdef GDK_WINDOWING_X11
-static void egg_tray_icon_update_manager_window (EggTrayIcon *icon,
- gboolean dock_if_realized);
-static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
-#endif
-
-GType
-egg_tray_icon_get_type (void)
-{
- static GType our_type = 0;
-
- if (our_type == 0)
- {
- static const GTypeInfo our_info =
- {
- sizeof (EggTrayIconClass),
- (GBaseInitFunc) NULL,
- (GBaseFinalizeFunc) NULL,
- (GClassInitFunc) egg_tray_icon_class_init,
- NULL, /* class_finalize */
- NULL, /* class_data */
- sizeof (EggTrayIcon),
- 0, /* n_preallocs */
- (GInstanceInitFunc) egg_tray_icon_init
- };
-
- our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
- }
-
- return our_type;
-}
-
-static void
-egg_tray_icon_init (EggTrayIcon *icon)
-{
- icon->stamp = 1;
- icon->orientation = GTK_ORIENTATION_HORIZONTAL;
-
- gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
-}
-
-static void
-egg_tray_icon_class_init (EggTrayIconClass *klass)
-{
- GObjectClass *gobject_class = (GObjectClass *)klass;
- GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
- GtkContainerClass *container_class = (GtkContainerClass *)klass;
-
- parent_class = g_type_class_peek_parent (klass);
-
- gobject_class->get_property = egg_tray_icon_get_property;
-
- widget_class->realize = egg_tray_icon_realize;
- widget_class->unrealize = egg_tray_icon_unrealize;
-
- container_class->add = egg_tray_icon_add;
-
- g_object_class_install_property (gobject_class,
- PROP_ORIENTATION,
- g_param_spec_enum ("orientation",
- _("Orientation"),
- _("The orientation of the tray."),
- GTK_TYPE_ORIENTATION,
- GTK_ORIENTATION_HORIZONTAL,
- G_PARAM_READABLE));
-
-#if defined (GDK_WINDOWING_X11)
- /* Nothing */
-#elif defined (GDK_WINDOWING_WIN32)
- g_warning ("Port eggtrayicon to Win32");
-#else
- g_warning ("Port eggtrayicon to this GTK+ backend");
-#endif
-}
-
-static void
-egg_tray_icon_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- EggTrayIcon *icon = EGG_TRAY_ICON (object);
-
- switch (prop_id)
- {
- case PROP_ORIENTATION:
- g_value_set_enum (value, icon->orientation);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-#ifdef GDK_WINDOWING_X11
-
-static Display *
-egg_tray_icon_get_x_display(EggTrayIcon *icon)
-{
- Display *xdisplay = NULL;
-
- GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (icon));
- if (!GDK_IS_DISPLAY (display))
- display = gdk_display_get_default ();
-
- xdisplay = GDK_DISPLAY_XDISPLAY (display);
-
- return xdisplay;
-}
-
-static void
-egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
-{
- Display *xdisplay;
- Atom type;
- int format;
- union {
- gulong *prop;
- guchar *prop_ch;
- } prop = { NULL };
- gulong nitems;
- gulong bytes_after;
- int error, result;
-
- g_assert (icon->manager_window != None);
-
- xdisplay = egg_tray_icon_get_x_display(icon);
- if (xdisplay == NULL)
- return;
-
- gdk_error_trap_push ();
- type = None;
- result = XGetWindowProperty (xdisplay,
- icon->manager_window,
- icon->orientation_atom,
- 0, G_MAXLONG, FALSE,
- XA_CARDINAL,
- &type, &format, &nitems,
- &bytes_after, &(prop.prop_ch));
- error = gdk_error_trap_pop ();
-
- if (error || result != Success)
- return;
-
- if (type == XA_CARDINAL)
- {
- GtkOrientation orientation;
-
- orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
- GTK_ORIENTATION_HORIZONTAL :
- GTK_ORIENTATION_VERTICAL;
-
- if (icon->orientation != orientation)
- {
- icon->orientation = orientation;
-
- g_object_notify (G_OBJECT (icon), "orientation");
- }
- }
-
- if (prop.prop)
- XFree (prop.prop);
-}
-
-static GdkFilterReturn
-egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
-{
- EggTrayIcon *icon = user_data;
- XEvent *xev = (XEvent *)xevent;
-
- if (xev->xany.type == ClientMessage &&
- xev->xclient.message_type == icon->manager_atom &&
- xev->xclient.data.l[1] == icon->selection_atom)
- {
- egg_tray_icon_update_manager_window (icon, TRUE);
- }
- else if (xev->xany.window == icon->manager_window)
- {
- if (xev->xany.type == PropertyNotify &&
- xev->xproperty.atom == icon->orientation_atom)
- {
- egg_tray_icon_get_orientation_property (icon);
- }
- if (xev->xany.type == DestroyNotify)
- {
- egg_tray_icon_manager_window_destroyed (icon);
- }
- }
- return GDK_FILTER_CONTINUE;
-}
-
-#endif
-
-static void
-egg_tray_icon_unrealize (GtkWidget *widget)
-{
-#ifdef GDK_WINDOWING_X11
- EggTrayIcon *icon = EGG_TRAY_ICON (widget);
- GdkWindow *root_window;
-
- if (icon->manager_window != None)
- {
- GdkWindow *gdkwin;
-
- gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
- icon->manager_window);
-
- gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
- }
-
- root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
-
- gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
-
- if (GTK_WIDGET_CLASS (parent_class)->unrealize)
- (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
-#endif
-}
-
-#ifdef GDK_WINDOWING_X11
-
-static void
-egg_tray_icon_send_manager_message (EggTrayIcon *icon,
- long message,
- Window window,
- long data1,
- long data2,
- long data3)
-{
- XClientMessageEvent ev;
- Display *display;
-
- ev.type = ClientMessage;
- ev.window = window;
- ev.message_type = icon->system_tray_opcode_atom;
- ev.format = 32;
- ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
- ev.data.l[1] = message;
- ev.data.l[2] = data1;
- ev.data.l[3] = data2;
- ev.data.l[4] = data3;
-
- display = egg_tray_icon_get_x_display(icon);
-
- if (display == NULL)
- return;
-
- gdk_error_trap_push ();
- XSendEvent (display,
- icon->manager_window, False, NoEventMask, (XEvent *)&ev);
- XSync (display, False);
- gdk_error_trap_pop ();
-}
-
-static void
-egg_tray_icon_send_dock_request (EggTrayIcon *icon)
-{
- egg_tray_icon_send_manager_message (icon,
- SYSTEM_TRAY_REQUEST_DOCK,
- icon->manager_window,
- gtk_plug_get_id (GTK_PLUG (icon)),
- 0, 0);
-}
-
-static void
-egg_tray_icon_update_manager_window (EggTrayIcon *icon,
- gboolean dock_if_realized)
-{
- Display *xdisplay;
-
- if (icon->manager_window != None)
- return;
-
- xdisplay = egg_tray_icon_get_x_display(icon);
-
- if (xdisplay == NULL)
- return;
-
- XGrabServer (xdisplay);
-
- icon->manager_window = XGetSelectionOwner (xdisplay,
- icon->selection_atom);
-
- if (icon->manager_window != None)
- XSelectInput (xdisplay,
- icon->manager_window, StructureNotifyMask|PropertyChangeMask);
-
- XUngrabServer (xdisplay);
- XFlush (xdisplay);
-
- if (icon->manager_window != None)
- {
- GdkWindow *gdkwin;
-
- gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
- icon->manager_window);
-
- gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
-
- if (dock_if_realized && GTK_WIDGET_REALIZED (icon))
- egg_tray_icon_send_dock_request (icon);
-
- egg_tray_icon_get_orientation_property (icon);
- }
-}
-
-static gboolean
-transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
-{
- gdk_window_clear_area (widget->window, event->area.x, event->area.y,
- event->area.width, event->area.height);
- return FALSE;
-}
-
-static void
-make_transparent_again (GtkWidget *widget, GtkStyle *previous_style,
- gpointer user_data)
-{
- gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
-}
-
-static void
-make_transparent (GtkWidget *widget, gpointer user_data)
-{
- if (GTK_WIDGET_NO_WINDOW (widget) || GTK_WIDGET_APP_PAINTABLE (widget))
- return;
-
- gtk_widget_set_app_paintable (widget, TRUE);
- gtk_widget_set_double_buffered (widget, FALSE);
- gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
- g_signal_connect (widget, "expose_event",
- G_CALLBACK (transparent_expose_event), NULL);
- g_signal_connect_after (widget, "style_set",
- G_CALLBACK (make_transparent_again), NULL);
-}
-
-static void
-egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
-{
- GdkWindow *gdkwin;
-
- g_return_if_fail (icon->manager_window != None);
-
- gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
- icon->manager_window);
-
- gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
-
- icon->manager_window = None;
-
- egg_tray_icon_update_manager_window (icon, TRUE);
-}
-
-#endif
-
-static void
-egg_tray_icon_realize (GtkWidget *widget)
-{
-#ifdef GDK_WINDOWING_X11
- EggTrayIcon *icon = EGG_TRAY_ICON (widget);
- GdkScreen *screen;
- Display *xdisplay;
- char buffer[256];
- GdkWindow *root_window;
-
- if (GTK_WIDGET_CLASS (parent_class)->realize)
- GTK_WIDGET_CLASS (parent_class)->realize (widget);
-
- make_transparent (widget, NULL);
-
- xdisplay = egg_tray_icon_get_x_display(icon);
-
- if (xdisplay == NULL)
- return;
-
- screen = gtk_widget_get_screen (widget);
-
- /* Now see if there's a manager window around */
- g_snprintf (buffer, sizeof (buffer),
- "_NET_SYSTEM_TRAY_S%d",
- gdk_screen_get_number (screen));
-
- icon->selection_atom = XInternAtom (xdisplay, buffer, False);
-
- icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
-
- icon->system_tray_opcode_atom = XInternAtom (xdisplay,
- "_NET_SYSTEM_TRAY_OPCODE",
- False);
-
- icon->orientation_atom = XInternAtom (xdisplay,
- "_NET_SYSTEM_TRAY_ORIENTATION",
- False);
-
- egg_tray_icon_update_manager_window (icon, FALSE);
- egg_tray_icon_send_dock_request (icon);
-
- root_window = gdk_screen_get_root_window (screen);
-
- /* Add a root window filter so that we get changes on MANAGER */
- gdk_window_add_filter (root_window,
- egg_tray_icon_manager_filter, icon);
-#endif
-}
-
-static void
-egg_tray_icon_add (GtkContainer *container, GtkWidget *widget)
-{
- g_signal_connect (widget, "realize",
- G_CALLBACK (make_transparent), NULL);
- GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
-}
-
-EggTrayIcon *
-egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
-{
- g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
-
- return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
-}
-
-EggTrayIcon*
-egg_tray_icon_new (const gchar *name)
-{
- return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
-}
-
-guint
-egg_tray_icon_send_message (EggTrayIcon *icon,
- gint timeout,
- const gchar *message,
- gint len)
-{
- guint stamp;
-
- g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
- g_return_val_if_fail (timeout >= 0, 0);
- g_return_val_if_fail (message != NULL, 0);
-
-#ifdef GDK_WINDOWING_X11
- if (icon->manager_window == None)
- return 0;
-#endif
-
- if (len < 0)
- len = strlen (message);
-
- stamp = icon->stamp++;
-
-#ifdef GDK_WINDOWING_X11
- /* Get ready to send the message */
- egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
- (Window)gtk_plug_get_id (GTK_PLUG (icon)),
- timeout, len, stamp);
-
- /* Now to send the actual message */
- gdk_error_trap_push ();
- while (len > 0)
- {
- XClientMessageEvent ev;
- Display *xdisplay;
-
- xdisplay = egg_tray_icon_get_x_display(icon);
-
- if (xdisplay == NULL)
- return 0;
-
- ev.type = ClientMessage;
- ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
- ev.format = 8;
- ev.message_type = XInternAtom (xdisplay,
- "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
- if (len > 20)
- {
- memcpy (&ev.data, message, 20);
- len -= 20;
- message += 20;
- }
- else
- {
- memcpy (&ev.data, message, len);
- len = 0;
- }
-
- XSendEvent (xdisplay,
- icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
- XSync (xdisplay, False);
- }
- gdk_error_trap_pop ();
-#endif
-
- return stamp;
-}
-
-void
-egg_tray_icon_cancel_message (EggTrayIcon *icon,
- guint id)
-{
- g_return_if_fail (EGG_IS_TRAY_ICON (icon));
- g_return_if_fail (id > 0);
-#ifdef GDK_WINDOWING_X11
- egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
- (Window)gtk_plug_get_id (GTK_PLUG (icon)),
- id, 0, 0);
-#endif
-}
-
-GtkOrientation
-egg_tray_icon_get_orientation (EggTrayIcon *icon)
-{
- g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
-
- return icon->orientation;
-}
diff --git a/src/eggtrayicon.h b/src/eggtrayicon.h
deleted file mode 100644
index 557fdb20c..000000000
--- a/src/eggtrayicon.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/* eggtrayicon.h
- * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-#ifndef __EGG_TRAY_ICON_H__
-#define __EGG_TRAY_ICON_H__
-
-#include <gtk/gtkplug.h>
-#ifdef GDK_WINDOWING_X11
-#include <gdk/gdkx.h>
-#endif
-
-G_BEGIN_DECLS
-
-#define EGG_TYPE_TRAY_ICON (egg_tray_icon_get_type ())
-#define EGG_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_ICON, EggTrayIcon))
-#define EGG_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
-#define EGG_IS_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_ICON))
-#define EGG_IS_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_ICON))
-#define EGG_TRAY_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
-
-typedef struct _EggTrayIcon EggTrayIcon;
-typedef struct _EggTrayIconClass EggTrayIconClass;
-
-struct _EggTrayIcon
-{
- GtkPlug parent_instance;
-
- guint stamp;
-
-#ifdef GDK_WINDOWING_X11
- Atom selection_atom;
- Atom manager_atom;
- Atom system_tray_opcode_atom;
- Atom orientation_atom;
- Window manager_window;
-#endif
- GtkOrientation orientation;
-};
-
-struct _EggTrayIconClass
-{
- GtkPlugClass parent_class;
-};
-
-GType egg_tray_icon_get_type (void);
-
-EggTrayIcon *egg_tray_icon_new_for_screen (GdkScreen *screen,
- const gchar *name);
-
-EggTrayIcon *egg_tray_icon_new (const gchar *name);
-
-guint egg_tray_icon_send_message (EggTrayIcon *icon,
- gint timeout,
- const char *message,
- gint len);
-void egg_tray_icon_cancel_message (EggTrayIcon *icon,
- guint id);
-
-GtkOrientation egg_tray_icon_get_orientation (EggTrayIcon *icon);
-
-G_END_DECLS
-
-#endif /* __EGG_TRAY_ICON_H__ */
diff --git a/src/features_window.py b/src/features_window.py
index 609959e65..336bbd467 100644
--- a/src/features_window.py
+++ b/src/features_window.py
@@ -33,7 +33,9 @@ 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')
@@ -83,10 +85,6 @@ class FeaturesWindow:
_('Passive popups notifying for new events.'),
_('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'),
_('Feature not available under Windows.')),
- _('Trayicon'): (self.trayicon_available,
- _('A icon in systemtray reflecting the current presence.'),
- _('Requires python-gnome2-extras or compiled trayicon module from Gajim sources.'),
- _('Requires PyGTK >= 2.10.')),
_('Automatic status'): (self.idle_available,
_('Ability to measure idle time, in order to set auto status.'),
_('Requires libxss library.'),
@@ -103,10 +101,6 @@ class FeaturesWindow:
_('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.'),
@@ -240,21 +234,13 @@ class FeaturesWindow:
return False
return True
- def trayicon_available(self):
- if os.name == 'nt':
- return True
- try:
- import systray
- except Exception:
- return False
- return True
-
def idle_available(self):
from common import sleepy
return sleepy.SUPPORTED
def latex_available(self):
- return gajim.HAVE_LATEX
+ from common import latex
+ return latex.check_for_latex_support()
def pycrypto_available(self):
return gajim.HAVE_PYCRYPTO
@@ -266,9 +252,6 @@ class FeaturesWindow:
return False
return True
- def pysexy_available(self):
- return gajim.HAVE_PYSEXY
-
def farsight_available(self):
return gajim.HAVE_FARSIGHT
diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py
index cae4549e6..55cb90281 100644
--- a/src/filetransfers_window.py
+++ b/src/filetransfers_window.py
@@ -33,6 +33,8 @@ import dialogs
from common import gajim
from common import helpers
+from common.protocol.bytestream import (is_transfer_active, is_transfer_paused,
+ is_transfer_stopped)
C_IMAGE = 0
C_LABELS = 1
@@ -55,18 +57,17 @@ class FileTransfersWindow:
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)
+
+ 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)
+ 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)
@@ -104,7 +105,7 @@ class FileTransfersWindow:
renderer = gtk.CellRendererProgress()
renderer.set_property('yalign', 0.5)
renderer.set_property('xalign', 0.5)
- col.pack_start(renderer, expand = False)
+ col.pack_start(renderer, expand=False)
col.add_attribute(renderer, 'text' , C_PROGRESS)
col.add_attribute(renderer, 'value' , C_PERCENT)
col.set_resizable(True)
@@ -134,15 +135,17 @@ class FileTransfersWindow:
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']
+ """
+ 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):
+ if not is_transfer_stopped(file_props):
active_transfers[0].append(file_props)
# 'account' is the recipient
@@ -150,12 +153,14 @@ class FileTransfersWindow:
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):
+ 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'''
+ """
+ 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:
@@ -181,8 +186,8 @@ class FileTransfersWindow:
else:
#You is a reply of who sent a file
sender = _('You')
- sectext += '\n\t' +_('Sender: %s') % sender
- sectext += '\n\t' +_('Recipient: ')
+ 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(
@@ -193,7 +198,7 @@ class FileTransfersWindow:
recipient = _('You')
sectext += recipient
if file_props['type'] == 'r':
- sectext += '\n\t' +_('Saved in: %s') % file_path
+ 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':
@@ -207,19 +212,21 @@ class FileTransfersWindow:
dialog.show_all()
def show_request_error(self, file_props):
- ''' show error dialog to the recipient saying that transfer
- has been canceled'''
+ """
+ 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'''
+ """
+ Show error dialog to the sender saying that transfer has been canceled
+ """
dialogs.InformationDialog(_('File transfer cancelled'),
-_('Connection with peer cannot be established.'))
+ _('Connection with peer cannot be established.'))
self.tree.get_selection().unselect_all()
- def show_stopped(self, jid, file_props, error_msg = ''):
+ def show_stopped(self, jid, file_props, error_msg=''):
if file_props['type'] == 'r':
file_name = os.path.basename(file_props['file-name'])
else:
@@ -232,7 +239,6 @@ _('Connection with peer cannot be established.'))
self.tree.get_selection().unselect_all()
def show_file_send_request(self, account, contact):
-
desc_entry = gtk.Entry()
def on_ok(widget):
@@ -253,8 +259,8 @@ _('Connection with peer cannot be established.'))
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()
+ on_response_ok=on_ok,
+ on_response_cancel=lambda e:dialog.destroy()
)
btn = gtk.Button(_('_Send'))
@@ -264,8 +270,8 @@ _('Connection with peer cannot be established.'))
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)
+ 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)
@@ -273,7 +279,9 @@ _('Connection with peer cannot be established.'))
desc_hbox.show_all()
def send_file(self, account, contact, file_path, file_desc=''):
- ''' start the real transfer(upload) of the file '''
+ """
+ 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.')
@@ -303,8 +311,10 @@ _('Connection with peer cannot be established.'))
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'''
+ """
+ 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(
@@ -366,14 +376,14 @@ _('Connection with peer cannot be established.'))
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,
+ 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))
+ 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:
@@ -383,8 +393,8 @@ _('Connection with peer cannot be established.'))
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))
+ 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()
@@ -394,7 +404,9 @@ _('Connection with peer cannot be established.'))
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' '''
+ """
+ Change the status of a transfer to state 'status'
+ """
iter_ = self.get_iter_by_sid(typ, sid)
if iter_ is None:
return
@@ -409,8 +421,10 @@ _('Connection with peer cannot be established.'))
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'''
+ """
+ Add extra spaces from both sides of the percent, so that progress string
+ has always a fixed size
+ """
_str = ' '
if percent != 100.:
_str += ' '
@@ -432,7 +446,7 @@ _('Connection with peer cannot be established.'))
#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
+ 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:
@@ -480,8 +494,10 @@ _('Connection with peer cannot be established.'))
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'''
+ 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]
@@ -546,8 +562,10 @@ _('Connection with peer cannot be established.'))
self.select_func(path)
def get_iter_by_sid(self, typ, sid):
- '''returns iter to the row, which holds file transfer, identified by the
- session id'''
+ """
+ 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'):
@@ -555,15 +573,16 @@ _('Connection with peer cannot be established.'))
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_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)
+ dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path)
return None
if stat[6] == 0:
dialogs.ErrorDialog(_('Invalid File'),
@@ -582,7 +601,9 @@ _('Connection with peer cannot be established.'))
return file_props
def add_transfer(self, account, contact, file_props):
- ''' add new transfer to FT window and show the FT window '''
+ """
+ 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
@@ -638,14 +659,13 @@ _('Connection with peer cannot be established.'))
self.tooltip.timeout = gobject.timeout_add(500,
self.show_tooltip, widget)
- def on_transfers_list_leave_notify_event(self, widget = None, event = None):
+ 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)
+ 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()
@@ -654,47 +674,20 @@ _('Connection with peer cannot be established.'))
# 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'''
+ """
+ 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 '''
+ """
+ Make all buttons/menuitems insensitive
+ """
self.pause_button.set_sensitive(False)
self.pause_menuitem.set_sensitive(False)
self.continue_menuitem.set_sensitive(False)
@@ -705,8 +698,10 @@ _('Connection with peer cannot be established.'))
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' '''
+ """
+ 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
@@ -716,7 +711,7 @@ _('Connection with peer cannot be established.'))
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):
+ 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)
@@ -724,11 +719,11 @@ _('Connection with peer cannot be established.'))
# no selection, disable the buttons
self.set_all_insensitive()
elif not is_stopped:
- if self.is_transfer_active(file_props):
+ if 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):
+ elif is_transfer_paused(file_props):
# file transfer is paused
self.toggle_pause_continue(False)
self.pause_button.set_sensitive(True)
@@ -743,8 +738,9 @@ _('Connection with peer cannot be established.'))
return True
def selection_changed(self, args):
- ''' selection has changed - change the sensitivity of the
- buttons/menuitems'''
+ """
+ Selection has changed - change the sensitivity of the buttons/menuitems
+ """
selection = args
selected = selection.get_selected_rows()
if selected[1] != []:
@@ -770,7 +766,7 @@ _('Connection with peer cannot be established.'))
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):
+ if is_transfer_stopped(file_props):
self._remove_transfer(iter_, sid, file_props)
i -= 1
self.tree.get_selection().unselect_all()
@@ -812,7 +808,7 @@ _('Connection with peer cannot be established.'))
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):
+ 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
@@ -848,7 +844,7 @@ _('Connection with peer cannot be established.'))
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])
+ 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,
@@ -870,10 +866,9 @@ _('Connection with peer cannot be established.'))
def show_context_menu(self, event, iter_):
# change the sensitive propery of the buttons and menuitems
- path = None
- if iter_ is not None:
+ if iter_:
path = self.model.get_path(iter_)
- self.set_buttons_sensitive(path, True)
+ self.set_buttons_sensitive(path, True)
event_button = gtkgui_helpers.get_possible_button_event(event)
self.file_transfers_menu.show_all()
@@ -881,7 +876,9 @@ _('Connection with peer cannot be established.'))
event_button, event.time)
def on_transfers_list_key_press_event(self, widget, event):
- '''when a key is pressed in the treeviews'''
+ """
+ When a key is pressed in the treeviews
+ """
self.tooltip.hide_tooltip()
iter_ = None
try:
@@ -920,16 +917,16 @@ _('Connection with peer cannot be established.'))
except TypeError:
self.tree.get_selection().unselect_all()
if event.button == 3: # Right click
- if path is not None:
+ if path:
self.tree.get_selection().select_path(path)
iter_ = self.model.get_iter(path)
self.show_context_menu(event, iter_)
- if path is not None:
+ if path:
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:
+ if not selected or not selected[1]:
return
s_iter = selected[1]
sid = self.model[s_iter][C_SID].decode('utf-8')
@@ -951,7 +948,7 @@ _('Connection with peer cannot be established.'))
def on_remove_menuitem_activate(self, widget):
selected = self.tree.get_selection().get_selected()
- if selected is None or selected[1] is None:
+ if not selected or not selected[1]:
return
s_iter = selected[1]
sid = self.model[s_iter][C_SID].decode('utf-8')
@@ -963,5 +960,4 @@ _('Connection with peer cannot be established.'))
if event.keyval == gtk.keysyms.Escape: # ESCAPE
self.window.hide()
-
# vim: se ts=3:
diff --git a/src/gajim-remote.py b/src/gajim-remote.py
index e70545a2f..a3f2d3fac 100644
--- a/src/gajim-remote.py
+++ b/src/gajim-remote.py
@@ -60,7 +60,6 @@ INTERFACE = 'org.gajim.dbus.RemoteInterface'
SERVICE = 'org.gajim.dbus'
BASENAME = 'gajim-remote-plugin'
-
class GajimRemote:
def __init__(self):
@@ -107,7 +106,7 @@ class GajimRemote:
_('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),
+ (_('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 '
@@ -265,6 +264,15 @@ class GajimRemote:
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'),
[
@@ -327,7 +335,9 @@ class GajimRemote:
self.print_result(res)
def print_result(self, res):
- ''' Print retrieved result to the output '''
+ """
+ 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'):
@@ -382,8 +392,9 @@ class GajimRemote:
return test
def init_connection(self):
- ''' create the onnection to the session dbus,
- or exit if it is not possible '''
+ """
+ Create the onnection to the session dbus, or exit if it is not possible
+ """
try:
self.sbus = dbus.SessionBus()
except Exception:
@@ -400,8 +411,10 @@ class GajimRemote:
self.method = interface.__getattr__(self.command)
def make_arguments_row(self, args):
- ''' return arguments list. Mandatory arguments are enclosed with:
- '<', '>', optional arguments - with '[', ']' '''
+ """
+ Return arguments list. Mandatory arguments are enclosed with:
+ '<', '>', optional arguments - with '[', ']'
+ """
s = ''
for arg in args:
if arg[2]:
@@ -411,7 +424,9 @@ class GajimRemote:
return s
def help_on_command(self, command):
- ''' return help message for a given 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])
@@ -426,7 +441,9 @@ class GajimRemote:
send_error(_('%s not found') % command)
def compose_help(self):
- ''' print usage, and list available commands '''
+ """
+ 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
@@ -439,7 +456,9 @@ class GajimRemote:
return s
def print_info(self, level, prop_dict, encode_return = False):
- ''' return formated string from data structure '''
+ """
+ Return formated string from data structure
+ """
if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)):
return ''
ret_str = ''
@@ -488,7 +507,9 @@ class GajimRemote:
return ret_str
def check_arguments(self):
- ''' Make check if all necessary arguments are given '''
+ """
+ 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:
@@ -561,7 +582,9 @@ class GajimRemote:
sys.exit(0)
def call_remote_method(self):
- ''' calls self.method with arguments from sys.argv[2:] '''
+ """
+ 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:
diff --git a/src/gajim.py b/src/gajim.py
index a2e58f4e7..2f2689ecb 100644
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -176,12 +176,12 @@ else:
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, 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:
import gtk.glade # check if user has libglade (in pygtk and in gtk)
@@ -322,8 +322,7 @@ def pid_alive():
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)
+ 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?')
@@ -383,6 +382,7 @@ if __name__ == '__main__':
# Session Management support
try:
import gnome.ui
+ raise ImportError
except ImportError:
pass
else:
diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py
index ebaebc8d6..e6c23929a 100644
--- a/src/gajim_themes_window.py
+++ b/src/gajim_themes_window.py
@@ -275,7 +275,9 @@ class GajimThemesWindow:
self._set_font()
def _set_color(self, state, widget, option):
- ''' set color value in prefs and update the UI '''
+ """
+ Set color value in prefs and update the UI
+ """
if state:
color = widget.get_color()
color_string = gtkgui_helpers.make_color_string(color)
@@ -297,7 +299,9 @@ class GajimThemesWindow:
gajim.interface.save_config()
def _set_font(self):
- ''' set font value in prefs and update the UI '''
+ """
+ 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()
@@ -317,13 +321,16 @@ class GajimThemesWindow:
gajim.interface.save_config()
def _toggle_font_widgets(self, font_props):
- ''' toggle font buttons with the bool values of font_props tuple'''
+ """
+ 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'''
+ """
+ Return a FontDescription from togglebuttons states
+ """
fd = pango.FontDescription()
if self.bold_togglebutton.get_active():
fd.set_weight(pango.WEIGHT_BOLD)
@@ -332,8 +339,10 @@ class GajimThemesWindow:
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' '''
+ """
+ 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:
@@ -343,7 +352,9 @@ class GajimThemesWindow:
self._toggle_font_widgets(font_props)
def _get_font_attrs(self):
- ''' get a string with letters of font attribures: 'BI' '''
+ """
+ Get a string with letters of font attribures: 'BI'
+ """
attrs = ''
if self.bold_togglebutton.get_active():
attrs += 'B'
@@ -353,7 +364,9 @@ class GajimThemesWindow:
def _get_font_props(self, font_name):
- ''' get tuple of font properties: Weight, Style '''
+ """
+ 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:
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index ed1f07f88..fc520a444 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -29,6 +29,7 @@
import os
import time
+import locale
import gtk
import pango
import gobject
@@ -63,7 +64,9 @@ 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'''
+ """
+ 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)
@@ -119,15 +122,16 @@ def tree_cell_data_func(column, renderer, model, iter_, tv=None):
renderer.set_property('font',
gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
+
class PrivateChatControl(ChatControl):
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.
+ # 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 = contact.jid.split('/')[0]
+ 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]
@@ -140,12 +144,10 @@ class PrivateChatControl(ChatControl):
self.TYPE_ID = 'pm'
def send_message(self, message, xhtml=None, process_commands=True):
- '''call this function to send our message'''
- if not message:
- return
-
+ """
+ Call this method to send the message
+ """
message = helpers.remove_invalid_xml_chars(message)
-
if not message:
return
@@ -153,7 +155,7 @@ class PrivateChatControl(ChatControl):
# the recipient did not go away
contact = gajim.contacts.get_first_contact_from_jid(self.account,
self.contact.jid)
- if contact is None:
+ 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)
@@ -191,8 +193,8 @@ class PrivateChatControl(ChatControl):
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.
+ # 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):
@@ -317,6 +319,7 @@ class GroupchatControl(ChatControlBase):
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.
@@ -381,7 +384,9 @@ class GroupchatControl(ChatControlBase):
self.widget.show_all()
def tree_compare_iters(self, model, iter1, iter2):
- '''Compare two iters to sort them'''
+ """
+ Compare two iters to sort them
+ """
type1 = model[iter1][C_TYPE]
type2 = model[iter2][C_TYPE]
if not type1 or not type2:
@@ -393,9 +398,7 @@ class GroupchatControl(ChatControlBase):
nick1 = nick1.decode('utf-8')
nick2 = nick2.decode('utf-8')
if type1 == 'role':
- if nick1 < nick2:
- return -1
- return 1
+ return locale.strcoll(nick1, nick2)
if type1 == 'contact':
gc_contact1 = gajim.contacts.get_gc_contact(self.account,
self.room_jid, nick1)
@@ -419,15 +422,13 @@ class GroupchatControl(ChatControlBase):
# We compare names
name1 = gc_contact1.get_shown_name()
name2 = gc_contact2.get_shown_name()
- if name1.lower() < name2.lower():
- return -1
- if name2.lower() < name1.lower():
- return 1
- return 0
+ return locale.strcoll(name1.lower(), name2.lower())
def on_msg_textview_populate_popup(self, textview, menu):
- '''we override the default context menu and we prepend Clear
- and the ability to insert a nick'''
+ """
+ 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)
@@ -446,20 +447,36 @@ class GroupchatControl(ChatControlBase):
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'''
+ """
+ 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]]:
+ 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.hpaned.set_position(hpaned_position)
+ ctrl.resize_occupant_treeview(hpaned_position)
def iter_contact_rows(self):
- '''iterate over all contact rows in the tree model'''
+ """
+ Iterate over all contact rows in the tree model
+ """
model = self.list_treeview.get_model()
role_iter = model.get_iter_root()
while role_iter:
@@ -470,7 +487,9 @@ class GroupchatControl(ChatControlBase):
role_iter = model.iter_next(role_iter)
def on_list_treeview_style_set(self, treeview, style):
- '''When style (theme) changes, redraw all contacts'''
+ """
+ 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')
@@ -492,10 +511,11 @@ class GroupchatControl(ChatControlBase):
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'''
+ """
+ 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
@@ -580,10 +600,10 @@ class GroupchatControl(ChatControlBase):
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
- '''
+ """
+ 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):
@@ -597,9 +617,10 @@ class GroupchatControl(ChatControlBase):
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.
- '''
+ """
+ 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()
@@ -613,17 +634,13 @@ class GroupchatControl(ChatControlBase):
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 = '<span %s>%s</span>' % (font_attrs_small,
- subject_text)
- else:
- subject_text = '<span %s>%s</span>' % (font_attrs_small, subject)
+ subject_text = self.urlfinder.sub(self.make_href, subject)
+ subject_text = '<span %s>%s</span>' % (font_attrs_small, subject_text)
# 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)
+ self.banner_status_label.show()
else:
subject_text = ''
self.event_box.set_has_tooltip(False)
@@ -633,7 +650,9 @@ class GroupchatControl(ChatControlBase):
self.banner_status_label.set_markup(subject_text)
def prepare_context_menu(self, hide_buttonbar_items=False):
- '''sets sensitivity state for configure_room'''
+ """
+ Set sensitivity state for configure_room
+ """
xml = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
menu = xml.get_widget('gc_control_popup_menu')
@@ -737,7 +756,7 @@ class GroupchatControl(ChatControlBase):
return menu
def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
- bookmark_room_menuitem, history_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,
@@ -752,7 +771,7 @@ class GroupchatControl(ChatControlBase):
menu.destroy()
def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None,
- status_code=[]):
+ status_code=[]):
if '100' in status_code:
# Room is not anonymous
self.is_anonymous = False
@@ -768,8 +787,8 @@ class GroupchatControl(ChatControlBase):
else:
self.print_conversation(msg, nick, tim, xhtml)
- def on_private_message(self, nick, msg, tim, xhtml, session,
- msg_id=None, encrypted=False):
+ 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
@@ -807,28 +826,18 @@ class GroupchatControl(ChatControlBase):
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
+ while role_iter:
user_iter = model.iter_children(role_iter)
- if not user_iter:
- fin2 = True
- while not fin2:
+ while user_iter:
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
+ else:
+ user_iter = model.iter_next(user_iter)
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):
+ def print_old_conversation(self, text, contact='', tim=None, xhtml = None):
if isinstance(text, str):
text = unicode(text, 'utf-8')
if contact:
@@ -847,11 +856,14 @@ class GroupchatControl(ChatControlBase):
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'''
+ 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 = []
@@ -930,8 +942,10 @@ class GroupchatControl(ChatControlBase):
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.'''
+ """
+ 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?
@@ -953,10 +967,11 @@ class GroupchatControl(ChatControlBase):
return (highlight, sound)
def check_and_possibly_add_focus_out_line(self):
- '''checks and possibly adds 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
- it removes previous line first'''
-
+ """
+ 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\
@@ -968,9 +983,10 @@ class GroupchatControl(ChatControlBase):
self.conv_textview.show_focus_out_line()
def needs_visual_notification(self, text):
- '''checks text to see whether any of the words in (muc_highlight_words
- and nick) appear.'''
-
+ """
+ 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.
@@ -1064,9 +1080,11 @@ class GroupchatControl(ChatControlBase):
self.list_treeview.columns_autosize()
def on_send_pm(self, widget=None, model=None, iter_=None, nick=None,
- msg=None):
- '''opens a chat window and if msg is not None sends private message to a
- contact in a room'''
+ 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')
@@ -1075,7 +1093,9 @@ class GroupchatControl(ChatControlBase):
ctrl.send_message(msg)
def on_send_file(self, widget, gc_contact):
- '''sends a file to a contact in the room'''
+ """
+ Send a file to a contact in the room
+ """
self._on_send_file(gc_contact)
def draw_contact(self, nick, selected=False, focus=False):
@@ -1112,7 +1132,8 @@ class GroupchatControl(ChatControlBase):
'%s</span>') % (colorstring, gobject.markup_escape_text(status))
if image.get_storage_type() == gtk.IMAGE_PIXBUF and \
- gc_contact.affiliation != 'none':
+ 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':
@@ -1135,8 +1156,8 @@ class GroupchatControl(ChatControlBase):
iter_ = self.get_contact_iter(nick)
if not iter_:
return
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.room_jid + \
- '/' + nick, True)
+ 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:
@@ -1160,8 +1181,10 @@ class GroupchatControl(ChatControlBase):
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'''
+ reason, actor, statusCode, new_nick, avatar_sha, tim=None):
+ """
+ When an occupant changes his or her status
+ """
if show == 'invisible':
return
@@ -1298,7 +1321,9 @@ class GroupchatControl(ChatControlBase):
'nick': nick,
'reason': _('system shutdown') }
self.print_conversation(s, 'info', tim=tim, graphics=False)
- elif 'destroyed' in statusCode: # Room has been destroyed
+ # 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,
@@ -1440,7 +1465,7 @@ class GroupchatControl(ChatControlBase):
self.print_conversation(st, tim=tim, graphics=False)
def add_contact_to_roster(self, nick, show, role, affiliation, status,
- jid=''):
+ jid=''):
model = self.list_treeview.get_model()
role_name = helpers.get_uf_role(role, plural=True)
@@ -1474,7 +1499,7 @@ class GroupchatControl(ChatControlBase):
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)
+ pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
if pixbuf == 'ask':
if j:
fjid = j
@@ -1492,21 +1517,18 @@ class GroupchatControl(ChatControlBase):
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')
+ role_iter = model.get_iter_root()
+ while role_iter:
+ role_name = model[role_iter][C_NICK].decode('utf-8')
if role == role_name:
- return iter_
- iter_ = model.iter_next(iter_)
- if not iter_:
- fin = True
+ return role_iter
+ role_iter = model.iter_next(role_iter)
return None
def remove_contact(self, nick):
- '''Remove a user from the contacts_list'''
+ """
+ Remove a user from the contacts_list
+ """
model = self.list_treeview.get_model()
iter_ = self.get_contact_iter(nick)
if not iter_:
@@ -1521,7 +1543,9 @@ class GroupchatControl(ChatControlBase):
model.remove(parent_iter)
def send_message(self, message, xhtml=None, process_commands=True):
- '''call this function to send our message'''
+ """
+ Call this function to send our message
+ """
if not message:
return
@@ -1535,9 +1559,9 @@ class GroupchatControl(ChatControlBase):
if message != '' or message != '\n':
self.save_sent_message(message)
-
+
# Send the message
- gajim.connections[self.account].send_gc_message(self.room_jid,
+ 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()
@@ -1748,7 +1772,9 @@ class GroupchatControl(ChatControlBase):
_('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'''
+ """
+ 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)
@@ -1771,7 +1797,7 @@ class GroupchatControl(ChatControlBase):
gajim.connections[self.account].send_invite(self.room_jid, contact_jid)
def handle_message_textview_mykey_press(self, widget, event_keyval,
- event_keymod):
+ event_keymod):
# NOTE: handles mykeypress which is custom signal connected to this
# CB in new_room(). for this singal see message_textview.py
@@ -1808,12 +1834,15 @@ class GroupchatControl(ChatControlBase):
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 begin.endswith(gc_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[:-len(gc_refer_to_nick_char + ' ')].endswith(self.nick_hits[0]):
+ 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
@@ -1900,19 +1929,25 @@ class GroupchatControl(ChatControlBase):
return True
def on_list_treeview_row_expanded(self, widget, iter_, path):
- '''When a row is expanded: change the icon of the arrow'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ Kick a user
+ """
def on_ok(reason):
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
'none', reason)
@@ -1922,7 +1957,9 @@ class GroupchatControl(ChatControlBase):
_('You may specify a reason below:'), ok_handler=on_ok)
def mk_menu(self, event, iter_):
- '''Make contact's popup menu'''
+ """
+ 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)
@@ -2036,7 +2073,7 @@ class GroupchatControl(ChatControlBase):
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')
+ 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)
@@ -2066,8 +2103,10 @@ class GroupchatControl(ChatControlBase):
return ctrl
def on_row_activated(self, widget, path):
- '''When an iter is activated (dubblick or single click if gnome is set
- this way'''
+ """
+ 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)):
@@ -2079,12 +2118,16 @@ class GroupchatControl(ChatControlBase):
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'''
+ """
+ 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'''
+ """
+ Popup user's group's or agent menu
+ """
# hide tooltip, no matter the button is pressed
self.tooltip.hide_tooltip()
try:
@@ -2195,27 +2238,37 @@ class GroupchatControl(ChatControlBase):
self.tooltip.hide_tooltip()
def grant_voice(self, widget, nick):
- '''grant voice privilege to a user'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ Ban a user
+ """
def on_ok(reason):
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'outcast', reason)
@@ -2227,37 +2280,51 @@ class GroupchatControl(ChatControlBase):
_('You may specify a reason below:'), ok_handler=on_ok)
def grant_membership(self, widget, jid):
- '''grant membership privilege to a user'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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']:
diff --git a/src/groups.py b/src/groups.py
index 1b78f5708..2588b11a4 100644
--- a/src/groups.py
+++ b/src/groups.py
@@ -26,7 +26,10 @@ import gtkgui_helpers
class GroupsPostWindow:
def __init__(self, account, servicejid, groupid):
- '''Open new 'create post' window to create message for groupid on servicejid service.'''
+ """
+ Open new 'create post' window to create message for groupid on servicejid
+ service
+ """
assert isinstance(servicejid, basestring)
assert isinstance(groupid, basestring)
@@ -43,11 +46,15 @@ class GroupsPostWindow:
self.window.show_all()
def on_cancel_button_clicked(self, w):
- '''Close window.'''
+ """
+ Close window
+ """
self.window.destroy()
def on_send_button_clicked(self, w):
- '''Gather info from widgets and send it as a message.'''
+ """
+ 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')
diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py
index bbc93f373..1c08fcd24 100644
--- a/src/gtkexcepthook.py
+++ b/src/gtkexcepthook.py
@@ -52,7 +52,6 @@ def _info(type_, value, tb):
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()
diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py
index 6a8b1b1cf..1e01829cf 100644
--- a/src/gtkgui_helpers.py
+++ b/src/gtkgui_helpers.py
@@ -30,17 +30,41 @@
import xml.sax.saxutils
import gtk
import gtk.glade
+import glib
import gobject
import pango
import os
import sys
-import vcard
-import dialogs
-
import logging
log = logging.getLogger('gajim.gtkgui_helpers')
+from common import i18n
+from common import gajim
+
+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)))
+
+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)))
+
+import vcard
+import dialogs
+
HAS_PYWIN32 = True
if os.name == 'nt':
@@ -51,8 +75,6 @@ if os.name == 'nt':
except ImportError:
HAS_PYWIN32 = False
-from common import i18n
-from common import gajim
from common import helpers
gtk.glade.bindtextdomain(i18n.APP, i18n.DIR)
@@ -61,14 +83,25 @@ gtk.glade.textdomain(i18n.APP)
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)
+
+def add_image_to_button(button, icon_name):
+ add_image_to_menuitem(button, icon_name)
+
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)
def get_completion_liststore(entry):
- ''' create a completion model for entry widget
- completion list consists of (Pixbuf, Text) rows'''
+ """
+ Create a completion model for entry widget completion list consists of
+ (Pixbuf, Text) rows
+ """
completion = gtk.EntryCompletion()
liststore = gtk.ListStore(gtk.gdk.Pixbuf, str)
@@ -86,7 +119,9 @@ def get_completion_liststore(entry):
def popup_emoticons_under_button(menu, button, parent_win):
- ''' pops emoticons menu under button, which is in 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
@@ -115,8 +150,9 @@ def popup_emoticons_under_button(menu, button, parent_win):
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'''
+ """
+ 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')
@@ -130,10 +166,10 @@ def get_theme_font_for_option(theme, option):
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' '''
-
+ """
+ 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
@@ -144,8 +180,8 @@ def get_default_font():
except Exception:
pass
- # try to get xfce default font
- # Xfce 4.2 adopts freedesktop.org's Base Directory Specification
+ # 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', '')
@@ -206,7 +242,9 @@ def user_runs_xfce():
return False
def get_running_processes():
- '''returns running processes or None (if not /proc exists)'''
+ """
+ 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
@@ -241,7 +279,9 @@ def get_running_processes():
return []
def move_window(window, x, y):
- '''moves the window but also checks if out of screen'''
+ """
+ Move the window, but also check if out of screen
+ """
if x < 0:
x = 0
if y < 0:
@@ -254,7 +294,9 @@ def move_window(window, x, y):
window.move(x, y)
def resize_window(window, w, h):
- '''resizes window but also checks if huge window or negative values'''
+ """
+ Resize window, but also checks if huge window or negative values
+ """
if not w or not h:
return
if w > screen_w:
@@ -354,8 +396,10 @@ def parse_server_xml(path_to_file):
print >> sys.stderr, _('Error parsing file:'), message
def set_unset_urgency_hint(window, unread_messages_no):
- '''sets/unsets urgency hint in window argument
- depending if we have unread messages or not'''
+ """
+ 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
@@ -363,8 +407,10 @@ def set_unset_urgency_hint(window, unread_messages_no):
window.props.urgency_hint = False
def get_abspath_for_script(scriptname, want_type = False):
- '''checks if we are svn or normal user and returns abspath to asked script
- if want_type is True we return 'svn' or 'install' '''
+ """
+ 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
@@ -403,8 +449,10 @@ def get_abspath_for_script(scriptname, want_type = False):
return path_to_script
def get_pixbuf_from_data(file_data, want_type = False):
- '''Gets image data and returns gtk.gdk.Pixbuf
- if want_type is True it also returns 'jpeg', 'png' etc'''
+ """
+ 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)
@@ -431,8 +479,11 @@ def get_invisible_cursor():
return cursor
def get_current_desktop(window):
- '''returns the current virtual desktop for given window
- NOTE: window is GDK 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
@@ -444,9 +495,12 @@ def get_current_desktop(window):
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
- window is GTK window'''
+ """
+ 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
@@ -468,7 +522,11 @@ def possibly_move_window_in_current_desktop(window):
return False
def file_is_locked(path_to_file):
- '''returns True if file is locked (WINDOWS ONLY)'''
+ """
+ Return True if file is locked
+
+ NOTE: Windows only.
+ """
if os.name != 'nt': # just in case
return
@@ -496,8 +554,10 @@ def file_is_locked(path_to_file):
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'''
+ """
+ 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?
@@ -516,9 +576,10 @@ def _get_fade_color(treeview, selected, focused):
int(bg.blue*p + fg.blue*q))
def get_scaled_pixbuf(pixbuf, kind):
- '''returns scaled pixbuf, keeping ratio etc or None
- kind is either "chat", "roster", "notification", "tooltip", "vcard"'''
-
+ """
+ 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')
@@ -543,21 +604,28 @@ def get_scaled_pixbuf(pixbuf, kind):
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):
- '''checks 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'''
+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)
+ 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
puny_jid = helpers.sanitize_filename(jid)
- if is_fake_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,
@@ -579,7 +647,7 @@ def get_avatar_pixbuf_from_cache(fjid, is_fake_jid = False, use_local = True):
return 'ask'
vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid,
- is_fake_jid)
+ is_groupchat_contact)
if not vcard_dict: # This can happen if cached vcard is too old
return 'ask'
if 'PHOTO' not in vcard_dict:
@@ -588,16 +656,21 @@ def get_avatar_pixbuf_from_cache(fjid, is_fake_jid = False, use_local = True):
return pixbuf
def make_gtk_month_python_month(month):
- '''gtk start counting months from 0, so January is 0
- but python's time start from 1, so align to python
- month MUST be integer'''
+ """
+ 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
def make_python_month_gtk_month(month):
return month - 1
def make_color_string(color):
- '''create #aabbcc color string from gtk 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]
@@ -612,10 +685,12 @@ def make_pixbuf_grayscale(pixbuf):
return pixbuf2
def get_path_to_generic_or_avatar(generic, jid = None, suffix = None):
- '''Chooses 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'''
+ """
+ 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)
@@ -632,9 +707,10 @@ def get_path_to_generic_or_avatar(generic, jid = None, suffix = None):
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'''
+ """
+ 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
@@ -655,7 +731,9 @@ def decode_filechooser_file_paths(file_paths):
return file_paths_list
def possibly_set_gajim_as_xmpp_handler():
- '''registers (by default only the first time) xmmp: to Gajim.'''
+ """
+ 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,
@@ -737,8 +815,10 @@ Description=xmpp
dlg.checkbutton.set_active(True)
def escape_underscore(s):
- '''Escape underlines to prevent them from being interpreted
- as keyboard accelerators'''
+ """
+ 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):
@@ -756,7 +836,9 @@ def get_state_image_from_file_path_show(file_path, show):
return image
def get_possible_button_event(event):
- '''mouse or keyboard caused the 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
@@ -765,39 +847,34 @@ def get_possible_button_event(event):
def destroy_widget(widget):
widget.destroy()
-def on_avatar_save_as_menuitem_activate(widget, jid, account,
-default_name = ''):
+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:
+ 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'
- type_ = 'jpeg'
- elif ext == 'jpg':
- type_ = 'jpeg'
+ elif extension == 'jpg':
+ image_format = 'jpeg'
else:
- type_ = ext
+ image_format = extension[1:] # remove leading dot
# Save image
try:
- pixbuf.save(file_path, type_)
- except Exception:
+ 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': type_, 'new_filename': new_file_path},
+ _('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()
@@ -839,7 +916,7 @@ default_name = ''):
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.set_current_name(default_name + '.jpeg')
dialog.connect('delete-event', lambda widget, event:
on_cancel(widget))
@@ -847,7 +924,9 @@ def on_bm_header_changed_state(widget, event):
widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
def create_combobox(value_list, selected_value = None):
- '''Value_list is [(label1, value1), ]'''
+ """
+ Value_list is [(label1, value1)]
+ """
liststore = gtk.ListStore(str, str)
combobox = gtk.ComboBox(liststore)
cell = gtk.CellRendererText()
@@ -864,7 +943,9 @@ def create_combobox(value_list, selected_value = None):
return combobox
def create_list_multi(value_list, selected_values=None):
- '''Value_list is [(label1, value1), ]'''
+ """
+ Value_list is [(label1, value1)]
+ """
liststore = gtk.ListStore(str, str)
treeview = gtk.TreeView(liststore)
treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
@@ -882,8 +963,10 @@ def create_list_multi(value_list, selected_values=None):
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'''
+ """
+ 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',
@@ -898,21 +981,27 @@ def load_iconset(path, pixbuf2=None, transport=False):
return _load_icon_list(list_, path, pixbuf2)
def load_icon(icon_name):
- '''load an icon from the iconset in 16x16'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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, '')
@@ -922,8 +1011,10 @@ def load_activity_icon(category, activity = None):
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.'''
+ """
+ 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
@@ -945,8 +1036,10 @@ def load_icons_meta():
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'''
+ """
+ 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
@@ -972,7 +1065,9 @@ def _load_icon_list(icons_list, path, pixbuf2 = None):
return imgs
def make_jabber_state_images():
- '''initialise jabber_state_images dict'''
+ """
+ Initialize jabber_state_images dictionary
+ """
iconset = gajim.config.get('iconset')
if iconset:
if helpers.get_iconset_path(iconset):
@@ -1002,8 +1097,10 @@ def reload_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.'''
+ """
+ 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)):
@@ -1013,7 +1110,9 @@ def label_set_autowrap(widget):
widget.connect_after('size-allocate', __label_size_allocate)
def __label_size_allocate(widget, allocation):
- '''Callback which re-allocates the size of a label.'''
+ """
+ Callback which re-allocates the size of a label
+ """
layout = widget.get_layout()
lw_old, lh_old = layout.get_size()
diff --git a/src/gui_interface.py b/src/gui_interface.py
index 8e1243264..095346554 100644
--- a/src/gui_interface.py
+++ b/src/gui_interface.py
@@ -50,11 +50,11 @@ 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
import gtkgui_helpers
-
import dialogs
import notify
import message_control
@@ -72,7 +72,7 @@ import common.sleepy
from common.xmpp import idlequeue
from common.zeroconf import connection_zeroconf
from common import resolver
-from common import caps
+from common import caps_cache
from common import proxy65_manager
from common import socks5
from common import helpers
@@ -93,7 +93,6 @@ config_filename = gajimpaths['CONFIG_FILE']
from common import optparser
parser = optparser.OptionsParser(config_filename)
-
import logging
log = logging.getLogger('gajim.interface')
@@ -202,9 +201,7 @@ class Interface:
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)
+ path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48)
notify.popup(_('Connection Failed'), account, account,
'connection_failed', path, array[0], array[1])
@@ -266,10 +263,10 @@ class Interface:
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.
- '''
+ 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):
@@ -444,7 +441,7 @@ class Interface:
# TODO: This causes problems when another
# resource signs off!
- conn.remove_transfers_for_contact(contact1)
+ conn.stop_all_active_file_transfers(contact1)
# disable encryption, since if any messages are
# lost they'll be not decryptable (note that
@@ -596,9 +593,7 @@ class Interface:
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)
+ 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)
@@ -656,13 +651,12 @@ class Interface:
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 = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
- 'unsubscribed.png')
- path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
+ path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48)
event_type = _('Unsubscribed')
notify.popup(event_type, jid, account, 'unsubscribed', path,
event_type, jid)
@@ -824,12 +818,18 @@ class Interface:
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
+ 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))
@@ -1120,9 +1120,7 @@ class Interface:
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)
+ 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)
@@ -1142,7 +1140,7 @@ class Interface:
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')
+ 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.'))
@@ -1279,8 +1277,7 @@ class Interface:
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)
+ 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'])
@@ -1290,8 +1287,7 @@ class Interface:
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')
+ 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',
@@ -1314,7 +1310,6 @@ class Interface:
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)
@@ -1325,6 +1320,7 @@ class Interface:
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']
@@ -1345,15 +1341,14 @@ class Interface:
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)
+ 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,
@@ -1375,11 +1370,9 @@ class Interface:
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')
+ 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)
- 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)
@@ -1450,11 +1443,11 @@ class Interface:
if event_type == _('File Transfer Completed'):
txt = _('You successfully received %(filename)s from %(name)s.')\
% {'filename': filename, 'name': name}
- img = 'ft_done.png'
+ img_name = 'gajim-ft_done'
else: # ft stopped
txt = _('File transfer of %(filename)s from %(name)s stopped.')\
% {'filename': filename, 'name': name}
- img = 'ft_stopped.png'
+ img_name = 'gajim-ft_stopped'
else:
receiver = file_props['receiver']
if hasattr(receiver, 'jid'):
@@ -1467,23 +1460,23 @@ class Interface:
if event_type == _('File Transfer Completed'):
txt = _('You successfully sent %(filename)s to %(name)s.')\
% {'filename': filename, 'name': name}
- img = 'ft_done.png'
+ img_name = 'gajim-ft_done'
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)
+ 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)
+ 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:
@@ -1520,7 +1513,9 @@ class Interface:
contact.resource)
def handle_event_signed_in(self, account, empty):
- '''SIGNED_IN event is emitted when we sign in, so handle it'''
+ """
+ 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
@@ -1560,6 +1555,10 @@ 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()
+ # 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)
@@ -1765,11 +1764,9 @@ class Interface:
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)
+ 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)
@@ -1827,7 +1824,9 @@ class Interface:
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'''
+ """
+ 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])
@@ -1946,10 +1945,10 @@ class Interface:
# ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
server = gajim.config.get_per('accounts', account, 'hostname')
def on_ok(is_checked):
- del self.instances[account]['online_dialog']['insecure_ssl']
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)
@@ -1990,6 +1989,34 @@ class Interface:
_('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],
@@ -2079,14 +2106,30 @@ class Interface:
'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 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 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,
@@ -2098,7 +2141,9 @@ class Interface:
################################################################################
def add_event(self, account, jid, type_, event_args):
- '''add an event to the gajim.events var'''
+ """
+ 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)
@@ -2228,7 +2273,8 @@ class Interface:
w = ctrl.parent_win
elif type_ in ('normal', 'file-request', 'file-request-error',
- 'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
+ '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:
@@ -2264,11 +2310,6 @@ class Interface:
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())
@@ -2427,7 +2468,9 @@ class Interface:
self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|[\ud800-\udfff]|[\ufffe-\uffff]'
def popup_emoticons_under_button(self, button, parent_win):
- ''' pops emoticons menu under button, located in parent_win'''
+ """
+ Popup the emoticons menu under button, located in parent_win
+ """
gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
button, parent_win)
@@ -2535,8 +2578,10 @@ class Interface:
################################################################################
def join_gc_room(self, account, room_jid, nick, password, minimize=False,
- is_continued=False):
- '''joins the room immediately'''
+ is_continued=False):
+ """
+ Join the room immediately
+ """
if not nick:
nick = gajim.nicks[account]
@@ -2596,40 +2641,36 @@ class Interface:
mw.new_tab(gc_control)
def new_private_chat(self, gc_contact, account, session=None):
- contact = gc_contact.as_contact()
- type_ = message_control.TYPE_PM
- fjid = gc_contact.room_jid + '/' + gc_contact.name
-
conn = gajim.connections[account]
-
- if not session and fjid in conn.sessions:
- sessions = [s for s in conn.sessions[fjid].values() if isinstance(s, ChatControlSession)]
+ if not session and gc_contact.get_full_jid() in conn.sessions:
+ sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].values()
+ if isinstance(s, ChatControlSession)]
# look for an existing session with a chat control
for s in sessions:
if s.control:
session = s
break
-
if not session and not len(sessions) == 0:
# there are no sessions with chat controls, just take the first one
session = sessions[0]
-
if not session:
# couldn't find an existing ChatControlSession, just make a new one
+ session = conn.make_new_session(gc_contact.get_full_jid(), None, 'pm')
- session = conn.make_new_session(fjid, None, 'pm')
-
+ contact = gc_contact.as_contact()
if not session.control:
- mw = self.msg_win_mgr.get_window(fjid, account)
- if not mw:
- mw = self.msg_win_mgr.create_window(contact, account, type_)
+ message_window = self.msg_win_mgr.get_window(gc_contact.get_full_jid(),
+ account)
+ if not message_window:
+ message_window = self.msg_win_mgr.create_window(contact, account,
+ message_control.TYPE_PM)
- session.control = PrivateChatControl(mw, gc_contact, contact, account,
- session)
- mw.new_tab(session.control)
+ session.control = PrivateChatControl(message_window, gc_contact,
+ contact, account, session)
+ message_window.new_tab(session.control)
- if len(gajim.events.get_events(account, fjid)):
+ if gajim.events.get_events(account, gc_contact.get_full_jid()):
# We call this here to avoid race conditions with widget validation
session.control.read_queue()
@@ -2738,8 +2779,8 @@ class Interface:
if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
status = status + '.png'
elif status == 'online':
- prefix = os.path.join(gajim.DATA_DIR, 'pixmaps')
- status = 'gajim.png'
+ 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')
@@ -2761,33 +2802,27 @@ class Interface:
listener.disconnect(self.music_track_changed_signal)
self.music_track_changed_signal = None
- def music_track_changed(self, unused_listener, music_track_info, account=''):
- if account == '':
+ def music_track_changed(self, unused_listener, music_track_info, account=None):
+ if not account:
accounts = gajim.connections.keys()
else:
accounts = [account]
- if music_track_info is None:
- artist = ''
- title = ''
- source = ''
- elif hasattr(music_track_info, 'paused') and music_track_info.paused == 0:
- artist = ''
- title = ''
- source = ''
+
+ 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 acct not in gajim.connections:
- continue
if not gajim.account_is_connected(acct):
continue
- if not gajim.connections[acct].pep_supported:
+ if not gajim.config.get_per('accounts', acct, 'publish_tune'):
continue
if gajim.connections[acct].music_track_info == music_track_info:
continue
- pep.user_send_tune(acct, artist, title, source)
+ gajim.connections[acct].send_tune(artist, title, source)
gajim.connections[acct].music_track_info = music_track_info
def get_bg_fg_colors(self):
@@ -2810,7 +2845,9 @@ class Interface:
return (bg_str, fg_str)
def read_sleepy(self):
- '''Check idle status and change that status if needed'''
+ """
+ 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
@@ -2864,7 +2901,9 @@ class Interface:
return True # renew timeout (loop for ever)
def autoconnect(self):
- '''auto connect at startup'''
+ """
+ Auto connect at startup
+ """
# dict of account that want to connect sorted by status
shows = {}
for a in gajim.connections:
@@ -2903,7 +2942,9 @@ class Interface:
helpers.launch_browser_mailer(kind, url)
def process_connections(self):
- ''' Called each foo (200) miliseconds. Check for idlequeue timeouts. '''
+ """
+ Called each foo (200) miliseconds. Check for idlequeue timeouts
+ """
try:
gajim.idlequeue.process()
except Exception:
@@ -2927,7 +2968,11 @@ class Interface:
sys.exit()
def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
- '''Saves an avatar to a separate file, and generate files for dbus notifications. An avatar can be given as a pixmap directly or as an decoded image.'''
+ """
+ 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:
@@ -2980,7 +3025,9 @@ class Interface:
(path_to_original_file, str(e)))
def remove_avatar_files(self, jid, puny_nick = None, local = False):
- '''remove avatar files of a jid'''
+ """
+ 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:
@@ -2997,7 +3044,9 @@ class Interface:
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'''
+ """
+ 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']
@@ -3015,8 +3064,10 @@ class Interface:
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'''
+ nick):
+ """
+ Add a bookmark for this account, sorted in bookmark list
+ """
bm = {
'name': name,
'jid': jid,
@@ -3093,7 +3144,7 @@ class Interface:
gajim.ipython_window = window
def run(self):
- if self.systray_capabilities and gajim.config.get('trayicon') != 'never':
+ if gajim.config.get('trayicon') != 'never':
self.show_systray()
self.roster = roster_window.RosterWindow()
@@ -3159,6 +3210,11 @@ class Interface:
}
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'):
@@ -3277,7 +3333,7 @@ class Interface:
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 = 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]
@@ -3341,24 +3397,11 @@ class Interface:
gtkgui_helpers.make_jabber_state_images()
self.systray_enabled = False
- self.systray_capabilities = False
-
- if (os.name == 'nt'):
- import statusicon
- self.systray = statusicon.StatusIcon()
- self.systray_capabilities = True
- else: # use ours, not GTK+ one
- # [FIXME: remove this when we migrate to 2.10 and we can do
- # cool tooltips somehow and (not dying to keep) animation]
- import systray
- self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES
- if self.systray_capabilities:
- self.systray = systray.Systray()
- else:
- gajim.config.set('trayicon', 'never')
- path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png')
- pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
+ 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)
@@ -3469,16 +3512,14 @@ class PassphraseRequest:
class ThreadInterface:
def __init__(self, func, func_args, callback, callback_args):
- '''Call a function in a thread
-
- :param func: the function to call in the thread
- :param func_args: list or arguments for this function
- :param callback: callback to call once function is finished
- :param callback_args: list of arguments for this callback
- '''
+ """
+ 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:
diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py
index 0c975d5b6..5fd799a99 100644
--- a/src/gui_menu_builder.py
+++ b/src/gui_menu_builder.py
@@ -28,9 +28,11 @@ 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 '''
+ 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()
@@ -61,7 +63,9 @@ room_account=None, cap=None):
return sub_menu
def build_invite_submenu(invite_menuitem, list_):
- '''list_ in a list of (contact, account)'''
+ """
+ list_ in a list of (contact, account)
+ """
roster = gajim.interface.roster
# used if we invite only one contact with several resources
contact_list = []
@@ -145,10 +149,12 @@ def build_invite_submenu(invite_menuitem, list_):
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'''
+ 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
@@ -194,18 +200,14 @@ control=None):
items_to_hide = []
# add a special img for send file menuitem
- path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png')
+ 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
- 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)
+ gtkgui_helpers.add_image_to_menuitem(rename_menuitem, 'gajim-kbd_input')
muc_icon = gtkgui_helpers.load_icon('muc_active')
if muc_icon:
@@ -320,7 +322,7 @@ control=None):
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_menuitems):
+ manage_contact_menuitem, convert_to_gc_menuitem):
item.set_no_show_all(True)
item.hide()
diff --git a/src/history_manager.py b/src/history_manager.py
index 9ab638364..82c8e1475 100644
--- a/src/history_manager.py
+++ b/src/history_manager.py
@@ -57,23 +57,23 @@ import getopt
from common import i18n
def parseOpts():
- 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
+ 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
config_path = parseOpts()
del parseOpts
@@ -102,19 +102,12 @@ C_NICKNAME
) = range(2, 6)
-try:
- import sqlite3 as sqlite # python 2.5
-except ImportError:
- try:
- from pysqlite2 import dbapi2 as sqlite
- except ImportError:
- raise exceptions.PysqliteNotAvailable
+import sqlite3 as sqlite
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)
+ 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):
@@ -290,11 +283,13 @@ class HistoryManager:
self._fill_logs_listview(jid)
def _get_jid_id(self, jid):
- '''jids table has jid and jid_id
+ """
+ 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
- '''
+
+ 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
@@ -304,22 +299,24 @@ class HistoryManager:
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
- '''
+ """
+ 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 all okay'''
-
+ """
+ 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 = ?',
@@ -331,8 +328,9 @@ class HistoryManager:
return True
def _jid_is_room_type(self, jid):
- '''returns True/False if given id is room type or not
- eg. if it is room'''
+ """
+ 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:
@@ -343,8 +341,10 @@ class HistoryManager:
return False
def _fill_logs_listview(self, jid):
- '''fill the listview with all messages that user sent to or
- received from 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)
@@ -403,7 +403,9 @@ class HistoryManager:
subject, nickname))
def _fill_search_results_listview(self, text):
- '''ask db and fill listview with results that match text'''
+ """
+ Ask db and fill listview with results that match text
+ """
self.search_results_liststore.clear()
like_sql = '%' + text + '%'
self.cur.execute('''
@@ -610,8 +612,8 @@ class HistoryManager:
liststore, list_of_paths))
def on_search_db_button_clicked(self, widget):
- text = self.search_entry.get_text()
- if text == '':
+ text = self.search_entry.get_text().decode('utf-8')
+ if not text:
return
self.welcome_vbox.hide()
diff --git a/src/history_window.py b/src/history_window.py
index 2ad639fb7..1478720be 100644
--- a/src/history_window.py
+++ b/src/history_window.py
@@ -43,23 +43,25 @@ constants = Constants()
# Completion dict
(
-C_INFO_JID,
-C_INFO_ACCOUNT,
-C_INFO_NAME,
-C_INFO_COMPLETION
+ C_INFO_JID,
+ C_INFO_ACCOUNT,
+ C_INFO_NAME,
+ C_INFO_COMPLETION
) = range(4)
# contact_name, date, message, time
(
-C_LOG_JID,
-C_CONTACT_NAME,
-C_UNIXTIME,
-C_MESSAGE,
-C_TIME
+ C_LOG_JID,
+ C_CONTACT_NAME,
+ C_UNIXTIME,
+ C_MESSAGE,
+ C_TIME
) = range(5)
class HistoryWindow:
- '''Class for browsing logs of conversations with contacts'''
+ """
+ Class for browsing logs of conversations with contacts
+ """
def __init__(self, jid = None, account = None):
xml = gtkgui_helpers.get_glade('history_window.glade')
@@ -132,15 +134,16 @@ class HistoryWindow:
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).
+ """
+ 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 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()
- '''
+ 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:
@@ -208,8 +211,10 @@ class HistoryWindow:
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'''
+ """
+ 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:
@@ -247,7 +252,9 @@ class HistoryWindow:
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'''
+ """
+ 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]
@@ -324,9 +331,9 @@ class HistoryWindow:
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)
- '''
+ """
+ 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
@@ -362,7 +369,9 @@ class HistoryWindow:
return show
def _add_lines_for_date(self, year, month, day):
- '''adds all the lines for given date in textbuffer'''
+ """
+ Add all the lines for given date in textbuffer
+ """
self.history_buffer.set_text('') # clear the buffer first
self.last_time_printout = 0
@@ -376,7 +385,9 @@ class HistoryWindow:
line[5])
def _add_new_line(self, contact_name, tim, kind, show, message, subject):
- '''add a new line in textbuffer'''
+ """
+ Add a new line in textbuffer
+ """
if not message and kind not in (constants.KIND_STATUS,
constants.KIND_GCSTATUS):
return
@@ -538,8 +549,10 @@ class HistoryWindow:
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'''
+ """
+ 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)
@@ -568,7 +581,9 @@ class HistoryWindow:
# and highlight all that
def _scroll_to_result(self, unix_time):
- '''scrolls to the result using unix_time and highlight line'''
+ """
+ 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)
@@ -602,7 +617,9 @@ class HistoryWindow:
' '.join(no_log_for))
def open_history(self, jid, account):
- '''Load chat history of the specified jid'''
+ """
+ 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
diff --git a/src/htmltextview.py b/src/htmltextview.py
index 36f3ac131..e91af9462 100644
--- a/src/htmltextview.py
+++ b/src/htmltextview.py
@@ -25,7 +25,7 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
-'''
+"""
A gtk.TextView-based renderer for XHTML-IM, as described in:
http://www.jabber.org/jeps/jep-0071.html
@@ -33,8 +33,7 @@ Starting with the version posted by Gustavo Carneiro,
I (Santiago Gala) am trying to make it more compatible
with the markup that docutils generate, and also more
modular.
-
-'''
+"""
import gobject
import pango
@@ -187,7 +186,6 @@ for name in BLOCK_HEAD:
)
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)
@@ -200,10 +198,11 @@ def style_iter(style):
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.
+ 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)
@@ -240,8 +239,10 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
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'''
+ """
+ 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)
@@ -488,7 +489,9 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
# Wait maximum 1s for connection
socket.setdefaulttimeout(1)
try:
- f = urllib2.urlopen(attrs['src'])
+ 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
@@ -556,11 +559,11 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
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.
- '''
+ """
+ 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]:
@@ -804,6 +807,10 @@ class HtmlTextView(gtk.TextView):
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
@@ -873,7 +880,45 @@ class HtmlTextView(gtk.TextView):
#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_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_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
+
+ 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
@@ -898,14 +943,16 @@ if __name__ == '__main__':
htmlview = ConversationTextview(None)
- path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
+ 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_to_file)
-
+ htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path)
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'''
+ """
+ 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,
diff --git a/src/ipython_view.py b/src/ipython_view.py
index 4bc8a9564..a901643f6 100644
--- a/src/ipython_view.py
+++ b/src/ipython_view.py
@@ -29,8 +29,8 @@
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-'''
-Provides IPython console widget.
+"""
+Provides IPython console widget
@author: Eitan Isaacson
@organization: IBM Corporation
@@ -40,7 +40,7 @@ Provides IPython console widget.
All rights reserved. This program and the accompanying materials are made
available under the terms of the BSD which accompanies this distribution, and
is available at U{http://www.opensource.org/licenses/bsd-license.php}
-'''
+"""
import gtk, gobject
import re
@@ -55,10 +55,10 @@ except ImportError:
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.
+ without blockage
@ivar IP: IPython instance.
@type IP: IPython.iplib.InteractiveShell
@@ -70,12 +70,10 @@ class IterableIPShell:
@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):
- '''
-
-
+ """
+ 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.
@@ -90,7 +88,7 @@ class IterableIPShell:
@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:
@@ -121,9 +119,9 @@ class IterableIPShell:
self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
def execute(self):
- '''
- Executes the current line provided by the shell object.
- '''
+ """
+ Execute the current line provided by the shell object
+ """
self.history_level = 0
orig_stdout = sys.stdout
sys.stdout = IPython.Shell.Term.cout
@@ -156,32 +154,32 @@ class IterableIPShell:
sys.stdout = orig_stdout
def historyBack(self):
- '''
- Provides one history command back.
+ """
+ Provide 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.
+ """
+ Provide 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.
+ """
+ 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:
@@ -190,17 +188,17 @@ class IterableIPShell:
return rv
def updateNamespace(self, ns_dict):
- '''
- Add the current dictionary to the shell namespace.
+ """
+ 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.
+ """
+ Returns an auto completed line and/or posibilities for completion
@param line: Given line so far.
@type line: string
@@ -208,7 +206,7 @@ class IterableIPShell:
@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])
@@ -222,7 +220,9 @@ class IterableIPShell:
return True
def common_prefix(seq):
- """Returns the common prefix of a sequence of strings"""
+ """
+ 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:
@@ -233,8 +233,8 @@ class IterableIPShell:
def shell(self, cmd,verbose=0,debug=0,header=''):
- '''
- Replacement method to allow shell commands without them blocking.
+ """
+ Replacement method to allow shell commands without them blocking
@param cmd: Shell command to execute.
@type cmd: string
@@ -244,7 +244,7 @@ class IterableIPShell:
@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:
@@ -254,8 +254,8 @@ class IterableIPShell:
input_.close()
class ConsoleView(gtk.TextView):
- '''
- Specialized text view for console-like workflow.
+ """
+ Specialized text view for console-like workflow
@cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
@type ANSI_COLORS: dictionary
@@ -268,7 +268,8 @@ class ConsoleView(gtk.TextView):
@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',
@@ -279,9 +280,9 @@ class ConsoleView(gtk.TextView):
'1;36': 'LightCyan', '1;37': 'White'}
def __init__(self):
- '''
- Initialize console view.
- '''
+ """
+ Initialize console view
+ """
gtk.TextView.__init__(self)
self.modify_font(pango.FontDescription('Mono'))
self.set_cursor_visible(True)
@@ -305,14 +306,14 @@ class ConsoleView(gtk.TextView):
gobject.idle_add(self._write, text, editable)
def _write(self, text, editable=False):
- '''
- Write given text to buffer.
+ """
+ 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,
@@ -339,12 +340,12 @@ class ConsoleView(gtk.TextView):
gobject.idle_add(self._showPrompt, prompt)
def _showPrompt(self, prompt):
- '''
- Prints prompt at start of line.
+ """
+ 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())
@@ -353,24 +354,24 @@ class ConsoleView(gtk.TextView):
gobject.idle_add(self._changeLine, text)
def _changeLine(self, text):
- '''
- Replace currently entered command line with given 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)
def getCurrentLine(self):
- '''
- Get text in current command line.
+ """
+ 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)
@@ -380,12 +381,12 @@ class ConsoleView(gtk.TextView):
gobject.idle_add(self._showReturned, text)
def _showReturned(self, text):
- '''
- Show returned text from last command and print new prompt.
+ """
+ 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(
@@ -400,10 +401,10 @@ class ConsoleView(gtk.TextView):
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.
+ line
@param widget: Widget that key press accored in.
@type widget: gtk.Widget
@@ -412,7 +413,7 @@ class ConsoleView(gtk.TextView):
@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()
@@ -445,9 +446,9 @@ class ConsoleView(gtk.TextView):
return self.onKeyPressExtend(event)
def onKeyPressExtend(self, event):
- '''
- For some reason we can't extend onKeyPress directly (bug #500900).
- '''
+ """
+ For some reason we can't extend onKeyPress directly (bug #500900)
+ """
pass
class IPythonView(ConsoleView, IterableIPShell):
@@ -456,9 +457,9 @@ class IPythonView(ConsoleView, IterableIPShell):
a GTK+ IPython console.
'''
def __init__(self):
- '''
- Initialize. Redirect I/O to console.
- '''
+ """
+ Initialize. Redirect I/O to console
+ """
ConsoleView.__init__(self)
self.cout = StringIO()
IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout,
@@ -470,24 +471,24 @@ class IPythonView(ConsoleView, IterableIPShell):
self.interrupt = False
def raw_input(self, prompt=''):
- '''
- Custom raw_input() replacement. Get's current line from console buffer.
+ """
+ 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.
+ autocompletions, etc
@param widget: Widget that key press occured in.
@type widget: gtk.Widget
@@ -496,7 +497,7 @@ class IPythonView(ConsoleView, IterableIPShell):
@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()
@@ -524,9 +525,9 @@ class IPythonView(ConsoleView, IterableIPShell):
return True
def _processLine(self):
- '''
- Process current command line.
- '''
+ """
+ Process current command line
+ """
self.history_pos = 0
self.execute()
rv = self.cout.getvalue()
diff --git a/src/message_control.py b/src/message_control.py
index eaf3bf5fd..e01fee934 100644
--- a/src/message_control.py
+++ b/src/message_control.py
@@ -39,7 +39,10 @@ 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}
@@ -67,57 +70,87 @@ class MessageControl(object):
return fjid
def set_control_active(self, state):
- '''Called when the control becomes active (state is True)
- or inactive (state is False)'''
+ """
+ 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
+ """
+ Called to check if control can be minimized
+
+ 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
+ """
+ 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
def allow_shutdown(self, method, on_response_yes, on_response_no,
- on_response_minimize):
- '''Called to check is a control is allowed to shutdown.
+ 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_minimize
+
+ Derived classes MAY implement this.
+ """
on_response_yes(self)
def shutdown(self):
- # NOTE: Derived classes MUST implement this
+ """
+ Derived classes MUST implement this
+ """
pass
def repaint_themed_widgets(self):
- pass # NOTE: Derived classes SHOULD implement this
+ """
+ Derived classes SHOULD implement this
+ """
+ pass
def update_ui(self):
- pass # NOTE: Derived classes SHOULD implement this
+ """
+ Derived classes SHOULD implement this
+ """
+ pass
def toggle_emoticons(self):
- pass # NOTE: Derived classes MAY implement this
+ """
+ Derived classes MAY implement this
+ """
+ pass
def update_font(self):
- pass # NOTE: Derived classes SHOULD implement this
+ """
+ Derived classes SHOULD implement this
+ """
+ pass
def update_tags(self):
- pass # NOTE: Derived classes SHOULD implement this
+ """
+ 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'''
- # NOTE: Derived classes MUST implement this
+ """
+ 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)
+ # return (label_str, None)
pass
def get_tab_image(self, count_unread=True):
@@ -126,11 +159,15 @@ class MessageControl(object):
return None
def prepare_context_menu(self):
- # NOTE: Derived classes SHOULD implement this
+ """
+ Derived classes SHOULD implement this
+ """
return None
def chat_buttons_set_visible(self, state):
- # NOTE: Derived classes MAY implement this
+ """
+ Derived classes MAY implement this
+ """
self.hide_chat_buttons = state
def got_connected(self):
@@ -170,8 +207,8 @@ class MessageControl(object):
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=[]):
+ 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
diff --git a/src/message_textview.py b/src/message_textview.py
index 40ba81067..ca9368244 100644
--- a/src/message_textview.py
+++ b/src/message_textview.py
@@ -21,15 +21,20 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
+import gc
+
import gtk
import gobject
import pango
+
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'''
+ """
+ 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
@@ -52,31 +57,31 @@ class MessageTextView(gtk.TextView):
self.set_pixels_below_lines(2)
self.lang = None # Lang used for spell checking
- buffer = self.get_buffer()
+ _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'] = _buffer.create_tag('bold')
self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD)
self.begin_tags['bold'] = '<strong>'
self.end_tags['bold'] = '</strong>'
- self.other_tags['italic'] = buffer.create_tag('italic')
+ self.other_tags['italic'] = _buffer.create_tag('italic')
self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC)
self.begin_tags['italic'] = '<em>'
self.end_tags['italic'] = '</em>'
- self.other_tags['underline'] = buffer.create_tag('underline')
+ self.other_tags['underline'] = _buffer.create_tag('underline')
self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE)
self.begin_tags['underline'] = '<span style="text-decoration: underline;">'
self.end_tags['underline'] = '</span>'
- self.other_tags['strike'] = buffer.create_tag('strike')
+ self.other_tags['strike'] = _buffer.create_tag('strike')
self.other_tags['strike'].set_property('strikethrough', True)
self.begin_tags['strike'] = '<span style="text-decoration: line-through;">'
self.end_tags['strike'] = '</span>'
def make_clickable_urls(self, text):
- buffer = self.get_buffer()
+ _buffer = self.get_buffer()
start = 0
end = 0
@@ -91,7 +96,7 @@ class MessageTextView(gtk.TextView):
text_before_special_text = text[index:start]
else:
text_before_special_text = ''
- end_iter = buffer.get_end_iter()
+ end_iter = _buffer.get_end_iter()
# we insert normal text
new_text += text_before_special_text + \
'<a href="'+ url +'">' + url + '</a>'
@@ -104,50 +109,49 @@ class MessageTextView(gtk.TextView):
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
+ return active_tags
def get_active_iters(self):
- buffer = self.get_buffer()
- return_val = buffer.get_selection_bounds()
+ _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()
+ start, finish = _buffer.get_bounds()
return (start, finish)
def set_tag(self, widget, tag):
- buffer = self.get_buffer()
+ _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)
+ _buffer.remove_tag_by_name(tag, start, finish)
else:
if tag == 'underline':
- buffer.remove_tag_by_name('strike', start, finish)
+ _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)
+ _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()
+ _buffer = self.get_buffer()
start, finish = self.get_active_iters()
- buffer.remove_all_tags(start, finish)
+ _buffer.remove_all_tags(start, finish)
def color_set(self, widget, response, color):
if response == -6:
widget.destroy()
return
- buffer = self.get_buffer()
+ _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 = _buffer.create_tag(tag_name)
tagColor.set_property('foreground', color_string)
self.begin_tags[tag_name] = '<span style="color: ' + color_string + ';">'
self.end_tags[tag_name] = '</span>'
@@ -156,16 +160,16 @@ class MessageTextView(gtk.TextView):
start, finish = self.get_active_iters()
for tag in self.color_tags:
- buffer.remove_tag_by_name(tag, start, finish)
+ _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
- buffer = self.get_buffer()
+ _buffer = self.get_buffer()
font = font.get_font_name()
font_desc = pango.FontDescription(font)
@@ -179,7 +183,7 @@ class MessageTextView(gtk.TextView):
tag_name = 'font' + font
if not tag_name in self.fonts_tags:
- tagFont = buffer.create_tag(tag_name)
+ tagFont = _buffer.create_tag(tag_name)
tagFont.set_property('font', family + ' ' + str(size))
self.begin_tags[tag_name] = \
'<span style="font-family: ' + family + '; ' + \
@@ -190,27 +194,27 @@ class MessageTextView(gtk.TextView):
start, finish = self.get_active_iters()
for tag in self.fonts_tags:
- buffer.remove_tag_by_name(tag, start, finish)
+ _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)
+ _buffer.apply_tag_by_name('bold', start, finish)
else:
- buffer.remove_tag_by_name('bold', start, finish)
+ _buffer.remove_tag_by_name('bold', start, finish)
if style == pango.STYLE_ITALIC:
- buffer.apply_tag_by_name('italic', start, finish)
+ _buffer.apply_tag_by_name('italic', start, finish)
else:
- buffer.remove_tag_by_name('italic', start, finish)
+ _buffer.remove_tag_by_name('italic', start, finish)
def get_xhtml(self):
- buffer = self.get_buffer()
- old = buffer.get_start_iter()
+ _buffer = self.get_buffer()
+ old = _buffer.get_start_iter()
tags = {}
tags['bold'] = False
- iter = buffer.get_start_iter()
- old = buffer.get_start_iter()
+ iter = _buffer.get_start_iter()
+ old = _buffer.get_start_iter()
text = ''
modified = False
@@ -228,7 +232,7 @@ class MessageTextView(gtk.TextView):
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))
+ 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):
@@ -260,7 +264,7 @@ class MessageTextView(gtk.TextView):
for tag in old_tags:
text += self.begin_tags[tag]
- text += xhtml_special(buffer.get_text(old, buffer.get_end_iter()))
+ 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:
@@ -272,16 +276,16 @@ class MessageTextView(gtk.TextView):
else:
return None
-
def destroy(self):
- import gc
- gobject.idle_add(lambda:gc.collect())
+ 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)
+ """
+ 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
diff --git a/src/message_window.py b/src/message_window.py
index 8adb580d6..7c97f3fb0 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -42,8 +42,9 @@ from common import gajim
####################
class MessageWindow(object):
- '''Class for windows which contain message like things; chats,
- groupchats, etc.'''
+ """
+ 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)]
@@ -160,7 +161,9 @@ class MessageWindow(object):
self.account = new_name
def change_jid(self, account, old_jid, new_jid):
- ''' call then when the full jid of a contral change'''
+ """
+ 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]:
@@ -417,7 +420,9 @@ class MessageWindow(object):
return True
def _on_close_button_clicked(self, button, control):
- '''When close button is pressed: close a tab'''
+ """
+ When close button is pressed: close a tab
+ """
self.remove_tab(control, self.CLOSE_CLOSE_BUTTON)
def show_icon(self):
@@ -444,7 +449,9 @@ class MessageWindow(object):
self.window.set_icon(icon.get_pixbuf())
def show_title(self, urgent=True, control=None):
- '''redraw the window's title'''
+ """
+ Redraw the window's title
+ """
if not control:
control = self.get_active_control()
if not control:
@@ -512,8 +519,10 @@ class MessageWindow(object):
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'''
+ """
+ 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)
@@ -616,7 +625,9 @@ class MessageWindow(object):
self.show_icon()
def repaint_themed_widgets(self):
- '''Repaint controls in the window with theme color'''
+ """
+ Repaint controls in the window with theme color
+ """
# iterate through controls and repaint
for ctrl in self.controls():
ctrl.repaint_themed_widgets()
@@ -650,21 +661,11 @@ class MessageWindow(object):
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'''
+ """
+ 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')
@@ -686,7 +687,9 @@ class MessageWindow(object):
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'''
+ """
+ Change the JID key of a control
+ """
try:
# Check if controls exists
ctrl = self._controls[acct][old_jid]
@@ -814,10 +817,10 @@ class MessageWindow(object):
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
- '''
+ """
+ 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 \
@@ -844,7 +847,9 @@ class MessageWindow(object):
return (page_num, to_right)
def find_page_num_according_to_tab_label(self, tab_label):
- '''Find the page num of the 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)
@@ -856,18 +861,21 @@ class MessageWindow(object):
################################################################################
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,)),
}
# 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,
+ 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'
@@ -875,12 +883,14 @@ class MessageWindowMgr(gobject.GObject):
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'''
+ """
+ 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 = {}
@@ -931,7 +941,9 @@ class MessageWindowMgr(gobject.GObject):
return False
def _resize_window(self, win, acct, type_):
- '''Resizes window according to config settings'''
+ """
+ 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'),
@@ -958,7 +970,9 @@ class MessageWindowMgr(gobject.GObject):
win.parent_paned.set_position(parent_size[0])
def _position_window(self, win, acct, type_):
- '''Moves window according to config settings'''
+ """
+ Moves window according to config settings
+ """
if (self.mode in [self.ONE_MSG_WINDOW_NEVER,
self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]):
return
@@ -1054,15 +1068,19 @@ class MessageWindowMgr(gobject.GObject):
return
def get_control(self, jid, acct):
- '''Amongst all windows, return the MessageControl for jid'''
+ """
+ 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?'''
+ """
+ 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
diff --git a/src/negotiation.py b/src/negotiation.py
index bd5a32123..a7cf5c043 100644
--- a/src/negotiation.py
+++ b/src/negotiation.py
@@ -27,14 +27,15 @@ from common import gajim
from common import xmpp
def describe_features(features):
- '''a human-readable description of the features that have been negotiated'''
+ """
+ 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
diff --git a/src/network_manager_listener.py b/src/network_manager_listener.py
index 15a40bbf6..3f1f774ca 100644
--- a/src/network_manager_listener.py
+++ b/src/network_manager_listener.py
@@ -26,21 +26,27 @@ from common import gajim
def device_now_active(self, *args):
- '''For Network Manager 0.6'''
+ """
+ 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 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'''
+ """
+ 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,
diff --git a/src/notify.py b/src/notify.py
index 3d9eb6051..37a7fd267 100644
--- a/src/notify.py
+++ b/src/notify.py
@@ -69,7 +69,9 @@ def server_display(server):
win.present()
def get_show_in_roster(event, account, contact, session=None):
- '''Return True if this event must be shown in roster, else False'''
+ """
+ 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)
@@ -84,7 +86,9 @@ def get_show_in_roster(event, account, contact, session=None):
return True
def get_show_in_systray(event, account, contact, type_=None):
- '''Return True if this event must be shown in systray, else False'''
+ """
+ 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':
@@ -98,8 +102,9 @@ def get_show_in_systray(event, account, contact, type_=None):
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'''
+ """
+ Returns the number of the first (top most) advanced notification else None
+ """
num = 0
notif = gajim.config.get_per('notifications', str(num))
while notif:
@@ -146,10 +151,11 @@ def get_advanced_notification(event, account, contact):
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'''
+ """
+ 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
@@ -228,16 +234,16 @@ def notify(event, jid, account, parameters, advanced_notif_num=None):
show_image = 'online.png'
suffix = '_notif_size_colored'
transport_name = gajim.get_transport_name_from_jid(jid)
- img = None
+ img_path = None
if transport_name:
- img = os.path.join(helpers.get_transport_path(transport_name),
+ img_path = os.path.join(helpers.get_transport_path(transport_name),
'48x48', show_image)
- if not img or not os.path.isfile(img):
+ if not img_path or not os.path.isfile(img_path):
iconset = gajim.config.get('iconset')
- img = os.path.join(helpers.get_iconset_path(iconset), '48x48',
+ img_path = 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)
+ 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)}
@@ -267,16 +273,14 @@ def notify(event, jid, account, parameters, advanced_notif_num=None):
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')
+ 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 = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
- 'priv_msg_recv.png')
+ 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,
@@ -286,14 +290,13 @@ def notify(event, jid, account, parameters, advanced_notif_num=None):
else: # chat message
event_type = _('New Message')
- img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
- 'chat_msg_recv.png')
+ img_name = 'gajim-chat_msg_recv'
title = _('New Message from %(nickname)s') % \
{'nickname': nickname}
text = message
- path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
+ img_path = gtkgui_helpers.get_icon_path(img_name, 48)
popup(event_type, jid, account, message_type,
- path_to_image=path, title=title, text=text)
+ path_to_image=img_path, title=title, text=text)
if do_sound:
snd_file = None
@@ -327,17 +330,16 @@ def notify(event, jid, account, parameters, advanced_notif_num=None):
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
+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.'''
-
+ 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
+ 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')):
@@ -414,9 +416,12 @@ def on_pynotify_notification_clicked(notification, action):
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.'''
+ """
+ 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 = []
@@ -464,8 +469,11 @@ class NotificationResponseManager:
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
@@ -516,9 +524,8 @@ class DesktopNotification:
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
+ 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)
@@ -541,8 +548,7 @@ class DesktopNotification:
notification_text = ('<html><img src="%(image)s" align=left />' \
'%(title)s<br/>%(text)s</html>') % {'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'))
+ gajim_icon = gtkgui_helpers.get_icon_path('gajim', 48)
self.notif.Notify(
dbus.String(_('Gajim')), # app_name (string)
dbus.UInt32(0), # replaces_id (uint)
@@ -584,9 +590,8 @@ class DesktopNotification:
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]
+ 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}
diff --git a/src/profile_window.py b/src/profile_window.py
index a417ede08..c0705adc8 100644
--- a/src/profile_window.py
+++ b/src/profile_window.py
@@ -36,7 +36,9 @@ 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')
@@ -68,7 +70,7 @@ class ProfileWindow:
return True # loop forever
def remove_statusbar(self, message_id):
- self.statusbar.remove(self.context_id, message_id)
+ self.statusbar.remove_message(self.context_id, message_id)
self.remove_statusbar_timeout_id = None
def on_profile_window_destroy(self, widget):
@@ -173,20 +175,22 @@ class ProfileWindow:
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 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)
+ use_local=False)
- if pixbuf:
+ 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, None, nick + '.jpeg')
+ self.jid, self.account, nick)
menu.append(menuitem)
# show clear
menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
@@ -246,7 +250,7 @@ class ProfileWindow:
self.set_value(i + '_entry', vcard_[i])
if self.update_progressbar_timeout_id is not None:
if self.message_id:
- self.statusbar.remove(self.context_id, 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,
@@ -257,7 +261,9 @@ class ProfileWindow:
self.update_progressbar_timeout_id = None
def add_to_vcard(self, vcard_, entry, txt):
- '''Add an information to the vCard dictionary'''
+ """
+ Add an information to the vCard dictionary
+ """
entries = entry.split('_')
loc = vcard_
if len(entries) == 3: # We need to use lists
@@ -280,7 +286,9 @@ class ProfileWindow:
return vcard_
def make_vcard(self):
- '''make the vCard dictionary'''
+ """
+ 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',
@@ -322,8 +330,7 @@ class ProfileWindow:
nick = ''
if 'NICKNAME' in vcard_:
nick = vcard_['NICKNAME']
- from common import pep
- pep.user_send_nickname(self.account, nick)
+ gajim.connections[self.account].send_nickname(nick)
if nick == '':
nick = gajim.config.get_per('accounts', self.account, 'name')
gajim.nicks[self.account] = nick
@@ -342,7 +349,7 @@ class ProfileWindow:
def vcard_not_published(self):
if self.message_id:
- self.statusbar.remove(self.context_id, 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,
diff --git a/src/remote_control.py b/src/remote_control.py
index f50370708..377b9e1e8 100644
--- a/src/remote_control.py
+++ b/src/remote_control.py
@@ -29,6 +29,8 @@
import gobject
import gtk
import os
+import base64
+import mimetypes
from common import gajim
from common import helpers
@@ -64,9 +66,10 @@ 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
- '''
+ """
+ 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)):
@@ -110,8 +113,12 @@ class Remote:
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
@@ -193,14 +200,18 @@ class SignalObject(dbus.service.Object):
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).'''
+ """
+ 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'''
+ """
+ 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())
@@ -210,8 +221,9 @@ class SignalObject(dbus.service.Object):
@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'''
+ """
+ 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()))
@@ -220,7 +232,9 @@ class SignalObject(dbus.service.Object):
return DBUS_STRING(status)
def _get_account_and_contact(self, account, jid):
- '''get the account (if not given) and contact instance from jid'''
+ """
+ Get the account (if not given) and contact instance from jid
+ """
connected_account = None
contact = None
accounts = gajim.contacts.get_accounts()
@@ -247,8 +261,10 @@ class SignalObject(dbus.service.Object):
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'''
+ """
+ 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
@@ -273,8 +289,10 @@ class SignalObject(dbus.service.Object):
@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' '''
+ """
+ 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)
@@ -289,8 +307,10 @@ class SignalObject(dbus.service.Object):
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'''
+ """
+ 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:
@@ -305,22 +325,27 @@ class SignalObject(dbus.service.Object):
@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 '''
+ """
+ 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 '''
+ """
+ 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'.'''
+ """
+ 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)
@@ -332,8 +357,10 @@ class SignalObject(dbus.service.Object):
@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' '''
+ """
+ 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)
@@ -384,12 +411,18 @@ class SignalObject(dbus.service.Object):
@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. '''
+ """
+ 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)
+ '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:
@@ -398,15 +431,22 @@ class SignalObject(dbus.service.Object):
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)
+ 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'''
+ """
+ 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]
@@ -429,14 +469,17 @@ class SignalObject(dbus.service.Object):
@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.'''
+ """
+ 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.
- '''
+ """
+ Get vcard info for a contact. Return cached value of the vcard
+ """
if not isinstance(jid, unicode):
jid = unicode(jid)
if not jid:
@@ -452,7 +495,9 @@ class SignalObject(dbus.service.Object):
@dbus.service.method(INTERFACE, in_signature='', out_signature='as')
def list_accounts(self):
- '''list register accounts'''
+ """
+ List register accounts
+ """
result = gajim.contacts.get_accounts()
result_array = dbus.Array([], signature='s')
if result and len(result) > 0:
@@ -462,7 +507,9 @@ class SignalObject(dbus.service.Object):
@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'''
+ """
+ Show info on account: resource, jid, nick, prio, message
+ """
result = DBUS_DICT_SS()
if account in gajim.connections:
# account is valid
@@ -479,8 +526,10 @@ class SignalObject(dbus.service.Object):
@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'''
+ """
+ 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:
@@ -500,7 +549,9 @@ class SignalObject(dbus.service.Object):
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
def toggle_roster_appearance(self):
- ''' shows/hides the roster window '''
+ """
+ Show/hide the roster window
+ """
win = gajim.interface.roster.window
if win.get_property('visible'):
gobject.idle_add(win.hide)
@@ -514,7 +565,9 @@ class SignalObject(dbus.service.Object):
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
def toggle_ipython(self):
- ''' shows/hides the ipython window '''
+ """
+ Show/hide the ipython window
+ """
win = gajim.ipython_window
if win:
if win.window.is_visible():
@@ -615,9 +668,10 @@ class SignalObject(dbus.service.Object):
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
- '''
+ """
+ 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:
@@ -643,7 +697,9 @@ class SignalObject(dbus.service.Object):
return jid
def _contacts_as_dbus_structure(self, contacts):
- ''' get info from list of Contact objects and create dbus dict '''
+ """
+ Get info from list of Contact objects and create dbus dict
+ """
if not contacts:
return None
prim_contact = None # primary contact
@@ -692,6 +748,31 @@ class SignalObject(dbus.service.Object):
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:
diff --git a/src/roster_window.py b/src/roster_window.py
index fa580c7bb..a02fe588b 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -38,6 +38,7 @@ import gobject
import os
import sys
import time
+import locale
import common.sleepy
import history_window
@@ -60,6 +61,7 @@ from common import helpers
from common.exceptions import GajimGeneralException
from common import i18n
from common import pep
+from common import location_listener
from message_window import MessageWindowMgr
@@ -72,30 +74,32 @@ 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_AVATAR_PIXBUF, # avatar_pixbuf
-C_PADLOCK_PIXBUF, # use for account row only
-) = range(10)
+ 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.
+ """
+ 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:
@@ -112,16 +116,15 @@ class RosterWindow:
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.
+ """
+ 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:
@@ -137,14 +140,13 @@ class RosterWindow:
def _get_self_contact_iter(self, account, model=None):
- ''' Return the gtk.TreeIter of SelfContact or None if not found.
+ """
+ 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)
@@ -162,15 +164,15 @@ class RosterWindow:
def _get_contact_iter(self, jid, account, contact=None, model=None):
- ''' Return a list of gtk.TreeIter of the given contact.
+ """
+ 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?)
@@ -228,23 +230,25 @@ class RosterWindow:
def _iter_is_separator(self, model, titer):
- ''' Return True if the given iter is a separator.
+ """
+ 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
def _iter_contact_rows(self, model=None):
- '''Iterate over all contact rows in given model.
+ """
+ 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()
@@ -265,10 +269,9 @@ class RosterWindow:
#############################################################################
def add_account(self, account):
- '''
- Add account to roster and draw it. Do nothing if it is
- already in.
- '''
+ """
+ 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
@@ -279,7 +282,7 @@ class RosterWindow:
self.model.append(None, [
gajim.interface.jabber_state_images['16'][show],
_('Merged accounts'), 'account', '', 'all',
- None, None, None, None, None])
+ None, None, None, None, None, None])
else:
show = gajim.SHOW_LIST[gajim.connections[account].connected]
our_jid = gajim.get_jid_from_account(account)
@@ -294,23 +297,23 @@ class RosterWindow:
self.model.append(None, [
gajim.interface.jabber_state_images['16'][show],
gobject.markup_escape_text(account), 'account',
- our_jid, account, None, None, None, None,
+ 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.
- '''
+ """
+ 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:
@@ -321,15 +324,18 @@ class RosterWindow:
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.
+ 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.
+ 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
@@ -339,7 +345,7 @@ class RosterWindow:
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
@@ -354,7 +360,8 @@ class RosterWindow:
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))
+ '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.
@@ -369,7 +376,7 @@ class RosterWindow:
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])
+ 'group', group, account, None, None, None, None, None, None])
self.draw_group(group, account)
if contact.is_transport():
@@ -384,7 +391,7 @@ class RosterWindow:
i_ = self.model.append(child_iterG, (None,
contact.get_shown_name(), typestr,
contact.jid, account, None, None, None,
- None, None))
+ None, None, None))
added_iters.append(i_)
# Restore the group expand state
@@ -399,7 +406,8 @@ class RosterWindow:
return added_iters
def _remove_entity(self, contact, account, groups=None):
- '''Remove the given contact from roster data model.
+ """
+ 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
@@ -410,7 +418,7 @@ class RosterWindow:
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
@@ -450,8 +458,8 @@ class RosterWindow:
return True
def _add_metacontact_family(self, family, account):
- '''
- Add the give Metacontact family to roster data model.
+ """
+ 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
@@ -459,7 +467,7 @@ class RosterWindow:
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)
@@ -497,12 +505,12 @@ class RosterWindow:
return brothers
def _remove_metacontact_family(self, family, account):
- '''
- Remove the given Metacontact family from roster data model.
+ """
+ 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]
@@ -561,7 +569,9 @@ class RosterWindow:
def _recalibrate_metacontact_family(self, family, account):
- '''Regroup metacontact family if necessary.'''
+ """
+ Regroup metacontact family if necessary
+ """
brothers = []
nearby_family, big_brother_jid, big_brother_account = \
@@ -609,10 +619,11 @@ class RosterWindow:
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.
+ """
+ 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)
@@ -622,7 +633,7 @@ class RosterWindow:
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, None))
self.draw_completely(jid, account)
self.draw_account(account)
@@ -634,7 +645,8 @@ class RosterWindow:
self._recalibrate_metacontact_family(family, account)
def add_contact(self, jid, account):
- '''Add contact to roster and draw him.
+ """
+ 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.
@@ -646,8 +658,7 @@ class RosterWindow:
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
@@ -695,7 +706,8 @@ class RosterWindow:
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 roster
Remove contact from all its group. Remove empty groups or redraw
otherwise.
@@ -708,8 +720,7 @@ class RosterWindow:
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
@@ -761,13 +772,14 @@ class RosterWindow:
return True
def rename_self_contact(self, old_jid, new_jid, account):
- '''Rename the self_contact jid
+ """
+ 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:
@@ -776,9 +788,9 @@ class RosterWindow:
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.
- '''
+ """
+ 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):
@@ -817,7 +829,9 @@ class RosterWindow:
def remove_groupchat(self, jid, account):
- '''Remove groupchat from roster and redraw account and group.'''
+ """
+ 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]:
@@ -830,8 +844,9 @@ class RosterWindow:
# 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.'''
+ """
+ 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
@@ -843,57 +858,60 @@ class RosterWindow:
return contact
def remove_transport(self, jid, account):
- '''Remove transport from roster and redraw account and group.'''
+ """
+ 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
+ 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,
+
+ changed_contacts.append({'jid':jid, 'name':contact.name,
'groups':contact.groups})
-
- gajim.connections[acc].update_contacts(changed_contacts)
-
+
+ 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.
+ """
+ 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.
@@ -903,8 +921,7 @@ class RosterWindow:
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:
@@ -921,7 +938,8 @@ class RosterWindow:
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.
+ """
+ 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.
@@ -931,8 +949,7 @@ class RosterWindow:
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:
@@ -969,7 +986,7 @@ class RosterWindow:
self._recalibrate_metacontact_family(family, account)
self.draw_contact(jid, account)
- #FIXME: integrate into add_contact()
+ # 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,
@@ -1024,55 +1041,26 @@ class RosterWindow:
self.model[child_iter][C_NAME] = account_name
- if gajim.config.get('show_mood_in_roster') \
- and 'mood' in gajim.connections[account].mood \
- and gajim.connections[account].mood['mood'].strip() in MOODS:
-
- self.model[child_iter][C_MOOD_PIXBUF] = gtkgui_helpers.load_mood_icon(
- gajim.connections[account].mood['mood'].strip()).get_pixbuf()
-
- elif gajim.config.get('show_mood_in_roster') \
- and 'mood' in gajim.connections[account].mood:
- self.model[child_iter][C_MOOD_PIXBUF] = \
- gtkgui_helpers.load_mood_icon('unknown'). \
- get_pixbuf()
+ 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 gajim.connections[account].activity \
- and gajim.connections[account].activity['activity'].strip() \
- in ACTIVITIES:
- if 'subactivity' in gajim.connections[account].activity \
- and gajim.connections[account].activity['subactivity'].strip() \
- in ACTIVITIES[gajim.connections[account].activity['activity'].strip()]:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = \
- gtkgui_helpers.load_activity_icon(
- gajim.connections[account].activity['activity'].strip(),
- gajim.connections[account].activity['subactivity'].strip()). \
- get_pixbuf()
- else:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = \
- gtkgui_helpers.load_activity_icon(
- gajim.connections[account].activity['activity'].strip()). \
- get_pixbuf()
- elif gajim.config.get('show_activity_in_roster') \
- and 'activity' in gajim.connections[account].activity:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = \
- gtkgui_helpers.load_activity_icon('unknown'). \
- get_pixbuf()
+ 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 ('artist' in gajim.connections[account].tune \
- or 'title' in gajim.connections[account].tune):
- path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
- self.model[child_iter][C_TUNE_PIXBUF] = \
- gtk.gdk.pixbuf_new_from_file(path)
+ 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
def draw_group(self, group, account):
@@ -1110,7 +1098,9 @@ class RosterWindow:
return False
def draw_contact(self, jid, account, selected=False, focus=False):
- '''draw the correct state image, name BUT not avatar'''
+ """
+ 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
@@ -1278,80 +1268,48 @@ class RosterWindow:
return False
-
- def draw_mood(self, jid, account):
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_mood_in_roster'):
- return
- jid = self.model[iters[0]][C_JID]
- jid = jid.decode('utf-8')
- contact = gajim.contacts.get_contact(account, jid)
- if 'mood' in contact.mood and contact.mood['mood'].strip() in MOODS:
- pixbuf = gtkgui_helpers.load_mood_icon(
- contact.mood['mood'].strip()).get_pixbuf()
- elif 'mood' in contact.mood:
- pixbuf = gtkgui_helpers.load_mood_icon(
- 'unknown').get_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')
+ elif pep_type == 'location':
+ return gajim.config.get('show_location_in_roster')
else:
- pixbuf = None
- for child_iter in iters:
- self.model[child_iter][C_MOOD_PIXBUF] = pixbuf
- return False
+ 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_activity(self, jid, account):
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_activity_in_roster'):
+ 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
- jid = self.model[iters[0]][C_JID]
- jid = jid.decode('utf-8')
- contact = gajim.contacts.get_contact(account, jid)
- if 'activity' in contact.activity \
- and contact.activity['activity'].strip() in ACTIVITIES:
- if 'subactivity' in contact.activity \
- and contact.activity['subactivity'].strip() in \
- ACTIVITIES[contact.activity['activity'].strip()]:
- pixbuf = gtkgui_helpers.load_activity_icon(
- contact.activity['activity'].strip(),
- contact.activity['subactivity'].strip()).get_pixbuf()
- else:
- pixbuf = gtkgui_helpers.load_activity_icon(
- contact.activity['activity'].strip()).get_pixbuf()
- elif 'activity' in contact.activity:
- pixbuf = gtkgui_helpers.load_activity_icon(
- 'unknown').get_pixbuf()
- else:
- pixbuf = None
- for child_iter in iters:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = pixbuf
- return False
-
- def draw_tune(self, jid, account):
+ model_column = self._pep_type_to_model_column[pep_type]
iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_tunes_in_roster'):
+ if not iters:
return
- jid = self.model[iters[0]][C_JID]
- jid = jid.decode('utf-8')
+ jid = self.model[iters[0]][C_JID].decode('utf-8')
contact = gajim.contacts.get_contact(account, jid)
- if 'artist' in contact.tune or 'title' in contact.tune:
- path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
- pixbuf = gtk.gdk.pixbuf_new_from_file(path)
+ if pep_type in contact.pep:
+ pixbuf = contact.pep[pep_type].asPixbufIcon()
else:
pixbuf = None
for child_iter in iters:
- self.model[child_iter][C_TUNE_PIXBUF] = pixbuf
- return False
-
+ 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]
- jid = jid.decode('utf-8')
+ jid = self.model[iters[0]][C_JID].decode('utf-8')
pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
- if pixbuf is None or pixbuf == 'ask':
+ if pixbuf in (None, 'ask'):
scaled_pixbuf = None
else:
scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
@@ -1361,15 +1319,14 @@ class RosterWindow:
def draw_completely(self, jid, account):
self.draw_contact(jid, account)
- self.draw_mood(jid, account)
- self.draw_activity(jid, account)
- self.draw_tune(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
- '''
+ """
+ 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
@@ -1387,12 +1344,13 @@ class RosterWindow:
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.
+ """
+ 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)
@@ -1411,13 +1369,16 @@ class RosterWindow:
gobject.idle_add(task.next)
def setup_and_draw_roster(self):
- '''create new empty model and draw roster'''
+ """
+ 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)
+ # 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, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf)
self.model.set_sort_func(1, self._compareIters)
self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
@@ -1435,8 +1396,9 @@ class RosterWindow:
def select_contact(self, jid, account):
- '''Select contact in roster. If contact is hidden but has events,
- show him.'''
+ """
+ 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
@@ -1455,7 +1417,9 @@ class RosterWindow:
def _adjust_account_expand_collapse_state(self, account):
- '''Expand/collapse account row based on self.collapsed_rows'''
+ """
+ Expand/collapse account row based on self.collapsed_rows
+ """
iterA = self._get_account_iter(account)
if not iterA:
# thank you modelfilter
@@ -1469,7 +1433,9 @@ class RosterWindow:
def _adjust_group_expand_collapse_state(self, group, account):
- '''Expand/collapse group row based on self.collapsed_rows'''
+ """
+ Expand/collapse group row based on self.collapsed_rows
+ """
iterG = self._get_group_iter(group, account)
if not iterG:
# Group not visible
@@ -1496,7 +1462,9 @@ class RosterWindow:
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'''
+ """
+ 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
@@ -1521,7 +1489,9 @@ class RosterWindow:
return True
def _visible_func(self, model, titer):
- '''Determine whether iter should be visible in the treeview'''
+ """
+ Determine whether iter should be visible in the treeview
+ """
type_ = model[titer][C_TYPE]
if not type_:
return False
@@ -1593,7 +1563,9 @@ class RosterWindow:
return True
def _compareIters(self, model, iter1, iter2, data=None):
- '''Compare two iters to sort them'''
+ """
+ Compare two iters to sort them
+ """
name1 = model[iter1][C_NAME]
name2 = model[iter2][C_NAME]
if not name1 or not name2:
@@ -1628,9 +1600,7 @@ class RosterWindow:
account1 = account1.decode('utf-8')
account2 = account2.decode('utf-8')
if type1 == 'account':
- if account1 < account2:
- return -1
- return 1
+ return locale.strcoll(account1, account2)
jid1 = model[iter1][C_JID].decode('utf-8')
jid2 = model[iter2][C_JID].decode('utf-8')
if type1 == 'contact':
@@ -1677,20 +1647,23 @@ class RosterWindow:
elif show1 > show2:
return 1
# We compare names
- if name1.lower() < name2.lower():
+ cmp_result = locale.strcoll(name1.lower(), name2.lower())
+ if cmp_result < 0:
return -1
- if name2.lower() < name1.lower():
+ if cmp_result > 0:
return 1
if type1 == 'contact' and type2 == 'contact':
# We compare account names
- if account1.lower() < account2.lower():
+ cmp_result = locale.strcoll(account1.lower(), account2.lower())
+ if cmp_result < 0:
return -1
- if account2.lower() < account1.lower():
+ if cmp_result > 0:
return 1
# We compare jids
- if jid1.lower() < jid2.lower():
+ cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
+ if cmp_result < 0:
return -1
- if jid2.lower() < jid1.lower():
+ if cmp_result > 0:
return 1
return 0
@@ -1700,8 +1673,10 @@ class RosterWindow:
################################################################################
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'''
+ """
+ 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]
@@ -1726,7 +1701,9 @@ class RosterWindow:
gajim.logger.set_read_messages([result[0]])
def fill_contacts_and_groups_dicts(self, array, account):
- '''fill gajim.contacts and gajim.groups'''
+ """
+ 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():
@@ -1800,11 +1777,12 @@ class RosterWindow:
return False
def on_event_removed(self, event_list):
- '''Remove contacts on last events removed.
+ """
+ Remove contacts on last events removed
- Only performed if removal was requested before but the contact
- still had pending events
- '''
+ 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)
@@ -1820,7 +1798,9 @@ class RosterWindow:
self.show_title()
def open_event(self, account, jid, event):
- '''If an event was handled, return True, else return False'''
+ """
+ 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_)
@@ -1866,6 +1846,11 @@ class RosterWindow:
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
################################################################################
@@ -1896,14 +1881,18 @@ class RosterWindow:
def authorize(self, widget, jid, account):
- '''Authorize a contact (by re-sending auth menuitem)'''
+ """
+ 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'''
+ 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)
@@ -1930,7 +1919,9 @@ class RosterWindow:
self.add_contact(jid, account)
def revoke_auth(self, widget, jid, account):
- '''Revoke a contact's authorization'''
+ """
+ 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)
@@ -1966,36 +1957,36 @@ class RosterWindow:
self.send_status_continue(account, status, txt, auto, to)
- def send_pep(self, account, pep_dict=None):
- '''Sends pep information (activity, mood)'''
- if not pep_dict:
- return
- # activity
- if 'activity' in pep_dict and pep_dict['activity'] in pep.ACTIVITIES:
+ def send_pep(self, account, pep_dict):
+ connection = gajim.connections[account]
+
+ if 'activity' in pep_dict:
activity = pep_dict['activity']
- if 'subactivity' in pep_dict and \
- pep_dict['subactivity'] in pep.ACTIVITIES[activity]:
- subactivity = pep_dict['subactivity']
- else:
- subactivity = 'other'
- if 'activity_text' in pep_dict:
- activity_text = pep_dict['activity_text']
- else:
- activity_text = ''
- pep.user_send_activity(account, activity, subactivity, activity_text)
+ subactivity = pep_dict.get('subactivity', None)
+ activity_text = pep_dict.get('activity_text', None)
+ connection.send_activity(activity, subactivity, activity_text)
else:
- pep.user_send_activity(account, '')
+ connection.retract_activity()
- # mood
- if 'mood' in pep_dict and pep_dict['mood'] in pep.MOODS:
+ if 'mood' in pep_dict:
mood = pep_dict['mood']
- if 'mood_text' in pep_dict:
- mood_text = pep_dict['mood_text']
- else:
- mood_text = ''
- pep.user_send_mood(account, mood, mood_text)
+ mood_text = pep_dict.get('mood_text', None)
+ connection.send_mood(mood, mood_text)
else:
- pep.user_send_mood(account, '')
+ 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()
def send_status_continue(self, account, status, txt, auto, to):
if gajim.account_is_connected(account) and not to:
@@ -2010,8 +2001,7 @@ class RosterWindow:
gajim.connections[account].send_custom_status(status, txt, to)
else:
if status in ('invisible', 'offline'):
- pep.delete_pep(gajim.get_jid_from_account(account), \
- account)
+ 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)
@@ -2038,7 +2028,9 @@ class RosterWindow:
def chg_contact_status(self, contact, show, status, account):
- '''When a contact changes his or her status'''
+ """
+ When a contact changes his or her status
+ """
contact_instances = gajim.contacts.get_contacts(account, contact.jid)
contact.show = show
contact.status = status
@@ -2089,14 +2081,16 @@ class RosterWindow:
contact_instances)
if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
and not contact.is_groupchat():
- pep.delete_pep(contact.jid, account)
+ self.delete_pep(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'''
+ """
+ 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)
@@ -2131,13 +2125,15 @@ class RosterWindow:
self.update_status_combobox()
def get_status_message(self, show, on_response, show_pep=True,
- always_ask=False):
- ''' get the status message by:
+ 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'):
@@ -2219,7 +2215,9 @@ class RosterWindow:
gajim.config.get('roster_height'))
def close_all_from_dict(self, dic):
- '''close all the windows in the given dictionary'''
+ """
+ Close all the windows in the given dictionary
+ """
for w in dic.values():
if isinstance(w, dict):
self.close_all_from_dict(w)
@@ -2227,9 +2225,10 @@ class RosterWindow:
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
- '''
+ """
+ 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):
@@ -2237,7 +2236,9 @@ class RosterWindow:
force = force)
def on_roster_window_delete_event(self, widget, event):
- '''Main window X button was clicked'''
+ """
+ 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()
@@ -2287,14 +2288,18 @@ class RosterWindow:
gajim.interface.hide_systray()
def quit_gtkgui_interface(self):
- '''When we quit the gtk interface : exit gtk'''
+ """
+ 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 '''
+ """
+ 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:
@@ -2302,7 +2307,7 @@ class RosterWindow:
get_msg = True
break
- def on_continue2(message, pep_dict):
+ def on_continue3(message, pep_dict):
self.quit_on_next_offline = 0
accounts_to_disconnect = []
for acct in accounts:
@@ -2317,6 +2322,25 @@ class RosterWindow:
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
+
+ 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
@@ -2363,7 +2387,9 @@ class RosterWindow:
self.make_menu()
def on_edit_menuitem_activate(self, widget):
- '''need to call make_menu to build profile, avatar item'''
+ """
+ Need to call make_menu to build profile, avatar item
+ """
self.make_menu()
def on_bookmark_menuitem_activate(self, widget, account, bookmark):
@@ -2414,7 +2440,9 @@ class RosterWindow:
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'''
+ """
+ Call vcard_information_window class to display contact's information
+ """
if gajim.connections[account].is_zeroconf:
self.on_info_zeroconf(widget, contact, account)
return
@@ -2503,11 +2531,10 @@ class RosterWindow:
account_name = account
if gajim.account_is_connected(account):
account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
- contact = gajim.contacts.create_contact(jid=jid, account=account, name=account_name,
- show=connection.get_status(), sub='', status=connection.status,
- resource=connection.server_resource,
- priority=connection.priority, mood=connection.mood,
- tune=connection.tune, activity=connection.activity)
+ 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')
@@ -2544,16 +2571,22 @@ class RosterWindow:
self.show_tooltip, contacts)
def on_agent_logging(self, widget, jid, state, account):
- '''When an agent is requested to log in or off'''
+ """
+ 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'''
+ """
+ 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'''
+ """
+ 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:
@@ -2597,8 +2630,10 @@ class RosterWindow:
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)'''
+ """
+ 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
@@ -2661,7 +2696,9 @@ class RosterWindow:
_('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. '''
+ """
+ When clicked on the 'unblock' button in context menu.
+ """
accounts = []
if group is None:
for (contact, account) in list_:
@@ -2878,7 +2915,9 @@ class RosterWindow:
dialogs.EditGroupsDialog(list_)
def on_history(self, widget, contact, account):
- '''When history menuitem is activated: call log window'''
+ """
+ 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)
@@ -2887,7 +2926,9 @@ class RosterWindow:
HistoryWindow(contact.jid, account)
def on_disconnect(self, widget, jid, account):
- '''When disconnect menuitem is activated: disconect from room'''
+ """
+ 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()
@@ -2895,7 +2936,9 @@ class RosterWindow:
self.remove_groupchat(jid, account)
def on_reconnect(self, widget, jid, account):
- '''When disconnect menuitem is activated: disconect from room'''
+ """
+ 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,
@@ -2922,8 +2965,9 @@ class RosterWindow:
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 '''
+ """
+ Resource parameter MUST NOT be used if more than one contact in list
+ """
account_list = []
jid_list = []
for (contact, account) in list_:
@@ -2952,9 +2996,10 @@ class RosterWindow:
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 '''
+ 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
@@ -2967,7 +3012,9 @@ class RosterWindow:
self.on_groupchat_maximized(widget, contact.jid, account)
def on_groupchat_maximized(self, widget, jid, account):
- '''When a groupchat is maximised'''
+ """
+ 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)
@@ -3023,7 +3070,9 @@ class RosterWindow:
self.tooltip.hide_tooltip()
def on_roster_treeview_key_press_event(self, widget, event):
- '''when a key is pressed in the treeviews'''
+ """
+ When a key is pressed in the treeviews
+ """
self.tooltip.hide_tooltip()
if event.keyval == gtk.keysyms.Escape:
self.tree.get_selection().unselect_all()
@@ -3046,6 +3095,9 @@ class RosterWindow:
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_:
@@ -3055,9 +3107,6 @@ class RosterWindow:
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':
@@ -3170,7 +3219,9 @@ class RosterWindow:
self.tree.expand_row(path, False)
def on_req_usub(self, widget, list_):
- '''Remove a contact. list_ is a list of (contact, account) tuples'''
+ """
+ Remove a contact. list_ is a list of (contact, account) tuples
+ """
def on_ok(is_checked, list_):
remove_auth = True
if len(list_) == 1:
@@ -3229,7 +3280,9 @@ class RosterWindow:
on_response_ok = (on_ok2, list_))
def on_send_custom_status(self, widget, contact_list, show, group=None):
- '''send custom status'''
+ """
+ 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
@@ -3286,7 +3339,9 @@ class RosterWindow:
_('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'''
+ """
+ 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
@@ -3396,23 +3451,35 @@ class RosterWindow:
gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow()
def on_publish_tune_toggled(self, widget, account):
- act = widget.get_active()
- gajim.config.set_per('accounts', account, 'publish_tune', act)
- if act:
+ active = widget.get_active()
+ gajim.config.set_per('accounts', account, 'publish_tune', active)
+ if active:
gajim.interface.enable_music_listener()
else:
- # disable it only if no other account use it
- for acct in gajim.connections:
- if gajim.config.get_per('accounts', acct, 'publish_tune'):
+ 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()
- if gajim.connections[account].pep_supported:
- # As many implementations don't support retracting items, we send a
- # "Stopped" event first
- pep.user_send_tune(account, '')
- pep.user_retract_tune(account)
+ 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):
@@ -3426,7 +3493,9 @@ class RosterWindow:
dialogs.AddNewContactWindow(account)
def on_join_gc_activate(self, widget, account):
- '''when the join gc menuitem is clicked, show the join gc window'''
+ """
+ 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 '
@@ -3435,7 +3504,6 @@ class RosterWindow:
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)
@@ -3489,9 +3557,10 @@ class RosterWindow:
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.'''
+ """
+ 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
@@ -3558,8 +3627,10 @@ class RosterWindow:
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)'''
+ """
+ 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]
@@ -3623,12 +3694,16 @@ class RosterWindow:
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'''
+ """
+ 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'''
+ """
+ When a row is expanded change the icon of the arrow
+ """
self._toggeling_row = True
model = widget.get_model()
child_model = model.get_model()
@@ -3688,7 +3763,9 @@ class RosterWindow:
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'''
+ """
+ When a row is collapsed change the icon of the arrow
+ """
self._toggeling_row = True
model = widget.get_model()
child_model = model.get_model()
@@ -3732,10 +3809,11 @@ class RosterWindow:
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.
+ """
+ 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
@@ -3804,8 +3882,9 @@ class RosterWindow:
pass
def on_show_offline_contacts_menuitem_activate(self, widget):
- '''when show offline option is changed:
- redraw the treeview'''
+ """
+ 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')
@@ -3818,8 +3897,9 @@ class RosterWindow:
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'''
+ """
+ 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()
@@ -3935,6 +4015,9 @@ class RosterWindow:
_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,
@@ -4228,10 +4311,12 @@ class RosterWindow:
################################################################################
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'''
+ """
+ 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]:
@@ -4243,7 +4328,9 @@ class RosterWindow:
return gajim.interface.jabber_state_images[size]
def make_transport_state_images(self, transport):
- '''initialise opened and closed 'transport' iconset dict'''
+ """
+ Initialize opened and closed 'transport' iconset dict
+ """
if gajim.config.get('use_transports_iconsets'):
folder = os.path.join(helpers.get_transport_path(transport),
'16x16')
@@ -4340,7 +4427,9 @@ class RosterWindow:
win.repaint_themed_widgets()
def repaint_themed_widgets(self):
- '''Notify windows that contain themed widgets to repaint them'''
+ """
+ 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:
@@ -4359,13 +4448,17 @@ class RosterWindow:
ctrl.show_avatar()
def on_roster_treeview_style_set(self, treeview, style):
- '''When style (theme) changes, redraw all contacts'''
+ """
+ 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'''
+ """
+ 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)
@@ -4374,22 +4467,15 @@ class RosterWindow:
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')
+ """
+ When a row is added, set properties for icon renderer
+ """
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)
+ self._set_account_row_background_color(renderer)
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)
+ 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]:
@@ -4397,18 +4483,7 @@ class RosterWindow:
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)
+ 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)
@@ -4417,7 +4492,9 @@ class RosterWindow:
renderer.set_property('width', 26)
def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
- '''When a row is added, set properties for name renderer'''
+ """
+ When a row is added, set properties for name renderer
+ """
theme = gajim.config.get('roster_theme')
type_ = model[titer][C_TYPE]
if type_ == 'account':
@@ -4426,29 +4503,21 @@ class RosterWindow:
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)
+ 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)
- 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)
+ 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
@@ -4468,18 +4537,7 @@ class RosterWindow:
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)
+ 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)
@@ -4488,170 +4546,36 @@ class RosterWindow:
else:
renderer.set_property('xpad', 8)
-
- def _fill_mood_pixbuf_renderer(self, column, renderer, model, titer,
- data = None):
- '''When a row is added, set properties for avatar renderer'''
+ 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][C_MOOD_PIXBUF]:
- renderer.set_property('visible', True)
- else:
+ if not model[titer][data]:
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')
- if color:
- renderer.set_property(
- 'cell-background', color)
- else:
- renderer.set_property(
- 'cell-background', None)
- # align pixbuf to the right
- renderer.set_property('xalign', 1)
-
-
- def _fill_activity_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_ == 'group':
- renderer.set_property('visible', False)
- return
-
- # allocate space for the icon only if needed
- if model[titer][C_ACTIVITY_PIXBUF]:
- 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')
- if color:
- renderer.set_property(
- 'cell-background', color)
- else:
- renderer.set_property(
- 'cell-background', None)
- # align pixbuf to the right
- renderer.set_property('xalign', 1)
-
-
- def _fill_tune_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_ == 'group':
- renderer.set_property('visible', False)
- return
-
- # allocate space for the icon only if needed
- if model[titer][C_TUNE_PIXBUF]:
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')
- if color:
- renderer.set_property(
- 'cell-background', color)
- else:
- renderer.set_property(
- 'cell-background', None)
- # align pixbuf to the right
- renderer.set_property('xalign', 1)
+ if type_ == 'account':
+ self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
+ 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'''
- theme = gajim.config.get('roster_theme')
+ 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)
@@ -4661,56 +4585,72 @@ class RosterWindow:
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 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')
+ 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)
- 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)
+ 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_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)
+
################################################################################
### 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'''
+ """
+ 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')
@@ -5037,10 +4977,7 @@ class RosterWindow:
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)
+ gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
sub_menu.append(item)
item.connect('activate', self.on_change_status_message_activate,
account)
@@ -5059,17 +4996,22 @@ class RosterWindow:
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)
+ 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()
@@ -5138,10 +5080,7 @@ class RosterWindow:
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)
+ gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
sub_menu.append(item)
item.connect('activate', self.on_change_status_message_activate,
account)
@@ -5161,7 +5100,9 @@ class RosterWindow:
return account_context_menu
def make_account_menu(self, event, titer):
- '''Make account's popup menu'''
+ """
+ Make account's popup menu
+ """
model = self.modelfilter
account = model[titer][C_ACCOUNT].decode('utf-8')
@@ -5193,7 +5134,9 @@ class RosterWindow:
menu.popup(None, None, None, event_button, event.time)
def make_group_menu(self, event, titer):
- '''Make group's popup menu'''
+ """
+ Make group's popup menu
+ """
model = self.modelfilter
path = model.get_path(titer)
group = model[titer][C_JID].decode('utf-8')
@@ -5290,11 +5233,7 @@ class RosterWindow:
# 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)
+ 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)
@@ -5350,7 +5289,9 @@ class RosterWindow:
menu.popup(None, None, None, event_button, event.time)
def make_contact_menu(self, event, titer):
- '''Make contact\'s popup menu'''
+ """
+ Make contact's popup menu
+ """
model = self.modelfilter
jid = model[titer][C_JID].decode('utf-8')
tree_path = model.get_path(titer)
@@ -5362,7 +5303,9 @@ class RosterWindow:
menu.popup(None, None, None, event_button, event.time)
def make_multiple_contact_menu(self, event, iters):
- '''Make group's popup menu'''
+ """
+ Make group's popup menu
+ """
model = self.modelfilter
list_ = [] # list of (jid, account) tuples
one_account_offline = False
@@ -5462,7 +5405,9 @@ class RosterWindow:
menu.popup(None, None, None, event_button, event.time)
def make_transport_menu(self, event, titer):
- '''Make transport\'s popup menu'''
+ """
+ Make transport's popup menu
+ """
model = self.modelfilter
jid = model[titer][C_JID].decode('utf-8')
path = model.get_path(titer)
@@ -5546,11 +5491,7 @@ class RosterWindow:
# 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)
+ 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):
@@ -5649,7 +5590,9 @@ class RosterWindow:
menu.popup(None, None, None, event_button, event.time)
def get_and_connect_advanced_menuitem_menu(self, account):
- '''adds FOR ACCOUNT options'''
+ """
+ Add FOR ACCOUNT options
+ """
xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade')
advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu')
@@ -5696,8 +5639,9 @@ class RosterWindow:
return advanced_menuitem_menu
def add_history_manager_menuitem(self, menu):
- '''adds a seperator and History Manager menuitem BELOW for account
- menuitems'''
+ """
+ Add a seperator and History Manager menuitem BELOW for account menuitems
+ """
item = gtk.SeparatorMenuItem() # separator
menu.append(item)
@@ -5710,20 +5654,24 @@ class RosterWindow:
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'''
+ """
+ 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
+ # 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:
- item = gtk.MenuItem(bookmark['name'], False) # Do not use underline
+ # 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)
@@ -5771,6 +5719,15 @@ class RosterWindow:
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
+ """
+ account = gajim.connections.keys()[0]
+ self.on_join_gc_activate(None, account)
+ return True
+
################################################################################
###
################################################################################
@@ -5821,7 +5778,7 @@ class RosterWindow:
self.popups_notification_height = 0
self.popup_notification_windows = []
- # Remove contact from roster when last event opened
+ # 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)
@@ -5862,7 +5819,7 @@ class RosterWindow:
# 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')
+ 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
@@ -5917,8 +5874,7 @@ class RosterWindow:
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.add_attribute(render_pixbuf, 'pixbuf', C_AVATAR_PIXBUF)
col.set_cell_data_func(render_pixbuf,
self._fill_avatar_pixbuf_renderer, None)
@@ -5941,19 +5897,30 @@ class RosterWindow:
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_mood_pixbuf_renderer, None)
+ 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_activity_pixbuf_renderer, None)
+ 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_tune_pixbuf_renderer, None)
+ 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)
+
+ 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()
@@ -6003,19 +5970,27 @@ class RosterWindow:
if gajim.config.get('show_roster_on_startup'):
self.window.show_all()
else:
- if not gajim.config.get('trayicon') or not \
- gajim.interface.systray_capabilities:
- # cannot happen via GUI, but I put this incase user touches
- # config. without trayicon, he or she should see the roster!
+ 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
- gajim.interface.instances['account_creation_wizard'] = \
- config.AccountCreationWizardWindow()
+ 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:
diff --git a/src/search_window.py b/src/search_window.py
index 3a21db543..bce009207 100644
--- a/src/search_window.py
+++ b/src/search_window.py
@@ -32,8 +32,9 @@ import dataforms_widget
class SearchWindow:
def __init__(self, account, jid):
- '''Create new window.'''
-
+ """
+ Create new window
+ """
# an account object
self.account = account
self.jid = jid
@@ -231,5 +232,4 @@ class SearchWindow:
self.window.set_title('%s - Search - Gajim' % \
self.data_form_widget.title)
-
# vim: se ts=3:
diff --git a/src/session.py b/src/session.py
index a2e911a55..e13eb0b8f 100644
--- a/src/session.py
+++ b/src/session.py
@@ -57,7 +57,9 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
self.detach_from_control()
def get_chatstate(self, msg, msgtxt):
- '''extracts chatstate from a <message/> stanza'''
+ """
+ Extract chatstate from a <message/> stanza
+ """
composing_xep = None
chatstate = None
@@ -83,7 +85,9 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
return (composing_xep, chatstate)
def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg):
- '''dispatch a received <message> stanza'''
+ """
+ Dispatch a received <message> stanza
+ """
msg_type = msg.getType()
subject = msg.getSubject()
resource = gajim.get_resource_from_jid(full_jid_with_resource)
@@ -271,9 +275,11 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
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'''
+ 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
diff --git a/src/statusicon.py b/src/statusicon.py
index 90bdb8c91..17bfa7b83 100644
--- a/src/statusicon.py
+++ b/src/statusicon.py
@@ -25,26 +25,73 @@
import sys
import gtk
-import systray
+import gobject
+import os
+
+import dialogs
+import config
+import tooltips
+import gtkgui_helpers
+import tooltips
from common import gajim
from common import helpers
+from common import pep
+
+class StatusIcon:
+ """
+ Class for the notification area icon
+ """
-class StatusIcon(systray.Systray):
- '''Class for the notification area icon'''
- #NOTE: gtk api does NOT allow:
- # leave, enter motion notify
- # and can't do cool tooltips we use
def __init__(self):
- systray.Systray.__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 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_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)
self.set_img()
self.status_icon.set_visible(True)
@@ -53,6 +100,11 @@ class StatusIcon(systray.Systray):
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 hide_icon(self):
self.status_icon.set_visible(False)
self.unsubscribe_events()
@@ -61,25 +113,321 @@ class StatusIcon(systray.Systray):
self.on_left_click()
def set_img(self):
- '''apart from image, we also update tooltip text here'''
+ """
+ Apart from image, we also update tooltip text here
+ """
if not gajim.interface.systray_enabled:
return
- text = helpers.get_notification_icon_tooltip_text()
- self.status_icon.set_tooltip(text)
if gajim.events.get_nb_systray_events():
- state = 'event'
self.status_icon.set_blinking(True)
else:
- state = self.status
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'][state]
+ # 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:
+ # 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 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_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()
+
+ 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
+
+ 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)
+
+ # 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'])
+
+ 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.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_with_private_storage = 0
+
+ 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')
+
+ 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)
+
+ 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
+
+ # 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 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()
+
+ 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'))
+
+ 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)
+
+ 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_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_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?
+
+ # 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 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_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:
diff --git a/src/systray.py b/src/systray.py
deleted file mode 100644
index e2c09331b..000000000
--- a/src/systray.py
+++ /dev/null
@@ -1,459 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/systray.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Norman Rasmussen <norman AT rasmussen.co.za>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Julien Pivotto <roidelapluie AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import gtk
-import gobject
-import os
-
-import dialogs
-import config
-import tooltips
-import gtkgui_helpers
-
-from common import gajim
-from common import helpers
-from common import pep
-
-HAS_SYSTRAY_CAPABILITIES = True
-
-try:
- import egg.trayicon as trayicon # gnomepythonextras trayicon
-except Exception:
- try:
- import trayicon # our trayicon
- except Exception:
- gajim.log.debug('No trayicon module available')
- HAS_SYSTRAY_CAPABILITIES = False
-
-
-class Systray:
- '''Class for icon in the notification area
- This class is both base class (for statusicon.py) and normal class
- for trayicon in GNU/Linux'''
-
- def __init__(self):
- self.single_message_handler_id = None
- self.new_chat_handler_id = None
- self.t = None
- # click somewhere else does not popdown menu. workaround this.
- self.added_hide_menuitem = False
- self.img_tray = gtk.Image()
- 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 = []
-
- 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 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 set_img(self):
- if not gajim.interface.systray_enabled:
- return
- if gajim.events.get_nb_systray_events():
- state = 'event'
- else:
- state = self.status
- if state != 'event' and gajim.config.get('trayicon') == 'on_event':
- self.t.hide()
- else:
- self.t.show()
- image = gajim.interface.jabber_state_images['16'][state]
- if image.get_storage_type() == gtk.IMAGE_ANIMATION:
- self.img_tray.set_from_animation(image.get_animation())
- elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
- self.img_tray.set_from_pixbuf(image.get_pixbuf())
-
- 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 on_single_message_menuitem_activate(self, widget, account):
- dialogs.SingleMessageWindow(account, action = 'send')
-
- 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()
-
- 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
-
- 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)
-
- # 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'])
-
- 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.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_with_private_storage = 0
-
- 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')
-
- 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)
-
- 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
-
- # 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 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()
-
- 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'))
-
- 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)
-
- 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_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_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?
-
- # 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 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_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()
-
- def show_tooltip(self, widget):
- position = widget.window.get_origin()
- if self.tooltip.id == position:
- size = widget.window.get_size()
- self.tooltip.show_tooltip('', size[1], position[1])
-
- def on_tray_motion_notify_event(self, widget, event):
- position = widget.window.get_origin()
- if self.tooltip.timeout > 0:
- if self.tooltip.id != position:
- self.tooltip.hide_tooltip()
- if self.tooltip.timeout == 0 and \
- self.tooltip.id != position:
- self.tooltip.id = position
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, widget)
-
- def on_tray_leave_notify_event(self, widget, event):
- position = widget.window.get_origin()
- if self.tooltip.timeout > 0 and \
- self.tooltip.id == position:
- self.tooltip.hide_tooltip()
-
- def on_tray_destroyed(self, widget):
- '''re-add trayicon when systray is destroyed'''
- self.t = None
- if gajim.interface.systray_enabled:
- self.show_icon()
-
- def show_icon(self):
- if not self.t:
- self.t = trayicon.TrayIcon('Gajim')
- self.t.connect('destroy', self.on_tray_destroyed)
- eb = gtk.EventBox()
- # avoid draw seperate bg color in some gtk themes
- eb.set_visible_window(False)
- eb.set_events(gtk.gdk.POINTER_MOTION_MASK)
- eb.connect('button-press-event', self.on_clicked)
- eb.connect('motion-notify-event', self.on_tray_motion_notify_event)
- eb.connect('leave-notify-event', self.on_tray_leave_notify_event)
- self.tooltip = tooltips.NotificationAreaTooltip()
-
- self.img_tray = gtk.Image()
- eb.add(self.img_tray)
- self.t.add(eb)
- self.set_img()
- self.subscribe_events()
- self.t.show_all()
-
- def hide_icon(self):
- if self.t:
- self.t.destroy()
- self.t = None
- self.unsubscribe_events()
-
-# vim: se ts=3:
diff --git a/src/tooltips.py b/src/tooltips.py
index fa96cbd8c..aeaf06b1c 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -41,7 +41,9 @@ from common import helpers
from common.pep import MOODS, ACTIVITIES
class BaseTooltip:
- ''' Base Tooltip class;
+ """
+ Base Tooltip class
+
Usage:
tooltip = BaseTooltip()
....
@@ -57,22 +59,28 @@ class BaseTooltip:
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 populate(self, data):
- ''' this method must be overriden by all extenders
- This is the most simple implementation: show data as value of a label
- '''
+ """
+ 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 '''
+ """
+ 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)
@@ -86,10 +94,12 @@ class BaseTooltip:
self.screen = self.win.get_screen()
def _get_icon_name_for_tooltip(self, contact):
- ''' helper function used for tooltip contacts/acounts
+ """
+ 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', ''):
@@ -107,9 +117,8 @@ class BaseTooltip:
self.screen.get_width() + half_width:
self.preferred_position[0] = self.screen.get_width() - \
requisition.width
- else:
+ elif not self.check_last_time:
self.preferred_position[0] -= half_width
- self.screen.get_height()
if self.preferred_position[1] + requisition.height > \
self.screen.get_height():
# flip tooltip up
@@ -133,11 +142,14 @@ class BaseTooltip:
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
- '''
+ """
+ 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)
@@ -161,10 +173,15 @@ class BaseTooltip:
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
@@ -201,8 +218,10 @@ class StatusTable:
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 '''
+ show_lock=False, indent=True):
+ """
+ Append a new row with status icon to the table
+ """
self.current_row += 1
state_file = show.replace(' ', '_')
files = []
@@ -235,7 +254,10 @@ class StatusTable:
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)
@@ -269,7 +291,7 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
for line in acct['event_lines']:
self.add_text_row(' ' + line, 1)
- def populate(self, data):
+ def populate(self, data=''):
self.create_window()
self.create_table()
@@ -280,10 +302,13 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
self.table.set_property('column-spacing', 1)
self.hbox.add(self.table)
- self.win.add(self.hbox)
+ 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()
@@ -378,7 +403,10 @@ class GCTooltip(BaseTooltip):
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()
@@ -474,6 +502,20 @@ class RosterTooltip(NotificationAreaTooltip):
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':
@@ -541,6 +583,23 @@ class RosterTooltip(NotificationAreaTooltip):
properties.append((_('OpenPGP: '),
gobject.markup_escape_text(keyID)))
+ 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))
+
while properties:
property_ = properties.pop(0)
vcard_current_row += 1
@@ -561,7 +620,7 @@ class RosterTooltip(NotificationAreaTooltip):
vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
vertical_fill, 0, 0)
else:
- if isinstance(property_[0], (unicode, str)): #FIXME: rm unicode?
+ if isinstance(property_[0], (unicode, str)): # FIXME: rm unicode?
label.set_markup(property_[0])
label.set_line_wrap(True)
else:
@@ -574,75 +633,46 @@ class RosterTooltip(NotificationAreaTooltip):
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 _append_pep_info(self, contact, properties):
- '''
- Append Tune, Mood, Activity information of the specified contact
+ """
+ Append Tune, Mood, Activity, Location information of the specified contact
to the given property list.
- '''
- if 'mood' in contact.mood:
- mood = contact.mood['mood'].strip()
- mood = MOODS.get(mood, mood)
- mood = gobject.markup_escape_text(mood)
- mood_string = _('Mood:') + ' <b>%s</b>' % mood
- if 'text' in contact.mood \
- and contact.mood['text'] != '':
- mood_text = contact.mood['text'].strip()
- mood_text = \
- gobject.markup_escape_text(mood_text)
- mood_string += ' (%s)' % mood_text
+ """
+ if 'mood' in contact.pep:
+ mood = contact.pep['mood'].asMarkupText()
+ mood_string = _('Mood:') + ' %s' % mood
properties.append((mood_string, None))
- if 'activity' in contact.activity:
- activity = act_plain = \
- contact.activity['activity'].strip()
- activity = gobject.markup_escape_text(activity)
- if act_plain in ACTIVITIES:
- activity = ACTIVITIES[activity]['category']
- activity_string = _('Activity:') + ' <b>%s' % activity
- if 'subactivity' in contact.activity:
- activity_sub = \
- contact.activity['subactivity'].strip()
- if act_plain in ACTIVITIES and activity_sub in \
- ACTIVITIES[act_plain]:
- activity_sub = ACTIVITIES[act_plain][activity_sub]
- activity_sub = \
- gobject.markup_escape_text(activity_sub)
- activity_string += ': %s</b>' % activity_sub
- else:
- activity_string += '</b>'
- if 'text' in contact.activity:
- activity_text = contact.activity['text'].strip()
- activity_text = gobject.markup_escape_text(
- activity_text)
- activity_string += ' (%s)' % activity_text
+ if 'activity' in contact.pep:
+ activity = contact.pep['activity'].asMarkupText()
+ activity_string = _('Activity:') + ' %s' % activity
properties.append((activity_string, None))
- if 'artist' in contact.tune \
- or 'title' in contact.tune:
- if 'artist' in contact.tune:
- artist = contact.tune['artist'].strip()
- artist = gobject.markup_escape_text(artist)
- else:
- artist = _('Unknown Artist')
- if 'title' in contact.tune:
- title = contact.tune['title'].strip()
- title = gobject.markup_escape_text(title)
- else:
- title = _('Unknown Title')
- if 'source' in contact.tune:
- source = contact.tune['source'].strip()
- source = gobject.markup_escape_text(source)
- else:
- source = _('Unknown Source')
- tune_string = _('Tune:') + ' ' + \
- _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
- 'from <i>%(source)s</i>') % {'title': title,
- 'artist': artist, 'source': source}
+ 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))
+
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)
@@ -726,7 +756,9 @@ class FileTransfersTooltip(BaseTooltip):
class ServiceDiscoveryTooltip(BaseTooltip):
- ''' Tooltip that is shown when hovering over a service discovery row '''
+ """
+ Tooltip that is shown when hovering over a service discovery row
+ """
def populate(self, status):
self.create_window()
label = gtk.Label()
diff --git a/src/trayicon.defs b/src/trayicon.defs
deleted file mode 100644
index 6d253cf83..000000000
--- a/src/trayicon.defs
+++ /dev/null
@@ -1,59 +0,0 @@
-;; -*- scheme -*-
-
-; object definitions ...
-(define-object TrayIcon
- (in-module "Egg")
- (parent "GtkPlug")
- (c-name "EggTrayIcon")
- (gtype-id "EGG_TYPE_TRAY_ICON")
-)
-
-;; Enumerations and flags ...
-
-
-;; From eggtrayicon.h
-
-(define-function egg_tray_icon_get_type
- (c-name "egg_tray_icon_get_type")
- (return-type "GType")
-)
-
-(define-function egg_tray_icon_new_for_screen
- (c-name "egg_tray_icon_new_for_screen")
- (return-type "EggTrayIcon*")
- (parameters
- '("GdkScreen*" "screen")
- '("const-gchar*" "name")
- )
-)
-
-(define-function egg_tray_icon_new
- (c-name "egg_tray_icon_new")
- (is-constructor-of "EggTrayIcon")
- (return-type "EggTrayIcon*")
- (parameters
- '("const-gchar*" "name")
- )
-)
-
-(define-method send_message
- (of-object "EggTrayIcon")
- (c-name "egg_tray_icon_send_message")
- (return-type "guint")
- (parameters
- '("gint" "timeout")
- '("const-char*" "message")
- '("gint" "len")
- )
-)
-
-(define-method cancel_message
- (of-object "EggTrayIcon")
- (c-name "egg_tray_icon_cancel_message")
- (return-type "none")
- (parameters
- '("guint" "id")
- )
-)
-
-
diff --git a/src/trayicon.override b/src/trayicon.override
deleted file mode 100644
index 75bd7ed8f..000000000
--- a/src/trayicon.override
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4 -*-
- * src/trayicon.override
- *
- * Copyright (C) 2004-2005 Yann Leboulanger <asterix AT lagaule.org>
- *
- * This file is part of Gajim.
- *
- * Gajim is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; version 3 only.
- *
- * Gajim is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Gajim. If not, see <http://www.gnu.org/licenses/>.
- */
-%%
-headers
-#include <Python.h>
-
-#include "pygobject.h"
-#include "eggtrayicon.h"
-%%
-modulename trayicon
-%%
-import gtk.Plug as PyGtkPlug_Type
-import gtk.gdk.Screen as PyGdkScreen_Type
-%%
-ignore-glob
- *_get_type
-%%
-override egg_tray_icon_send_message kwargs
-static PyObject*
-_wrap_egg_tray_icon_send_message(PyGObject *self, PyObject *args, PyObject *kwargs)
-{
- static char *kwlist[] = {"timeout", "message", NULL};
- int timeout, len, ret;
- char *message;
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "is#:TrayIcon.send_message", kwlist, &timeout, &message, &len))
- return NULL;
- ret = egg_tray_icon_send_message(EGG_TRAY_ICON(self->obj), timeout, message, len);
- return PyInt_FromLong(ret);
-}
diff --git a/src/trayiconmodule.c b/src/trayiconmodule.c
deleted file mode 100644
index 7a2b1206f..000000000
--- a/src/trayiconmodule.c
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4 -*-
- * src/trayiconmodule.c
- *
- * Copyright (C) 2004-2005 Yann Leboulanger <asterix AT lagaule.org>
- *
- * This file is part of Gajim.
- *
- * Gajim is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; version 3 only.
- *
- * Gajim is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Gajim. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/* include this first, before NO_IMPORT_PYGOBJECT is defined */
-#include <pygobject.h>
-
-void trayicon_register_classes (PyObject *d);
-
-extern PyMethodDef trayicon_functions[];
-
-DL_EXPORT(void)
-inittrayicon(void)
-{
- PyObject *m, *d;
-
- init_pygobject ();
-
- m = Py_InitModule ("trayicon", trayicon_functions);
- d = PyModule_GetDict (m);
-
- trayicon_register_classes (d);
-
- if (PyErr_Occurred ()) {
- Py_FatalError ("can't initialise module trayicon :(");
- }
-}
diff --git a/src/vcard.py b/src/vcard.py
index 5ee86044b..b2f8fd084 100644
--- a/src/vcard.py
+++ b/src/vcard.py
@@ -45,8 +45,11 @@ 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'''
+ """
+ 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
@@ -71,7 +74,9 @@ def get_avatar_pixbuf_encoded_mime(photo):
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
@@ -151,14 +156,15 @@ class VcardWindow:
self.window.destroy()
def on_PHOTO_eventbox_button_press_event(self, widget, event):
- '''If right-clicked, show popup'''
+ """
+ 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')
+ self.contact.jid, self.account, self.contact.get_shown_name())
menu.append(menuitem)
menu.connect('selection-done', lambda w:w.destroy())
# show the menu
@@ -465,14 +471,15 @@ class ZeroconfVcardWindow:
self.window.destroy()
def on_PHOTO_eventbox_button_press_event(self, widget, event):
- '''If right-clicked, show popup'''
+ """
+ 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')
+ self.contact.jid, self.account, self.contact.get_shown_name())
menu.append(menuitem)
menu.connect('selection-done', lambda w:w.destroy())
# show the menu
@@ -534,10 +541,6 @@ class ZeroconfVcardWindow:
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)
-
self.xml.get_widget('resource_prio_label').set_text(resources)
resource_prio_label_eventbox = self.xml.get_widget(
'resource_prio_label_eventbox')
@@ -545,8 +548,6 @@ class ZeroconfVcardWindow:
self.fill_status_label()
- # 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'):