merge diff from default

This commit is contained in:
Yann Leboulanger 2009-11-25 21:15:36 +01:00
commit a59011138b
122 changed files with 4560 additions and 5244 deletions

View File

@ -2,13 +2,9 @@ syntax: glob
*.orig
*.gmo
*.in
*.la
*.lo
*.m4
*.pyc
*.pyo
*.o
*.Plo
*~
autom4te.cache
data/defs.py
@ -23,6 +19,4 @@ Makefile
syntax: regexp
^config\.*
^config\/
^src\/\.libs
^src\/trayicon.c
^scripts\/gajim.*

View File

@ -1,5 +1,6 @@
CURRENT DEVELOPERS:
Alexander Cherniuk (ts33kr AT gmail.com)
Nikos Kouremenos (kourem AT gmail.com)
Yann Leboulanger (asterix AT lagaule.org)
Julien Pivotto (roidelapluie AT gmail.com)

View File

@ -50,7 +50,6 @@ MAINTAINERCLEANFILES = \
aclocal.m4 \
libtool \
po/POTFILES.in \
src/trayicon_la-eggtrayicon.loT \
m4/intltool.m4
MAINTAINERCLEANDIRS = \

View File

@ -16,7 +16,7 @@ Welcome to Gajim and thank you for trying out our client.
<h2>Runtime Requirements</h2>
<ul>
<li>python2.5 or higher</li>
<li>pygtk2.12 or higher</li>
<li>pygtk2.16 or higher</li>
<li>python-libglade</li>
<li>pysqlite2 (if you have python 2.5, you already have this)</li>
</ul>
@ -34,7 +34,6 @@ Gajim is a GTK+ app that loves GNOME. You can do 'make' so you don't require gno
<li>For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi</li>
<li>dnsutils (or whatever package provides the nslookup binary) for SRV support</li>
<li>gtkspell and aspell-LANG where lang is your locale eg. en, fr etc</li>
<li>GnomePythonExtras 2.10 or above (aka gnome-python-desktop) so you can avoid compiling trayicon and gtkspell</li>
<li>gnome-python-desktop (for GnomeKeyring support)</li>
<li>notification-daemon or notify-python (and D-Bus) to get cooler popups</li>
<li>D-Bus running to have gajim-remote working. Some distributions split dbus-x11, which is needed for dbus to work with Gajim. Version >= 0.80 is required.</li>
@ -53,8 +52,6 @@ the xml lib that *comes* with python and not pyxml or whatever.
<ul>
<li>python-dev</li>
<li>python-gtk2-dev</li>
<li>libgtk2.0-dev aka. gtk2-devel</li>
<li>libgtkspell-dev (for the gtkspell module)</li>
<li>intltool (>= 0.40.1)</li>
</ul>

View File

@ -1,10 +1,10 @@
Anders Ström
Christophe Got
Dennis Craven
Dmitry Korzhevin
Guillaume Morin
Gvorcek Spajreh
Josef Vybíral
Membris Khan
Rederick Asher
Jakub Szypulka

View File

@ -39,7 +39,7 @@ AM_NLS
dnl ****
dnl pygtk and gtk+
dnl ****
PKG_CHECK_MODULES([PYGTK], [gtk+-2.0 >= 2.12.0 pygtk-2.0 >= 2.12.0])
PKG_CHECK_MODULES([PYGTK], [gtk+-2.0 >= 2.16.0 pygtk-2.0 >= 2.16.0])
AC_SUBST(PYGTK_CFLAGS)
AC_SUBST(PYGTK_LIBS)
PYGTK_DEFS=`$PKG_CONFIG --variable=defsdir pygtk-2.0`
@ -50,15 +50,6 @@ if test "x$PYTHON" = "x:"; then
AC_MSG_ERROR([Python not found])
fi
dnl ****
dnl tray icon
dnl ****
AC_ARG_ENABLE(trayicon,
[ --disable-trayicon do not build trayicon module [default yes]],
enable_trayicon=$enableval, enable_trayicon=yes)
test "x$enable_trayicon" = "xyes" && have_trayicon=true || have_trayicon=false
AM_CONDITIONAL(BUILD_TRAYICON, $have_trayicon)
ACLOCAL_AMFLAGS="\${ACLOCAL_FLAGS}"
AC_SUBST(ACLOCAL_AMFLAGS)
@ -91,8 +82,3 @@ AC_CONFIG_FILES([
po/Makefile.in
])
AC_OUTPUT
echo "
*****************************
Build features:
trayicon ......... ${have_trayicon}
*****************************"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,52 @@
# coding=utf-8
emoticons = {
'angel.png': ['O:-)', 'o:-)', '0:-)'],
'curl-lip.png': [':-)', ':)', '=)'],
'sarcastic.png': [';D', ';-D'],
'sad.png': [':-(', ':(', '=('],
'wink.png': [';-)', ';)'],
'tongue.png': [':-P', ':P', ':-p', ':p'],
'glasses-cool': ['8-)', 'B-)', '(H)'],
'laugh.png': [':-D', ':D'],
'embarrassed.png': [':-[', ':['],
'shout.png': ['=-O', ':-O', ':O'],
'kiss.png': [':-{}', ':-*', ':*', '(K)'],
'crying.png': [":'-(", ":'(", ';-(', ';(', ";'-("],
'shut-mouth': [':-X'],
'hypnotized.png': ['%)', '%-)'],
'freaked-out.png': [':o'],
'thinking.png': [':-|', ':|'],
'arrogant.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'],
'devil.png': [':->', ']:->', '>:-)', '>:)', '(6)'],
'musical-note.png': ['[:-}'],
'sick.png': [':-!'],
'giggle.png': ['*JOKINGLY*'],
'cute.png': ['*KISSED*'],
'sleepy.png': ['*TIRED*'],
'terror.png': ['*STOP*'],
'handshake.png': ['*KISSING*'],
'rose.png': ['@}->--', '(F)'],
'good.png': ['(Y)', '*THUMBS UP*'],
'beer.png': ['(B)', '*DRINK*'],
'love.png': ['<3', '(L)', '*IN LOVE*'],
'bomb.png': ['@='],
'question.png': ['*HELP*'],
'cowboy.png': ['\m/'],
'fingers-crossed.png': ['*OK*'],
'alien.png': ['*WASSUP*'],
'disapointed.png': ['*SORRY*'],
'clap.png': ['*BRAVO*'],
'rotfl.png': ['*ROFL*'],
'dont-know.png': ['*PARDON*'],
'confused.png': ['*NO*'],
'silly.png': ['*CRAZY*'],
'doh.png': ['*DONT_KNOW*'],
'party.png': ['*DANCE*'],
'dance.png': ['*YAHOO*'],
'victory.png': ['*HI*'],
'go-away.png': ['*BYE*'],
'smirk.png': ['*YES*'],
'pissed-off.png': ['*WALL*'],
'mail.png': ['*WRITE*', '(E)'],
'tremble.png': ['*SCRATCH*'],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,3 +1,3 @@
#!/bin/sh
cd "$(dirname $0)/src"
exec python -OOt gajim.py $@
exec python -OOt gajim.py "$@"

