merge diff from default
|
@ -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.*
|
||||
|
|
1
AUTHORS
|
@ -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)
|
||||
|
|
|
@ -50,7 +50,6 @@ MAINTAINERCLEANFILES = \
|
|||
aclocal.m4 \
|
||||
libtool \
|
||||
po/POTFILES.in \
|
||||
src/trayicon_la-eggtrayicon.loT \
|
||||
m4/intltool.m4
|
||||
|
||||
MAINTAINERCLEANDIRS = \
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
16
configure.ac
|
@ -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}
|
||||
*****************************"
|
||||
|
|
BIN
data/emoticons/tango/alien.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
data/emoticons/tango/angel.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
data/emoticons/tango/arrogant.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/beer.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/bomb.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
data/emoticons/tango/clap.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/confused.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/cowboy.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
data/emoticons/tango/crying.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/curl-lip.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/cute.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/dance.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/devil.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
data/emoticons/tango/disapointed.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/doh.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/dont-know.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/embarrassed.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
52
data/emoticons/tango/emoticons.py
Normal 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*'],
|
||||
}
|
BIN
data/emoticons/tango/fingers-crossed.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/freaked-out.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/giggle.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/glasses-cool.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/go-away.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/good.png
Normal file
After Width: | Height: | Size: 1,011 B |
BIN
data/emoticons/tango/handshake.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
data/emoticons/tango/hypnotized.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/kiss.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/laugh.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/love.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
data/emoticons/tango/mail.png
Normal file
After Width: | Height: | Size: 881 B |
BIN
data/emoticons/tango/musical-note.png
Normal file
After Width: | Height: | Size: 941 B |
BIN
data/emoticons/tango/party.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
data/emoticons/tango/pissed-off.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/question.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
data/emoticons/tango/rose.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
data/emoticons/tango/rotfl.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/sad.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/sarcastic.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/shout.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/shut-mouth.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/sick.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/silly.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/sleepy.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
data/emoticons/tango/smirk.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/terror.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/thinking.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/tongue.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
data/emoticons/tango/tremble.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
data/emoticons/tango/victory.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
data/emoticons/tango/wink.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
cd "$(dirname $0)/src"
|
||||
exec python -OOt gajim.py $@
|
||||
exec python -OOt gajim.py "$@"
|
||||
|
|
|
@ -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 "$@"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.'''
|
||||
|
|
1044
src/common/jingle.py
109
src/common/jingle_content.py
Normal 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
|
@ -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
|
613
src/common/jingle_session.py
Normal 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))
|
||||
|
135
src/common/jingle_transport.py
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)' %
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
141
src/config.py
|
@ -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()
|
||||
|
|
|
@ -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')):
|
||||
|
|
|
@ -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()
|
||||
|
|
384
src/dialogs.py
|
@ -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')
|
||||
|
|
388
src/disco.py
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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__ */
|
|
@ -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
|
||||
|
|