View File

@ -33,5 +33,4 @@ export datadir=@DATADIR@/gajim
PYTHON_EXEC=@PYTHON@
cd ${datadir}/src
export PYTHONPATH="$PYTHONPATH:@LIBDIR@/gajim"
exec ${PYTHON_EXEC} -OO $APP.py "$@"

View File

@ -1,31 +1,7 @@
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
@ -56,12 +32,7 @@ EXTRA_DIST = $(gajimsrc_PYTHON) \
$(gajimsrc2_PYTHON) \
$(gajimsrc3_PYTHON) \
$(gajimsrc4_PYTHON) \
$(gajimsrc5_PYTHON) \
eggtrayicon.c \
trayiconmodule.c \
eggtrayicon.h \
trayicon.defs \
trayicon.override
$(gajimsrc5_PYTHON)
dist-hook:
rm -f $(distdir)/ipython_view.py

View File

@ -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
# widget callbacks
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
# 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.

View File

@ -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':

View File

@ -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(

View File

@ -90,8 +90,9 @@ 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')
@ -99,7 +100,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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,33 +136,45 @@ 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()
# 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
@ -212,7 +227,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.
@ -386,7 +401,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)
@ -400,8 +417,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
menu.show_all()
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:
@ -445,12 +464,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.'))
@ -465,7 +488,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')
@ -512,9 +537,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]:
@ -589,11 +616,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
@ -652,8 +679,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
@ -668,10 +698,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
@ -711,10 +742,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
@ -789,8 +823,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()
@ -808,12 +844,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'),
@ -875,7 +915,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,
@ -895,7 +937,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
@ -907,7 +951,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)
@ -935,7 +981,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()
@ -965,7 +1013,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
@ -986,10 +1036,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
@ -1082,8 +1134,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()
@ -1142,7 +1196,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)
@ -1154,7 +1210,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)
@ -1173,7 +1231,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,
@ -1279,14 +1339,12 @@ 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.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
@ -1430,118 +1488,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()
def _update_jingle(self, jingle_type):
if jingle_type not in ('audio', 'video'):
@ -1631,10 +1595,10 @@ class ChatControl(ChatControlBase):
self._set_jingle_state('video', state, sid=sid, reason=reason)
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:
@ -1657,13 +1621,17 @@ 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)
@ -1681,7 +1649,9 @@ class ChatControl(ChatControlBase):
return True
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
@ -1734,9 +1704,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
@ -1919,8 +1890,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
@ -1955,7 +1929,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
@ -2019,10 +1995,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
@ -2046,10 +2023,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
@ -2079,7 +2056,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')
@ -2101,16 +2080,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':
@ -2248,12 +2230,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,
@ -2261,9 +2243,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.
@ -2409,7 +2393,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)
@ -2593,8 +2579,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:
@ -2649,8 +2636,10 @@ 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
@ -2717,14 +2706,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)
@ -2741,7 +2734,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):
@ -2788,7 +2783,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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,42 @@ 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)
class StandardChatCommands(CommandContainer):
"""
This command container contains standard command which are unique to a chat.
@ -98,6 +142,22 @@ class StandardChatCommands(CommandContainer):
raise CommandError(_('Command is not supported for zeroconf accounts'))
gajim.connections[self.account].sendPing(self.contact)
@command('dtmf')
@documentation(_("Sends DTMF events through an open audio session"))
def dtmf(self, events):
if not self.audio_sid:
raise CommandError(_("There is no open audio session with this contact"))
# Valid values for DTMF tones are *, # or a number
events = [event for event in events
if event in ('*', '#') or event.isdigit()]
if events:
session = gajim.connections[self.account].get_jingle_session(
self.contact.get_full_jid(), self.audio_sid)
content = session.get_content('audio')
content.batch_dtmf(events)
else:
raise CommandError(_("No valid DTMF event specified"))
@command('audio')
@documentation(_("Toggle audio session"))
def audio(self):

View File

@ -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

View File

@ -37,8 +37,11 @@ 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
# Features where we cannot safely assume that the other side supports them
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION]
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION,
NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO]
# Query entry status codes
NEW = 0

File diff suppressed because it is too large Load Diff

View File

@ -46,11 +46,10 @@ 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.pep import ConnectionPEP
from common.caps import ConnectionCaps
from common.message_archiving import ConnectionArchive
from common.message_archiving import ARCHIVING_COLLECTIONS_ARRIVED
@ -323,15 +322,20 @@ class ConnectionBytestream:
field.setValue(common.xmpp.NS_BYTESTREAM)
self.connection.send(iq)
def _ft_get_our_jid(self):
our_jid = gajim.get_jid_from_account(self.name)
resource = self.server_resource
return our_jid + '/' + resource
def _ft_get_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
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
file_props['sender'] = self._ft_get_our_jid()
fjid = self._ft_get_receiver_jid(file_props)
iq = common.xmpp.Protocol(name = 'iq', to = fjid,
typ = 'set')
iq.setID(file_props['sid'])
@ -424,6 +428,9 @@ class ConnectionBytestream:
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise common.xmpp.NodeProcessed
def _ft_get_from(self, iq_obj):
return helpers.get_full_jid_from_iq(iq_obj)
def _bytestreamSetCB(self, con, iq_obj):
log.debug('_bytestreamSetCB')
target = unicode(iq_obj.getAttr('to'))
@ -440,7 +447,7 @@ class ConnectionBytestream:
'target': target,
'id': id_,
'sid': sid,
'initiator': helpers.get_full_jid_from_iq(iq_obj)
'initiator': self._ft_get_from(iq_obj)
}
for attr in item.getAttrs():
host_dict[attr] = item.getAttr(attr)
@ -478,7 +485,7 @@ class ConnectionBytestream:
return
if not real_id.startswith('au_'):
return
frm = helpers.get_full_jid_from_iq(iq_obj)
frm = self._ft_get_from(iq_obj)
id_ = real_id[3:]
if id_ in self.files_props:
file_props = self.files_props[id_]
@ -488,9 +495,12 @@ class ConnectionBytestream:
gajim.socks5queue.activate_proxy(host['idx'])
raise common.xmpp.NodeProcessed
def _ft_get_streamhost_jid_attr(self, streamhost):
return helpers.parse_jid(streamhost.getAttr('jid'))
def _bytestreamResultCB(self, con, iq_obj):
log.debug('_bytestreamResultCB')
frm = helpers.get_full_jid_from_iq(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)
@ -518,7 +528,7 @@ class ConnectionBytestream:
gajim.socks5queue.activate_proxy(host['idx'])
break
raise common.xmpp.NodeProcessed
jid = helpers.parse_jid(streamhost.getAttr('jid'))
jid = self._ft_get_streamhost_jid_attr(streamhost)
if 'streamhost-used' in file_props and \
file_props['streamhost-used'] is True:
raise common.xmpp.NodeProcessed
@ -575,7 +585,7 @@ class ConnectionBytestream:
if 'request-id' in file_props:
# we have already sent streamhosts info
return
file_props['receiver'] = helpers.get_full_jid_from_iq(iq_obj)
file_props['receiver'] = self._ft_get_from(iq_obj)
si = iq_obj.getTag('si')
file_tag = si.getTag('file')
range_tag = None
@ -601,9 +611,9 @@ class ConnectionBytestream:
def _siSetCB(self, con, iq_obj):
log.debug('_siSetCB')
jid = helpers.get_jid_from_iq(iq_obj)
jid = self._ft_get_from(iq_obj)
file_props = {'type': 'r'}
file_props['sender'] = helpers.get_full_jid_from_iq(iq_obj)
file_props['sender'] = jid
file_props['request-id'] = unicode(iq_obj.getAttr('id'))
si = iq_obj.getTag('si')
profile = si.getAttr('profile')
@ -641,7 +651,7 @@ class ConnectionBytestream:
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['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)
@ -662,7 +672,7 @@ class ConnectionBytestream:
if file_props is None:
# file properties for jid is none
return
jid = helpers.get_jid_from_iq(iq_obj)
jid = self._ft_get_from(iq_obj)
file_props['error'] = -3
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
raise common.xmpp.NodeProcessed
@ -1533,13 +1543,15 @@ sent a message to.'''
return sess
class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
def __init__(self):
ConnectionArchive.__init__(self)
ConnectionVcard.__init__(self)
ConnectionBytestream.__init__(self)
ConnectionCommands.__init__(self)
ConnectionPubSub.__init__(self)
ConnectionPEP.__init__(self, account=self.name, dispatcher=self,
pubsub_connection=self)
ConnectionJingle.__init__(self)
ConnectionHandlersBase.__init__(self)
self.gmail_url = None
@ -1817,7 +1829,7 @@ class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestrea
# 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()
@ -1942,9 +1954,23 @@ class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestrea
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)
@ -1955,15 +1981,7 @@ class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestrea
def _messageCB(self, con, msg):
'''Called when we receive a message'''
log.debug('MessageCB')
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):
@ -2229,42 +2247,6 @@ class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestrea
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'''
ptype = prs.getType()
@ -2832,6 +2814,11 @@ class ConnectionHandlers(ConnectionArchive, ConnectionVcard, ConnectionBytestrea
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',

View File

@ -94,7 +94,7 @@ class Contact(CommonContact):
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={}):
composing_xep=None):
CommonContact.__init__(self, jid, account, resource, show, status, name,
our_chatstate, composing_xep, chatstate, client_caps=client_caps)
@ -110,9 +110,7 @@ class Contact(CommonContact):
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.pep = {}
def get_full_jid(self):
if self.resource:
@ -229,23 +227,25 @@ class Contacts:
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={}):
composing_xep=None):
account = self._accounts.get(account, account) # Use Account object if available
return Contact(jid=jid, account=account, name=name, groups=groups,
show=show, status=status, sub=sub, ask=ask, resource=resource, priority=priority,
keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate,
chatstate=chatstate, last_status_time=last_status_time,
composing_xep=composing_xep, mood=mood, tune=tune, activity=activity)
composing_xep=composing_xep)
def create_self_contact(self, jid, account, resource, show, status, priority, keyID=''):
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)
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

View File

@ -569,8 +569,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 = ''
@ -777,53 +775,6 @@ 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.'''

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,109 @@
##
## 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). '''
contents = {}
def get_jingle_content(node):
namespace = node.getNamespace()
if namespace in contents:
return contents[namespace](node)
else:
return None
class JingleContent(object):
''' An abstraction of content in Jingle sessions. '''
def __init__(self, session, transport):
self.session = session
self.transport = transport
# will be filled by JingleSession.add_content()
# don't uncomment these lines, we will catch more buggy code then
# (a JingleContent not added to session shouldn't send anything)
#self.creator = None
#self.name = None
self.accepted = False
self.sent = False
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.__transportInfoCB],
'content-add': [self.__transportInfoCB],
'content-modify': [],
'content-reject': [],
'content-remove': [],
'description-info': [],
'security-info': [],
'session-accept': [self.__transportInfoCB],
'session-info': [],
'session-initiate': [self.__transportInfoCB],
'session-terminate': [],
'transport-info': [self.__transportInfoCB],
'transport-replace': [],
'transport-accept': [],
'transport-reject': [],
'iq-result': [],
'iq-error': [],
# these are called when *we* sent these stanzas
'content-accept-sent': [self.__fillJingleStanza],
'content-add-sent': [self.__fillJingleStanza],
'session-initiate-sent': [self.__fillJingleStanza],
'session-accept-sent': [self.__fillJingleStanza],
'session-terminate-sent': [],
}
def is_ready(self):
return (self.accepted and not self.sent)
def add_remote_candidates(self, candidates):
''' Add a list of candidates to the list of remote candidates. '''
pass
def stanzaCB(self, stanza, content, error, action):
''' Called when something related to our content was sent by peer. '''
if action in self.callbacks:
for callback in self.callbacks[action]:
callback(stanza, content, error, action)
def __transportInfoCB(self, stanza, content, error, action):
''' Got a new transport candidate. '''
candidates = self.transport.parse_transport_stanza(
content.getTag('transport'))
if candidates:
self.add_remote_candidates(candidates)
def __content(self, payload=[]):
''' Build a XML content-wrapper for our data. '''
return xmpp.Node('content',
attrs={'name': self.name, 'creator': self.creator},
payload=payload)
def send_candidate(self, candidate):
content = self.__content()
content.addChild(self.transport.make_transport([candidate]))
self.session.send_transport_info(content)
def __fillJingleStanza(self, stanza, content, error, action):
''' Add our things to session-initiate stanza. '''
self._fillContent(content)
self.sent = True
content.addChild(node=self.transport.make_transport())
def destroy(self):
self.callbacks = None
del self.session.contents[(self.creator, self.name)]

321
src/common/jingle_rtp.py Normal file
View File

@ -0,0 +1,321 @@
##
## 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 xmpp
import farsight, gst
from jingle_transport import JingleTransportICEUDP
from jingle_content import contents, JingleContent
# TODO: Will that be even used?
def get_first_gst_element(elements):
''' Returns, if it exists, the first available element of the list. '''
for name in elements:
factory = gst.element_factory_find(name)
if factory:
return factory.create()
class JingleRTPContent(JingleContent):
def __init__(self, session, media, transport=None):
if transport is None:
transport = JingleTransportICEUDP()
JingleContent.__init__(self, session, transport)
self.media = media
self._dtmf_running = False
self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO,
'video': farsight.MEDIA_TYPE_VIDEO}[media]
self.got_codecs = False
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 is_ready(self):
return (JingleContent.is_ready(self) and self.candidates_ready
and self.p2psession.get_property('codecs-ready'))
def add_remote_candidates(self, candidates):
JingleContent.add_remote_candidates(self, candidates)
#FIXME: connectivity should not be etablished yet
# Instead, it should be etablished after session-accept!
if self.sent:
self.p2pstream.set_remote_candidates(candidates)
def 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.transport.candidates.append(candidate)
if self.candidates_ready:
#FIXME: Is this case even possible?
self.send_candidate(candidate)
elif name == 'farsight-component-state-changed':
state = message.structure['state']
print message.structure['component'], state
if state == farsight.STREAM_STATE_FAILED:
reason = xmpp.Node('reason')
reason.setTag('failed-transport')
self.session._session_terminate(reason)
elif name == 'farsight-error':
print 'Farsight error #%d!' % message.structure['error-no']
print 'Message: %s' % message.structure['error-msg']
print 'Debug: %s' % message.structure['debug-msg']
else:
print name
def __contentAcceptCB(self, stanza, content, error, action):
if self.accepted:
if self.transport.remote_candidates:
self.p2pstream.set_remote_candidates(self.transport.remote_candidates)
self.transport.remote_candidates = []
#TODO: farsight.DIRECTION_BOTH only if senders='both'
self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH)
self.session.content_negociated(self.media)
def __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 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()
''' 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, transport=None):
JingleRTPContent.__init__(self, session, 'video', transport)
self.setup_stream()
''' Things to control the gstreamer's pipeline '''
def setup_stream(self):
#TODO: Everything is not working properly:
# sometimes, one window won't show up,
# sometimes it'll freeze...
JingleRTPContent.setup_stream(self)
# the local parts
src_vid = gst.element_factory_make('videotestsrc')
src_vid.set_property('is-live', True)
videoscale = gst.element_factory_make('videoscale')
caps = gst.element_factory_make('capsfilter')
caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
colorspace = gst.element_factory_make('ffmpegcolorspace')
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)
def get_content(desc):
if desc['media'] == 'audio':
return JingleAudio
elif desc['media'] == 'video':
return JingleVideo
else:
return None
contents[xmpp.NS_JINGLE_RTP] = get_content

View File

@ -0,0 +1,613 @@
##
## 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:
# * Have JingleContent here
# * 'senders' attribute of 'content' element
# * security preconditions
# * actions:
# - content-modify
# - description-info, 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
#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. '''
def __init__(self, con, weinitiate, jid, sid=None):
''' con -- connection object,
weinitiate -- boolean, are we the initiator?
jid - jid of the other entity'''
self.contents = {} # negotiated contents
self.connection = con # connection to use
# our full jid
#FIXME: Get rid of gajim here?
self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \
con.server_resource
self.peerjid = jid # jid we connect to
# jid we use as the initiator
self.initiator = weinitiate and self.ourjid or self.peerjid
# jid we use as the responder
self.responder = weinitiate and self.peerjid or self.ourjid
# are we an initiator?
self.weinitiate = weinitiate
# what state is session in? (one from JingleStates)
self.state = JingleStates.ended
if not sid:
sid = con.connection.getAnID()
self.sid = sid # sessionid
self.accepted = True # is this session accepted by user
# callbacks to call on proper contents
# use .prepend() to add new callbacks, especially when you're going
# to send error instead of ack
self.callbacks = {
'content-accept': [self.__contentAcceptCB, self.__broadcastCB,
self.__defaultCB],
'content-add': [self.__contentAddCB, self.__broadcastCB,
self.__defaultCB], #TODO
'content-modify': [self.__defaultCB], #TODO
'content-reject': [self.__defaultCB, self.__contentRemoveCB], #TODO
'content-remove': [self.__defaultCB, self.__contentRemoveCB],
'description-info': [self.__broadcastCB, self.__defaultCB], #TODO
'security-info': [self.__defaultCB], #TODO
'session-accept': [self.__sessionAcceptCB, self.__contentAcceptCB,
self.__broadcastCB, self.__defaultCB],
'session-info': [self.__sessionInfoCB, self.__broadcastCB, self.__defaultCB],
'session-initiate': [self.__sessionInitiateCB, self.__broadcastCB,
self.__defaultCB],
'session-terminate': [self.__sessionTerminateCB, self.__broadcastAllCB,
self.__defaultCB],
'transport-info': [self.__broadcastCB, self.__defaultCB],
'transport-replace': [self.__broadcastCB, self.__transportReplaceCB], #TODO
'transport-accept': [self.__defaultCB], #TODO
'transport-reject': [self.__defaultCB], #TODO
'iq-result': [],
'iq-error': [self.__errorCB],
}
''' Interaction with user '''
def approve_session(self):
''' Called when user accepts session in UI (when we aren't the initiator).
'''
self.accept_session()
def decline_session(self):
''' Called when user declines session in UI (when we aren't the initiator)
'''
reason = xmpp.Node('reason')
reason.addChild('decline')
self._session_terminate(reason)
def approve_content(self, media):
content = self.get_content(media)
if content:
content.accepted = True
self.on_session_state_changed(content)
def reject_content(self, media):
content = self.get_content(media)
if content:
if self.state == JingleStates.active:
self.__content_reject(content)
content.destroy()
self.on_session_state_changed()
def end_session(self):
''' Called when user stops or cancel session in UI. '''
reason = xmpp.Node('reason')
if self.state == JingleStates.active:
reason.addChild('success')
else:
reason.addChild('cancel')
self._session_terminate(reason)
''' Middle-level functions to manage contents. Handle local content
cache and send change notifications. '''
def get_content(self, media=None):
if media is None:
return None
for content in self.contents.values():
if content.media == media:
return content
def add_content(self, name, content, creator='we'):
''' Add new content to session. If the session is active,
this will send proper stanza to update session.
Creator must be one of ('we', 'peer', 'initiator', 'responder')'''
assert creator in ('we', 'peer', 'initiator', 'responder')
if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \
not self.weinitiate):
creator = 'initiator'
elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \
not self.weinitiate):
creator = 'responder'
content.creator = creator
content.name = name
self.contents[(creator, name)] = content
if (creator == 'initiator') == self.weinitiate:
# The content is from us, accept it
content.accepted = True
def remove_content(self, creator, name):
''' We do not need this now '''
#TODO:
if (creator, name) in self.contents:
content = self.contents[(creator, name)]
if len(self.contents) > 1:
self.__content_remove(content)
self.contents[(creator, name)].destroy()
if len(self.contents) == 0:
self.end_session()
def modify_content(self, creator, name, *someother):
''' We do not need this now '''
pass
def on_session_state_changed(self, content=None):
if self.state == JingleStates.ended:
# Session not yet started, only one action possible: session-initiate
if self.is_ready() and self.weinitiate:
self.__session_initiate()
elif self.state == JingleStates.pending:
# We can either send a session-accept or a content-add
if self.is_ready() and not self.weinitiate:
self.__session_accept()
elif content and (content.creator == 'initiator') == self.weinitiate:
self.__content_add(content)
elif content and self.weinitiate:
self.__content_accept(content)
elif self.state == JingleStates.active:
# We can either send a content-add or a content-accept
if not content:
return
if (content.creator == 'initiator') == self.weinitiate:
# We initiated this content. It's a pending content-add.
self.__content_add(content)
else:
# The other side created this content, we accept it.
self.__content_accept(content)
def is_ready(self):
''' Returns True when all codecs and candidates are ready
(for all contents). '''
return (all((content.is_ready() for content in self.contents.itervalues()))
and self.accepted)
''' Middle-level function to do stanza exchange. '''
def accept_session(self):
''' Mark the session as accepted. '''
self.accepted = True
self.on_session_state_changed()
def start_session(self):
''' Mark the session as ready to be started. '''
self.accepted = True
self.on_session_state_changed()
def send_session_info(self):
pass
def send_content_accept(self, content):
assert self.state != JingleStates.ended
stanza, jingle = self.__make_jingle('content-accept')
jingle.addChild(node=content)
self.connection.connection.send(stanza)
def send_transport_info(self, content):
assert self.state != JingleStates.ended
stanza, jingle = self.__make_jingle('transport-info')
jingle.addChild(node=content)
self.connection.connection.send(stanza)
''' Session callbacks. '''
def stanzaCB(self, stanza):
''' A callback for ConnectionJingle. It gets stanza, then
tries to send it to all internally registered callbacks.
First one to raise xmpp.NodeProcessed breaks function.'''
jingle = stanza.getTag('jingle')
error = stanza.getTag('error')
if error:
# it's an iq-error stanza
action = 'iq-error'
elif jingle:
# it's a jingle action
action = jingle.getAttr('action')
if action not in self.callbacks:
self.__send_error(stanza, 'bad_request')
return
#FIXME: If we aren't initiated and it's not a session-initiate...
if action != 'session-initiate' and self.state == JingleStates.ended:
self.__send_error(stanza, 'item-not-found', 'unknown-session')
return
else:
# it's an iq-result (ack) stanza
action = 'iq-result'
callables = self.callbacks[action]
try:
for callable in callables:
callable(stanza=stanza, jingle=jingle, error=error, action=action)
except xmpp.NodeProcessed:
pass
except TieBreak:
self.__send_error(stanza, 'conflict', 'tiebreak')
except OutOfOrder:
self.__send_error(stanza, 'unexpected-request', 'out-of-order')#FIXME
def __defaultCB(self, stanza, jingle, error, action):
''' Default callback for action stanzas -- simple ack
and stop processing. '''
response = stanza.buildReply('result')
self.connection.connection.send(response)
def __errorCB(self, stanza, jingle, error, action):
#FIXME
text = error.getTagData('text')
jingle_error = None
xmpp_error = None
for child in error.getChildren():
if child.getNamespace() == xmpp.NS_JINGLE_ERRORS:
jingle_error = child.getName()
elif child.getNamespace() == xmpp.NS_STANZAS:
xmpp_error = child.getName()
self.__dispatch_error(xmpp_error, jingle_error, text)
#FIXME: Not sure when we would want to do that...
if xmpp_error == 'item-not-found':
self.connection.delete_jingle_session(self.peerjid, self.sid)
def __transportReplaceCB(self, stanza, jingle, error, action):
for content in jingle.iterTags('content'):
creator = content['creator']
name = content['name']
if (creator, name) in self.contents:
transport_ns = content.getTag('transport').getNamespace()
if transport_ns == xmpp.JINGLE_ICE_UDP:
#FIXME: We don't manage anything else than ICE-UDP now...
#What was the previous transport?!?
#Anyway, content's transport is not modifiable yet
pass
else:
stanza, jingle = self.__make_jingle('transport-reject')
content = jingle.setTag('content', attrs={'creator': creator,
'name': name})
content.setTag('transport', namespace=transport_ns)
self.connection.connection.send(stanza)
raise xmpp.NodeProcessed
else:
#FIXME: This ressource is unknown to us, what should we do?
#For now, reject the transport
stanza, jingle = self.__make_jingle('transport-reject')
c = jingle.setTag('content', attrs={'creator': creator,
'name': name})
c.setTag('transport', namespace=transport_ns)
self.connection.connection.send(stanza)
raise xmpp.NodeProcessed
def __sessionInfoCB(self, stanza, jingle, error, action):
#TODO: ringing, active, (un)hold, (un)mute
payload = jingle.getPayload()
if len(payload) > 0:
self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info')
raise xmpp.NodeProcessed
def __contentRemoveCB(self, stanza, jingle, error, action):
for content in jingle.iterTags('content'):
creator = content['creator']
name = content['name']
if (creator, name) in self.contents:
content = self.contents[(creator, name)]
#TODO: this will fail if content is not an RTP content
self.connection.dispatch('JINGLE_DISCONNECTED',
(self.peerjid, self.sid, content.media, 'removed'))
content.destroy()
if len(self.contents) == 0:
reason = xmpp.Node('reason')
reason.setTag('success')
self._session_terminate(reason)
def __sessionAcceptCB(self, stanza, jingle, error, action):
if self.state != JingleStates.pending: #FIXME
raise OutOfOrder
self.state = JingleStates.active
def __contentAcceptCB(self, stanza, jingle, error, action):
''' Called when we get content-accept stanza or equivalent one
(like session-accept).'''
# check which contents are accepted
for content in jingle.iterTags('content'):
creator = content['creator']
name = content['name']#TODO...
def __contentAddCB(self, stanza, jingle, error, action):
if self.state == JingleStates.ended:
raise OutOfOrder
parse_result = self.__parse_contents(jingle)
contents = parse_result[2]
rejected_contents = parse_result[3]
for name, creator in rejected_contents:
#TODO:
content = JingleContent()
self.add_content(name, content, creator)
self.__content_reject(content)
self.contents[(content.creator, content.name)].destroy()
self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
contents))
def __sessionInitiateCB(self, stanza, jingle, error, action):
''' We got a jingle session request from other entity,
therefore we are the receiver... Unpack the data,
inform the user. '''
if self.state != JingleStates.ended:
raise OutOfOrder
self.initiator = jingle['initiator']
self.responder = self.ourjid
self.peerjid = self.initiator
self.accepted = False # user did not accept this session yet
# TODO: If the initiator is unknown to the receiver (e.g., via presence
# subscription) and the receiver has a policy of not communicating via
# Jingle with unknown entities, it SHOULD return a <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'):
transport = get_jingle_transport(element.getTag('transport'))
content_type = get_jingle_content(element.getTag('description'))
if content_type:
contents_ok = True
if transport:
content = content_type(self, transport)
self.add_content(element['name'],
content, 'peer')
contents.append((content.media,))
transports_ok = True
else:
contents_rejected.append((element['name'], 'peer'))
else:
contents_rejected.append((element['name'], 'peer'))
return (contents_ok, transports_ok, contents, contents_rejected)
def __dispatch_error(self, error, jingle_error=None, text=None):
if jingle_error:
error = jingle_error
if text:
text = '%s (%s)' % (error, text)
else:
text = error
self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text))
def __reason_from_stanza(self, stanza):
reason = 'success'
reasons = ['success', 'busy', 'cancel', 'connectivity-error',
'decline', 'expired', 'failed-application', 'failed-transport',
'general-error', 'gone', 'incompatible-parameters', 'media-error',
'security-error', 'timeout', 'unsupported-applications',
'unsupported-transports']
tag = stanza.getTag('reason')
if tag:
text = tag.getTagData('text')
for r in reasons:
if tag.getTag(r):
reason = r
break
return (reason, text)
''' Methods that make/send proper pieces of XML. They check if the session
is in appropriate state. '''
def __make_jingle(self, action):
stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
attrs = {'action': action,
'sid': self.sid}
if action == 'session-initiate':
attrs['initiator'] = self.initiator
elif action == 'session-accept':
attrs['responder'] = self.responder
jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
return stanza, jingle
def __send_error(self, stanza, error, jingle_error=None, text=None):
err = xmpp.Error(stanza, error)
err.setNamespace(xmpp.NS_STANZAS)
if jingle_error:
err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS)
if text:
err.setTagData('text', text)
self.connection.connection.send(err)
self.__dispatch_error(error, jingle_error, text)
def __append_content(self, jingle, content):
''' Append <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))

View File

@ -0,0 +1,135 @@
##
## 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]()
else:
return None
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()
transport = xmpp.Node('transport', payload=candidates)
return transport
def parse_transport_stanza(self, transport):
''' Returns the list of transport candidates from a transport stanza. '''
return []
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

View File

@ -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,

View File

@ -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,377 @@ 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
TUNE_DATA = ['artist', 'title', 'source', 'track', 'length']
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']
import gobject
import gtk
(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']
import logging
log = logging.getLogger('gajim.c.pep')
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()
from common import helpers
from common import atom
from common import xmpp
from common import gajim
def user_tune(items, name, jid):
has_child = False
retract = False
artist = None
title = None
source = None
track = None
length = None
import gtkgui_helpers
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']
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
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']
def _translate_mood(self, mood):
if mood in MOODS:
return MOODS[mood]
else:
return mood
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
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
def user_activity(items, name, jid):
has_child = False
retract = False
activity = None
subactivity = None
text = None
artist = tune.get('artist', _('Unknown Artist'))
artist = gobject.markup_escape_text(artist)
title = tune.get('title', _('Unknown Title'))
title = gobject.markup_escape_text(title)
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
source = tune.get('source', _('Unknown Source'))
source = gobject.markup_escape_text(source)
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']
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
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']
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 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()
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
def user_nickname(items, name, jid):
has_child = False
retract = False
nick = None
SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP,
UserNicknamePEP]
for item in items.getTags('item'):
child = item.getTag('nick')
if child is not None:
has_child = True
nick = child.getData()
break
class ConnectionPEP(object):
if items.getTag('retract') is not None:
retract = True
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')
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')
for pep_class in SUPPORTED_PERSONAL_USER_EVENTS:
pep = pep_class.get_tag_as_PEP(jid, self._account, event_tag)
if pep:
self._dispatcher.dispatch('PEP_RECEIVED', (jid, pep.type))
items = event_tag.getTag('items')
if items:
for item in items.getTags('item'):
entry = item.getTag('entry')
if entry:
# for each entry in feed (there shouldn't be more than one,
# but to be sure...
self._dispatcher.dispatch('ATOM_ENTRY',
(atom.OldEntry(node=entry),))
raise xmpp.NodeProcessed
def send_activity(self, activity, subactivity=None, message=None):
if not self.pep_supported:
return
item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY})
if activity:
i = item.addChild(activity)
if subactivity:
i.addChild(subactivity)
if message:
i = item.addChild('text')
i.addData(message)
self._pubsub_connection.send_pb_publish('', xmpp.NS_ACTIVITY, item, '0')
def retract_activity(self):
if not self.pep_supported:
return
# not all server support retract, so send empty pep first
self.send_activity(None)
self._pubsub_connection.send_pb_retract('', xmpp.NS_ACTIVITY, '0')
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)
def send_mood(self, mood, message=None):
if not self.pep_supported:
return
item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD})
if mood:
item.addChild(mood)
if message:
i = item.addChild('text')
i.addData(message)
self._pubsub_connection.send_pb_publish('', xmpp.NS_MOOD, item, '0')
def retract_mood(self):
if not self.pep_supported:
return
self.send_mood(None)
self._pubsub_connection.send_pb_retract('', xmpp.NS_MOOD, '0')
def send_tune(self, artist='', title='', source='', track=0, length=0,
items=None):
if not self.pep_supported:
return
item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE})
if artist:
i = item.addChild('artist')
i.addData(artist)
if title:
i = item.addChild('title')
i.addData(title)
if source:
i = item.addChild('source')
i.addData(source)
if track:
i = item.addChild('track')
i.addData(track)
if length:
i = item.addChild('length')
i.addData(length)
if items:
item.addChild(payload=items)
self._pubsub_connection.send_pb_publish('', xmpp.NS_TUNE, item, '0')
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 retract_tune(self):
if not self.pep_supported:
return
# not all server support retract, so send empty pep first
self.send_tune(None)
self._pubsub_connection.send_pb_retract('', xmpp.NS_TUNE, '0')
def 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)
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')
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()
def retract_nickname(self):
if not self.pep_supported:
return
# not all server support retract, so send empty pep first
self.send_nickname(None)
self._pubsub_connection.send_pb_retract('', xmpp.NS_NICK, '0')
# vim: se ts=3:

View File

@ -112,7 +112,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

View File

@ -247,25 +247,23 @@ class XMPPDispatcher(PlugIn):
'''
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)' %

View File

@ -93,6 +93,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'

View File

@ -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'
@ -650,7 +651,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 +663,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
@ -719,6 +720,7 @@ class NonBlockingHTTP(NonBlockingTCP):
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 +728,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,6 +735,12 @@ 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)

View File

@ -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 *
@ -288,6 +288,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',
@ -395,6 +396,7 @@ class P2PConnection(IdleObject, PlugIn):
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 +418,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()
@ -578,6 +583,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 +639,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 +652,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,6 +738,14 @@ 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

View File

@ -35,6 +35,8 @@ from common import helpers
from common import gajim
from common.zeroconf import zeroconf
from common.commands import ConnectionCommands
from common.message_archiving import ConnectionArchive
from common.pep import ConnectionPEP
import logging
log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
@ -57,10 +59,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
@ -69,318 +71,27 @@ class ConnectionVcard(connection_handlers.ConnectionVcard):
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
def _ft_get_from(self, iq_obj):
return unicode(iq_obj.getFrom())
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 _ft_get_our_jid(self):
return gajim.get_jid_from_account(self.name)
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 _ft_get_receiver_jid(self, file_props):
return file_props['receiver'].jid
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
def _ft_get_streamhost_jid_attr(self, streamhost):
return streamhost.getAttr('jid')
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):
ConnectionCommands, ConnectionPEP, ConnectionArchive,
connection_handlers.ConnectionHandlersBase):
def __init__(self):
ConnectionVcard.__init__(self)
ConnectionBytestream.__init__(self)
ConnectionCommands.__init__(self)
ConnectionArchive.__init__(self)
connection_handlers.ConnectionHandlersBase.__init__(self)
try:

View File

@ -43,66 +43,27 @@ 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):
class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
'''Connection class'''
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
self.archive_pref_supported = False
CommonConnection.__init__(self, name)
def get_config_values_or_default(self):
''' get name, host, port from config, or
@ -111,79 +72,63 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
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,
'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,
'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)
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,15 +156,6 @@ 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'''
@ -237,9 +175,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 +209,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 +223,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 +235,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 +260,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)
self.dispatch('SIGNED_IN', ())
#stay offline when zeroconf does something wrong
# stay offline when zeroconf does something wrong
if check:
self.dispatch('STATUS', show)
else:
@ -359,136 +282,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 +348,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 +359,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:

View File

@ -63,17 +63,23 @@ 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)
@ -297,8 +303,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'):
@ -497,8 +501,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)
@ -587,7 +593,9 @@ class PreferencesWindow:
self.toggle_emoticons()
def toggle_emoticons(self):
'''Update emoticons state in Opened Chat Windows'''
"""
Update emoticons state in Opened Chat Windows
"""
for win in gajim.interface.msg_win_mgr.windows():
win.toggle_emoticons()
@ -781,7 +789,9 @@ class PreferencesWindow:
self.sounds_preferences.window.present()
def update_text_tags(self):
'''Update color tags in Opened Chat Windows'''
"""
Update color tags in opened chat windows
"""
for win in gajim.interface.msg_win_mgr.windows():
win.update_tags()
@ -802,7 +812,9 @@ class PreferencesWindow:
gajim.interface.save_config()
def update_text_font(self):
'''Update text font in Opened Chat Windows'''
"""
Update text font in opened chat windows
"""
for win in gajim.interface.msg_win_mgr.windows():
win.update_font()
@ -881,7 +893,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)
@ -1348,7 +1362,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']
@ -1416,7 +1433,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 +1461,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 +1496,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 +1764,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 +1774,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
@ -2411,8 +2438,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 +2450,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 +2486,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 +2537,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 +2690,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 +2741,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 +2944,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 +2971,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 +2986,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 +3005,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 +3039,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 +3188,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
@ -3445,8 +3486,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 +3523,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 +3545,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 +3555,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()

View File

@ -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
@ -175,8 +179,10 @@ class ConversationTextview(gobject.GObject):
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 +646,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 +693,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 +705,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()
@ -969,13 +981,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 +1033,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 +1057,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(
@ -1161,9 +1175,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 +1278,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 +1334,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')):

View File

@ -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()

View File

@ -61,9 +61,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 +101,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 +111,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 +208,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 +269,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 +442,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 +538,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 +554,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 +654,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 +679,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 +823,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 +1007,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 +1139,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)
@ -1184,7 +1215,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 +1306,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 +1369,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 +1399,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 +1429,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 +1442,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 +1454,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 +1503,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 +1555,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 +1625,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 +1665,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 +1704,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 +1725,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 +1760,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 +1770,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 +1855,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 +1875,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 +1964,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 +1974,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 +1988,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 +2004,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 +2014,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,7 +2106,9 @@ 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']
@ -2039,7 +2140,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 +2153,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 +2243,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 +2311,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 +2376,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'),
@ -2433,10 +2544,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='',
@ -2843,10 +2954,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 +2983,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 +3042,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 +3073,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 +3097,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 +3109,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 +3161,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):
@ -3416,8 +3534,10 @@ class ArchivingPreferencesWindow:
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'''
@ -3429,6 +3549,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'
@ -3524,6 +3646,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:
@ -3671,7 +3795,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(
@ -3714,6 +3838,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)
@ -3749,9 +3875,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 = []
@ -3908,9 +4035,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')
@ -3937,10 +4065,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]
@ -3976,8 +4108,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()
@ -4068,8 +4202,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')
@ -4189,7 +4325,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
@ -4580,8 +4718,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
@ -4732,7 +4872,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
@ -4811,7 +4953,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')

View File

@ -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:
@ -284,7 +303,9 @@ class ServicesCache:
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 +337,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 +361,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 +385,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 +403,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 +421,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 +437,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 +454,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 +546,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 +588,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 +624,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 +640,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 +675,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 +694,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 +718,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 +786,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 +803,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 +850,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 +867,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 +894,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 +918,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 +930,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 +949,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 +963,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 +973,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 +986,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 +996,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 +1007,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 +1032,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 +1060,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 +1076,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 +1107,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 +1127,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 +1139,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 +1162,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 +1224,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 +1341,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 +1367,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 +1378,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 +1391,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 +1485,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 +1498,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 +1522,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 +1548,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 +1792,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 +1821,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 +1881,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 +1947,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)
@ -1840,7 +1976,9 @@ class DiscussionGroupsBrowser(AgentBrowser):
gajim.connections[account].send_pb_subscription_query(jid, self._subscriptionsCB)
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 +2029,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 +2102,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 +2122,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,7 +2133,9 @@ 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
@ -1998,7 +2144,9 @@ class DiscussionGroupsBrowser(AgentBrowser):
gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._subscribeCB, groupnode)
def on_unsubscribe_button_clicked(self, widget):
'''Called when 'unsubscribe' button is pressed. Send unsubscription request.'''
"""
Called when 'unsubscribe' button is pressed. Send unsubscription request
"""
model, iter_ = self.window.services_treeview.get_selection().get_selected()
if iter_ is None: return
@ -2007,8 +2155,10 @@ class DiscussionGroupsBrowser(AgentBrowser):
gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._unsubscribeCB, groupnode)
def _subscriptionsCB(self, conn, request):
''' We got the subscribed groups list stanza. Now, if we already
have items on the list, we should actualize them. '''
"""
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:
@ -2036,7 +2186,9 @@ class DiscussionGroupsBrowser(AgentBrowser):
raise xmpp.NodeProcessed
def _subscribeCB(self, conn, request, groupnode):
'''We have just subscribed to a node. Update UI'''
"""
We have just subscribed to a node. Update UI
"""
self.subscriptions.add(groupnode)
model = self.window.services_treeview.get_model()
@ -2050,7 +2202,9 @@ class DiscussionGroupsBrowser(AgentBrowser):
raise xmpp.NodeProcessed
def _unsubscribeCB(self, conn, request, groupnode):
'''We have just unsubscribed from a node. Update UI'''
"""
We have just unsubscribed from a node. Update UI
"""
self.subscriptions.remove(groupnode)
model = self.window.services_treeview.get_model()

View File

@ -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;
}

View File

@ -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__ */

View File

@ -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.'),
@ -240,15 +238,6 @@ 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

Some files were not shown because too many files have changed in this diff Show More