diff --git a/.hgignore b/.hgignore
index 1f56821c2..962a2aafb 100644
--- a/.hgignore
+++ b/.hgignore
@@ -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.*
diff --git a/AUTHORS b/AUTHORS
index a3cfefe88..3d1502324 100644
--- a/AUTHORS
+++ b/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)
diff --git a/Makefile.am b/Makefile.am
index 019f7a1d9..f708a0855 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -50,7 +50,6 @@ MAINTAINERCLEANFILES = \
aclocal.m4 \
libtool \
po/POTFILES.in \
- src/trayicon_la-eggtrayicon.loT \
m4/intltool.m4
MAINTAINERCLEANDIRS = \
diff --git a/README.html b/README.html
index 558d23a80..8ddade813 100644
--- a/README.html
+++ b/README.html
@@ -16,7 +16,7 @@ Welcome to Gajim and thank you for trying out our client.
Runtime Requirements
- python2.5 or higher
-- pygtk2.12 or higher
+- pygtk2.16 or higher
- python-libglade
- pysqlite2 (if you have python 2.5, you already have this)
@@ -34,7 +34,6 @@ Gajim is a GTK+ app that loves GNOME. You can do 'make' so you don't require gno
For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi
dnsutils (or whatever package provides the nslookup binary) for SRV support
gtkspell and aspell-LANG where lang is your locale eg. en, fr etc
-GnomePythonExtras 2.10 or above (aka gnome-python-desktop) so you can avoid compiling trayicon and gtkspell
gnome-python-desktop (for GnomeKeyring support)
notification-daemon or notify-python (and D-Bus) to get cooler popups
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.
@@ -53,8 +52,6 @@ the xml lib that *comes* with python and not pyxml or whatever.
- python-dev
- python-gtk2-dev
-- libgtk2.0-dev aka. gtk2-devel
-- libgtkspell-dev (for the gtkspell module)
- intltool (>= 0.40.1)
diff --git a/THANKS.artists b/THANKS.artists
index 5cdff9134..7f8f24c99 100644
--- a/THANKS.artists
+++ b/THANKS.artists
@@ -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
-
diff --git a/configure.ac b/configure.ac
index 6363cb90b..af9840989 100644
--- a/configure.ac
+++ b/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}
-*****************************"
diff --git a/data/emoticons/tango/alien.png b/data/emoticons/tango/alien.png
new file mode 100644
index 000000000..ca83124f8
Binary files /dev/null and b/data/emoticons/tango/alien.png differ
diff --git a/data/emoticons/tango/angel.png b/data/emoticons/tango/angel.png
new file mode 100644
index 000000000..ad98a51ca
Binary files /dev/null and b/data/emoticons/tango/angel.png differ
diff --git a/data/emoticons/tango/arrogant.png b/data/emoticons/tango/arrogant.png
new file mode 100644
index 000000000..42b85ff14
Binary files /dev/null and b/data/emoticons/tango/arrogant.png differ
diff --git a/data/emoticons/tango/beer.png b/data/emoticons/tango/beer.png
new file mode 100644
index 000000000..e955770a3
Binary files /dev/null and b/data/emoticons/tango/beer.png differ
diff --git a/data/emoticons/tango/bomb.png b/data/emoticons/tango/bomb.png
new file mode 100644
index 000000000..9f28ff03b
Binary files /dev/null and b/data/emoticons/tango/bomb.png differ
diff --git a/data/emoticons/tango/clap.png b/data/emoticons/tango/clap.png
new file mode 100644
index 000000000..80b0ae1b2
Binary files /dev/null and b/data/emoticons/tango/clap.png differ
diff --git a/data/emoticons/tango/confused.png b/data/emoticons/tango/confused.png
new file mode 100644
index 000000000..e1a76fc38
Binary files /dev/null and b/data/emoticons/tango/confused.png differ
diff --git a/data/emoticons/tango/cowboy.png b/data/emoticons/tango/cowboy.png
new file mode 100644
index 000000000..35e71afc8
Binary files /dev/null and b/data/emoticons/tango/cowboy.png differ
diff --git a/data/emoticons/tango/crying.png b/data/emoticons/tango/crying.png
new file mode 100644
index 000000000..016b82eb5
Binary files /dev/null and b/data/emoticons/tango/crying.png differ
diff --git a/data/emoticons/tango/curl-lip.png b/data/emoticons/tango/curl-lip.png
new file mode 100644
index 000000000..bf12d34c8
Binary files /dev/null and b/data/emoticons/tango/curl-lip.png differ
diff --git a/data/emoticons/tango/cute.png b/data/emoticons/tango/cute.png
new file mode 100644
index 000000000..ca6b4018a
Binary files /dev/null and b/data/emoticons/tango/cute.png differ
diff --git a/data/emoticons/tango/dance.png b/data/emoticons/tango/dance.png
new file mode 100644
index 000000000..709701d9b
Binary files /dev/null and b/data/emoticons/tango/dance.png differ
diff --git a/data/emoticons/tango/devil.png b/data/emoticons/tango/devil.png
new file mode 100644
index 000000000..3e043d07b
Binary files /dev/null and b/data/emoticons/tango/devil.png differ
diff --git a/data/emoticons/tango/disapointed.png b/data/emoticons/tango/disapointed.png
new file mode 100644
index 000000000..b2aced9a3
Binary files /dev/null and b/data/emoticons/tango/disapointed.png differ
diff --git a/data/emoticons/tango/doh.png b/data/emoticons/tango/doh.png
new file mode 100644
index 000000000..d5cd84cc8
Binary files /dev/null and b/data/emoticons/tango/doh.png differ
diff --git a/data/emoticons/tango/dont-know.png b/data/emoticons/tango/dont-know.png
new file mode 100644
index 000000000..df2b647ca
Binary files /dev/null and b/data/emoticons/tango/dont-know.png differ
diff --git a/data/emoticons/tango/embarrassed.png b/data/emoticons/tango/embarrassed.png
new file mode 100644
index 000000000..93c1c2624
Binary files /dev/null and b/data/emoticons/tango/embarrassed.png differ
diff --git a/data/emoticons/tango/emoticons.py b/data/emoticons/tango/emoticons.py
new file mode 100644
index 000000000..43c7ce37f
--- /dev/null
+++ b/data/emoticons/tango/emoticons.py
@@ -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*'],
+}
\ No newline at end of file
diff --git a/data/emoticons/tango/fingers-crossed.png b/data/emoticons/tango/fingers-crossed.png
new file mode 100644
index 000000000..6494707c5
Binary files /dev/null and b/data/emoticons/tango/fingers-crossed.png differ
diff --git a/data/emoticons/tango/freaked-out.png b/data/emoticons/tango/freaked-out.png
new file mode 100644
index 000000000..3bb6d58e5
Binary files /dev/null and b/data/emoticons/tango/freaked-out.png differ
diff --git a/data/emoticons/tango/giggle.png b/data/emoticons/tango/giggle.png
new file mode 100644
index 000000000..e583178f6
Binary files /dev/null and b/data/emoticons/tango/giggle.png differ
diff --git a/data/emoticons/tango/glasses-cool.png b/data/emoticons/tango/glasses-cool.png
new file mode 100644
index 000000000..6fdb5fe85
Binary files /dev/null and b/data/emoticons/tango/glasses-cool.png differ
diff --git a/data/emoticons/tango/go-away.png b/data/emoticons/tango/go-away.png
new file mode 100644
index 000000000..efc98af5a
Binary files /dev/null and b/data/emoticons/tango/go-away.png differ
diff --git a/data/emoticons/tango/good.png b/data/emoticons/tango/good.png
new file mode 100644
index 000000000..127d07313
Binary files /dev/null and b/data/emoticons/tango/good.png differ
diff --git a/data/emoticons/tango/handshake.png b/data/emoticons/tango/handshake.png
new file mode 100644
index 000000000..5c6d6a531
Binary files /dev/null and b/data/emoticons/tango/handshake.png differ
diff --git a/data/emoticons/tango/hypnotized.png b/data/emoticons/tango/hypnotized.png
new file mode 100644
index 000000000..62823ce72
Binary files /dev/null and b/data/emoticons/tango/hypnotized.png differ
diff --git a/data/emoticons/tango/kiss.png b/data/emoticons/tango/kiss.png
new file mode 100644
index 000000000..8d0d2e8b1
Binary files /dev/null and b/data/emoticons/tango/kiss.png differ
diff --git a/data/emoticons/tango/laugh.png b/data/emoticons/tango/laugh.png
new file mode 100644
index 000000000..fdb4f50c0
Binary files /dev/null and b/data/emoticons/tango/laugh.png differ
diff --git a/data/emoticons/tango/love.png b/data/emoticons/tango/love.png
new file mode 100644
index 000000000..5171deed6
Binary files /dev/null and b/data/emoticons/tango/love.png differ
diff --git a/data/emoticons/tango/mail.png b/data/emoticons/tango/mail.png
new file mode 100644
index 000000000..027bdb090
Binary files /dev/null and b/data/emoticons/tango/mail.png differ
diff --git a/data/emoticons/tango/musical-note.png b/data/emoticons/tango/musical-note.png
new file mode 100644
index 000000000..5990ae544
Binary files /dev/null and b/data/emoticons/tango/musical-note.png differ
diff --git a/data/emoticons/tango/party.png b/data/emoticons/tango/party.png
new file mode 100644
index 000000000..bcc7a4ffe
Binary files /dev/null and b/data/emoticons/tango/party.png differ
diff --git a/data/emoticons/tango/pissed-off.png b/data/emoticons/tango/pissed-off.png
new file mode 100644
index 000000000..a1c8c8c64
Binary files /dev/null and b/data/emoticons/tango/pissed-off.png differ
diff --git a/data/emoticons/tango/question.png b/data/emoticons/tango/question.png
new file mode 100644
index 000000000..fc657b46a
Binary files /dev/null and b/data/emoticons/tango/question.png differ
diff --git a/data/emoticons/tango/rose.png b/data/emoticons/tango/rose.png
new file mode 100644
index 000000000..ff7180ed3
Binary files /dev/null and b/data/emoticons/tango/rose.png differ
diff --git a/data/emoticons/tango/rotfl.png b/data/emoticons/tango/rotfl.png
new file mode 100644
index 000000000..9f985a2e6
Binary files /dev/null and b/data/emoticons/tango/rotfl.png differ
diff --git a/data/emoticons/tango/sad.png b/data/emoticons/tango/sad.png
new file mode 100644
index 000000000..643fd4379
Binary files /dev/null and b/data/emoticons/tango/sad.png differ
diff --git a/data/emoticons/tango/sarcastic.png b/data/emoticons/tango/sarcastic.png
new file mode 100644
index 000000000..ece6dd522
Binary files /dev/null and b/data/emoticons/tango/sarcastic.png differ
diff --git a/data/emoticons/tango/shout.png b/data/emoticons/tango/shout.png
new file mode 100644
index 000000000..c7aa7a9d7
Binary files /dev/null and b/data/emoticons/tango/shout.png differ
diff --git a/data/emoticons/tango/shut-mouth.png b/data/emoticons/tango/shut-mouth.png
new file mode 100644
index 000000000..c98a29780
Binary files /dev/null and b/data/emoticons/tango/shut-mouth.png differ
diff --git a/data/emoticons/tango/sick.png b/data/emoticons/tango/sick.png
new file mode 100644
index 000000000..777e6fd84
Binary files /dev/null and b/data/emoticons/tango/sick.png differ
diff --git a/data/emoticons/tango/silly.png b/data/emoticons/tango/silly.png
new file mode 100644
index 000000000..b944b7e9f
Binary files /dev/null and b/data/emoticons/tango/silly.png differ
diff --git a/data/emoticons/tango/sleepy.png b/data/emoticons/tango/sleepy.png
new file mode 100644
index 000000000..25449039c
Binary files /dev/null and b/data/emoticons/tango/sleepy.png differ
diff --git a/data/emoticons/tango/smirk.png b/data/emoticons/tango/smirk.png
new file mode 100644
index 000000000..77e97ee5c
Binary files /dev/null and b/data/emoticons/tango/smirk.png differ
diff --git a/data/emoticons/tango/terror.png b/data/emoticons/tango/terror.png
new file mode 100644
index 000000000..104d01252
Binary files /dev/null and b/data/emoticons/tango/terror.png differ
diff --git a/data/emoticons/tango/thinking.png b/data/emoticons/tango/thinking.png
new file mode 100644
index 000000000..9d1d752b1
Binary files /dev/null and b/data/emoticons/tango/thinking.png differ
diff --git a/data/emoticons/tango/tongue.png b/data/emoticons/tango/tongue.png
new file mode 100644
index 000000000..144318dd1
Binary files /dev/null and b/data/emoticons/tango/tongue.png differ
diff --git a/data/emoticons/tango/tremble.png b/data/emoticons/tango/tremble.png
new file mode 100644
index 000000000..3a39e8796
Binary files /dev/null and b/data/emoticons/tango/tremble.png differ
diff --git a/data/emoticons/tango/victory.png b/data/emoticons/tango/victory.png
new file mode 100644
index 000000000..d0b635e9b
Binary files /dev/null and b/data/emoticons/tango/victory.png differ
diff --git a/data/emoticons/tango/wink.png b/data/emoticons/tango/wink.png
new file mode 100644
index 000000000..662baa8ce
Binary files /dev/null and b/data/emoticons/tango/wink.png differ
diff --git a/launch.sh b/launch.sh
index bcb968553..0c73c667c 100755
--- a/launch.sh
+++ b/launch.sh
@@ -1,3 +1,3 @@
#!/bin/sh
cd "$(dirname $0)/src"
-exec python -OOt gajim.py $@
+exec python -OOt gajim.py "$@"
diff --git a/scripts/gajim.in b/scripts/gajim.in
index 711ddac1d..178d69b16 100644
--- a/scripts/gajim.in
+++ b/scripts/gajim.in
@@ -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 "$@"
diff --git a/src/Makefile.am b/src/Makefile.am
index 24e7e2234..9c5e23b85 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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
diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py
index 9b96f0fee..01d34eee6 100644
--- a/src/adhoc_commands.py
+++ b/src/adhoc_commands.py
@@ -35,17 +35,22 @@ import dialogs
import dataforms_widget
class CommandWindow:
- '''Class for a window for single ad-hoc commands session. Note, that
- there might be more than one for one account/jid pair in one moment.
+ """
+ Class for a window for single ad-hoc commands session
- TODO: maybe put this window into MessageWindow? consider this when
- TODO: it will be possible to manage more than one window of one
- TODO: account/jid pair in MessageWindowMgr.
+ Note, that there might be more than one for one account/jid pair in one
+ moment.
- TODO: gtk 2.10 has a special wizard-widget, consider using it...'''
+ TODO: Maybe put this window into MessageWindow? consider this when it will
+ be possible to manage more than one window of one.
+ TODO: Account/jid pair in MessageWindowMgr.
+ TODO: GTK 2.10 has a special wizard-widget, consider using it...
+ """
def __init__(self, account, jid, commandnode=None):
- '''Create new window.'''
+ """
+ Create new window
+ """
# an account object
self.account = gajim.connections[account]
@@ -89,16 +94,29 @@ class CommandWindow:
self.xml.signal_autoconnect(self)
self.window.show_all()
-# these functions are set up by appropriate stageX methods
- def stage_finish(self, *anything): pass
- def stage_back_button_clicked(self, *anything): assert False
- def stage_forward_button_clicked(self, *anything): assert False
- def stage_execute_button_clicked(self, *anything): assert False
- def stage_close_button_clicked(self, *anything): assert False
- def stage_adhoc_commands_window_delete_event(self, *anything): assert False
- def do_nothing(self, *anything): return False
+ # These functions are set up by appropriate stageX methods.
+ def stage_finish(self, *anything):
+ pass
-# 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.
diff --git a/src/advanced_configuration_window.py b/src/advanced_configuration_window.py
index c56ee27ad..b35461287 100644
--- a/src/advanced_configuration_window.py
+++ b/src/advanced_configuration_window.py
@@ -43,7 +43,9 @@ C_TYPE
GTKGUI_GLADE = 'manage_accounts_window.glade'
def rate_limit(rate):
- ''' call func at most *rate* times per second '''
+ """
+ Call func at most *rate* times per second
+ """
def decorator(func):
timeout = [None]
def f(*args, **kwargs):
@@ -131,8 +133,10 @@ class AdvancedConfigurationWindow(object):
gajim.interface.instances['advanced_config'] = self
def cb_value_column_data(self, col, cell, model, iter_):
- '''check if it's boolen or holds password stuff and if yes
- make the cellrenderertext not editable else it's editable'''
+ """
+ Check if it's boolen or holds password stuff and if yes make the
+ cellrenderertext not editable, else - it's editable
+ """
optname = model[iter_][C_PREFNAME]
opttype = model[iter_][C_TYPE]
if opttype == self.types['boolean'] or optname == 'password':
diff --git a/src/atom_window.py b/src/atom_window.py
index 56449846f..4712dfc41 100644
--- a/src/atom_window.py
+++ b/src/atom_window.py
@@ -35,7 +35,9 @@ class AtomWindow:
@classmethod
def newAtomEntry(cls, entry):
- ''' Queue new entry, open window if there's no one opened. '''
+ """
+ Queue new entry, open window if there's no one opened
+ """
cls.entries.append(entry)
if cls.window is None:
@@ -48,7 +50,9 @@ class AtomWindow:
cls.window = None
def __init__(self):
- ''' Create new window... only if we have anything to show. '''
+ """
+ Create new window... only if we have anything to show
+ """
assert len(self.__class__.entries)>0
self.entry = None # the entry actually displayed
@@ -69,7 +73,9 @@ class AtomWindow:
self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK)
def displayNextEntry(self):
- ''' Get next entry from the queue and display it in the window. '''
+ """
+ Get next entry from the queue and display it in the window
+ """
assert len(self.__class__.entries)>0
newentry = self.__class__.entries.pop(0)
@@ -103,8 +109,10 @@ class AtomWindow:
self.entry = newentry
def updateCounter(self):
- ''' We display number of events on the top of window, sometimes it needs to be
- changed...'''
+ """
+ Display number of events on the top of window, sometimes it needs to be
+ changed
+ """
count = len(self.__class__.entries)
if count>0:
self.new_entry_label.set_text(i18n.ngettext(
diff --git a/src/chat_control.py b/src/chat_control.py
index 670978f70..7850a2e3e 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -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 = '%s' % 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 = '' + gobject.markup_escape_text(activity)
- if subactivity:
- tooltip += ': ' + gobject.markup_escape_text(subactivity)
- tooltip += ''
- 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(
- _('"%(title)s" by %(artist)s\n'
- 'from %(source)s') % {'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)
diff --git a/src/command_system/__init__.py b/src/command_system/__init__.py
index c0a48f863..ff61c186e 100644
--- a/src/command_system/__init__.py
+++ b/src/command_system/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/dispatching.py b/src/command_system/dispatching.py
index 2bfd76b96..7f365a915 100644
--- a/src/command_system/dispatching.py
+++ b/src/command_system/dispatching.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/errors.py b/src/command_system/errors.py
index 992e83ccf..877a22acb 100644
--- a/src/command_system/errors.py
+++ b/src/command_system/errors.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/framework.py b/src/command_system/framework.py
index db9eb2e78..30f5cd53c 100644
--- a/src/command_system/framework.py
+++ b/src/command_system/framework.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/implementation/__init__.py b/src/command_system/implementation/__init__.py
index 66d097f42..c77c23e3f 100644
--- a/src/command_system/implementation/__init__.py
+++ b/src/command_system/implementation/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/implementation/custom.py b/src/command_system/implementation/custom.py
index 4f54670da..e4aa32dbf 100644
--- a/src/command_system/implementation/custom.py
+++ b/src/command_system/implementation/custom.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/implementation/hosts.py b/src/command_system/implementation/hosts.py
index b38bb1a35..a90dab464 100644
--- a/src/command_system/implementation/hosts.py
+++ b/src/command_system/implementation/hosts.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/implementation/middleware.py b/src/command_system/implementation/middleware.py
index 9ef4bea29..2f262f8ed 100644
--- a/src/command_system/implementation/middleware.py
+++ b/src/command_system/implementation/middleware.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py
index 4cb19c466..d0e585fec 100644
--- a/src/command_system/implementation/standard.py
+++ b/src/command_system/implementation/standard.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# 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):
diff --git a/src/command_system/mapping.py b/src/command_system/mapping.py
index 707866a20..ecf8f0783 100644
--- a/src/command_system/mapping.py
+++ b/src/command_system/mapping.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 red-agent
+# Copyright (C) 2009 Alexander Cherniuk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/common/caps.py b/src/common/caps.py
index b4a8d2a05..f34dfcda4 100644
--- a/src/common/caps.py
+++ b/src/common/caps.py
@@ -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
diff --git a/src/common/connection.py b/src/common/connection.py
index 96d096131..3abb512de 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -98,17 +98,493 @@ ssl_error = {
32: _("Key usage does not include certificate signing"),
50: _("Application verification failure")
}
-class Connection(ConnectionHandlers):
- '''Connection class'''
+
+class CommonConnection:
+ '''
+ Common connection class, can be derivated for normal connection or zeroconf
+ connection
+ '''
def __init__(self, name):
- ConnectionHandlers.__init__(self)
self.name = name
# self.connected:
# 0=>offline,
# 1=>connection in progress,
- # 2=>authorised
+ # 2=>online
+ # 3=>free for chat
+ # ...
self.connected = 0
self.connection = None # xmpppy ClientCommon instance
+ self.on_purpose = False
+ self.is_zeroconf = False
+ self.password = ''
+ self.server_resource = self._compute_resource()
+ self.gpg = None
+ self.USE_GPG = False
+ if gajim.HAVE_GPG:
+ self.USE_GPG = True
+ self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
+ self.status = ''
+ self.old_show = ''
+ self.priority = gajim.get_priority(name, 'offline')
+ self.time_to_reconnect = None
+ self.bookmarks = []
+
+ self.blocked_list = []
+ self.blocked_contacts = []
+ self.blocked_groups = []
+ self.blocked_all = False
+
+ self.pep_supported = False
+ self.pep = {}
+ # Do we continue connection when we get roster (send presence,get vcard..)
+ self.continue_connect_info = None
+
+ # To know the groupchat jid associated with a sranza ID. Useful to
+ # request vcard or os info... to a real JID but act as if it comes from
+ # the fake jid
+ self.groupchat_jids = {} # {ID : groupchat_jid}
+
+ self.privacy_rules_supported = False
+ self.vcard_supported = False
+ self.private_storage_supported = False
+
+ self.muc_jid = {} # jid of muc server for each transport type
+
+ self.get_config_values_or_default()
+
+ def _compute_resource(self):
+ resource = gajim.config.get_per('accounts', self.name, 'resource')
+ # All valid resource substitution strings should be added to this hash.
+ if resource:
+ resource = Template(resource).safe_substitute({
+ 'hostname': socket.gethostname()
+ })
+ return resource
+
+ def dispatch(self, event, data):
+ '''always passes account name as first param'''
+ gajim.interface.dispatch(event, self.name, data)
+
+ def _reconnect(self):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def quit(self, kill_core):
+ if kill_core and gajim.account_is_connected(self.name):
+ self.disconnect(on_purpose=True)
+
+ def test_gpg_passphrase(self, password):
+ '''Returns 'ok', 'bad_pass' or 'expired' '''
+ if not self.gpg:
+ return False
+ self.gpg.passphrase = password
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ signed = self.gpg.sign('test', keyID)
+ self.gpg.password = None
+ if signed == 'KEYEXPIRED':
+ return 'expired'
+ elif signed == 'BAD_PASSPHRASE':
+ return 'bad_pass'
+ return 'ok'
+
+ def get_signed_msg(self, msg, callback = None):
+ '''returns the signed message if possible
+ or an empty string if gpg is not used
+ or None if waiting for passphrase.
+ callback is the function to call when user give the passphrase'''
+ signed = ''
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ if keyID and self.USE_GPG:
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+ if self.gpg.passphrase is None and not use_gpg_agent:
+ # We didn't set a passphrase
+ return None
+ if self.gpg.passphrase is not None or use_gpg_agent:
+ signed = self.gpg.sign(msg, keyID)
+ if signed == 'BAD_PASSPHRASE':
+ self.USE_GPG = False
+ signed = ''
+ self.dispatch('BAD_PASSPHRASE', ())
+ return signed
+
+ def _on_disconnected(self):
+ ''' called when a disconnect request has completed successfully'''
+ self.disconnect(on_purpose=True)
+ self.dispatch('STATUS', 'offline')
+
+ def get_status(self):
+ return gajim.SHOW_LIST[self.connected]
+
+ def check_jid(self, jid):
+ '''this function must be implemented by derivated classes.
+ It has to return the valid jid, or raise a helpers.InvalidFormat exception
+ '''
+ raise NotImplementedError
+
+ def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
+ chatstate=None, msg_id=None, composing_xep=None, resource=None,
+ user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
+ original_message=None, delayed=None, callback=None):
+ if not self.connection or self.connected < 2:
+ return 1
+ try:
+ jid = self.check_jid(jid)
+ except helpers.InvalidFormat:
+ self.dispatch('ERROR', (_('Invalid Jabber ID'),
+ _('It is not possible to send a message to %s, this JID is not '
+ 'valid.') % jid))
+ return
+
+ if msg and not xhtml and gajim.config.get(
+ 'rst_formatting_outgoing_messages'):
+ from common.rst_xhtml_generator import create_xhtml
+ xhtml = create_xhtml(msg)
+ if not msg and chatstate is None and form_node is None:
+ return
+ fjid = jid
+ if resource:
+ fjid += '/' + resource
+ msgtxt = msg
+ msgenc = ''
+
+ if session:
+ fjid = session.get_to()
+
+ if keyID and self.USE_GPG:
+ xhtml = None
+ if keyID == 'UNKNOWN':
+ error = _('Neither the remote presence is signed, nor a key was '
+ 'assigned.')
+ elif keyID.endswith('MISMATCH'):
+ error = _('The contact\'s key (%s) does not match the key assigned '
+ 'in Gajim.' % keyID[:8])
+ else:
+ def encrypt_thread(msg, keyID, always_trust=False):
+ # encrypt message. This function returns (msgenc, error)
+ return self.gpg.encrypt(msg, [keyID], always_trust)
+ def _on_encrypted(output):
+ msgenc, error = output
+ if error == 'NOT_TRUSTED':
+ def _on_always_trust(answer):
+ if answer:
+ gajim.thread_interface(encrypt_thread, [msg, keyID,
+ True], _on_encrypted, [])
+ else:
+ self._message_encrypted_cb(output, type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml,
+ subject, chatstate, composing_xep, forward_from,
+ delayed, session, form_node, user_nick, keyID,
+ callback)
+ self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust)
+ else:
+ self._message_encrypted_cb(output, type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml, subject,
+ chatstate, composing_xep, forward_from, delayed, session,
+ form_node, user_nick, keyID, callback)
+ gajim.thread_interface(encrypt_thread, [msg, keyID, False],
+ _on_encrypted, [])
+ return
+
+ self._message_encrypted_cb(('', error), type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml, subject, chatstate,
+ composing_xep, forward_from, delayed, session, form_node, user_nick,
+ keyID, callback)
+
+ self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
+ forward_from, delayed, session, form_node, user_nick, callback)
+
+ def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message,
+ fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from,
+ delayed, session, form_node, user_nick, keyID, callback):
+ msgenc, error = output
+
+ if msgenc and not error:
+ msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
+ lang = os.getenv('LANG')
+ if lang is not None and lang != 'en': # we're not english
+ # one in locale and one en
+ msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
+ ' (' + msgtxt + ')'
+ self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate,
+ composing_xep, forward_from, delayed, session, form_node, user_nick,
+ callback)
+ return
+ # Encryption failed, do not send message
+ tim = localtime()
+ self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
+
+ def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
+ forward_from, delayed, session, form_node, user_nick, callback):
+ if type_ == 'chat':
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
+ xhtml=xhtml)
+ else:
+ if subject:
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
+ subject=subject, xhtml=xhtml)
+ else:
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
+ xhtml=xhtml)
+ if msgenc:
+ msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
+
+ if form_node:
+ msg_iq.addChild(node=form_node)
+
+ # XEP-0172: user_nickname
+ if user_nick:
+ msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
+ user_nick)
+
+ # TODO: We might want to write a function so we don't need to
+ # reproduce that ugly if somewhere else.
+ if resource:
+ contact = gajim.contacts.get_contact(self.name, jid, resource)
+ else:
+ contact = gajim.contacts.get_contact_with_highest_priority(self.name,
+ jid)
+
+ # chatstates - if peer supports xep85 or xep22, send chatstates
+ # please note that the only valid tag inside a message containing a
+ # tag is the active event
+ if chatstate is not None and contact:
+ if ((composing_xep == 'XEP-0085' or not composing_xep) \
+ and composing_xep != 'asked_once') or \
+ contact.supports(common.xmpp.NS_CHATSTATES):
+ # XEP-0085
+ msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
+ if composing_xep in ('XEP-0022', 'asked_once') or \
+ not composing_xep:
+ # XEP-0022
+ chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
+ if chatstate is 'composing' or msgtxt:
+ chatstate_node.addChild(name='composing')
+
+ if forward_from:
+ addresses = msg_iq.addChild('addresses',
+ namespace=common.xmpp.NS_ADDRESS)
+ addresses.addChild('address', attrs = {'type': 'ofrom',
+ 'jid': forward_from})
+
+ # XEP-0203
+ if delayed:
+ our_jid = gajim.get_jid_from_account(self.name) + '/' + \
+ self.server_resource
+ timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed))
+ msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2,
+ attrs={'from': our_jid, 'stamp': timestamp})
+
+ # XEP-0184
+ if msgtxt and gajim.config.get_per('accounts', self.name,
+ 'request_receipt') and contact and contact.supports(
+ common.xmpp.NS_RECEIPTS):
+ msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
+
+ if session:
+ # XEP-0201
+ session.last_send = time.time()
+ msg_iq.setThread(session.thread_id)
+
+ # XEP-0200
+ if session.enable_encryption:
+ msg_iq = session.encrypt_stanza(msg_iq)
+
+ if callback:
+ callback(jid, msg, keyID, forward_from, session, original_message,
+ subject, type_, msg_iq)
+
+ def log_message(self, jid, msg, forward_from, session, original_message,
+ subject, type_):
+ if not forward_from and session and session.is_loggable():
+ ji = gajim.get_jid_without_resource(jid)
+ if gajim.config.should_log(self.name, ji):
+ log_msg = msg
+ if original_message is not None:
+ log_msg = original_message
+ if subject:
+ log_msg = _('Subject: %(subject)s\n%(message)s') % \
+ {'subject': subject, 'message': log_msg}
+ if log_msg:
+ if type_ == 'chat':
+ kind = 'chat_msg_sent'
+ else:
+ kind = 'single_msg_sent'
+ try:
+ gajim.logger.write(kind, jid, log_msg)
+ except exceptions.PysqliteOperationalError, e:
+ self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
+ except exceptions.DatabaseMalformed:
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to '
+ 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
+ ' or remove it (all history will be lost).') % \
+ common.logger.LOG_DB_PATH
+
+ def ack_subscribed(self, jid):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def ack_unsubscribed(self, jid):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def request_subscription(self, jid, msg='', name='', groups=[],
+ auto_auth=False):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def send_authorization(self, jid):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def refuse_authorization(self, jid):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def unsubscribe(self, jid, remove_auth = True):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def unsubscribe_agent(self, agent):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def update_contact(self, jid, name, groups):
+ if self.connection:
+ self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
+
+ def update_contacts(self, contacts):
+ '''update multiple roster items'''
+ if self.connection:
+ self.connection.getRoster().setItemMulti(contacts)
+
+ def new_account(self, name, config, sync=False):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def _on_new_account(self, con=None, con_type=None):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def account_changed(self, new_name):
+ self.name = new_name
+
+ def request_last_status_time(self, jid, resource):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def request_os_info(self, jid, resource):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def get_settings(self):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def get_bookmarks(self):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def store_bookmarks(self):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def get_metacontacts(self):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def send_agent_status(self, agent, ptype):
+ '''To be implemented by derivated classes'''
+ raise NotImplementedError
+
+ def gpg_passphrase(self, passphrase):
+ if self.gpg:
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+ if use_gpg_agent:
+ self.gpg.passphrase = None
+ else:
+ self.gpg.passphrase = passphrase
+
+ def ask_gpg_keys(self):
+ if self.gpg:
+ keys = self.gpg.get_keys()
+ return keys
+ return None
+
+ def ask_gpg_secrete_keys(self):
+ if self.gpg:
+ keys = self.gpg.get_secret_keys()
+ return keys
+ return None
+
+ def load_roster_from_db(self):
+ # Do nothing by default
+ return
+
+ def _event_dispatcher(self, realm, event, data):
+ if realm == '':
+ if event == common.xmpp.transports_nb.DATA_RECEIVED:
+ self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore'))
+ elif event == common.xmpp.transports_nb.DATA_SENT:
+ self.dispatch('STANZA_SENT', unicode(data))
+
+ def change_status(self, show, msg, auto=False):
+ if not show in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
+ return -1
+ if not msg:
+ msg = ''
+ sign_msg = False
+ if not auto and not show == 'offline':
+ sign_msg = True
+ if show != 'invisible':
+ # We save it only when privacy list is accepted
+ self.status = msg
+ if show != 'offline' and self.connected < 1:
+ # set old_show to requested 'show' in case we need to
+ # recconect before we auth to server
+ self.old_show = show
+ self.on_purpose = False
+ self.server_resource = self._compute_resource()
+ if gajim.HAVE_GPG:
+ self.USE_GPG = True
+ self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
+ self.connect_and_init(show, msg, sign_msg)
+
+ elif show == 'offline':
+ self.connected = 0
+ if self.connection:
+ p = common.xmpp.Presence(typ = 'unavailable')
+ p = self.add_sha(p, False)
+ if msg:
+ p.setStatus(msg)
+
+ self.connection.RegisterDisconnectHandler(self._on_disconnected)
+ self.connection.send(p, now=True)
+ self.connection.start_disconnect()
+ else:
+ self._on_disconnected()
+
+ elif show != 'offline' and self.connected > 0:
+ # dont'try to connect, when we are in state 'connecting'
+ if self.connected == 1:
+ return
+ if show == 'invisible':
+ self._change_to_invisible(msg)
+ return
+ was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
+ self.connected = gajim.SHOW_LIST.index(show)
+ if was_invisible:
+ self._change_from_invisible()
+ self._update_status(show, msg)
+
+class Connection(CommonConnection, ConnectionHandlers):
+ '''Connection class'''
+ def __init__(self, name):
+ CommonConnection.__init__(self, name)
+ ConnectionHandlers.__init__(self)
# this property is used to prevent double connections
self.last_connection = None # last ClientCommon instance
# If we succeed to connect, remember it so next time we try (after a
@@ -117,36 +593,39 @@ class Connection(ConnectionHandlers):
self.lang = None
if locale.getdefaultlocale()[0]:
self.lang = locale.getdefaultlocale()[0].split('_')[0]
- self.is_zeroconf = False
- self.gpg = None
- self.USE_GPG = False
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
- self.status = ''
- self.priority = gajim.get_priority(name, 'offline')
- self.old_show = ''
# increase/decrease default timeout for server responses
self.try_connecting_for_foo_secs = 45
# holds the actual hostname to which we are connected
self.connected_hostname = None
- self.time_to_reconnect = None
self.last_time_to_reconnect = None
self.new_account_info = None
self.new_account_form = None
- self.bookmarks = []
self.annotations = {}
- self.on_purpose = False
self.last_io = gajim.idlequeue.current_time()
self.last_sent = []
self.last_history_time = {}
self.password = passwords.get_password(name)
- self.server_resource = gajim.config.get_per('accounts', name, 'resource')
- # All valid resource substitution strings should be added to this hash.
- if self.server_resource:
- self.server_resource = Template(self.server_resource).safe_substitute({
- 'hostname': socket.gethostname()
- })
+
+ # Used to ask privacy only once at connection
+ self.music_track_info = 0
+ self.pubsub_supported = False
+ self.pubsub_publish_options_supported = False
+ # Do we auto accept insecure connection
+ self.connection_auto_accepted = False
+ self.pasword_callback = None
+
+ self.on_connect_success = None
+ self.on_connect_failure = None
+ self.retrycount = 0
+ self.jids_for_auto_auth = [] # list of jid to auto-authorize
+ self.available_transports = {} # list of available transports on this
+ # server {'icq': ['icq.server.com', 'icq2.server.com'], }
+ self.private_storage_supported = True
+ self.streamError = ''
+ self.secret_hmac = str(random.random())[2:]
+ # END __init__
+
+ def get_config_values_or_default(self):
if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
self.keepalives = gajim.config.get_per('accounts', self.name,
'keep_alive_every_foo_secs')
@@ -157,47 +636,9 @@ class Connection(ConnectionHandlers):
'ping_alive_every_foo_secs')
else:
self.pingalives = 0
- self.privacy_rules_supported = False
- # Used to ask privacy only once at connection
- self.privacy_rules_requested = False
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_all = False
- self.music_track_info = 0
- self.pubsub_supported = False
- self.pubsub_publish_options_supported = False
- self.pep_supported = False
- self.mood = {}
- self.tune = {}
- self.activity = {}
- # Do we continue connection when we get roster (send presence,get vcard..)
- self.continue_connect_info = None
- # Do we auto accept insecure connection
- self.connection_auto_accepted = False
- # To know the groupchat jid associated with a sranza ID. Useful to
- # request vcard or os info... to a real JID but act as if it comes from
- # the fake jid
- self.groupchat_jids = {} # {ID : groupchat_jid}
- self.pasword_callback = None
-
- self.on_connect_success = None
- self.on_connect_failure = None
- self.retrycount = 0
- self.jids_for_auto_auth = [] # list of jid to auto-authorize
- self.muc_jid = {} # jid of muc server for each transport type
- self.available_transports = {} # list of available transports on this
- # server {'icq': ['icq.server.com', 'icq2.server.com'], }
- self.vcard_supported = False
- self.private_storage_supported = True
- self.streamError = ''
- self.secret_hmac = str(random.random())[2:]
- # END __init__
-
- def dispatch(self, event, data):
- '''always passes account name as first param'''
- gajim.interface.dispatch(event, self.name, data)
+ def check_jid(self, jid):
+ return helpers.parse_jid(jid)
def _reconnect(self):
# Do not try to reco while we are already trying
@@ -224,6 +665,8 @@ class Connection(ConnectionHandlers):
if self.connection:
# make sure previous connection is completely closed
gajim.proxy65_manager.disconnect(self.connection)
+ self.terminate_sessions()
+ self.remove_all_transfers()
self.connection.disconnect()
self.last_connection = None
self.connection = None
@@ -279,6 +722,7 @@ class Connection(ConnectionHandlers):
_('Reconnect manually.')))
def _event_dispatcher(self, realm, event, data):
+ CommonConnection._event_dispatcher(self, realm, event, data)
if realm == common.xmpp.NS_REGISTER:
if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
# data is (agent, DataFrom, is_form, error_msg)
@@ -384,11 +828,6 @@ class Connection(ConnectionHandlers):
elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
# data is (dict)
self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
- elif realm == '':
- if event == common.xmpp.transports_nb.DATA_RECEIVED:
- self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
- elif event == common.xmpp.transports_nb.DATA_SENT:
- self.dispatch('STANZA_SENT', unicode(data))
def _select_next_host(self, hosts):
'''Selects the next host according to RFC2782 p.3 based on it's
@@ -802,10 +1241,6 @@ class Connection(ConnectionHandlers):
self.on_connect_auth = None
# END connect
- def quit(self, kill_core):
- if kill_core and gajim.account_is_connected(self.name):
- self.disconnect(on_purpose=True)
-
def add_lang(self, stanza):
if self.lang:
stanza.setAttr('xml:lang', self.lang)
@@ -985,45 +1420,11 @@ class Connection(ConnectionHandlers):
#Inform GUI we just signed in
self.dispatch('SIGNED_IN', ())
- def test_gpg_passphrase(self, password):
- '''Returns 'ok', 'bad_pass' or 'expired' '''
- if not self.gpg:
- return False
- self.gpg.passphrase = password
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- signed = self.gpg.sign('test', keyID)
- self.gpg.password = None
- if signed == 'KEYEXPIRED':
- return 'expired'
- elif signed == 'BAD_PASSPHRASE':
- return 'bad_pass'
- return 'ok'
-
def get_signed_presence(self, msg, callback = None):
if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'):
return self.get_signed_msg(msg, callback)
return ''
- def get_signed_msg(self, msg, callback = None):
- '''returns the signed message if possible
- or an empty string if gpg is not used
- or None if waiting for passphrase.
- callback is the function to call when user give the passphrase'''
- signed = ''
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- if keyID and self.USE_GPG:
- use_gpg_agent = gajim.config.get('use_gpg_agent')
- if self.gpg.passphrase is None and not use_gpg_agent:
- # We didn't set a passphrase
- return None
- if self.gpg.passphrase is not None or use_gpg_agent:
- signed = self.gpg.sign(msg, keyID)
- if signed == 'BAD_PASSPHRASE':
- self.USE_GPG = False
- signed = ''
- self.dispatch('BAD_PASSPHRASE', ())
- return signed
-
def connect_and_auth(self):
self.on_connect_success = self._connect_success
self.on_connect_failure = self._connect_failure
@@ -1080,92 +1481,30 @@ class Connection(ConnectionHandlers):
p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
self.connection.send(p)
- def change_status(self, show, msg, auto = False):
- if not show in gajim.SHOW_LIST:
- return -1
- sshow = helpers.get_xmpp_show(show)
- if not msg:
- msg = ''
- sign_msg = False
- if not auto and not show == 'offline':
- sign_msg = True
- if show != 'invisible':
- # We save it only when privacy list is accepted
- self.status = msg
- if show != 'offline' and self.connected < 1:
- # set old_show to requested 'show' in case we need to
- # recconect before we auth to server
- self.old_show = show
- self.on_purpose = False
- self.server_resource = gajim.config.get_per('accounts', self.name,
- 'resource')
- # All valid resource substitution strings should be added to this hash.
- if self.server_resource:
- self.server_resource = Template(self.server_resource).\
- safe_substitute({
- 'hostname': socket.gethostname()
- })
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
- self.connect_and_init(show, msg, sign_msg)
+ def _change_to_invisible(self, msg):
+ signed = self.get_signed_presence(msg)
+ self.send_invisible_presence(msg, signed)
- elif show == 'offline':
- self.connected = 0
- if self.connection:
- self.terminate_sessions()
-
- self.on_purpose = True
- p = common.xmpp.Presence(typ = 'unavailable')
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
- self.remove_all_transfers()
- self.time_to_reconnect = None
-
- self.connection.RegisterDisconnectHandler(self._on_disconnected)
- self.connection.send(p, now=True)
- self.connection.start_disconnect()
- #self.connection.start_disconnect(p, self._on_disconnected)
- else:
- self.time_to_reconnect = None
- self._on_disconnected()
-
- elif show != 'offline' and self.connected > 0:
- # dont'try to connect, when we are in state 'connecting'
- if self.connected == 1:
- return
- if show == 'invisible':
- signed = self.get_signed_presence(msg)
- self.send_invisible_presence(msg, signed)
- return
- was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
- self.connected = gajim.SHOW_LIST.index(show)
- if was_invisible and self.privacy_rules_supported:
- iq = self.build_privacy_rule('visible', 'allow')
- self.connection.send(iq)
- self.activate_privacy_rule('visible')
- priority = unicode(gajim.get_priority(self.name, sshow))
- p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- signed = self.get_signed_presence(msg)
- if signed:
- p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
- if self.connection:
- self.connection.send(p)
- self.priority = priority
- self.dispatch('STATUS', show)
-
- def _on_disconnected(self):
- ''' called when a disconnect request has completed successfully'''
- self.disconnect(on_purpose=True)
- self.dispatch('STATUS', 'offline')
-
- def get_status(self):
- return gajim.SHOW_LIST[self.connected]
+ def _change_from_invisible(self):
+ if self.privacy_rules_supported:
+ iq = self.build_privacy_rule('visible', 'allow')
+ self.connection.send(iq)
+ self.activate_privacy_rule('visible')
+ def _update_status(self, show, msg):
+ xmpp_show = helpers.get_xmpp_show(show)
+ priority = unicode(gajim.get_priority(self.name, xmpp_show))
+ p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show)
+ p = self.add_sha(p)
+ if msg:
+ p.setStatus(msg)
+ signed = self.get_signed_presence(msg)
+ if signed:
+ p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
+ if self.connection:
+ self.connection.send(p)
+ self.priority = priority
+ self.dispatch('STATUS', show)
def send_motd(self, jid, subject = '', msg = '', xhtml = None):
if not self.connection:
@@ -1179,202 +1518,23 @@ class Connection(ConnectionHandlers):
chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
original_message=None, delayed=None, callback=None, callback_args=[]):
- if not self.connection or self.connected < 2:
- return 1
- try:
+
+ def cb(jid, msg, keyID, forward_from, session, original_message, subject,
+ type_, msg_iq):
+ msg_id = self.connection.send(msg_iq)
jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat:
- self.dispatch('ERROR', (_('Invalid Jabber ID'),
- _('It is not possible to send a message to %s, this JID is not '
- 'valid.') % jid))
- return
+ self.dispatch('MSGSENT', (jid, msg, keyID))
+ if callback:
+ callback(msg_id, *callback_args)
- if msg and not xhtml and gajim.config.get(
- 'rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- xhtml = create_xhtml(msg)
- if not msg and chatstate is None and form_node is None:
- return
- fjid = jid
- if resource:
- fjid += '/' + resource
- msgtxt = msg
- msgenc = ''
+ self.log_message(jid, msg, forward_from, session, original_message,
+ subject, type_)
- if session:
- fjid = session.get_to()
-
- if keyID and self.USE_GPG:
- xhtml = None
- if keyID == 'UNKNOWN':
- error = _('Neither the remote presence is signed, nor a key was assigned.')
- elif keyID.endswith('MISMATCH'):
- error = _('The contact\'s key (%s) does not match the key assigned in Gajim.' % keyID[:8])
- else:
- def encrypt_thread(msg, keyID, always_trust=False):
- # encrypt message. This function returns (msgenc, error)
- return self.gpg.encrypt(msg, [keyID], always_trust)
- def _on_encrypted(output):
- msgenc, error = output
- if error == 'NOT_TRUSTED':
- def _on_always_trust(answer):
- if answer:
- gajim.thread_interface(encrypt_thread, [msg, keyID,
- True], _on_encrypted, [])
- else:
- self._on_message_encrypted(output, type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml,
- subject, chatstate, composing_xep, forward_from,
- delayed, session, form_node, user_nick, keyID,
- callback, callback_args)
- self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust)
- else:
- self._on_message_encrypted(output, type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml, subject,
- chatstate, composing_xep, forward_from, delayed, session,
- form_node, user_nick, keyID, callback, callback_args)
- gajim.thread_interface(encrypt_thread, [msg, keyID, False],
- _on_encrypted, [])
- return
-
- self._on_message_encrypted(('', error), type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml, subject, chatstate,
- composing_xep, forward_from, delayed, session, form_node, user_nick,
- keyID, callback, callback_args)
-
- self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
- resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
- forward_from, delayed, session, form_node, user_nick, callback,
- callback_args)
-
- def _on_message_encrypted(self, output, type_, msg, msgtxt, original_message,
- fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from,
- delayed, session, form_node, user_nick, keyID, callback, callback_args):
- msgenc, error = output
-
- if msgenc and not error:
- msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
- lang = os.getenv('LANG')
- if lang is not None and lang != 'en': # we're not english
- # one in locale and one en
- msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
- ' (' + msgtxt + ')'
- self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
- resource, jid, xhtml, subject, msgenc, keyID, chatstate,
- composing_xep, forward_from, delayed, session, form_node, user_nick,
- callback, callback_args)
- return
- # Encryption failed, do not send message
- tim = localtime()
- self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
-
- def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
- resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
- forward_from, delayed, session, form_node, user_nick, callback,
- callback_args):
- if type_ == 'chat':
- msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
- xhtml=xhtml)
- else:
- if subject:
- msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
- subject=subject, xhtml=xhtml)
- else:
- msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
- xhtml=xhtml)
- if msgenc:
- msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
-
- if form_node:
- msg_iq.addChild(node=form_node)
-
- # XEP-0172: user_nickname
- if user_nick:
- msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
- user_nick)
-
- # TODO: We might want to write a function so we don't need to
- # reproduce that ugly if somewhere else.
- if resource:
- contact = gajim.contacts.get_contact(self.name, jid, resource)
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(self.name,
- jid)
-
- # chatstates - if peer supports xep85 or xep22, send chatstates
- # please note that the only valid tag inside a message containing a
- # tag is the active event
- if chatstate is not None and contact:
- if ((composing_xep == 'XEP-0085' or not composing_xep) \
- and composing_xep != 'asked_once') or \
- contact.supports(common.xmpp.NS_CHATSTATES):
- # XEP-0085
- msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
- if composing_xep in ('XEP-0022', 'asked_once') or \
- not composing_xep:
- # XEP-0022
- chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
- if chatstate is 'composing' or msgtxt:
- chatstate_node.addChild(name='composing')
-
- if forward_from:
- addresses = msg_iq.addChild('addresses',
- namespace=common.xmpp.NS_ADDRESS)
- addresses.addChild('address', attrs = {'type': 'ofrom',
- 'jid': forward_from})
-
- # XEP-0203
- if delayed:
- our_jid = gajim.get_jid_from_account(self.name) + '/' + \
- self.server_resource
- timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed))
- msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2,
- attrs={'from': our_jid, 'stamp': timestamp})
-
- # XEP-0184
- if msgtxt and gajim.config.get_per('accounts', self.name,
- 'request_receipt') and contact and contact.supports(
- common.xmpp.NS_RECEIPTS):
- msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
-
- if session:
- # XEP-0201
- session.last_send = time.time()
- msg_iq.setThread(session.thread_id)
-
- # XEP-0200
- if session.enable_encryption:
- msg_iq = session.encrypt_stanza(msg_iq)
-
- msg_id = self.connection.send(msg_iq)
- if not forward_from and session and session.is_loggable():
- ji = gajim.get_jid_without_resource(jid)
- if gajim.config.should_log(self.name, ji):
- log_msg = msg
- if original_message is not None:
- log_msg = original_message
- if subject:
- log_msg = _('Subject: %(subject)s\n%(message)s') % \
- {'subject': subject, 'message': msg}
- if log_msg:
- if type_ == 'chat':
- kind = 'chat_msg_sent'
- else:
- kind = 'single_msg_sent'
- try:
- gajim.logger.write(kind, jid, log_msg)
- except exceptions.PysqliteOperationalError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
- except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to '
- 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
- ' or remove it (all history will be lost).') % \
- common.logger.LOG_DB_PATH
- self.dispatch('MSGSENT', (jid, msg, keyID))
-
- if callback:
- callback(msg_id, *callback_args)
+ self._prepare_message(jid, msg, keyID, type_=type_, subject=subject,
+ chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
+ resource=resource, user_nick=user_nick, xhtml=xhtml, session=session,
+ forward_from=forward_from, form_node=form_node,
+ original_message=original_message, delayed=delayed, callback=cb)
def send_contacts(self, contacts, jid):
'''Send contacts with RosterX (Xep-0144)'''
@@ -1479,17 +1639,6 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
self.connection.getRoster().delItem(agent)
- def update_contact(self, jid, name, groups):
- '''update roster item on jabber server'''
- if self.connection:
- self.connection.getRoster().setItem(jid = jid, name = name,
- groups = groups)
-
- def update_contacts(self, contacts):
- '''update multiple roster items on jabber server'''
- if self.connection:
- self.connection.getRoster().setItemMulti(contacts)
-
def send_new_account_infos(self, form, is_form):
if is_form:
# Get username and password and put them in new_account_info
@@ -1507,7 +1656,7 @@ class Connection(ConnectionHandlers):
self.new_account_form = form
self.new_account(self.name, self.new_account_info)
- def new_account(self, name, config, sync = False):
+ def new_account(self, name, config, sync=False):
# If a connection already exist we cannot create a new account
if self.connection:
return
@@ -1518,7 +1667,7 @@ class Connection(ConnectionHandlers):
self.on_connect_failure = self._on_new_account
self.connect(config)
- def _on_new_account(self, con = None, con_type = None):
+ def _on_new_account(self, con=None, con_type=None):
if not con_type:
if len(self._connection_types) or len(self._hosts):
# There are still other way to try to connect
@@ -1530,9 +1679,6 @@ class Connection(ConnectionHandlers):
self.connection = con
common.xmpp.features_nb.getRegInfo(con, self._hostname)
- def account_changed(self, new_name):
- self.name = new_name
-
def request_last_status_time(self, jid, resource, groupchat_jid=None):
'''groupchat_jid is used when we want to send a request to a real jid
and act as if the answer comes from the groupchat_jid'''
@@ -1923,26 +2069,6 @@ class Connection(ConnectionHandlers):
query.addChild(node = form)
self.connection.send(iq)
- def gpg_passphrase(self, passphrase):
- if self.gpg:
- use_gpg_agent = gajim.config.get('use_gpg_agent')
- if use_gpg_agent:
- self.gpg.passphrase = None
- else:
- self.gpg.passphrase = passphrase
-
- def ask_gpg_keys(self):
- if self.gpg:
- keys = self.gpg.get_keys()
- return keys
- return None
-
- def ask_gpg_secrete_keys(self):
- if self.gpg:
- keys = self.gpg.get_secret_keys()
- return keys
- return None
-
def change_password(self, password):
if not self.connection:
return
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 856653eb2..d4a89eca0 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -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 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',
diff --git a/src/common/contacts.py b/src/common/contacts.py
index 86175567f..5a9df3499 100644
--- a/src/common/contacts.py
+++ b/src/common/contacts.py
@@ -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
diff --git a/src/common/helpers.py b/src/common/helpers.py
index 70bdfc9fe..341ebb15f 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -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.'''
diff --git a/src/common/jingle.py b/src/common/jingle.py
index f05886533..8a369ff7e 100644
--- a/src/common/jingle.py
+++ b/src/common/jingle.py
@@ -13,17 +13,6 @@
''' Handles the jingle signalling protocol. '''
#TODO:
-# * things in XEP 0166, including:
-# - 'senders' attribute of 'content' element
-# - security preconditions
-# * actions:
-# - content-modify
-# - description-info, session-info
-# - security-info
-# - transport-accept, transport-reject
-# * sid/content related:
-# - tiebreaking
-# - if there already is a session, use it
# * things in XEP 0176, including:
# - http://xmpp.org/extensions/xep-0176.html#protocol-restarts
# - http://xmpp.org/extensions/xep-0176.html#fallback
@@ -36,1034 +25,15 @@
# - codecs
# - STUN
-# * DONE: figure out why it doesn't work with pidgin:
-# That's a bug in pidgin: http://xmpp.org/extensions/xep-0176.html#protocol-checks
+# * figure out why it doesn't work with pidgin:
+# That's maybe a bug in pidgin:
+# http://xmpp.org/extensions/xep-0176.html#protocol-checks
-# * timeout
-
-# * split this file in several modules
-# For example, a file dedicated for XEP0166, one for XEP0176,
-# and one for XEP0167
-
-# * handle different kinds of sink and src elements
-
-import gajim
import xmpp
import helpers
-import farsight, gst
-
-def get_first_gst_element(elements):
- ''' Returns, if it exists, the first available element of the list. '''
- for name in elements:
- factory = gst.element_factory_find(name)
- if factory:
- return factory.create()
-
-#FIXME: Move it to JingleSession.States?
-class JingleStates(object):
- ''' States in which jingle session may exist. '''
- ended = 0
- pending = 1
- active = 2
-
-#FIXME: Move it to JingleTransport.Type?
-class TransportType(object):
- ''' Possible types of a JingleTransport '''
- datagram = 1
- streaming = 2
-
-class OutOfOrder(Exception):
- ''' Exception that should be raised when an action is received when in the wrong state. '''
-
-class TieBreak(Exception):
- ''' Exception that should be raised in case of a tie, when we overrule the other action. '''
-
-class JingleSession(object):
- ''' This represents one jingle session. '''
- def __init__(self, con, weinitiate, jid, sid=None):
- ''' con -- connection object,
- weinitiate -- boolean, are we the initiator?
- jid - jid of the other entity'''
- self.contents = {} # negotiated contents
- self.connection = con # connection to use
- # our full jid
- self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \
- con.server_resource
- self.peerjid = jid # jid we connect to
- # jid we use as the initiator
- self.initiator = weinitiate and self.ourjid or self.peerjid
- # jid we use as the responder
- self.responder = weinitiate and self.peerjid or self.ourjid
- # are we an initiator?
- self.weinitiate = weinitiate
- # what state is session in? (one from JingleStates)
- self.state = JingleStates.ended
- if not sid:
- sid = con.connection.getAnID()
- self.sid = sid # sessionid
-
- self.accepted = True # is this session accepted by user
-
- # callbacks to call on proper contents
- # use .prepend() to add new callbacks, especially when you're going
- # to send error instead of ack
- self.callbacks = {
- 'content-accept': [self.__contentAcceptCB, self.__broadcastCB,
- self.__defaultCB],
- 'content-add': [self.__contentAddCB, self.__broadcastCB,
- self.__defaultCB], #TODO
- 'content-modify': [self.__defaultCB], #TODO
- 'content-reject': [self.__defaultCB, self.__contentRemoveCB], #TODO
- 'content-remove': [self.__defaultCB, self.__contentRemoveCB],
- 'description-info': [self.__broadcastCB, self.__defaultCB], #TODO
- 'security-info': [self.__defaultCB], #TODO
- 'session-accept': [self.__sessionAcceptCB, self.__contentAcceptCB,
- self.__broadcastCB, self.__defaultCB],
- 'session-info': [self.__sessionInfoCB, self.__broadcastCB, self.__defaultCB],
- 'session-initiate': [self.__sessionInitiateCB, self.__broadcastCB,
- self.__defaultCB],
- 'session-terminate': [self.__sessionTerminateCB, self.__broadcastAllCB,
- self.__defaultCB],
- 'transport-info': [self.__broadcastCB, self.__defaultCB],
- 'transport-replace': [self.__broadcastCB, self.__transportReplaceCB], #TODO
- 'transport-accept': [self.__defaultCB], #TODO
- 'transport-reject': [self.__defaultCB], #TODO
- 'iq-result': [],
- 'iq-error': [self.__errorCB],
- }
-
- ''' Interaction with user '''
- def approve_session(self):
- ''' Called when user accepts session in UI (when we aren't the initiator).
- '''
- self.accept_session()
-
- def decline_session(self):
- ''' Called when user declines session in UI (when we aren't the initiator)
- '''
- reason = xmpp.Node('reason')
- reason.addChild('decline')
- self._session_terminate(reason)
-
- def approve_content(self, media):
- content = self.get_content(media)
- if content:
- content.accepted = True
- self.on_session_state_changed(content)
-
- def reject_content(self, media):
- content = self.get_content(media)
- if content:
- if self.state == JingleStates.active:
- self.__content_reject(content)
- content.destroy()
- self.on_session_state_changed()
-
- def end_session(self):
- ''' Called when user stops or cancel session in UI. '''
- reason = xmpp.Node('reason')
- if self.state == JingleStates.active:
- reason.addChild('success')
- else:
- reason.addChild('cancel')
- self._session_terminate(reason)
-
- ''' Middle-level functions to manage contents. Handle local content
- cache and send change notifications. '''
- def get_content(self, media=None):
- if media == 'audio':
- cls = JingleVoIP
- elif media == 'video':
- cls = JingleVideo
- #elif media == None:
- # cls = JingleContent
- else:
- return None
-
- for content in self.contents.values():
- if isinstance(content, cls):
- return content
-
- def add_content(self, name, content, creator='we'):
- ''' Add new content to session. If the session is active,
- this will send proper stanza to update session.
- Creator must be one of ('we', 'peer', 'initiator', 'responder')'''
- assert creator in ('we', 'peer', 'initiator', 'responder')
-
- if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \
- not self.weinitiate):
- creator = 'initiator'
- elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \
- not self.weinitiate):
- creator = 'responder'
- content.creator = creator
- content.name = name
- self.contents[(creator, name)] = content
-
- if (creator == 'initiator') == self.weinitiate:
- # The content is from us, accept it
- content.accepted = True
-
- def remove_content(self, creator, name):
- ''' We do not need this now '''
- #TODO:
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- if len(self.contents) > 1:
- self.__content_remove(content)
- self.contents[(creator, name)].destroy()
- if len(self.contents) == 0:
- self.end_session()
-
- def modify_content(self, creator, name, *someother):
- ''' We do not need this now '''
- pass
-
- def on_session_state_changed(self, content=None):
- if self.state == JingleStates.ended:
- # Session not yet started, only one action possible: session-initiate
- if self.is_ready() and self.weinitiate:
- self.__session_initiate()
- elif self.state == JingleStates.pending:
- # We can either send a session-accept or a content-add
- if self.is_ready() and not self.weinitiate:
- self.__session_accept()
- elif content and (content.creator == 'initiator') == self.weinitiate:
- self.__content_add(content)
- elif content and self.weinitiate:
- self.__content_accept(content)
- elif self.state == JingleStates.active:
- # We can either send a content-add or a content-accept
- if not content:
- return
- if (content.creator == 'initiator') == self.weinitiate:
- # We initiated this content. It's a pending content-add.
- self.__content_add(content)
- else:
- # The other side created this content, we accept it.
- self.__content_accept(content)
-
- def is_ready(self):
- ''' Returns True when all codecs and candidates are ready
- (for all contents). '''
- return (all((content.is_ready() for content in self.contents.itervalues()))
- and self.accepted)
-
- ''' Middle-level function to do stanza exchange. '''
- def accept_session(self):
- ''' Mark the session as accepted. '''
- self.accepted = True
- self.on_session_state_changed()
-
- def start_session(self):
- ''' Mark the session as ready to be started. '''
- self.accepted = True
- self.on_session_state_changed()
-
- def send_session_info(self):
- pass
-
- def send_content_accept(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-accept')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- def send_transport_info(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('transport-info')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- ''' Session callbacks. '''
- def stanzaCB(self, stanza):
- ''' A callback for ConnectionJingle. It gets stanza, then
- tries to send it to all internally registered callbacks.
- First one to raise xmpp.NodeProcessed breaks function.'''
- jingle = stanza.getTag('jingle')
- error = stanza.getTag('error')
- if error:
- # it's an iq-error stanza
- action = 'iq-error'
- elif jingle:
- # it's a jingle action
- action = jingle.getAttr('action')
- if action not in self.callbacks:
- self.__send_error(stanza, 'bad_request')
- return
- #FIXME: If we aren't initiated and it's not a session-initiate...
- if action != 'session-initiate' and self.state == JingleStates.ended:
- self.__send_error(stanza, 'item-not-found', 'unknown-session')
- return
- else:
- # it's an iq-result (ack) stanza
- action = 'iq-result'
-
- callables = self.callbacks[action]
-
- try:
- for callable in callables:
- callable(stanza=stanza, jingle=jingle, error=error, action=action)
- except xmpp.NodeProcessed:
- pass
- except TieBreak:
- self.__send_error(stanza, 'conflict', 'tiebreak')
- except OutOfOrder:
- self.__send_error(stanza, 'unexpected-request', 'out-of-order')#FIXME
-
- def __defaultCB(self, stanza, jingle, error, action):
- ''' Default callback for action stanzas -- simple ack
- and stop processing. '''
- response = stanza.buildReply('result')
- self.connection.connection.send(response)
-
- def __errorCB(self, stanza, jingle, error, action):
- #FIXME
- text = error.getTagData('text')
- jingle_error = None
- xmpp_error = None
- for child in error.getChildren():
- if child.getNamespace() == xmpp.NS_JINGLE_ERRORS:
- jingle_error = child.getName()
- elif child.getNamespace() == xmpp.NS_STANZAS:
- xmpp_error = child.getName()
- self.__dispatch_error(xmpp_error, jingle_error, text)
- #FIXME: Not sure when we would want to do that...
- if xmpp_error == 'item-not-found':
- self.connection.delete_jingle_session(self.peerjid, self.sid)
-
- def __transportReplaceCB(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- transport_ns = content.getTag('transport').getNamespace()
- if transport_ns == xmpp.JINGLE_ICE_UDP:
- #FIXME: We don't manage anything else than ICE-UDP now...
- #What was the previous transport?!?
- #Anyway, content's transport is not modifiable yet
- pass
- else:
- stanza, jingle = self.__make_jingle('transport-reject')
- content = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- content.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise xmpp.NodeProcessed
- else:
- #FIXME: This ressource is unknown to us, what should we do?
- #For now, reject the transport
- stanza, jingle = self.__make_jingle('transport-reject')
- c = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- c.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise xmpp.NodeProcessed
-
- def __sessionInfoCB(self, stanza, jingle, error, action):
- #TODO: ringing, active, (un)hold, (un)mute
- payload = jingle.getPayload()
- if len(payload) > 0:
- self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info')
- raise xmpp.NodeProcessed
-
- def __contentRemoveCB(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- #TODO: this will fail if content is not an RTP content
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, content.media, 'removed'))
- content.destroy()
- if len(self.contents) == 0:
- reason = xmpp.Node('reason')
- reason.setTag('success')
- self._session_terminate(reason)
-
- def __sessionAcceptCB(self, stanza, jingle, error, action):
- if self.state != JingleStates.pending: #FIXME
- raise OutOfOrder
- self.state = JingleStates.active
-
- def __contentAcceptCB(self, stanza, jingle, error, action):
- ''' Called when we get content-accept stanza or equivalent one
- (like session-accept).'''
- # check which contents are accepted
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']#TODO...
-
- def __contentAddCB(self, stanza, jingle, error, action):
- if self.state == JingleStates.ended:
- raise OutOfOrder
-
- parse_result = self.__parse_contents(jingle)
- contents = parse_result[2]
- rejected_contents = parse_result[3]
-
- for name, creator in rejected_contents:
- #TODO:
- content = JingleContent()
- self.add_content(name, content, creator)
- self.__content_reject(content)
- self.contents[(content.creator, content.name)].destroy()
-
- self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
- contents))
-
- def __sessionInitiateCB(self, stanza, jingle, error, action):
- ''' We got a jingle session request from other entity,
- therefore we are the receiver... Unpack the data,
- inform the user. '''
-
- if self.state != JingleStates.ended:
- raise OutOfOrder
-
- self.initiator = jingle['initiator']
- self.responder = self.ourjid
- self.peerjid = self.initiator
- self.accepted = False # user did not accept this session yet
-
- # TODO: If the initiator is unknown to the receiver (e.g., via presence
- # subscription) and the receiver has a policy of not communicating via
- # Jingle with unknown entities, it SHOULD return a
- # error.
-
- # Lets check what kind of jingle session does the peer want
- contents_ok, transports_ok, contents, pouet = self.__parse_contents(jingle)
-
- # If there's no content we understand...
- if not contents_ok:
- # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
- reason = xmpp.Node('reason')
- reason.setTag('unsupported-applications')
- self.__defaultCB(stanza, jingle, error, action)
- self._session_terminate(reason)
- raise xmpp.NodeProcessed
-
- if not transports_ok:
- # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
- reason = xmpp.Node('reason')
- reason.setTag('unsupported-transports')
- self.__defaultCB(stanza, jingle, error, action)
- self._session_terminate(reason)
- raise xmpp.NodeProcessed
-
- self.state = JingleStates.pending
-
- # Send event about starting a session
- self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
- contents))
-
- def __broadcastCB(self, stanza, jingle, error, action):
- ''' Broadcast the stanza contents to proper content handlers. '''
- for content in jingle.iterTags('content'):
- name = content['name']
- creator = content['creator']
- cn = self.contents[(creator, name)]
- cn.stanzaCB(stanza, content, error, action)
-
- def __sessionTerminateCB(self, stanza, jingle, error, action):
- self.connection.delete_jingle_session(self.peerjid, self.sid)
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- text = reason#TODO
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, None, text))
-
- def __broadcastAllCB(self, stanza, jingle, error, action):
- ''' Broadcast the stanza to all content handlers. '''
- for content in self.contents.itervalues():
- content.stanzaCB(stanza, None, error, action)
-
- ''' Internal methods. '''
- def __parse_contents(self, jingle):
- #TODO: Needs some reworking
- contents = []
- contents_rejected = []
- contents_ok = False
- transports_ok = False
-
- for element in jingle.iterTags('content'):
- desc = element.getTag('description')
- desc_ns = desc.getNamespace()
- tran_ns = element.getTag('transport').getNamespace()
- if desc_ns == xmpp.NS_JINGLE_RTP and desc['media'] in ('audio', 'video'):
- contents_ok = True
- #TODO: Everything here should be moved somewhere else
- if tran_ns == xmpp.NS_JINGLE_ICE_UDP:
- if desc['media'] == 'audio':
- self.add_content(element['name'], JingleVoIP(self), 'peer')
- else:
- self.add_content(element['name'], JingleVideo(self), 'peer')
- contents.append((desc['media'],))
- transports_ok = True
- else:
- contents_rejected.append((element['name'], 'peer'))
- else:
- contents_rejected.append((element['name'], 'peer'))
-
- return (contents_ok, transports_ok, contents, contents_rejected)
-
- def __dispatch_error(self, error, jingle_error=None, text=None):
- if jingle_error:
- error = jingle_error
- if text:
- text = '%s (%s)' % (error, text)
- else:
- text = error
- self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text))
-
- def __reason_from_stanza(self, stanza):
- reason = 'success'
- reasons = ['success', 'busy', 'cancel', 'connectivity-error',
- 'decline', 'expired', 'failed-application', 'failed-transport',
- 'general-error', 'gone', 'incompatible-parameters', 'media-error',
- 'security-error', 'timeout', 'unsupported-applications',
- 'unsupported-transports']
- tag = stanza.getTag('reason')
- if tag:
- text = tag.getTagData('text')
- for r in reasons:
- if tag.getTag(r):
- reason = r
- break
- return (reason, text)
-
- ''' Methods that make/send proper pieces of XML. They check if the session
- is in appropriate state. '''
- def __make_jingle(self, action):
- stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
- attrs = {'action': action,
- 'sid': self.sid}
- if action == 'session-initiate':
- attrs['initiator'] = self.initiator
- elif action == 'session-accept':
- attrs['responder'] = self.responder
- jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
- return stanza, jingle
-
- def __send_error(self, stanza, error, jingle_error=None, text=None):
- err = xmpp.Error(stanza, error)
- err.setNamespace(xmpp.NS_STANZAS)
- if jingle_error:
- err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS)
- if text:
- err.setTagData('text', text)
- self.connection.connection.send(err)
- self.__dispatch_error(error, jingle_error, text)
-
- def __append_content(self, jingle, content):
- ''' Append element to element,
- with (full=True) or without (full=False)
- children. '''
- jingle.addChild('content',
- attrs={'name': content.name, 'creator': content.creator})
-
- def __append_contents(self, jingle):
- ''' Append all elements to .'''
- # TODO: integrate with __appendContent?
- # TODO: parameters 'name', 'content'?
- for content in self.contents.values():
- self.__append_content(jingle, content)
-
- def __session_initiate(self):
- assert self.state == JingleStates.ended
- stanza, jingle = self.__make_jingle('session-initiate')
- self.__append_contents(jingle)
- self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent')
- self.connection.connection.send(stanza)
- self.state = JingleStates.pending
-
- def __session_accept(self):
- assert self.state == JingleStates.pending
- stanza, jingle = self.__make_jingle('session-accept')
- self.__append_contents(jingle)
- self.__broadcastCB(stanza, jingle, None, 'session-accept-sent')
- self.connection.connection.send(stanza)
- self.state = JingleStates.active
-
- def __session_info(self, payload=None):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('session-info')
- if payload:
- jingle.addChild(node=payload)
- self.connection.connection.send(stanza)
-
- def _session_terminate(self, reason=None):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('session-terminate')
- if reason is not None:
- jingle.addChild(node=reason)
- self.__broadcastAllCB(stanza, jingle, None, 'session-terminate-sent')
- self.connection.connection.send(stanza)
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- text = reason
- self.connection.delete_jingle_session(self.peerjid, self.sid)
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, None, text))
-
- def __content_add(self, content):
- #TODO: test
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-add')
- self.__append_content(jingle, content)
- self.__broadcastCB(stanza, jingle, None, 'content-add-sent')
- self.connection.connection.send(stanza)
-
- def __content_accept(self, content):
- #TODO: test
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-accept')
- self.__append_content(jingle, content)
- self.__broadcastCB(stanza, jingle, None, 'content-accept-sent')
- self.connection.connection.send(stanza)
-
- def __content_reject(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-reject')
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- #TODO: this will fail if content is not an RTP content
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, content.media, 'rejected'))
-
- def __content_modify(self):
- assert self.state != JingleStates.ended
-
- def __content_remove(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-remove')
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- #TODO: this will fail if content is not an RTP content
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, content.media, 'removed'))
-
- def content_negociated(self, media):
- self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid,
- media))
-
-#TODO:
-#class JingleTransport(object):
-# ''' An abstraction of a transport in Jingle sessions. '''
-# def __init__(self):
-# pass
-
-
-class JingleContent(object):
- ''' An abstraction of content in Jingle sessions. '''
- def __init__(self, session, node=None):
- self.session = session
- # will be filled by JingleSession.add_content()
- # don't uncomment these lines, we will catch more buggy code then
- # (a JingleContent not added to session shouldn't send anything)
- #self.creator = None
- #self.name = None
- self.accepted = False
- self.sent = False
- self.candidates = [] # Local transport candidates
- self.remote_candidates = [] # Remote transport candidates
-
- self.senders = 'both' #FIXME
- self.allow_sending = True # Used for stream direction, attribute 'senders'
-
- self.callbacks = {
- # these are called when *we* get stanzas
- 'content-accept': [self.__transportInfoCB],
- 'content-add': [self.__transportInfoCB],
- 'content-modify': [],
- 'content-reject': [],
- 'content-remove': [],
- 'description-info': [],
- 'security-info': [],
- 'session-accept': [self.__transportInfoCB],
- 'session-info': [],
- 'session-initiate': [self.__transportInfoCB],
- 'session-terminate': [],
- 'transport-info': [self.__transportInfoCB],
- 'transport-replace': [],
- 'transport-accept': [],
- 'transport-reject': [],
- 'iq-result': [],
- 'iq-error': [],
- # these are called when *we* sent these stanzas
- 'content-accept-sent': [self.__fillJingleStanza],
- 'content-add-sent': [self.__fillJingleStanza],
- 'session-initiate-sent': [self.__fillJingleStanza],
- 'session-accept-sent': [self.__fillJingleStanza],
- 'session-terminate-sent': [],
- }
-
- def is_ready(self):
- #print '[%s] %s, %s' % (self.media, self.candidates_ready,
- # self.p2psession.get_property('codecs-ready'))
- return (self.accepted and self.candidates_ready and not self.sent
- and self.p2psession.get_property('codecs-ready'))
-
- def stanzaCB(self, stanza, content, error, action):
- ''' Called when something related to our content was sent by peer. '''
- if action in self.callbacks:
- for callback in self.callbacks[action]:
- callback(stanza, content, error, action)
-
- def __transportInfoCB(self, stanza, content, error, action):
- ''' Got a new transport candidate. '''
- candidates = []
- transport = content.getTag('transport')
- for candidate in transport.iterTags('candidate'):
- cand = farsight.Candidate()
- cand.component_id = int(candidate['component'])
- cand.ip = str(candidate['ip'])
- cand.port = int(candidate['port'])
- cand.foundation = str(candidate['foundation'])
- #cand.type = farsight.CANDIDATE_TYPE_LOCAL
- cand.priority = int(candidate['priority'])
-
- if candidate['protocol'] == 'udp':
- cand.proto = farsight.NETWORK_PROTOCOL_UDP
- else:
- # we actually don't handle properly different tcp options in jingle
- cand.proto = farsight.NETWORK_PROTOCOL_TCP
-
- cand.username = str(transport['ufrag'])
- cand.password = str(transport['pwd'])
-
- #FIXME: huh?
- types = {'host': farsight.CANDIDATE_TYPE_HOST,
- 'srflx': farsight.CANDIDATE_TYPE_SRFLX,
- 'prflx': farsight.CANDIDATE_TYPE_PRFLX,
- 'relay': farsight.CANDIDATE_TYPE_RELAY,
- 'multicast': farsight.CANDIDATE_TYPE_MULTICAST}
- if 'type' in candidate and candidate['type'] in types:
- cand.type = types[candidate['type']]
- else:
- print 'Unknown type %s', candidate['type']
- candidates.append(cand)
- #FIXME: connectivity should not be etablished yet
- # Instead, it should be etablished after session-accept!
- if len(candidates) > 0:
- if self.sent:
- self.p2pstream.set_remote_candidates(candidates)
- else:
- self.remote_candidates.extend(candidates)
- #self.p2pstream.set_remote_candidates(candidates)
- #print self.media, self.creator, self.name, candidates
-
- def __content(self, payload=[]):
- ''' Build a XML content-wrapper for our data. '''
- return xmpp.Node('content',
- attrs={'name': self.name, 'creator': self.creator},
- payload=payload)
-
- def __candidate(self, candidate):
- types = {farsight.CANDIDATE_TYPE_HOST: 'host',
- farsight.CANDIDATE_TYPE_SRFLX: 'srflx',
- farsight.CANDIDATE_TYPE_PRFLX: 'prflx',
- farsight.CANDIDATE_TYPE_RELAY: 'relay',
- farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'}
- attrs = {
- 'component': candidate.component_id,
- 'foundation': '1', # hack
- 'generation': '0',
- 'ip': candidate.ip,
- 'network': '0',
- 'port': candidate.port,
- 'priority': int(candidate.priority), # hack
- }
- if candidate.type in types:
- attrs['type'] = types[candidate.type]
- if candidate.proto == farsight.NETWORK_PROTOCOL_UDP:
- attrs['protocol'] = 'udp'
- else:
- # we actually don't handle properly different tcp options in jingle
- attrs['protocol'] = 'tcp'
- return xmpp.Node('candidate', attrs=attrs)
-
- def iter_candidates(self):
- for candidate in self.candidates:
- yield self.__candidate(candidate)
-
- def send_candidate(self, candidate):
- content = self.__content()
- transport = content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport')
-
- if candidate.username and candidate.password:
- transport['ufrag'] = candidate.username
- transport['pwd'] = candidate.password
-
- transport.addChild(node=self.__candidate(candidate))
- self.session.send_transport_info(content)
-
- def __fillJingleStanza(self, stanza, content, error, action):
- ''' Add our things to session-initiate stanza. '''
- self._fillContent(content)
-
- self.sent = True
-
- if self.candidates and self.candidates[0].username and \
- self.candidates[0].password:
- attrs = {'ufrag': self.candidates[0].username,
- 'pwd': self.candidates[0].password}
- else:
- attrs = {}
- content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport', attrs=attrs,
- payload=self.iter_candidates())
-
- def destroy(self):
- self.callbacks = None
- del self.session.contents[(self.creator, self.name)]
-
-
-class JingleRTPContent(JingleContent):
- def __init__(self, session, media, node=None):
- JingleContent.__init__(self, session, node)
- self.media = media
- self.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 _fillContent(self, content):
- content.addChild(xmpp.NS_JINGLE_RTP + ' description',
- attrs={'media': self.media}, payload=self.iter_codecs())
-
- def _setup_funnel(self):
- self.funnel = gst.element_factory_make('fsfunnel')
- self.pipeline.add(self.funnel)
- self.funnel.set_state(gst.STATE_PLAYING)
- self.sink.set_state(gst.STATE_PLAYING)
- self.funnel.link(self.sink)
-
- def _on_src_pad_added(self, stream, pad, codec):
- if not self.funnel:
- self._setup_funnel()
- pad.link(self.funnel.get_pad('sink%d'))
-
- def _on_gst_message(self, bus, message):
- if message.type == gst.MESSAGE_ELEMENT:
- name = message.structure.get_name()
- if name == 'farsight-new-active-candidate-pair':
- pass
- elif name == 'farsight-recv-codecs-changed':
- pass
- elif name == 'farsight-codecs-changed':
- if self.is_ready():
- self.session.on_session_state_changed(self)
- #TODO: description-info
- elif name == 'farsight-local-candidates-prepared':
- self.candidates_ready = True
- if self.is_ready():
- self.session.on_session_state_changed(self)
- elif name == 'farsight-new-local-candidate':
- candidate = message.structure['candidate']
- self.candidates.append(candidate)
- if self.candidates_ready:
- #FIXME: Is this case even possible?
- self.send_candidate(candidate)
- elif name == 'farsight-component-state-changed':
- state = message.structure['state']
- print message.structure['component'], state
- if state == farsight.STREAM_STATE_FAILED:
- reason = xmpp.Node('reason')
- reason.setTag('failed-transport')
- self.session._session_terminate(reason)
- elif name == 'farsight-error':
- print 'Farsight error #%d!' % message.structure['error-no']
- print 'Message: %s' % message.structure['error-msg']
- print 'Debug: %s' % message.structure['debug-msg']
- else:
- print name
-
- def __contentAcceptCB(self, stanza, content, error, action):
- if self.accepted:
- if len(self.remote_candidates) > 0:
- self.p2pstream.set_remote_candidates(self.remote_candidates)
- self.remote_candidates = []
- #TODO: farsight.DIRECTION_BOTH only if senders='both'
- self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH)
- self.session.content_negociated(self.media)
-
- def __getRemoteCodecsCB(self, stanza, content, error, action):
- ''' Get peer codecs from what we get from peer. '''
- if self.got_codecs:
- return
-
- codecs = []
- for codec in content.getTag('description').iterTags('payload-type'):
- c = farsight.Codec(int(codec['id']), codec['name'],
- self.farsight_media, int(codec['clockrate']))
- if 'channels' in codec:
- c.channels = int(codec['channels'])
- else:
- c.channels = 1
- c.optional_params = [(str(p['name']), str(p['value'])) for p in \
- codec.iterTags('parameter')]
- codecs.append(c)
-
- if len(codecs) > 0:
- #FIXME: Handle this case:
- # glib.GError: There was no intersection between the remote codecs and
- # the local ones
- self.p2pstream.set_remote_codecs(codecs)
- self.got_codecs = True
-
- def iter_codecs(self):
- codecs = self.p2psession.get_property('codecs')
- for codec in codecs:
- attrs = {'name': codec.encoding_name,
- 'id': codec.id,
- 'channels': codec.channels}
- if codec.clock_rate:
- attrs['clockrate'] = codec.clock_rate
- if codec.optional_params:
- payload = (xmpp.Node('parameter', {'name': name, 'value': value})
- for name, value in codec.optional_params)
- else: payload = ()
- yield xmpp.Node('payload-type', attrs, payload)
-
- def __stop(self, *things):
- self.pipeline.set_state(gst.STATE_NULL)
-
- def __del__(self):
- self.__stop()
-
- def destroy(self):
- JingleContent.destroy(self)
- self.p2pstream.disconnect_by_func(self._on_src_pad_added)
- self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
-
-
-class JingleVoIP(JingleRTPContent):
- ''' Jingle VoIP sessions consist of audio content transported
- over an ICE UDP protocol. '''
- def __init__(self, session, node=None):
- JingleRTPContent.__init__(self, session, 'audio', node)
- self.setup_stream()
-
-
- ''' Things to control the gstreamer's pipeline '''
- def setup_stream(self):
- JingleRTPContent.setup_stream(self)
-
- # Configure SPEEX
- # Workaround for psi (not needed since rev
- # 147aedcea39b43402fe64c533d1866a25449888a):
- # place 16kHz before 8kHz, as buggy psi versions will take in
- # account only the first codec
-
- codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
- farsight.MEDIA_TYPE_AUDIO, 16000),
- farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
- farsight.MEDIA_TYPE_AUDIO, 8000)]
- self.p2psession.set_codec_preferences(codecs)
-
- # the local parts
- # TODO: use gconfaudiosink?
- # sink = get_first_gst_element(['alsasink', 'osssink', 'autoaudiosink'])
- self.sink = gst.element_factory_make('alsasink')
- self.sink.set_property('sync', False)
- #sink.set_property('latency-time', 20000)
- #sink.set_property('buffer-time', 80000)
-
- # TODO: use gconfaudiosrc?
- src_mic = gst.element_factory_make('alsasrc')
- src_mic.set_property('blocksize', 320)
-
- self.mic_volume = gst.element_factory_make('volume')
- self.mic_volume.set_property('volume', 1)
-
- # link gst elements
- self.pipeline.add(self.sink, src_mic, self.mic_volume)
- src_mic.link(self.mic_volume)
-
- self.mic_volume.get_pad('src').link(self.p2psession.get_property(
- 'sink-pad'))
- self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
-
- # The following is needed for farsight to process ICE requests:
- self.pipeline.set_state(gst.STATE_PLAYING)
-
-
-class JingleVideo(JingleRTPContent):
- def __init__(self, session, node=None):
- JingleRTPContent.__init__(self, session, 'video', node)
- self.setup_stream()
-
- ''' Things to control the gstreamer's pipeline '''
- def setup_stream(self):
- #TODO: Everything is not working properly:
- # sometimes, one window won't show up,
- # sometimes it'll freeze...
- JingleRTPContent.setup_stream(self)
- # the local parts
- src_vid = gst.element_factory_make('videotestsrc')
- src_vid.set_property('is-live', True)
- videoscale = gst.element_factory_make('videoscale')
- caps = gst.element_factory_make('capsfilter')
- caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
- colorspace = gst.element_factory_make('ffmpegcolorspace')
-
- self.pipeline.add(src_vid, videoscale, caps, colorspace)
- gst.element_link_many(src_vid, videoscale, caps, colorspace)
-
- self.sink = gst.element_factory_make('xvimagesink')
- self.pipeline.add(self.sink)
-
- colorspace.get_pad('src').link(self.p2psession.get_property('sink-pad'))
- self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
-
- # The following is needed for farsight to process ICE requests:
- self.pipeline.set_state(gst.STATE_PLAYING)
+from jingle_session import JingleSession
+from jingle_rtp import JingleAudio, JingleVideo
class ConnectionJingle(object):
@@ -1128,11 +98,11 @@ class ConnectionJingle(object):
return self.get_jingle_session(jid, media='audio').sid
jingle = self.get_jingle_session(jid, media='video')
if jingle:
- jingle.add_content('voice', JingleVoIP(jingle))
+ jingle.add_content('voice', JingleAudio(jingle))
else:
jingle = JingleSession(self, weinitiate=True, jid=jid)
self.add_jingle(jingle)
- jingle.add_content('voice', JingleVoIP(jingle))
+ jingle.add_content('voice', JingleAudio(jingle))
jingle.start_session()
return jingle.sid
diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py
new file mode 100644
index 000000000..2feb5d8de
--- /dev/null
+++ b/src/common/jingle_content.py
@@ -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)]
diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py
new file mode 100644
index 000000000..d67b0f465
--- /dev/null
+++ b/src/common/jingle_rtp.py
@@ -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
diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py
new file mode 100644
index 000000000..9118affdf
--- /dev/null
+++ b/src/common/jingle_session.py
@@ -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
+ # error.
+
+ # Lets check what kind of jingle session does the peer want
+ contents_ok, transports_ok, contents, pouet = self.__parse_contents(jingle)
+
+ # If there's no content we understand...
+ if not contents_ok:
+ # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
+ reason = xmpp.Node('reason')
+ reason.setTag('unsupported-applications')
+ self.__defaultCB(stanza, jingle, error, action)
+ self._session_terminate(reason)
+ raise xmpp.NodeProcessed
+
+ if not transports_ok:
+ # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
+ reason = xmpp.Node('reason')
+ reason.setTag('unsupported-transports')
+ self.__defaultCB(stanza, jingle, error, action)
+ self._session_terminate(reason)
+ raise xmpp.NodeProcessed
+
+ self.state = JingleStates.pending
+
+ # Send event about starting a session
+ self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
+ contents))
+
+ def __broadcastCB(self, stanza, jingle, error, action):
+ ''' Broadcast the stanza contents to proper content handlers. '''
+ for content in jingle.iterTags('content'):
+ name = content['name']
+ creator = content['creator']
+ cn = self.contents[(creator, name)]
+ cn.stanzaCB(stanza, content, error, action)
+
+ def __sessionTerminateCB(self, stanza, jingle, error, action):
+ self.connection.delete_jingle_session(self.peerjid, self.sid)
+ reason, text = self.__reason_from_stanza(jingle)
+ if reason not in ('success', 'cancel', 'decline'):
+ self.__dispatch_error(reason, reason, text)
+ if text:
+ text = '%s (%s)' % (reason, text)
+ else:
+ text = reason#TODO
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, None, text))
+
+ def __broadcastAllCB(self, stanza, jingle, error, action):
+ ''' Broadcast the stanza to all content handlers. '''
+ for content in self.contents.itervalues():
+ content.stanzaCB(stanza, None, error, action)
+
+ ''' Internal methods. '''
+ def __parse_contents(self, jingle):
+ #TODO: Needs some reworking
+ contents = []
+ contents_rejected = []
+ contents_ok = False
+ transports_ok = False
+
+ for element in jingle.iterTags('content'):
+ transport = get_jingle_transport(element.getTag('transport'))
+ content_type = get_jingle_content(element.getTag('description'))
+ if content_type:
+ contents_ok = True
+ if transport:
+ content = content_type(self, transport)
+ self.add_content(element['name'],
+ content, 'peer')
+ contents.append((content.media,))
+ transports_ok = True
+ else:
+ contents_rejected.append((element['name'], 'peer'))
+ else:
+ contents_rejected.append((element['name'], 'peer'))
+
+ return (contents_ok, transports_ok, contents, contents_rejected)
+
+ def __dispatch_error(self, error, jingle_error=None, text=None):
+ if jingle_error:
+ error = jingle_error
+ if text:
+ text = '%s (%s)' % (error, text)
+ else:
+ text = error
+ self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text))
+
+ def __reason_from_stanza(self, stanza):
+ reason = 'success'
+ reasons = ['success', 'busy', 'cancel', 'connectivity-error',
+ 'decline', 'expired', 'failed-application', 'failed-transport',
+ 'general-error', 'gone', 'incompatible-parameters', 'media-error',
+ 'security-error', 'timeout', 'unsupported-applications',
+ 'unsupported-transports']
+ tag = stanza.getTag('reason')
+ if tag:
+ text = tag.getTagData('text')
+ for r in reasons:
+ if tag.getTag(r):
+ reason = r
+ break
+ return (reason, text)
+
+ ''' Methods that make/send proper pieces of XML. They check if the session
+ is in appropriate state. '''
+ def __make_jingle(self, action):
+ stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
+ attrs = {'action': action,
+ 'sid': self.sid}
+ if action == 'session-initiate':
+ attrs['initiator'] = self.initiator
+ elif action == 'session-accept':
+ attrs['responder'] = self.responder
+ jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
+ return stanza, jingle
+
+ def __send_error(self, stanza, error, jingle_error=None, text=None):
+ err = xmpp.Error(stanza, error)
+ err.setNamespace(xmpp.NS_STANZAS)
+ if jingle_error:
+ err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS)
+ if text:
+ err.setTagData('text', text)
+ self.connection.connection.send(err)
+ self.__dispatch_error(error, jingle_error, text)
+
+ def __append_content(self, jingle, content):
+ ''' Append element to element,
+ with (full=True) or without (full=False)
+ children. '''
+ jingle.addChild('content',
+ attrs={'name': content.name, 'creator': content.creator})
+
+ def __append_contents(self, jingle):
+ ''' Append all elements to .'''
+ # TODO: integrate with __appendContent?
+ # TODO: parameters 'name', 'content'?
+ for content in self.contents.values():
+ self.__append_content(jingle, content)
+
+ def __session_initiate(self):
+ assert self.state == JingleStates.ended
+ stanza, jingle = self.__make_jingle('session-initiate')
+ self.__append_contents(jingle)
+ self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent')
+ self.connection.connection.send(stanza)
+ self.state = JingleStates.pending
+
+ def __session_accept(self):
+ assert self.state == JingleStates.pending
+ stanza, jingle = self.__make_jingle('session-accept')
+ self.__append_contents(jingle)
+ self.__broadcastCB(stanza, jingle, None, 'session-accept-sent')
+ self.connection.connection.send(stanza)
+ self.state = JingleStates.active
+
+ def __session_info(self, payload=None):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('session-info')
+ if payload:
+ jingle.addChild(node=payload)
+ self.connection.connection.send(stanza)
+
+ def _session_terminate(self, reason=None):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('session-terminate')
+ if reason is not None:
+ jingle.addChild(node=reason)
+ self.__broadcastAllCB(stanza, jingle, None, 'session-terminate-sent')
+ self.connection.connection.send(stanza)
+ reason, text = self.__reason_from_stanza(jingle)
+ if reason not in ('success', 'cancel', 'decline'):
+ self.__dispatch_error(reason, reason, text)
+ if text:
+ text = '%s (%s)' % (reason, text)
+ else:
+ text = reason
+ self.connection.delete_jingle_session(self.peerjid, self.sid)
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, None, text))
+
+ def __content_add(self, content):
+ #TODO: test
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-add')
+ self.__append_content(jingle, content)
+ self.__broadcastCB(stanza, jingle, None, 'content-add-sent')
+ self.connection.connection.send(stanza)
+
+ def __content_accept(self, content):
+ #TODO: test
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-accept')
+ self.__append_content(jingle, content)
+ self.__broadcastCB(stanza, jingle, None, 'content-accept-sent')
+ self.connection.connection.send(stanza)
+
+ def __content_reject(self, content):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-reject')
+ self.__append_content(jingle, content)
+ self.connection.connection.send(stanza)
+ #TODO: this will fail if content is not an RTP content
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, content.media, 'rejected'))
+
+ def __content_modify(self):
+ assert self.state != JingleStates.ended
+
+ def __content_remove(self, content):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-remove')
+ self.__append_content(jingle, content)
+ self.connection.connection.send(stanza)
+ #TODO: this will fail if content is not an RTP content
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, content.media, 'removed'))
+
+ def content_negociated(self, media):
+ self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid,
+ media))
+
diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py
new file mode 100644
index 000000000..487c66d70
--- /dev/null
+++ b/src/common/jingle_transport.py
@@ -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
+
diff --git a/src/common/passwords.py b/src/common/passwords.py
index 98a7f61d9..9000085c4 100644
--- a/src/common/passwords.py
+++ b/src/common/passwords.py
@@ -122,6 +122,8 @@ class GnomePasswordStorage(PasswordStorage):
user = gajim.config.get_per('accounts', account_name, 'name')
display_name = _('XMPP account %s@%s') % (user, server)
attributes1 = dict(server=str(server), user=str(user), protocol='xmpp')
+ if password is None:
+ password = str()
try:
auth_token = gnomekeyring.item_create_sync(
self.keyring, gnomekeyring.ITEM_NETWORK_PASSWORD,
diff --git a/src/common/pep.py b/src/common/pep.py
index 54012514f..5ba476c0c 100644
--- a/src/common/pep.py
+++ b/src/common/pep.py
@@ -23,9 +23,6 @@
## along with Gajim. If not, see .
##
-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 = '%s' % gobject.markup_escape_text(mood)
+ if 'text' in self._pep_specific_data:
+ text = self._pep_specific_data['text']
+ markuptext += ' (%s)' % gobject.markup_escape_text(text)
+ return markuptext
- 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 = _('"%(title)s" by %(artist)s\n'
+ 'from %(source)s') % {'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 = '' + gobject.markup_escape_text(activity)
+ if subactivity:
+ markuptext += ': ' + gobject.markup_escape_text(subactivity)
+ markuptext += ''
+ if text:
+ markuptext += ' (%s)' % gobject.markup_escape_text(text)
+ return markuptext
+
+
+class UserNicknamePEP(AbstractPEP):
+ '''XEP-0172: User Nickname'''
+
+ type = 'nickname'
+ namespace = xmpp.NS_NICK
+
+ def _extract_info(self, items):
+ nick = ''
+ for item in items.getTags('item'):
+ child = item.getTag('nick')
+ if child:
+ nick = child.getData()
+ break
+
+ retracted = items.getTag('retract') or not nick
+ return (nick, retracted)
+
+ def _update_contacts(self, jid, account):
+ nick = '' if self._retracted else self._pep_specific_data
+ for contact in gajim.contacts.get_contacts(account, jid):
+ contact.contact_name = nick
+
+ def _update_account(self, account):
+ if self._retracted:
+ gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name')
+ else:
+ gajim.nicks[account] = self._pep_specific_data
-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 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:
diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py
index 489885f76..7d82ae4e3 100644
--- a/src/common/xmpp/client_nb.py
+++ b/src/common/xmpp/client_nb.py
@@ -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
diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py
index 8f4746f68..477d3e6c4 100644
--- a/src/common/xmpp/dispatcher_nb.py
+++ b/src/common/xmpp/dispatcher_nb.py
@@ -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)' %
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 39930d675..3cf75fea3 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -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'
diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py
index 704cad170..e6d63bcb3 100644
--- a/src/common/xmpp/transports_nb.py
+++ b/src/common/xmpp/transports_nb.py
@@ -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)
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
index 730eb3ec3..ab863709d 100644
--- a/src/common/zeroconf/client_zeroconf.py
+++ b/src/common/zeroconf/client_zeroconf.py
@@ -23,7 +23,7 @@ from common.xmpp.idlequeue import IdleObject
from common.xmpp import dispatcher_nb, simplexml
from common.xmpp.plugin import *
from common.xmpp.simplexml import ustr
-from common.xmpp.transports_nb import DATA_RECEIVED, DATA_SENT
+from common.xmpp.transports_nb import DATA_RECEIVED, DATA_SENT, DATA_ERROR
from common.zeroconf import zeroconf
from common.xmpp.protocol import *
@@ -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
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
index 09818be47..8c036f3f8 100644
--- a/src/common/zeroconf/connection_handlers_zeroconf.py
+++ b/src/common/zeroconf/connection_handlers_zeroconf.py
@@ -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:
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
index 1a073ad38..1b43117c2 100644
--- a/src/common/zeroconf/connection_zeroconf.py
+++ b/src/common/zeroconf/connection_zeroconf.py
@@ -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
- # 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 , add
- if not msg_id: # avoid putting 'None' in 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:
diff --git a/src/config.py b/src/config.py
index a8e3b0902..adf5078c5 100644
--- a/src/config.py
+++ b/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 value1
- infos in a table {entry1: value1, }'''
+ """
+ Class for forms that are in XML format value1 infos in a
+ table {entry1: value1}
+ """
+
def __init__(self, infos):
gtk.Table.__init__(self)
self.infos = infos
@@ -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()
diff --git a/src/conversation_textview.py b/src/conversation_textview.py
index 837e2a0ce..7744ef685 100644
--- a/src/conversation_textview.py
+++ b/src/conversation_textview.py
@@ -67,13 +67,15 @@ def has_focus(widget):
class TextViewImage(gtk.Image):
- def __init__(self, anchor):
+ def __init__(self, anchor, text):
super(TextViewImage, self).__init__()
self.anchor = anchor
self._selected = False
self._disconnect_funcs = []
self.connect('parent-set', self.on_parent_set)
self.connect('expose-event', self.on_expose)
+ self.set_tooltip_text(text)
+ self.anchor.set_data('plaintext', text)
def _get_selected(self):
parent = self.get_parent()
@@ -156,8 +158,10 @@ class TextViewImage(gtk.Image):
class ConversationTextview(gobject.GObject):
- '''Class for the conversation textview (where user reads already said
- messages) for chat/groupchat windows'''
+ """
+ Class for the conversation textview (where user reads already said messages)
+ for chat/groupchat windows
+ """
__gsignals__ = dict(
quote = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
None, # return value
@@ -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')):
diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py
index 18195dccd..4a9681dab 100644
--- a/src/dataforms_widget.py
+++ b/src/dataforms_widget.py
@@ -38,7 +38,10 @@ import itertools
class DataFormWidget(gtk.Alignment, object):
# "public" interface
- ''' Data Form widget. Use like any other widget. '''
+ """
+ Data Form widget. Use like any other widget
+ """
+
def __init__(self, dataformnode=None):
''' Create a widget. '''
gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0)
@@ -65,7 +68,9 @@ class DataFormWidget(gtk.Alignment, object):
selection.set_mode(gtk.SELECTION_MULTIPLE)
def set_data_form(self, dataform):
- ''' Set the data form (xmpp.DataForm) displayed in widget. '''
+ """
+ Set the data form (xmpp.DataForm) displayed in widget
+ """
assert isinstance(dataform, dataforms.DataForm)
self.del_data_form()
@@ -84,7 +89,9 @@ class DataFormWidget(gtk.Alignment, object):
gtkgui_helpers.label_set_autowrap(self.instructions_label)
def get_data_form(self):
- ''' Data form displayed in the widget or None if no form. '''
+ """
+ Data form displayed in the widget or None if no form
+ """
return self._data_form
def del_data_form(self):
@@ -95,8 +102,10 @@ class DataFormWidget(gtk.Alignment, object):
'Data form presented in a widget')
def get_title(self):
- ''' Get the title of data form, as a unicode object. If no
- title or no form, returns u''. Useful for setting window title. '''
+ """
+ Get the title of data form, as a unicode object. If no title or no form,
+ returns u''. Useful for setting window title
+ """
if self._data_form is not None:
if self._data_form.title is not None:
return self._data_form.title
@@ -117,9 +126,11 @@ class DataFormWidget(gtk.Alignment, object):
pass
def clean_data_form(self):
- '''Remove data about existing form. This metod is empty, because
- it is rewritten by build_*_data_form, according to type of form
- which is actually displayed.'''
+ """
+ Remove data about existing form. This metod is empty, because it is
+ rewritten by build_*_data_form, according to type of form which is
+ actually displayed
+ """
pass
def build_single_data_form(self):
@@ -138,14 +149,18 @@ class DataFormWidget(gtk.Alignment, object):
self.clean_data_form = self.clean_single_data_form
def clean_single_data_form(self):
- '''(Called as clean_data_form, read the docs of clean_data_form()).
- Remove form from widget.'''
+ """
+ Called as clean_data_form, read the docs of clean_data_form(). Remove
+ form from widget
+ """
self.singleform.destroy()
self.clean_data_form = self.empty_method # we won't call it twice
del self.singleform
def build_multiple_data_form(self):
- '''Invoked when new multiple form is to be created.'''
+ """
+ Invoked when new multiple form is to be created
+ """
assert isinstance(self._data_form, dataforms.MultipleDataForm)
self.clean_data_form()
@@ -196,13 +211,17 @@ class DataFormWidget(gtk.Alignment, object):
self.refresh_multiple_buttons()
def clean_multiple_data_form(self):
- '''(Called as clean_data_form, read the docs of clean_data_form()).
- Remove form from widget.'''
+ """
+ Called as clean_data_form, read the docs of clean_data_form(). Remove
+ form from widget
+ """
self.clean_data_form = self.empty_method # we won't call it twice
del self.multiplemodel
def refresh_multiple_buttons(self):
- ''' Checks for treeview state and makes control buttons sensitive.'''
+ """
+ Checks for treeview state and makes control buttons sensitive
+ """
selection = self.records_treeview.get_selection()
model = self.records_treeview.get_model()
count = selection.count_selected_rows()
@@ -273,9 +292,12 @@ class DataFormWidget(gtk.Alignment, object):
self.refresh_multiple_buttons()
class SingleForm(gtk.Table, object):
- ''' Widget that represent DATAFORM_SINGLE mode form. Because this is used
- not only to display single forms, but to form input windows of multiple-type
- forms, it is in another class.'''
+ """
+ Widget that represent DATAFORM_SINGLE mode form. Because this is used not
+ only to display single forms, but to form input windows of multiple-type
+ forms, it is in another class
+ """
+
def __init__(self, dataform):
assert isinstance(dataform, dataforms.SimpleDataForm)
@@ -284,9 +306,11 @@ class SingleForm(gtk.Table, object):
self.set_row_spacings(6)
def decorate_with_tooltip(widget, field):
- ''' Adds a tooltip containing field's description to a widget.
- Creates EventBox if widget doesn't have its own gdk window.
- Returns decorated widget. '''
+ """
+ Adds a tooltip containing field's description to a widget. Creates
+ EventBox if widget doesn't have its own gdk window. Returns decorated
+ widget
+ """
if field.description != '':
if widget.flags() & gtk.NO_WINDOW:
evbox = gtk.EventBox()
diff --git a/src/dialogs.py b/src/dialogs.py
index d52c41770..1060b2dd7 100644
--- a/src/dialogs.py
+++ b/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(\
- _('%s would like you to %s some contacts in your '
- 'roster.') % (jid_from, _(self.action)))
+ self.type_label.set_label(
+ _('%(jid)s would like you to %(action)s some contacts '
+ 'in your roster.') % {'jid': jid_from,
+ 'action': TRANSLATED_ACTION[self.action]})
if message_body:
- buffer = self.body_textview.get_buffer()
- buffer.set_text(self.message_body)
+ 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')
diff --git a/src/disco.py b/src/disco.py
index 0d3621f13..c38c7ae7f 100644
--- a/src/disco.py
+++ b/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()
diff --git a/src/eggtrayicon.c b/src/eggtrayicon.c
deleted file mode 100644
index 56b3a0fb9..000000000
--- a/src/eggtrayicon.c
+++ /dev/null
@@ -1,584 +0,0 @@
-/* eggtrayicon.c
- * Copyright (C) 2002 Anders Carlsson
- *
- * 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
-#include
-#include
-
-#include "eggtrayicon.h"
-
-#include
-#if defined (GDK_WINDOWING_X11)
-#include
-#include
-#elif defined (GDK_WINDOWING_WIN32)
-#include
-#endif
-
-#ifndef EGG_COMPILATION
-#ifndef _
-#define _(x) dgettext (GETTEXT_PACKAGE, x)
-#define N_(x) x
-#endif
-#else
-#define _(x) x
-#define N_(x) x
-#endif
-
-#define SYSTEM_TRAY_REQUEST_DOCK 0
-#define SYSTEM_TRAY_BEGIN_MESSAGE 1
-#define SYSTEM_TRAY_CANCEL_MESSAGE 2
-
-#define SYSTEM_TRAY_ORIENTATION_HORZ 0
-#define SYSTEM_TRAY_ORIENTATION_VERT 1
-
-enum {
- PROP_0,
- PROP_ORIENTATION
-};
-
-static GtkPlugClass *parent_class = NULL;
-
-static void egg_tray_icon_init (EggTrayIcon *icon);
-static void egg_tray_icon_class_init (EggTrayIconClass *klass);
-
-static void egg_tray_icon_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec);
-
-static void egg_tray_icon_add (GtkContainer *container, GtkWidget *widget);
-
-static void egg_tray_icon_realize (GtkWidget *widget);
-static void egg_tray_icon_unrealize (GtkWidget *widget);
-
-#ifdef GDK_WINDOWING_X11
-static void egg_tray_icon_update_manager_window (EggTrayIcon *icon,
- gboolean dock_if_realized);
-static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
-#endif
-
-GType
-egg_tray_icon_get_type (void)
-{
- static GType our_type = 0;
-
- if (our_type == 0)
- {
- static const GTypeInfo our_info =
- {
- sizeof (EggTrayIconClass),
- (GBaseInitFunc) NULL,
- (GBaseFinalizeFunc) NULL,
- (GClassInitFunc) egg_tray_icon_class_init,
- NULL, /* class_finalize */
- NULL, /* class_data */
- sizeof (EggTrayIcon),
- 0, /* n_preallocs */
- (GInstanceInitFunc) egg_tray_icon_init
- };
-
- our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
- }
-
- return our_type;
-}
-
-static void
-egg_tray_icon_init (EggTrayIcon *icon)
-{
- icon->stamp = 1;
- icon->orientation = GTK_ORIENTATION_HORIZONTAL;
-
- gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
-}
-
-static void
-egg_tray_icon_class_init (EggTrayIconClass *klass)
-{
- GObjectClass *gobject_class = (GObjectClass *)klass;
- GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
- GtkContainerClass *container_class = (GtkContainerClass *)klass;
-
- parent_class = g_type_class_peek_parent (klass);
-
- gobject_class->get_property = egg_tray_icon_get_property;
-
- widget_class->realize = egg_tray_icon_realize;
- widget_class->unrealize = egg_tray_icon_unrealize;
-
- container_class->add = egg_tray_icon_add;
-
- g_object_class_install_property (gobject_class,
- PROP_ORIENTATION,
- g_param_spec_enum ("orientation",
- _("Orientation"),
- _("The orientation of the tray."),
- GTK_TYPE_ORIENTATION,
- GTK_ORIENTATION_HORIZONTAL,
- G_PARAM_READABLE));
-
-#if defined (GDK_WINDOWING_X11)
- /* Nothing */
-#elif defined (GDK_WINDOWING_WIN32)
- g_warning ("Port eggtrayicon to Win32");
-#else
- g_warning ("Port eggtrayicon to this GTK+ backend");
-#endif
-}
-
-static void
-egg_tray_icon_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- EggTrayIcon *icon = EGG_TRAY_ICON (object);
-
- switch (prop_id)
- {
- case PROP_ORIENTATION:
- g_value_set_enum (value, icon->orientation);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-#ifdef GDK_WINDOWING_X11
-
-static Display *
-egg_tray_icon_get_x_display(EggTrayIcon *icon)
-{
- Display *xdisplay = NULL;
-
- GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (icon));
- if (!GDK_IS_DISPLAY (display))
- display = gdk_display_get_default ();
-
- xdisplay = GDK_DISPLAY_XDISPLAY (display);
-
- return xdisplay;
-}
-
-static void
-egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
-{
- Display *xdisplay;
- Atom type;
- int format;
- union {
- gulong *prop;
- guchar *prop_ch;
- } prop = { NULL };
- gulong nitems;
- gulong bytes_after;
- int error, result;
-
- g_assert (icon->manager_window != None);
-
- xdisplay = egg_tray_icon_get_x_display(icon);
- if (xdisplay == NULL)
- return;
-
- gdk_error_trap_push ();
- type = None;
- result = XGetWindowProperty (xdisplay,
- icon->manager_window,
- icon->orientation_atom,
- 0, G_MAXLONG, FALSE,
- XA_CARDINAL,
- &type, &format, &nitems,
- &bytes_after, &(prop.prop_ch));
- error = gdk_error_trap_pop ();
-
- if (error || result != Success)
- return;
-
- if (type == XA_CARDINAL)
- {
- GtkOrientation orientation;
-
- orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
- GTK_ORIENTATION_HORIZONTAL :
- GTK_ORIENTATION_VERTICAL;
-
- if (icon->orientation != orientation)
- {
- icon->orientation = orientation;
-
- g_object_notify (G_OBJECT (icon), "orientation");
- }
- }
-
- if (prop.prop)
- XFree (prop.prop);
-}
-
-static GdkFilterReturn
-egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
-{
- EggTrayIcon *icon = user_data;
- XEvent *xev = (XEvent *)xevent;
-
- if (xev->xany.type == ClientMessage &&
- xev->xclient.message_type == icon->manager_atom &&
- xev->xclient.data.l[1] == icon->selection_atom)
- {
- egg_tray_icon_update_manager_window (icon, TRUE);
- }
- else if (xev->xany.window == icon->manager_window)
- {
- if (xev->xany.type == PropertyNotify &&
- xev->xproperty.atom == icon->orientation_atom)
- {
- egg_tray_icon_get_orientation_property (icon);
- }
- if (xev->xany.type == DestroyNotify)
- {
- egg_tray_icon_manager_window_destroyed (icon);
- }
- }
- return GDK_FILTER_CONTINUE;
-}
-
-#endif
-
-static void
-egg_tray_icon_unrealize (GtkWidget *widget)
-{
-#ifdef GDK_WINDOWING_X11
- EggTrayIcon *icon = EGG_TRAY_ICON (widget);
- GdkWindow *root_window;
-
- if (icon->manager_window != None)
- {
- GdkWindow *gdkwin;
-
- gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
- icon->manager_window);
-
- gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
- }
-
- root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
-
- gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
-
- if (GTK_WIDGET_CLASS (parent_class)->unrealize)
- (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
-#endif
-}
-
-#ifdef GDK_WINDOWING_X11
-
-static void
-egg_tray_icon_send_manager_message (EggTrayIcon *icon,
- long message,
- Window window,
- long data1,
- long data2,
- long data3)
-{
- XClientMessageEvent ev;
- Display *display;
-
- ev.type = ClientMessage;
- ev.window = window;
- ev.message_type = icon->system_tray_opcode_atom;
- ev.format = 32;
- ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
- ev.data.l[1] = message;
- ev.data.l[2] = data1;
- ev.data.l[3] = data2;
- ev.data.l[4] = data3;
-
- display = egg_tray_icon_get_x_display(icon);
-
- if (display == NULL)
- return;
-
- gdk_error_trap_push ();
- XSendEvent (display,
- icon->manager_window, False, NoEventMask, (XEvent *)&ev);
- XSync (display, False);
- gdk_error_trap_pop ();
-}
-
-static void
-egg_tray_icon_send_dock_request (EggTrayIcon *icon)
-{
- egg_tray_icon_send_manager_message (icon,
- SYSTEM_TRAY_REQUEST_DOCK,
- icon->manager_window,
- gtk_plug_get_id (GTK_PLUG (icon)),
- 0, 0);
-}
-
-static void
-egg_tray_icon_update_manager_window (EggTrayIcon *icon,
- gboolean dock_if_realized)
-{
- Display *xdisplay;
-
- if (icon->manager_window != None)
- return;
-
- xdisplay = egg_tray_icon_get_x_display(icon);
-
- if (xdisplay == NULL)
- return;
-
- XGrabServer (xdisplay);
-
- icon->manager_window = XGetSelectionOwner (xdisplay,
- icon->selection_atom);
-
- if (icon->manager_window != None)
- XSelectInput (xdisplay,
- icon->manager_window, StructureNotifyMask|PropertyChangeMask);
-
- XUngrabServer (xdisplay);
- XFlush (xdisplay);
-
- if (icon->manager_window != None)
- {
- GdkWindow *gdkwin;
-
- gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
- icon->manager_window);
-
- gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
-
- if (dock_if_realized && GTK_WIDGET_REALIZED (icon))
- egg_tray_icon_send_dock_request (icon);
-
- egg_tray_icon_get_orientation_property (icon);
- }
-}
-
-static gboolean
-transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
-{
- gdk_window_clear_area (widget->window, event->area.x, event->area.y,
- event->area.width, event->area.height);
- return FALSE;
-}
-
-static void
-make_transparent_again (GtkWidget *widget, GtkStyle *previous_style,
- gpointer user_data)
-{
- gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
-}
-
-static void
-make_transparent (GtkWidget *widget, gpointer user_data)
-{
- if (GTK_WIDGET_NO_WINDOW (widget) || GTK_WIDGET_APP_PAINTABLE (widget))
- return;
-
- gtk_widget_set_app_paintable (widget, TRUE);
- gtk_widget_set_double_buffered (widget, FALSE);
- gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
- g_signal_connect (widget, "expose_event",
- G_CALLBACK (transparent_expose_event), NULL);
- g_signal_connect_after (widget, "style_set",
- G_CALLBACK (make_transparent_again), NULL);
-}
-
-static void
-egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
-{
- GdkWindow *gdkwin;
-
- g_return_if_fail (icon->manager_window != None);
-
- gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
- icon->manager_window);
-
- gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
-
- icon->manager_window = None;
-
- egg_tray_icon_update_manager_window (icon, TRUE);
-}
-
-#endif
-
-static void
-egg_tray_icon_realize (GtkWidget *widget)
-{
-#ifdef GDK_WINDOWING_X11
- EggTrayIcon *icon = EGG_TRAY_ICON (widget);
- GdkScreen *screen;
- Display *xdisplay;
- char buffer[256];
- GdkWindow *root_window;
-
- if (GTK_WIDGET_CLASS (parent_class)->realize)
- GTK_WIDGET_CLASS (parent_class)->realize (widget);
-
- make_transparent (widget, NULL);
-
- xdisplay = egg_tray_icon_get_x_display(icon);
-
- if (xdisplay == NULL)
- return;
-
- screen = gtk_widget_get_screen (widget);
-
- /* Now see if there's a manager window around */
- g_snprintf (buffer, sizeof (buffer),
- "_NET_SYSTEM_TRAY_S%d",
- gdk_screen_get_number (screen));
-
- icon->selection_atom = XInternAtom (xdisplay, buffer, False);
-
- icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
-
- icon->system_tray_opcode_atom = XInternAtom (xdisplay,
- "_NET_SYSTEM_TRAY_OPCODE",
- False);
-
- icon->orientation_atom = XInternAtom (xdisplay,
- "_NET_SYSTEM_TRAY_ORIENTATION",
- False);
-
- egg_tray_icon_update_manager_window (icon, FALSE);
- egg_tray_icon_send_dock_request (icon);
-
- root_window = gdk_screen_get_root_window (screen);
-
- /* Add a root window filter so that we get changes on MANAGER */
- gdk_window_add_filter (root_window,
- egg_tray_icon_manager_filter, icon);
-#endif
-}
-
-static void
-egg_tray_icon_add (GtkContainer *container, GtkWidget *widget)
-{
- g_signal_connect (widget, "realize",
- G_CALLBACK (make_transparent), NULL);
- GTK_CONTAINER_CLASS (parent_class)->add (container, widget);
-}
-
-EggTrayIcon *
-egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
-{
- g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
-
- return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
-}
-
-EggTrayIcon*
-egg_tray_icon_new (const gchar *name)
-{
- return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
-}
-
-guint
-egg_tray_icon_send_message (EggTrayIcon *icon,
- gint timeout,
- const gchar *message,
- gint len)
-{
- guint stamp;
-
- g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
- g_return_val_if_fail (timeout >= 0, 0);
- g_return_val_if_fail (message != NULL, 0);
-
-#ifdef GDK_WINDOWING_X11
- if (icon->manager_window == None)
- return 0;
-#endif
-
- if (len < 0)
- len = strlen (message);
-
- stamp = icon->stamp++;
-
-#ifdef GDK_WINDOWING_X11
- /* Get ready to send the message */
- egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
- (Window)gtk_plug_get_id (GTK_PLUG (icon)),
- timeout, len, stamp);
-
- /* Now to send the actual message */
- gdk_error_trap_push ();
- while (len > 0)
- {
- XClientMessageEvent ev;
- Display *xdisplay;
-
- xdisplay = egg_tray_icon_get_x_display(icon);
-
- if (xdisplay == NULL)
- return 0;
-
- ev.type = ClientMessage;
- ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
- ev.format = 8;
- ev.message_type = XInternAtom (xdisplay,
- "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
- if (len > 20)
- {
- memcpy (&ev.data, message, 20);
- len -= 20;
- message += 20;
- }
- else
- {
- memcpy (&ev.data, message, len);
- len = 0;
- }
-
- XSendEvent (xdisplay,
- icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
- XSync (xdisplay, False);
- }
- gdk_error_trap_pop ();
-#endif
-
- return stamp;
-}
-
-void
-egg_tray_icon_cancel_message (EggTrayIcon *icon,
- guint id)
-{
- g_return_if_fail (EGG_IS_TRAY_ICON (icon));
- g_return_if_fail (id > 0);
-#ifdef GDK_WINDOWING_X11
- egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
- (Window)gtk_plug_get_id (GTK_PLUG (icon)),
- id, 0, 0);
-#endif
-}
-
-GtkOrientation
-egg_tray_icon_get_orientation (EggTrayIcon *icon)
-{
- g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
-
- return icon->orientation;
-}
diff --git a/src/eggtrayicon.h b/src/eggtrayicon.h
deleted file mode 100644
index 557fdb20c..000000000
--- a/src/eggtrayicon.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/* eggtrayicon.h
- * Copyright (C) 2002 Anders Carlsson
- *
- * 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
-#ifdef GDK_WINDOWING_X11
-#include
-#endif
-
-G_BEGIN_DECLS
-
-#define EGG_TYPE_TRAY_ICON (egg_tray_icon_get_type ())
-#define EGG_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_ICON, EggTrayIcon))
-#define EGG_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
-#define EGG_IS_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_ICON))
-#define EGG_IS_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_ICON))
-#define EGG_TRAY_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
-
-typedef struct _EggTrayIcon EggTrayIcon;
-typedef struct _EggTrayIconClass EggTrayIconClass;
-
-struct _EggTrayIcon
-{
- GtkPlug parent_instance;
-
- guint stamp;
-
-#ifdef GDK_WINDOWING_X11
- Atom selection_atom;
- Atom manager_atom;
- Atom system_tray_opcode_atom;
- Atom orientation_atom;
- Window manager_window;
-#endif
- GtkOrientation orientation;
-};
-
-struct _EggTrayIconClass
-{
- GtkPlugClass parent_class;
-};
-
-GType egg_tray_icon_get_type (void);
-
-EggTrayIcon *egg_tray_icon_new_for_screen (GdkScreen *screen,
- const gchar *name);
-
-EggTrayIcon *egg_tray_icon_new (const gchar *name);
-
-guint egg_tray_icon_send_message (EggTrayIcon *icon,
- gint timeout,
- const char *message,
- gint len);
-void egg_tray_icon_cancel_message (EggTrayIcon *icon,
- guint id);
-
-GtkOrientation egg_tray_icon_get_orientation (EggTrayIcon *icon);
-
-G_END_DECLS
-
-#endif /* __EGG_TRAY_ICON_H__ */
diff --git a/src/features_window.py b/src/features_window.py
index 609959e65..d784fc50a 100644
--- a/src/features_window.py
+++ b/src/features_window.py
@@ -33,7 +33,9 @@ from common import helpers
from common import kwalletbinding
class FeaturesWindow:
- '''Class for features window'''
+ """
+ Class for features window
+ """
def __init__(self):
self.xml = gtkgui_helpers.get_glade('features_window.glade')
@@ -83,10 +85,6 @@ class FeaturesWindow:
_('Passive popups notifying for new events.'),
_('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'),
_('Feature not available under Windows.')),
- _('Trayicon'): (self.trayicon_available,
- _('A icon in systemtray reflecting the current presence.'),
- _('Requires python-gnome2-extras or compiled trayicon module from Gajim sources.'),
- _('Requires PyGTK >= 2.10.')),
_('Automatic status'): (self.idle_available,
_('Ability to measure idle time, in order to set auto status.'),
_('Requires libxss library.'),
@@ -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
diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py
index cae4549e6..d3d23a156 100644
--- a/src/filetransfers_window.py
+++ b/src/filetransfers_window.py
@@ -134,7 +134,9 @@ class FileTransfersWindow:
self.xml.signal_autoconnect(self)
def find_transfer_by_jid(self, account, jid):
- ''' find all transfers with peer 'jid' that belong to 'account' '''
+ """
+ Find all transfers with peer 'jid' that belong to 'account'
+ """
active_transfers = [[],[]] # ['senders', 'receivers']
# 'account' is the sender
@@ -155,7 +157,9 @@ class FileTransfersWindow:
return active_transfers
def show_completed(self, jid, file_props):
- ''' show a dialog saying that file (file_props) has been transferred'''
+ """
+ Show a dialog saying that file (file_props) has been transferred
+ """
def on_open(widget, file_props):
dialog.destroy()
if 'file-name' not in file_props:
@@ -207,14 +211,16 @@ class FileTransfersWindow:
dialog.show_all()
def show_request_error(self, file_props):
- ''' show error dialog to the recipient saying that transfer
- has been canceled'''
+ """
+ Show error dialog to the recipient saying that transfer has been canceled
+ """
dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.'))
self.tree.get_selection().unselect_all()
def show_send_error(self, file_props):
- ''' show error dialog to the sender saying that transfer
- has been canceled'''
+ """
+ Show error dialog to the sender saying that transfer has been canceled
+ """
dialogs.InformationDialog(_('File transfer cancelled'),
_('Connection with peer cannot be established.'))
self.tree.get_selection().unselect_all()
@@ -273,7 +279,9 @@ _('Connection with peer cannot be established.'))
desc_hbox.show_all()
def send_file(self, account, contact, file_path, file_desc=''):
- ''' start the real transfer(upload) of the file '''
+ """
+ Start the real transfer(upload) of the file
+ """
if gtkgui_helpers.file_is_locked(file_path):
pritext = _('Gajim cannot access this file')
sextext = _('This file is being used by another process.')
@@ -303,8 +311,10 @@ _('Connection with peer cannot be established.'))
gajim.connections[account].send_file_approval(file_props)
def show_file_request(self, account, contact, file_props):
- ''' show dialog asking for comfirmation and store location of new
- file requested by a contact'''
+ """
+ Show dialog asking for comfirmation and store location of new file
+ requested by a contact
+ """
if file_props is None or 'name' not in file_props:
return
sec_text = '\t' + _('File: %s') % gobject.markup_escape_text(
@@ -394,7 +404,9 @@ _('Connection with peer cannot be established.'))
self.window.render_icon(self.icons[ident], gtk.ICON_SIZE_MENU))
def set_status(self, typ, sid, status):
- ''' change the status of a transfer to state 'status' '''
+ """
+ Change the status of a transfer to state 'status'
+ """
iter_ = self.get_iter_by_sid(typ, sid)
if iter_ is None:
return
@@ -409,8 +421,10 @@ _('Connection with peer cannot be established.'))
self.select_func(path)
def _format_percent(self, percent):
- ''' add extra spaces from both sides of the percent, so that
- progress string has always a fixed size'''
+ """
+ Add extra spaces from both sides of the percent, so that progress string
+ has always a fixed size
+ """
_str = ' '
if percent != 100.:
_str += ' '
@@ -481,7 +495,9 @@ _('Connection with peer cannot be established.'))
del(file_props)
def set_progress(self, typ, sid, transfered_size, iter_ = None):
- ''' change the progress of a transfer with new transfered size'''
+ """
+ Change the progress of a transfer with new transfered size
+ """
if sid not in self.files_props[typ]:
return
file_props = self.files_props[typ][sid]
@@ -546,8 +562,10 @@ _('Connection with peer cannot be established.'))
self.select_func(path)
def get_iter_by_sid(self, typ, sid):
- '''returns iter to the row, which holds file transfer, identified by the
- session id'''
+ """
+ Return iter to the row, which holds file transfer, identified by the
+ session id
+ """
iter_ = self.model.get_iter_root()
while iter_:
if typ + sid == self.model[iter_][C_SID].decode('utf-8'):
@@ -555,9 +573,10 @@ _('Connection with peer cannot be established.'))
iter_ = self.model.iter_next(iter_)
def get_send_file_props(self, account, contact, file_path, file_name,
- file_desc=''):
- ''' create new file_props dict and set initial file transfer
- properties in it'''
+ file_desc=''):
+ """
+ Create new file_props dict and set initial file transfer properties in it
+ """
file_props = {'file-name' : file_path, 'name' : file_name,
'type' : 's', 'desc' : file_desc}
if os.path.isfile(file_path):
@@ -582,7 +601,9 @@ _('Connection with peer cannot be established.'))
return file_props
def add_transfer(self, account, contact, file_props):
- ''' add new transfer to FT window and show the FT window '''
+ """
+ Add new transfer to FT window and show the FT window
+ """
self.on_transfers_list_leave_notify_event(None)
if file_props is None:
return
@@ -686,15 +707,19 @@ _('Connection with peer cannot be established.'))
return True
def set_cleanup_sensitivity(self):
- ''' check if there are transfer rows and set cleanup_button
- sensitive, or insensitive if model is empty'''
+ """
+ Check if there are transfer rows and set cleanup_button sensitive, or
+ insensitive if model is empty
+ """
if len(self.model) == 0:
self.cleanup_button.set_sensitive(False)
else:
self.cleanup_button.set_sensitive(True)
def set_all_insensitive(self):
- ''' make all buttons/menuitems insensitive '''
+ """
+ Make all buttons/menuitems insensitive
+ """
self.pause_button.set_sensitive(False)
self.pause_menuitem.set_sensitive(False)
self.continue_menuitem.set_sensitive(False)
@@ -705,8 +730,10 @@ _('Connection with peer cannot be established.'))
self.set_cleanup_sensitivity()
def set_buttons_sensitive(self, path, is_row_selected):
- ''' make buttons/menuitems sensitive as appropriate to
- the state of file transfer located at path 'path' '''
+ """
+ Make buttons/menuitems sensitive as appropriate to the state of file
+ transfer located at path 'path'
+ """
if path is None:
self.set_all_insensitive()
return
@@ -743,8 +770,9 @@ _('Connection with peer cannot be established.'))
return True
def selection_changed(self, args):
- ''' selection has changed - change the sensitivity of the
- buttons/menuitems'''
+ """
+ Selection has changed - change the sensitivity of the buttons/menuitems
+ """
selection = args
selected = selection.get_selected_rows()
if selected[1] != []:
@@ -881,7 +909,9 @@ _('Connection with peer cannot be established.'))
event_button, event.time)
def on_transfers_list_key_press_event(self, widget, event):
- '''when a key is pressed in the treeviews'''
+ """
+ When a key is pressed in the treeviews
+ """
self.tooltip.hide_tooltip()
iter_ = None
try:
@@ -963,5 +993,4 @@ _('Connection with peer cannot be established.'))
if event.keyval == gtk.keysyms.Escape: # ESCAPE
self.window.hide()
-
# vim: se ts=3:
diff --git a/src/gajim-remote.py b/src/gajim-remote.py
index 8926cf341..09db604d0 100644
--- a/src/gajim-remote.py
+++ b/src/gajim-remote.py
@@ -60,7 +60,6 @@ INTERFACE = 'org.gajim.dbus.RemoteInterface'
SERVICE = 'org.gajim.dbus'
BASENAME = 'gajim-remote'
-
class GajimRemote:
def __init__(self):
@@ -327,7 +326,9 @@ class GajimRemote:
self.print_result(res)
def print_result(self, res):
- ''' Print retrieved result to the output '''
+ """
+ Print retrieved result to the output
+ """
if res is not None:
if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'):
if self.command in ('send_message', 'send_single_message'):
@@ -382,8 +383,9 @@ class GajimRemote:
return test
def init_connection(self):
- ''' create the onnection to the session dbus,
- or exit if it is not possible '''
+ """
+ Create the onnection to the session dbus, or exit if it is not possible
+ """
try:
self.sbus = dbus.SessionBus()
except Exception:
@@ -398,8 +400,10 @@ class GajimRemote:
self.method = interface.__getattr__(self.command)
def make_arguments_row(self, args):
- ''' return arguments list. Mandatory arguments are enclosed with:
- '<', '>', optional arguments - with '[', ']' '''
+ """
+ Return arguments list. Mandatory arguments are enclosed with:
+ '<', '>', optional arguments - with '[', ']'
+ """
s = ''
for arg in args:
if arg[2]:
@@ -409,7 +413,9 @@ class GajimRemote:
return s
def help_on_command(self, command):
- ''' return help message for a given command '''
+ """
+ Return help message for a given command
+ """
if command in self.commands:
command_props = self.commands[command]
arguments_str = self.make_arguments_row(command_props[1])
@@ -424,7 +430,9 @@ class GajimRemote:
send_error(_('%s not found') % command)
def compose_help(self):
- ''' print usage, and list available commands '''
+ """
+ Print usage, and list available commands
+ """
s = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME
for command in sorted(self.commands):
s += ' ' + command
@@ -437,7 +445,9 @@ class GajimRemote:
return s
def print_info(self, level, prop_dict, encode_return = False):
- ''' return formated string from data structure '''
+ """
+ Return formated string from data structure
+ """
if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)):
return ''
ret_str = ''
@@ -486,7 +496,9 @@ class GajimRemote:
return ret_str
def check_arguments(self):
- ''' Make check if all necessary arguments are given '''
+ """
+ Make check if all necessary arguments are given
+ """
argv_len = self.argv_len - 2
args = self.commands[self.command][1]
if len(args) < argv_len:
@@ -559,7 +571,9 @@ class GajimRemote:
sys.exit(0)
def call_remote_method(self):
- ''' calls self.method with arguments from sys.argv[2:] '''
+ """
+ Calls self.method with arguments from sys.argv[2:]
+ """
args = [i.decode(PREFERRED_ENCODING) for i in self.arguments]
args = [dbus.String(i) for i in args]
try:
diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py
index ebaebc8d6..e6c23929a 100644
--- a/src/gajim_themes_window.py
+++ b/src/gajim_themes_window.py
@@ -275,7 +275,9 @@ class GajimThemesWindow:
self._set_font()
def _set_color(self, state, widget, option):
- ''' set color value in prefs and update the UI '''
+ """
+ Set color value in prefs and update the UI
+ """
if state:
color = widget.get_color()
color_string = gtkgui_helpers.make_color_string(color)
@@ -297,7 +299,9 @@ class GajimThemesWindow:
gajim.interface.save_config()
def _set_font(self):
- ''' set font value in prefs and update the UI '''
+ """
+ Set font value in prefs and update the UI
+ """
state = self.textfont_checkbutton.get_active()
if state:
font_string = self.text_fontbutton.get_font_name()
@@ -317,13 +321,16 @@ class GajimThemesWindow:
gajim.interface.save_config()
def _toggle_font_widgets(self, font_props):
- ''' toggle font buttons with the bool values of font_props tuple'''
+ """
+ Toggle font buttons with the bool values of font_props tuple
+ """
self.bold_togglebutton.set_active(font_props[0])
self.italic_togglebutton.set_active(font_props[1])
def _get_font_description(self):
- ''' return a FontDescription from togglebuttons
- states'''
+ """
+ Return a FontDescription from togglebuttons states
+ """
fd = pango.FontDescription()
if self.bold_togglebutton.get_active():
fd.set_weight(pango.WEIGHT_BOLD)
@@ -332,8 +339,10 @@ class GajimThemesWindow:
return fd
def _set_font_widgets(self, font_attrs):
- ''' set the correct toggle state of font style buttons by
- a font string of type 'BI' '''
+ """
+ Set the correct toggle state of font style buttons by a font string of
+ type 'BI'
+ """
font_props = [False, False, False]
if font_attrs:
if font_attrs.find('B') != -1:
@@ -343,7 +352,9 @@ class GajimThemesWindow:
self._toggle_font_widgets(font_props)
def _get_font_attrs(self):
- ''' get a string with letters of font attribures: 'BI' '''
+ """
+ Get a string with letters of font attribures: 'BI'
+ """
attrs = ''
if self.bold_togglebutton.get_active():
attrs += 'B'
@@ -353,7 +364,9 @@ class GajimThemesWindow:
def _get_font_props(self, font_name):
- ''' get tuple of font properties: Weight, Style '''
+ """
+ Get tuple of font properties: weight, style
+ """
font_props = [False, False, False]
font_description = pango.FontDescription(font_name)
if font_description.get_weight() != pango.WEIGHT_NORMAL:
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index e71eefe32..6db3398f5 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -29,6 +29,7 @@
import os
import time
+import locale
import gtk
import pango
import gobject
@@ -63,7 +64,9 @@ C_AVATAR, # avatar of the contact
) = range(5)
def set_renderer_color(treeview, renderer, set_background=True):
- '''set style for group row, using PRELIGHT system color'''
+ """
+ Set style for group row, using PRELIGHT system color
+ """
if set_background:
bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT]
renderer.set_property('cell-background-gdk', bgcolor)
@@ -140,7 +143,9 @@ class PrivateChatControl(ChatControl):
self.TYPE_ID = 'pm'
def send_message(self, message, xhtml=None, process_commands=True):
- '''call this function to send our message'''
+ """
+ Call this method to send the message
+ """
if not message:
return
@@ -317,6 +322,7 @@ class GroupchatControl(ChatControlBase):
id_ = self.list_treeview.connect('style-set',
self.on_list_treeview_style_set)
self.handlers[id_] = self.list_treeview
+ self.resize_from_another_muc = False
# we want to know when the the widget resizes, because that is
# an indication that the hpaned has moved...
# FIXME: Find a better indicator that the hpaned has moved.
@@ -381,7 +387,9 @@ class GroupchatControl(ChatControlBase):
self.widget.show_all()
def tree_compare_iters(self, model, iter1, iter2):
- '''Compare two iters to sort them'''
+ """
+ Compare two iters to sort them
+ """
type1 = model[iter1][C_TYPE]
type2 = model[iter2][C_TYPE]
if not type1 or not type2:
@@ -393,9 +401,7 @@ class GroupchatControl(ChatControlBase):
nick1 = nick1.decode('utf-8')
nick2 = nick2.decode('utf-8')
if type1 == 'role':
- if nick1 < nick2:
- return -1
- return 1
+ return locale.strcoll(nick1, nick2)
if type1 == 'contact':
gc_contact1 = gajim.contacts.get_gc_contact(self.account,
self.room_jid, nick1)
@@ -419,15 +425,13 @@ class GroupchatControl(ChatControlBase):
# We compare names
name1 = gc_contact1.get_shown_name()
name2 = gc_contact2.get_shown_name()
- if name1.lower() < name2.lower():
- return -1
- if name2.lower() < name1.lower():
- return 1
- return 0
+ return locale.strcoll(name1.lower(), name2.lower())
def on_msg_textview_populate_popup(self, textview, menu):
- '''we override the default context menu and we prepend Clear
- and the ability to insert a nick'''
+ """
+ Override the default context menu and we prepend Clear
+ and the ability to insert a nick
+ """
ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
item = gtk.SeparatorMenuItem()
menu.prepend(item)
@@ -446,20 +450,36 @@ class GroupchatControl(ChatControlBase):
menu.show_all()
+ def resize_occupant_treeview(self, position):
+ self.resize_from_another_muc = True
+ self.hpaned.set_position(position)
+ def reset_flag():
+ self.resize_from_another_muc = False
+ # Reset the flag when everything will be redrawn, and in particular when
+ # on_treeview_size_allocate will have been called.
+ gobject.idle_add(reset_flag)
+
def on_treeview_size_allocate(self, widget, allocation):
- '''The MUC treeview has resized. Move the hpaned in all tabs to match'''
+ """
+ The MUC treeview has resized. Move the hpaned in all tabs to match
+ """
+ if self.resize_from_another_muc:
+ # Don't send the event to other MUC
+ return
hpaned_position = self.hpaned.get_position()
for account in gajim.gc_connected:
for room_jid in [i for i in gajim.gc_connected[account] if \
- gajim.gc_connected[account][i]]:
+ gajim.gc_connected[account][i] and i != self.room_jid]:
ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account)
if not ctrl:
ctrl = gajim.interface.minimized_controls[account][room_jid]
if ctrl:
- ctrl.hpaned.set_position(hpaned_position)
+ ctrl.resize_occupant_treeview(hpaned_position)
def iter_contact_rows(self):
- '''iterate over all contact rows in the tree model'''
+ """
+ Iterate over all contact rows in the tree model
+ """
model = self.list_treeview.get_model()
role_iter = model.get_iter_root()
while role_iter:
@@ -470,7 +490,9 @@ class GroupchatControl(ChatControlBase):
role_iter = model.iter_next(role_iter)
def on_list_treeview_style_set(self, treeview, style):
- '''When style (theme) changes, redraw all contacts'''
+ """
+ When style (theme) changes, redraw all contacts
+ """
# Get the room_jid from treeview
for contact in self.iter_contact_rows():
nick = contact[C_NICK].decode('utf-8')
@@ -492,10 +514,11 @@ class GroupchatControl(ChatControlBase):
self.draw_contact(nick, selected=True, focus=True)
def get_tab_label(self, chatstate):
- '''Markup the label if necessary. Returns a tuple such as:
- (new_label_str, color)
- either of which can be None
- if chatstate is given that means we have HE SENT US a chatstate'''
+ """
+ Markup the label if necessary. Returns a tuple such as: (new_label_str,
+ color) either of which can be None if chatstate is given that means we
+ have HE SENT US a chatstate
+ """
has_focus = self.parent_win.window.get_property('has-toplevel-focus')
current_tab = self.parent_win.get_active_control() == self
@@ -580,10 +603,10 @@ class GroupchatControl(ChatControlBase):
banner_status_img.set_from_pixbuf(scaled_pix)
def get_continued_conversation_name(self):
- '''Get the name of a continued conversation.
- Will return Continued Conversation if there isn't any other
- contact in the room
- '''
+ """
+ Get the name of a continued conversation. Will return Continued
+ Conversation if there isn't any other contact in the room
+ """
nicks = []
for nick in gajim.contacts.get_nick_list(self.account,
self.room_jid):
@@ -597,9 +620,10 @@ class GroupchatControl(ChatControlBase):
return title
def draw_banner_text(self):
- '''Draw the text in the fat line at the top of the window that
- houses the room jid, subject.
- '''
+ """
+ Draw the text in the fat line at the top of the window that houses the
+ room jid, subject
+ """
self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
font_attrs, font_attrs_small = self.get_font_attrs()
@@ -633,7 +657,9 @@ class GroupchatControl(ChatControlBase):
self.banner_status_label.set_markup(subject_text)
def prepare_context_menu(self, hide_buttonbar_items=False):
- '''sets sensitivity state for configure_room'''
+ """
+ Set sensitivity state for configure_room
+ """
xml = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
menu = xml.get_widget('gc_control_popup_menu')
@@ -737,7 +763,7 @@ class GroupchatControl(ChatControlBase):
return menu
def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
- bookmark_room_menuitem, history_menuitem):
+ bookmark_room_menuitem, history_menuitem):
# destroy accelerators
ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n,
@@ -752,7 +778,7 @@ class GroupchatControl(ChatControlBase):
menu.destroy()
def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None,
- status_code=[]):
+ status_code=[]):
if '100' in status_code:
# Room is not anonymous
self.is_anonymous = False
@@ -768,8 +794,8 @@ class GroupchatControl(ChatControlBase):
else:
self.print_conversation(msg, nick, tim, xhtml)
- def on_private_message(self, nick, msg, tim, xhtml, session,
- msg_id=None, encrypted=False):
+ def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None,
+ encrypted=False):
# Do we have a queue?
fjid = self.room_jid + '/' + nick
no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
@@ -827,8 +853,7 @@ class GroupchatControl(ChatControlBase):
fin = True
return None
- def print_old_conversation(self, text, contact='', tim=None,
- xhtml = None):
+ def print_old_conversation(self, text, contact='', tim=None, xhtml = None):
if isinstance(text, str):
text = unicode(text, 'utf-8')
if contact:
@@ -847,11 +872,14 @@ class GroupchatControl(ChatControlBase):
small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml)
def print_conversation(self, text, contact='', tim=None, xhtml=None,
- graphics=True):
- '''Print a line in the conversation:
- if contact is set: it's a message from someone or an info message (contact
- = 'info' in such a case)
- if contact is not set: it's a message from the server or help'''
+ graphics=True):
+ """
+ Print a line in the conversation
+
+ If contact is set: it's a message from someone or an info message
+ (contact = 'info' in such a case).
+ If contact is not set: it's a message from the server or help.
+ """
if isinstance(text, str):
text = unicode(text, 'utf-8')
other_tags_for_name = []
@@ -930,8 +958,10 @@ class GroupchatControl(ChatControlBase):
return nb
def highlighting_for_message(self, text, tim):
- '''Returns a 2-Tuple. The first says whether or not to highlight the
- text, the second, what sound to play.'''
+ """
+ Returns a 2-Tuple. The first says whether or not to highlight the text,
+ the second, what sound to play
+ """
highlight, sound = (None, None)
# Are any of the defined highlighting words in the text?
@@ -953,10 +983,11 @@ class GroupchatControl(ChatControlBase):
return (highlight, sound)
def check_and_possibly_add_focus_out_line(self):
- '''checks and possibly adds focus out line for room_jid if it needs it
- and does not already have it as last event. If it goes to add this line
- it removes previous line first'''
-
+ """
+ Check and possibly add focus out line for room_jid if it needs it and
+ does not already have it as last event. If it goes to add this line
+ - remove previous line first
+ """
win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account)
if win and self.room_jid == win.get_active_jid() and\
win.window.get_property('has-toplevel-focus') and\
@@ -968,9 +999,10 @@ class GroupchatControl(ChatControlBase):
self.conv_textview.show_focus_out_line()
def needs_visual_notification(self, text):
- '''checks text to see whether any of the words in (muc_highlight_words
- and nick) appear.'''
-
+ """
+ Check text to see whether any of the words in (muc_highlight_words and
+ nick) appear
+ """
special_words = gajim.config.get('muc_highlight_words').split(';')
special_words.append(self.nick)
# Strip empties: ''.split(';') == [''] and would highlight everything.
@@ -1064,9 +1096,11 @@ class GroupchatControl(ChatControlBase):
self.list_treeview.columns_autosize()
def on_send_pm(self, widget=None, model=None, iter_=None, nick=None,
- msg=None):
- '''opens a chat window and if msg is not None sends private message to a
- contact in a room'''
+ msg=None):
+ """
+ Open a chat window and if msg is not None - send private message to a
+ contact in a room
+ """
if nick is None:
nick = model[iter_][C_NICK].decode('utf-8')
@@ -1075,7 +1109,9 @@ class GroupchatControl(ChatControlBase):
ctrl.send_message(msg)
def on_send_file(self, widget, gc_contact):
- '''sends a file to a contact in the room'''
+ """
+ Send a file to a contact in the room
+ """
self._on_send_file(gc_contact)
def draw_contact(self, nick, selected=False, focus=False):
@@ -1160,8 +1196,10 @@ class GroupchatControl(ChatControlBase):
self.draw_role(role)
def chg_contact_status(self, nick, show, status, role, affiliation, jid,
- reason, actor, statusCode, new_nick, avatar_sha, tim=None):
- '''When an occupant changes his or her status'''
+ reason, actor, statusCode, new_nick, avatar_sha, tim=None):
+ """
+ When an occupant changes his or her status
+ """
if show == 'invisible':
return
@@ -1440,7 +1478,7 @@ class GroupchatControl(ChatControlBase):
self.print_conversation(st, tim=tim, graphics=False)
def add_contact_to_roster(self, nick, show, role, affiliation, status,
- jid=''):
+ jid=''):
model = self.list_treeview.get_model()
role_name = helpers.get_uf_role(role, plural=True)
@@ -1506,7 +1544,9 @@ class GroupchatControl(ChatControlBase):
return None
def remove_contact(self, nick):
- '''Remove a user from the contacts_list'''
+ """
+ Remove a user from the contacts_list
+ """
model = self.list_treeview.get_model()
iter_ = self.get_contact_iter(nick)
if not iter_:
@@ -1521,7 +1561,9 @@ class GroupchatControl(ChatControlBase):
model.remove(parent_iter)
def send_message(self, message, xhtml=None, process_commands=True):
- '''call this function to send our message'''
+ """
+ Call this function to send our message
+ """
if not message:
return
@@ -1744,7 +1786,9 @@ class GroupchatControl(ChatControlBase):
_('You may also enter an alternate venue:'), ok_handler=on_ok)
def _on_bookmark_room_menuitem_activate(self, widget):
- '''bookmark the room, without autojoin and not minimized'''
+ """
+ Bookmark the room, without autojoin and not minimized
+ """
password = gajim.gc_passwords.get(self.room_jid, '')
gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \
'0', '0', password, self.nick)
@@ -1767,7 +1811,7 @@ class GroupchatControl(ChatControlBase):
gajim.connections[self.account].send_invite(self.room_jid, contact_jid)
def handle_message_textview_mykey_press(self, widget, event_keyval,
- event_keymod):
+ event_keymod):
# NOTE: handles mykeypress which is custom signal connected to this
# CB in new_room(). for this singal see message_textview.py
@@ -1896,19 +1940,25 @@ class GroupchatControl(ChatControlBase):
return True
def on_list_treeview_row_expanded(self, widget, iter_, path):
- '''When a row is expanded: change the icon of the arrow'''
+ """
+ When a row is expanded: change the icon of the arrow
+ """
model = widget.get_model()
image = gajim.interface.jabber_state_images['16']['opened']
model[iter_][C_IMG] = image
def on_list_treeview_row_collapsed(self, widget, iter_, path):
- '''When a row is collapsed: change the icon of the arrow'''
+ """
+ When a row is collapsed: change the icon of the arrow
+ """
model = widget.get_model()
image = gajim.interface.jabber_state_images['16']['closed']
model[iter_][C_IMG] = image
def kick(self, widget, nick):
- '''kick a user'''
+ """
+ Kick a user
+ """
def on_ok(reason):
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
'none', reason)
@@ -1918,7 +1968,9 @@ class GroupchatControl(ChatControlBase):
_('You may specify a reason below:'), ok_handler=on_ok)
def mk_menu(self, event, iter_):
- '''Make contact's popup menu'''
+ """
+ Make contact's popup menu
+ """
model = self.list_treeview.get_model()
nick = model[iter_][C_NICK].decode('utf-8')
c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
@@ -2062,8 +2114,10 @@ class GroupchatControl(ChatControlBase):
return ctrl
def on_row_activated(self, widget, path):
- '''When an iter is activated (dubblick or single click if gnome is set
- this way'''
+ """
+ When an iter is activated (dubblick or single click if gnome is set this
+ way
+ """
model = widget.get_model()
if len(path) == 1: # It's a group
if (widget.row_expanded(path)):
@@ -2075,12 +2129,16 @@ class GroupchatControl(ChatControlBase):
self._start_private_message(nick)
def on_list_treeview_row_activated(self, widget, path, col=0):
- '''When an iter is double clicked: open the chat window'''
+ """
+ When an iter is double clicked: open the chat window
+ """
if not gajim.single_click:
self.on_row_activated(widget, path)
def on_list_treeview_button_press_event(self, widget, event):
- '''popup user's group's or agent menu'''
+ """
+ Popup user's group's or agent menu
+ """
# hide tooltip, no matter the button is pressed
self.tooltip.hide_tooltip()
try:
@@ -2191,27 +2249,37 @@ class GroupchatControl(ChatControlBase):
self.tooltip.hide_tooltip()
def grant_voice(self, widget, nick):
- '''grant voice privilege to a user'''
+ """
+ Grant voice privilege to a user
+ """
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
'participant')
def revoke_voice(self, widget, nick):
- '''revoke voice privilege to a user'''
+ """
+ Revoke voice privilege to a user
+ """
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
'visitor')
def grant_moderator(self, widget, nick):
- '''grant moderator privilege to a user'''
+ """
+ Grant moderator privilege to a user
+ """
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
'moderator')
def revoke_moderator(self, widget, nick):
- '''revoke moderator privilege to a user'''
+ """
+ Revoke moderator privilege to a user
+ """
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
'participant')
def ban(self, widget, jid):
- '''ban a user'''
+ """
+ Ban a user
+ """
def on_ok(reason):
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'outcast', reason)
@@ -2223,32 +2291,44 @@ class GroupchatControl(ChatControlBase):
_('You may specify a reason below:'), ok_handler=on_ok)
def grant_membership(self, widget, jid):
- '''grant membership privilege to a user'''
+ """
+ Grant membership privilege to a user
+ """
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'member')
def revoke_membership(self, widget, jid):
- '''revoke membership privilege to a user'''
+ """
+ Revoke membership privilege to a user
+ """
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'none')
def grant_admin(self, widget, jid):
- '''grant administrative privilege to a user'''
+ """
+ Grant administrative privilege to a user
+ """
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'admin')
def revoke_admin(self, widget, jid):
- '''revoke administrative privilege to a user'''
+ """
+ Revoke administrative privilege to a user
+ """
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'member')
def grant_owner(self, widget, jid):
- '''grant owner privilege to a user'''
+ """
+ Grant owner privilege to a user
+ """
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'owner')
def revoke_owner(self, widget, jid):
- '''revoke owner privilege to a user'''
+ """
+ Revoke owner privilege to a user
+ """
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
'admin')
diff --git a/src/groups.py b/src/groups.py
index 1b78f5708..2588b11a4 100644
--- a/src/groups.py
+++ b/src/groups.py
@@ -26,7 +26,10 @@ import gtkgui_helpers
class GroupsPostWindow:
def __init__(self, account, servicejid, groupid):
- '''Open new 'create post' window to create message for groupid on servicejid service.'''
+ """
+ Open new 'create post' window to create message for groupid on servicejid
+ service
+ """
assert isinstance(servicejid, basestring)
assert isinstance(groupid, basestring)
@@ -43,11 +46,15 @@ class GroupsPostWindow:
self.window.show_all()
def on_cancel_button_clicked(self, w):
- '''Close window.'''
+ """
+ Close window
+ """
self.window.destroy()
def on_send_button_clicked(self, w):
- '''Gather info from widgets and send it as a message.'''
+ """
+ Gather info from widgets and send it as a message
+ """
# constructing item to publish... that's atom:entry element
item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'})
author = item.addChild('author')
diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py
index 6a8b1b1cf..273dc65b7 100644
--- a/src/gtkgui_helpers.py
+++ b/src/gtkgui_helpers.py
@@ -67,8 +67,10 @@ def get_glade(file_name, root = None):
return gtk.glade.XML(file_path, root=root, domain=i18n.APP)
def get_completion_liststore(entry):
- ''' create a completion model for entry widget
- completion list consists of (Pixbuf, Text) rows'''
+ """
+ Create a completion model for entry widget completion list consists of
+ (Pixbuf, Text) rows
+ """
completion = gtk.EntryCompletion()
liststore = gtk.ListStore(gtk.gdk.Pixbuf, str)
@@ -86,7 +88,9 @@ def get_completion_liststore(entry):
def popup_emoticons_under_button(menu, button, parent_win):
- ''' pops emoticons menu under button, which is in parent_win'''
+ """
+ Popup the emoticons menu under button, which is in parent_win
+ """
window_x1, window_y1 = parent_win.get_origin()
def position_menu_under_button(menu):
# inline function, which will not keep refs, when used as CB
@@ -115,8 +119,9 @@ def popup_emoticons_under_button(menu, button, parent_win):
menu.popup(None, None, position_menu_under_button, 1, 0)
def get_theme_font_for_option(theme, option):
- '''return string description of the font, stored in
- theme preferences'''
+ """
+ Return string description of the font, stored in theme preferences
+ """
font_name = gajim.config.get_per('themes', theme, option)
font_desc = pango.FontDescription()
font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs')
@@ -130,10 +135,10 @@ def get_theme_font_for_option(theme, option):
return fd.to_string()
def get_default_font():
- '''Get the desktop setting for application font
- first check for GNOME, then Xfce and last KDE
- it returns None on failure or else a string 'Font Size' '''
-
+ """
+ Get the desktop setting for application font first check for GNOME, then
+ Xfce and last KDE it returns None on failure or else a string 'Font Size'
+ """
try:
import gconf
# in try because daemon may not be there
@@ -206,7 +211,9 @@ def user_runs_xfce():
return False
def get_running_processes():
- '''returns running processes or None (if not /proc exists)'''
+ """
+ Return running processes or None (if /proc does not exist)
+ """
if os.path.isdir('/proc'):
# under Linux: checking if 'gnome-session' or
# 'startkde' programs were run before gajim, by
@@ -241,7 +248,9 @@ def get_running_processes():
return []
def move_window(window, x, y):
- '''moves the window but also checks if out of screen'''
+ """
+ Move the window, but also check if out of screen
+ """
if x < 0:
x = 0
if y < 0:
@@ -254,7 +263,9 @@ def move_window(window, x, y):
window.move(x, y)
def resize_window(window, w, h):
- '''resizes window but also checks if huge window or negative values'''
+ """
+ Resize window, but also checks if huge window or negative values
+ """
if not w or not h:
return
if w > screen_w:
@@ -354,8 +365,10 @@ def parse_server_xml(path_to_file):
print >> sys.stderr, _('Error parsing file:'), message
def set_unset_urgency_hint(window, unread_messages_no):
- '''sets/unsets urgency hint in window argument
- depending if we have unread messages or not'''
+ """
+ Sets/unset urgency hint in window argument depending if we have unread
+ messages or not
+ """
if gajim.config.get('use_urgency_hint'):
if unread_messages_no > 0:
window.props.urgency_hint = True
@@ -363,8 +376,10 @@ def set_unset_urgency_hint(window, unread_messages_no):
window.props.urgency_hint = False
def get_abspath_for_script(scriptname, want_type = False):
- '''checks if we are svn or normal user and returns abspath to asked script
- if want_type is True we return 'svn' or 'install' '''
+ """
+ Check if we are svn or normal user and return abspath to asked script if
+ want_type is True we return 'svn' or 'install'
+ """
if os.path.isdir('.svn'): # we are svn user
type_ = 'svn'
cwd = os.getcwd() # it's always ending with src
@@ -403,8 +418,10 @@ def get_abspath_for_script(scriptname, want_type = False):
return path_to_script
def get_pixbuf_from_data(file_data, want_type = False):
- '''Gets image data and returns gtk.gdk.Pixbuf
- if want_type is True it also returns 'jpeg', 'png' etc'''
+ """
+ Get image data and returns gtk.gdk.Pixbuf if want_type is True it also
+ returns 'jpeg', 'png' etc
+ """
pixbufloader = gtk.gdk.PixbufLoader()
try:
pixbufloader.write(file_data)
@@ -431,8 +448,11 @@ def get_invisible_cursor():
return cursor
def get_current_desktop(window):
- '''returns the current virtual desktop for given window
- NOTE: window is GDK window'''
+ """
+ Return the current virtual desktop for given window
+
+ NOTE: Window is a GDK window.
+ """
prop = window.property_get('_NET_CURRENT_DESKTOP')
if prop is None: # it means it's normal window (not root window)
# so we look for it's current virtual desktop in another property
@@ -444,9 +464,12 @@ def get_current_desktop(window):
return current_virtual_desktop_no
def possibly_move_window_in_current_desktop(window):
- '''moves GTK window to current virtual desktop if it is not in the
- current virtual desktop
- window is GTK window'''
+ """
+ Moves GTK window to current virtual desktop if it is not in the current
+ virtual desktop
+
+ NOTE: Window is a GDK window.
+ """
if os.name == 'nt':
return False
@@ -468,7 +491,11 @@ def possibly_move_window_in_current_desktop(window):
return False
def file_is_locked(path_to_file):
- '''returns True if file is locked (WINDOWS ONLY)'''
+ """
+ Return True if file is locked
+
+ NOTE: Windows only.
+ """
if os.name != 'nt': # just in case
return
@@ -496,8 +523,10 @@ def file_is_locked(path_to_file):
return False
def _get_fade_color(treeview, selected, focused):
- '''get a gdk color that is between foreground and background in 0.3
- 0.7 respectively colors of the cell for the given treeview'''
+ """
+ Get a gdk color that is between foreground and background in 0.3
+ 0.7 respectively colors of the cell for the given treeview
+ """
style = treeview.style
if selected:
if focused: # is the window focused?
@@ -516,9 +545,10 @@ def _get_fade_color(treeview, selected, focused):
int(bg.blue*p + fg.blue*q))
def get_scaled_pixbuf(pixbuf, kind):
- '''returns scaled pixbuf, keeping ratio etc or None
- kind is either "chat", "roster", "notification", "tooltip", "vcard"'''
-
+ """
+ Return scaled pixbuf, keeping ratio etc or None kind is either "chat",
+ "roster", "notification", "tooltip", "vcard"
+ """
# resize to a width / height for the avatar not to have distortion
# (keep aspect ratio)
width = gajim.config.get(kind + '_avatar_width')
@@ -544,12 +574,14 @@ def get_scaled_pixbuf(pixbuf, kind):
return scaled_buf
def get_avatar_pixbuf_from_cache(fjid, is_fake_jid = False, use_local = True):
- '''checks if jid has cached avatar and if that avatar is valid image
- (can be shown)
- returns None if there is no image in vcard
- returns 'ask' if cached vcard should not be used (user changed his vcard,
- so we have new sha) or if we don't have the vcard'''
+ """
+ Check if jid has cached avatar and if that avatar is valid image (can be
+ shown)
+ Returns None if there is no image in vcard/
+ Returns 'ask' if cached vcard should not be used (user changed his vcard, so
+ we have new sha) or if we don't have the vcard
+ """
jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
if gajim.config.get('hide_avatar_of_transport') and\
gajim.jid_is_transport(jid):
@@ -588,16 +620,21 @@ def get_avatar_pixbuf_from_cache(fjid, is_fake_jid = False, use_local = True):
return pixbuf
def make_gtk_month_python_month(month):
- '''gtk start counting months from 0, so January is 0
- but python's time start from 1, so align to python
- month MUST be integer'''
+ """
+ GTK starts counting months from 0, so January is 0 but Python's time start
+ from 1, so align to Python
+
+ NOTE: Month MUST be an integer.
+ """
return month + 1
def make_python_month_gtk_month(month):
return month - 1
def make_color_string(color):
- '''create #aabbcc color string from gtk color'''
+ """
+ Create #aabbcc color string from gtk color
+ """
col = '#'
for i in ('red', 'green', 'blue'):
h = hex(getattr(color, i) / (16*16)).split('x')[1]
@@ -612,10 +649,12 @@ def make_pixbuf_grayscale(pixbuf):
return pixbuf2
def get_path_to_generic_or_avatar(generic, jid = None, suffix = None):
- '''Chooses between avatar image and default image.
- Returns full path to the avatar image if it exists,
- otherwise returns full path to the image.
- generic must be with extension and suffix without'''
+ """
+ Choose between avatar image and default image
+
+ Returns full path to the avatar image if it exists, otherwise returns full
+ path to the image. generic must be with extension and suffix without
+ """
if jid:
# we want an avatar
puny_jid = helpers.sanitize_filename(jid)
@@ -632,9 +671,10 @@ def get_path_to_generic_or_avatar(generic, jid = None, suffix = None):
return os.path.abspath(generic)
def decode_filechooser_file_paths(file_paths):
- '''decode as UTF-8 under Windows and
- ask sys.getfilesystemencoding() in POSIX
- file_paths MUST be LIST'''
+ """
+ Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX
+ file_paths MUST be LIST
+ """
file_paths_list = list()
if os.name == 'nt': # decode as UTF-8 under Windows
@@ -655,7 +695,9 @@ def decode_filechooser_file_paths(file_paths):
return file_paths_list
def possibly_set_gajim_as_xmpp_handler():
- '''registers (by default only the first time) xmmp: to Gajim.'''
+ """
+ Register (by default only the first time) 'xmmp:' to Gajim
+ """
path_to_dot_kde = os.path.expanduser('~/.kde')
if os.path.exists(path_to_dot_kde):
path_to_kde_file = os.path.join(path_to_dot_kde,
@@ -737,8 +779,10 @@ Description=xmpp
dlg.checkbutton.set_active(True)
def escape_underscore(s):
- '''Escape underlines to prevent them from being interpreted
- as keyboard accelerators'''
+ """
+ Escape underlines to prevent them from being interpreted as keyboard
+ accelerators
+ """
return s.replace('_', '__')
def get_state_image_from_file_path_show(file_path, show):
@@ -756,7 +800,9 @@ def get_state_image_from_file_path_show(file_path, show):
return image
def get_possible_button_event(event):
- '''mouse or keyboard caused the event?'''
+ """
+ Mouse or keyboard caused the event?
+ """
if event.type == gtk.gdk.KEY_PRESS:
return 0 # no event.button so pass 0
# BUTTON_PRESS event, so pass event.button
@@ -847,7 +893,9 @@ def on_bm_header_changed_state(widget, event):
widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
def create_combobox(value_list, selected_value = None):
- '''Value_list is [(label1, value1), ]'''
+ """
+ Value_list is [(label1, value1)]
+ """
liststore = gtk.ListStore(str, str)
combobox = gtk.ComboBox(liststore)
cell = gtk.CellRendererText()
@@ -864,7 +912,9 @@ def create_combobox(value_list, selected_value = None):
return combobox
def create_list_multi(value_list, selected_values=None):
- '''Value_list is [(label1, value1), ]'''
+ """
+ Value_list is [(label1, value1)]
+ """
liststore = gtk.ListStore(str, str)
treeview = gtk.TreeView(liststore)
treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
@@ -882,8 +932,10 @@ def create_list_multi(value_list, selected_values=None):
return treeview
def load_iconset(path, pixbuf2=None, transport=False):
- '''load full iconset from the given path, and add
- pixbuf2 on top left of each static images'''
+ """
+ Load full iconset from the given path, and add pixbuf2 on top left of each
+ static images
+ """
path += '/'
if transport:
list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
@@ -898,21 +950,27 @@ def load_iconset(path, pixbuf2=None, transport=False):
return _load_icon_list(list_, path, pixbuf2)
def load_icon(icon_name):
- '''load an icon from the iconset in 16x16'''
+ """
+ Load an icon from the iconset in 16x16
+ """
iconset = gajim.config.get('iconset')
path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '')
icon_list = _load_icon_list([icon_name], path)
return icon_list[icon_name]
def load_mood_icon(icon_name):
- '''load an icon from the mood iconset in 16x16'''
+ """
+ Load an icon from the mood iconset in 16x16
+ """
iconset = gajim.config.get('mood_iconset')
path = os.path.join(helpers.get_mood_iconset_path(iconset), '')
icon_list = _load_icon_list([icon_name], path)
return icon_list[icon_name]
def load_activity_icon(category, activity = None):
- '''load an icon from the activity iconset in 16x16'''
+ """
+ Load an icon from the activity iconset in 16x16
+ """
iconset = gajim.config.get('activity_iconset')
path = os.path.join(helpers.get_activity_iconset_path(iconset),
category, '')
@@ -922,8 +980,10 @@ def load_activity_icon(category, activity = None):
return icon_list[activity]
def load_icons_meta():
- '''load and return - AND + small icons to put on top left of an icon
- for meta contacts.'''
+ """
+ Load and return - AND + small icons to put on top left of an icon for meta
+ contacts
+ """
iconset = gajim.config.get('iconset')
path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
# try to find opened_meta.png file, else opened.png else nopixbuf merge
@@ -945,8 +1005,10 @@ def load_icons_meta():
return pixo, pixc
def _load_icon_list(icons_list, path, pixbuf2 = None):
- '''load icons in icons_list from the given path,
- and add pixbuf2 on top left of each static images'''
+ """
+ Load icons in icons_list from the given path, and add pixbuf2 on top left of
+ each static images
+ """
imgs = {}
for icon in icons_list:
# try to open a pixfile with the correct method
@@ -972,7 +1034,9 @@ def _load_icon_list(icons_list, path, pixbuf2 = None):
return imgs
def make_jabber_state_images():
- '''initialise jabber_state_images dict'''
+ """
+ Initialize jabber_state_images dictionary
+ """
iconset = gajim.config.get('iconset')
if iconset:
if helpers.get_iconset_path(iconset):
@@ -1002,8 +1066,10 @@ def reload_jabber_state_images():
gajim.interface.roster.update_jabber_state_images()
def label_set_autowrap(widget):
- '''Make labels automatically re-wrap if their containers are resized.
- Accepts label or container widgets.'''
+ """
+ Make labels automatically re-wrap if their containers are resized.
+ Accepts label or container widgets
+ """
if isinstance (widget, gtk.Container):
children = widget.get_children()
for i in xrange (len (children)):
@@ -1013,7 +1079,9 @@ def label_set_autowrap(widget):
widget.connect_after('size-allocate', __label_size_allocate)
def __label_size_allocate(widget, allocation):
- '''Callback which re-allocates the size of a label.'''
+ """
+ Callback which re-allocates the size of a label
+ """
layout = widget.get_layout()
lw_old, lh_old = layout.get_size()
diff --git a/src/gui_interface.py b/src/gui_interface.py
index bff913616..76ab68c0c 100644
--- a/src/gui_interface.py
+++ b/src/gui_interface.py
@@ -54,7 +54,6 @@ if dbus_support.supported:
import gtkgui_helpers
-
import dialogs
import notify
import message_control
@@ -92,7 +91,6 @@ config_filename = gajimpaths['CONFIG_FILE']
from common import optparser
parser = optparser.OptionsParser(config_filename)
-
import logging
log = logging.getLogger('gajim.interface')
@@ -265,10 +263,10 @@ class Interface:
def handle_event_new_jid(self, account, data):
#('NEW_JID', account, (old_jid, new_jid))
- '''
+ """
This event is raised when our JID changed (most probably because we use
- anonymous account. We update contact and roster entry in this case.
- '''
+ anonymous account. We update contact and roster entry in this case
+ """
self.roster.rename_self_contact(data[0], data[1], account)
def edit_own_details(self, account):
@@ -1324,6 +1322,7 @@ class Interface:
def handle_event_file_request_error(self, account, array):
# ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
jid, file_props, errmsg = array
+ jid = gajim.get_jid_without_resource(jid)
ft = self.instances['file_transfers']
ft.set_status(file_props['type'], file_props['sid'], 'stop')
errno = file_props['error']
@@ -1353,6 +1352,7 @@ class Interface:
def handle_event_file_request(self, account, array):
jid = array[0]
+ jid = gajim.get_jid_without_resource(jid)
if jid not in gajim.contacts.get_jid_list(account):
keyID = ''
attached_keys = gajim.config.get_per('accounts', account,
@@ -1519,7 +1519,9 @@ class Interface:
contact.resource)
def handle_event_signed_in(self, account, empty):
- '''SIGNED_IN event is emitted when we sign in, so handle it'''
+ """
+ SIGNED_IN event is emitted when we sign in, so handle it
+ """
# ('SIGNED_IN', account, ())
# block signed in notifications for 30 seconds
gajim.block_signed_in_notifications[account] = True
@@ -1826,7 +1828,9 @@ class Interface:
dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2])
def handle_event_unique_room_id_supported(self, account, data):
- '''Receive confirmation that unique_room_id are supported'''
+ """
+ Receive confirmation that unique_room_id are supported
+ """
# ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
instance = data[1]
instance.unique_room_id_supported(data[0], data[2])
@@ -1945,10 +1949,10 @@ class Interface:
# ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
server = gajim.config.get_per('accounts', account, 'hostname')
def on_ok(is_checked):
- del self.instances[account]['online_dialog']['insecure_ssl']
if not is_checked[0]:
on_cancel()
return
+ del self.instances[account]['online_dialog']['insecure_ssl']
if is_checked[1]:
gajim.config.set_per('accounts', account,
'warn_when_insecure_ssl_connection', False)
@@ -1989,6 +1993,27 @@ class Interface:
_('PEP node %(node)s was not removed: %(message)s') % {
'node': data[1], 'message': data[2]})
+ def handle_event_pep_received(self, account, data):
+ # ('PEP_RECEIVED', account, (jid, pep_type))
+ jid = data[0]
+ pep_type = data[1]
+ ctrl = common.gajim.interface.msg_win_mgr.get_control(jid, account)
+
+ if jid == common.gajim.get_jid_from_account(account):
+ self.roster.draw_account(account)
+
+ if pep_type == 'nickname':
+ self.roster.draw_contact(jid, account)
+ if ctrl:
+ ctrl.update_ui()
+ win = ctrl.parent_win
+ win.redraw_tab(ctrl)
+ win.show_title()
+ else:
+ self.roster.draw_pep(jid, account, pep_type)
+ if ctrl:
+ ctrl.update_pep(pep_type)
+
def handle_event_archiving_changed(self, account, data):
if 'archiving_preferences' in self.instances[account]:
self.instances[account]['archiving_preferences'].archiving_changed(
@@ -2098,16 +2123,16 @@ class Interface:
'JINGLE_CONNECTED': [self.handle_event_jingle_connected],
'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected],
'JINGLE_ERROR': [self.handle_event_jingle_error],
+ 'PEP_RECEIVED': [self.handle_event_pep_received],
'ARCHIVING_CHANGED': [self.handle_event_archiving_changed],
'ARCHIVING_ERROR': [self.handle_event_archiving_error],
}
- def dispatch(self, event, account, data):
- '''
- Dispatches an network event to the event handlers of this class.
-
- Return true if it could be dispatched to alteast one handler.
- '''
+ def dispatch(self, event, account, data):
+ """
+ Dispatch an network event to the event handlers of this class. Return
+ true if it could be dispatched to alteast one handler
+ """
if event not in self.handlers:
log.warning('Unknown event %s dispatched to GUI: %s' % (event, data))
return False
@@ -2123,7 +2148,9 @@ class Interface:
################################################################################
def add_event(self, account, jid, type_, event_args):
- '''add an event to the gajim.events var'''
+ """
+ Add an event to the gajim.events var
+ """
# We add it to the gajim.events queue
# Do we have a queue?
jid = gajim.get_jid_without_resource(jid)
@@ -2452,7 +2479,9 @@ class Interface:
self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|[\ud800-\udfff]|[\ufffe-\uffff]'
def popup_emoticons_under_button(self, button, parent_win):
- ''' pops emoticons menu under button, located in parent_win'''
+ """
+ Popup the emoticons menu under button, located in parent_win
+ """
gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
button, parent_win)
@@ -2560,8 +2589,10 @@ class Interface:
################################################################################
def join_gc_room(self, account, room_jid, nick, password, minimize=False,
- is_continued=False):
- '''joins the room immediately'''
+ is_continued=False):
+ """
+ Join the room immediately
+ """
if not nick:
nick = gajim.nicks[account]
@@ -2786,33 +2817,27 @@ class Interface:
listener.disconnect(self.music_track_changed_signal)
self.music_track_changed_signal = None
- def music_track_changed(self, unused_listener, music_track_info, account=''):
- if account == '':
+ def music_track_changed(self, unused_listener, music_track_info, account=None):
+ if not account:
accounts = gajim.connections.keys()
else:
accounts = [account]
- if music_track_info is None:
- artist = ''
- title = ''
- source = ''
- elif hasattr(music_track_info, 'paused') and music_track_info.paused == 0:
- artist = ''
- title = ''
- source = ''
+
+ is_paused = hasattr(music_track_info, 'paused') and music_track_info.paused == 0
+ if not music_track_info or is_paused:
+ artist = title = source = ''
else:
artist = music_track_info.artist
title = music_track_info.title
source = music_track_info.album
for acct in accounts:
- if acct not in gajim.connections:
- continue
if not gajim.account_is_connected(acct):
continue
- if not gajim.connections[acct].pep_supported:
+ if not gajim.config.get_per('accounts', acct, 'publish_tune'):
continue
if gajim.connections[acct].music_track_info == music_track_info:
continue
- pep.user_send_tune(acct, artist, title, source)
+ gajim.connections[acct].send_tune(artist, title, source)
gajim.connections[acct].music_track_info = music_track_info
def get_bg_fg_colors(self):
@@ -2835,7 +2860,9 @@ class Interface:
return (bg_str, fg_str)
def read_sleepy(self):
- '''Check idle status and change that status if needed'''
+ """
+ Check idle status and change that status if needed
+ """
if not self.sleeper.poll():
# idle detection is not supported in that OS
return False # stop looping in vain
@@ -2889,7 +2916,9 @@ class Interface:
return True # renew timeout (loop for ever)
def autoconnect(self):
- '''auto connect at startup'''
+ """
+ Auto connect at startup
+ """
# dict of account that want to connect sorted by status
shows = {}
for a in gajim.connections:
@@ -2928,7 +2957,9 @@ class Interface:
helpers.launch_browser_mailer(kind, url)
def process_connections(self):
- ''' Called each foo (200) miliseconds. Check for idlequeue timeouts. '''
+ """
+ Called each foo (200) miliseconds. Check for idlequeue timeouts
+ """
try:
gajim.idlequeue.process()
except Exception:
@@ -2952,7 +2983,11 @@ class Interface:
sys.exit()
def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
- '''Saves an avatar to a separate file, and generate files for dbus notifications. An avatar can be given as a pixmap directly or as an decoded image.'''
+ """
+ Save an avatar to a separate file, and generate files for dbus
+ notifications. An avatar can be given as a pixmap directly or as an
+ decoded image
+ """
puny_jid = helpers.sanitize_filename(jid)
path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
if puny_nick:
@@ -3005,7 +3040,9 @@ class Interface:
(path_to_original_file, str(e)))
def remove_avatar_files(self, jid, puny_nick = None, local = False):
- '''remove avatar files of a jid'''
+ """
+ Remove avatar files of a jid
+ """
puny_jid = helpers.sanitize_filename(jid)
path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
if puny_nick:
@@ -3022,7 +3059,9 @@ class Interface:
os.remove(path_to_file + '_notif_size_bw' + ext)
def auto_join_bookmarks(self, account):
- '''autojoin bookmarked GCs that have 'auto join' on for this account'''
+ """
+ Autojoin bookmarked GCs that have 'auto join' on for this account
+ """
for bm in gajim.connections[account].bookmarks:
if bm['autojoin'] in ('1', 'true'):
jid = bm['jid']
@@ -3040,8 +3079,10 @@ class Interface:
self.roster.add_groupchat(jid, account)
def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
- nick):
- '''add a bookmark for this account, sorted in bookmark list'''
+ nick):
+ """
+ Add a bookmark for this account, sorted in bookmark list
+ """
bm = {
'name': name,
'jid': jid,
@@ -3118,7 +3159,7 @@ class Interface:
gajim.ipython_window = window
def run(self):
- if self.systray_capabilities and gajim.config.get('trayicon') != 'never':
+ if gajim.config.get('trayicon') != 'never':
self.show_systray()
self.roster = roster_window.RosterWindow()
@@ -3353,21 +3394,9 @@ class Interface:
gtkgui_helpers.make_jabber_state_images()
self.systray_enabled = False
- self.systray_capabilities = False
- if (os.name == 'nt'):
- import statusicon
- self.systray = statusicon.StatusIcon()
- self.systray_capabilities = True
- else: # use ours, not GTK+ one
- # [FIXME: remove this when we migrate to 2.10 and we can do
- # cool tooltips somehow and (not dying to keep) animation]
- import systray
- self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES
- if self.systray_capabilities:
- self.systray = systray.Systray()
- else:
- gajim.config.set('trayicon', 'never')
+ import statusicon
+ self.systray = statusicon.StatusIcon()
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png')
pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
@@ -3481,13 +3510,9 @@ class PassphraseRequest:
class ThreadInterface:
def __init__(self, func, func_args, callback, callback_args):
- '''Call a function in a thread
-
- :param func: the function to call in the thread
- :param func_args: list or arguments for this function
- :param callback: callback to call once function is finished
- :param callback_args: list of arguments for this callback
- '''
+ """
+ Call a function in a thread
+ """
def thread_function(func, func_args, callback, callback_args):
output = func(*func_args)
gobject.idle_add(callback, output, *callback_args)
diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py
index 0c975d5b6..453677aa2 100644
--- a/src/gui_menu_builder.py
+++ b/src/gui_menu_builder.py
@@ -28,9 +28,11 @@ from common import helpers
from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION
def build_resources_submenu(contacts, account, action, room_jid=None,
-room_account=None, cap=None):
- ''' Build a submenu with contact's resources.
- room_jid and room_account are for action self.on_invite_to_room '''
+ room_account=None, cap=None):
+ """
+ Build a submenu with contact's resources. room_jid and room_account are for
+ action self.on_invite_to_room
+ """
roster = gajim.interface.roster
sub_menu = gtk.Menu()
@@ -61,7 +63,9 @@ room_account=None, cap=None):
return sub_menu
def build_invite_submenu(invite_menuitem, list_):
- '''list_ in a list of (contact, account)'''
+ """
+ list_ in a list of (contact, account)
+ """
roster = gajim.interface.roster
# used if we invite only one contact with several resources
contact_list = []
@@ -145,10 +149,12 @@ def build_invite_submenu(invite_menuitem, list_):
invite_to_submenu.append(menuitem)
def get_contact_menu(contact, account, use_multiple_contacts=True,
-show_start_chat=True, show_encryption=False, show_buttonbar_items=True,
-control=None):
- ''' Build contact popup menu for roster and chat window.
- If control is not set, we hide invite_contacts_menuitem'''
+ show_start_chat=True, show_encryption=False, show_buttonbar_items=True,
+ control=None):
+ """
+ Build contact popup menu for roster and chat window. If control is not set,
+ we hide invite_contacts_menuitem
+ """
if not contact:
return
@@ -320,7 +326,7 @@ control=None):
for item in (send_custom_status_menuitem, send_single_message_menuitem,
invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem,
unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem,
- manage_contact_menuitem, convert_to_gc_menuitems):
+ manage_contact_menuitem, convert_to_gc_menuitem):
item.set_no_show_all(True)
item.hide()
diff --git a/src/history_manager.py b/src/history_manager.py
index 9ab638364..5d171b75c 100644
--- a/src/history_manager.py
+++ b/src/history_manager.py
@@ -290,11 +290,13 @@ class HistoryManager:
self._fill_logs_listview(jid)
def _get_jid_id(self, jid):
- '''jids table has jid and jid_id
+ """
+ jids table has jid and jid_id
logs table has log_id, jid_id, contact_name, time, kind, show, message
- so to ask logs we need jid_id that matches our jid in jids table
- this method wants jid and returns the jid_id for later sql-ing on logs
- '''
+
+ So to ask logs we need jid_id that matches our jid in jids table this
+ method wants jid and returns the jid_id for later sql-ing on logs
+ """
if jid.find('/') != -1: # if it has a /
jid_is_from_pm = self._jid_is_from_pm(jid)
if not jid_is_from_pm: # it's normal jid with resource
@@ -304,22 +306,24 @@ class HistoryManager:
return str(jid_id)
def _get_jid_from_jid_id(self, jid_id):
- '''jids table has jid and jid_id
- this method accepts jid_id and returns the jid for later sql-ing on logs
- '''
+ """
+ jids table has jid and jid_id
+
+ This method accepts jid_id and returns the jid for later sql-ing on logs
+ """
self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,))
jid = self.cur.fetchone()[0]
return jid
def _jid_is_from_pm(self, jid):
- '''if jid is gajim@conf/nkour it's likely a pm one, how we know
- gajim@conf is not a normal guy and nkour is not his resource?
- we ask if gajim@conf is already in jids (with type room jid)
- this fails if user disables logging for room and only enables for
- pm (so higly unlikely) and if we fail we do not go chaos
- (user will see the first pm as if it was message in room's public chat)
- and after that all okay'''
-
+ """
+ If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
+ is not a normal guy and nkour is not his resource? We ask if gajim@conf
+ is already in jids (with type room jid). This fails if user disables
+ logging for room and only enables for pm (so higly unlikely) and if we
+ fail we do not go chaos (user will see the first pm as if it was message
+ in room's public chat) and after that everything is ok
+ """
possible_room_jid = jid.split('/', 1)[0]
self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?',
@@ -331,8 +335,9 @@ class HistoryManager:
return True
def _jid_is_room_type(self, jid):
- '''returns True/False if given id is room type or not
- eg. if it is room'''
+ """
+ Return True/False if given id is room type or not eg. if it is room
+ """
self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,))
row = self.cur.fetchone()
if row is None:
@@ -343,8 +348,10 @@ class HistoryManager:
return False
def _fill_logs_listview(self, jid):
- '''fill the listview with all messages that user sent to or
- received from JID'''
+ """
+ Fill the listview with all messages that user sent to or received from
+ JID
+ """
# no need to lower jid in this context as jid is already lowered
# as we use those jids from db
jid_id = self._get_jid_id(jid)
@@ -403,7 +410,9 @@ class HistoryManager:
subject, nickname))
def _fill_search_results_listview(self, text):
- '''ask db and fill listview with results that match text'''
+ """
+ Ask db and fill listview with results that match text
+ """
self.search_results_liststore.clear()
like_sql = '%' + text + '%'
self.cur.execute('''
@@ -610,8 +619,8 @@ class HistoryManager:
liststore, list_of_paths))
def on_search_db_button_clicked(self, widget):
- text = self.search_entry.get_text()
- if text == '':
+ text = self.search_entry.get_text().decode('utf-8')
+ if not text:
return
self.welcome_vbox.hide()
diff --git a/src/htmltextview.py b/src/htmltextview.py
index 36f3ac131..5c12a049c 100644
--- a/src/htmltextview.py
+++ b/src/htmltextview.py
@@ -804,6 +804,10 @@ class HtmlTextView(gtk.TextView):
self.connect('motion-notify-event', self.__motion_notify_event)
self.connect('leave-notify-event', self.__leave_event)
self.connect('enter-notify-event', self.__motion_notify_event)
+ self.connect('realize', self.on_html_text_view_realized)
+ self.connect('unrealize', self.on_html_text_view_unrealized)
+ self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
+ self.get_buffer().connect_after('mark-set', self.on_text_buffer_mark_set)
self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL)
self.tooltip = tooltips.BaseTooltip()
self.config = gajim.config
@@ -873,7 +877,43 @@ class HtmlTextView(gtk.TextView):
#if not eob.starts_line():
# buffer_.insert(eob, '\n')
+ def on_html_text_view_copy_clipboard(self, unused_data):
+ clipboard = self.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD)
+ clipboard.set_text(self.get_selected_text())
+ self.emit_stop_by_name('copy-clipboard')
+ def on_html_text_view_realized(self, unused_data):
+ self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
+
+ def on_html_text_view_unrealized(self, unused_data):
+ self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
+
+ def on_text_buffer_mark_set(self, location, mark, unused_data):
+ bounds = self.get_buffer().get_selection_bounds()
+ if bounds:
+ clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)
+ clipboard.set_text(self.get_selected_text())
+
+ def get_selected_text(self):
+ bounds = self.get_buffer().get_selection_bounds()
+ selection = ''
+ if bounds:
+ (search_iter, end) = bounds
+
+ while (search_iter.compare(end)):
+ character = search_iter.get_char()
+ if character == u'\ufffc':
+ anchor = search_iter.get_child_anchor()
+ if anchor:
+ text = anchor.get_data('plaintext')
+ if text:
+ selection+=text
+ else:
+ selection+=character
+ else:
+ selection+=character
+ search_iter.forward_char()
+ return selection
change_cursor = None
diff --git a/src/message_textview.py b/src/message_textview.py
index 40ba81067..236ad1cb0 100644
--- a/src/message_textview.py
+++ b/src/message_textview.py
@@ -21,15 +21,20 @@
## along with Gajim. If not, see .
##
+import gc
+
import gtk
import gobject
import pango
+
import gtkgui_helpers
from common import gajim
class MessageTextView(gtk.TextView):
- '''Class for the message textview (where user writes new messages)
- for chat/groupchat windows'''
+ """
+ Class for the message textview (where user writes new messages) for
+ chat/groupchat windows
+ """
__gsignals__ = dict(
mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
None, # return value
@@ -272,13 +277,13 @@ class MessageTextView(gtk.TextView):
else:
return None
-
def destroy(self):
- import gc
- gobject.idle_add(lambda:gc.collect())
+ gobject.idle_add(gc.collect)
def clear(self, widget = None):
- '''clear text in the textview'''
+ """
+ Clear text in the textview
+ """
buffer_ = self.get_buffer()
start, end = buffer_.get_bounds()
buffer_.delete(start, end)
diff --git a/src/profile_window.py b/src/profile_window.py
index a417ede08..d00bfc335 100644
--- a/src/profile_window.py
+++ b/src/profile_window.py
@@ -68,7 +68,7 @@ class ProfileWindow:
return True # loop forever
def remove_statusbar(self, message_id):
- self.statusbar.remove(self.context_id, message_id)
+ self.statusbar.remove_message(self.context_id, message_id)
self.remove_statusbar_timeout_id = None
def on_profile_window_destroy(self, widget):
@@ -246,7 +246,7 @@ class ProfileWindow:
self.set_value(i + '_entry', vcard_[i])
if self.update_progressbar_timeout_id is not None:
if self.message_id:
- self.statusbar.remove(self.context_id, self.message_id)
+ self.statusbar.remove_message(self.context_id, self.message_id)
self.message_id = self.statusbar.push(self.context_id,
_('Information received'))
self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3,
@@ -322,8 +322,7 @@ class ProfileWindow:
nick = ''
if 'NICKNAME' in vcard_:
nick = vcard_['NICKNAME']
- from common import pep
- pep.user_send_nickname(self.account, nick)
+ gajim.connections[self.account].send_nickname(nick)
if nick == '':
nick = gajim.config.get_per('accounts', self.account, 'name')
gajim.nicks[self.account] = nick
@@ -342,7 +341,7 @@ class ProfileWindow:
def vcard_not_published(self):
if self.message_id:
- self.statusbar.remove(self.context_id, self.message_id)
+ self.statusbar.remove_message(self.context_id, self.message_id)
self.message_id = self.statusbar.push(self.context_id,
_('Information NOT published'))
self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3,
diff --git a/src/roster_window.py b/src/roster_window.py
index cfdb69cf7..dce525e61 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -38,6 +38,7 @@ import gobject
import os
import sys
import time
+import locale
import common.sleepy
import history_window
@@ -1022,55 +1023,21 @@ class RosterWindow:
self.model[child_iter][C_NAME] = account_name
- if gajim.config.get('show_mood_in_roster') \
- and 'mood' in gajim.connections[account].mood \
- and gajim.connections[account].mood['mood'].strip() in MOODS:
-
- self.model[child_iter][C_MOOD_PIXBUF] = gtkgui_helpers.load_mood_icon(
- gajim.connections[account].mood['mood'].strip()).get_pixbuf()
-
- elif gajim.config.get('show_mood_in_roster') \
- and 'mood' in gajim.connections[account].mood:
- self.model[child_iter][C_MOOD_PIXBUF] = \
- gtkgui_helpers.load_mood_icon('unknown'). \
- get_pixbuf()
+ pep = gajim.connections[account].pep
+ if gajim.config.get('show_mood_in_roster') and 'mood' in pep:
+ self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon()
else:
self.model[child_iter][C_MOOD_PIXBUF] = None
- if gajim.config.get('show_activity_in_roster') \
- and 'activity' in gajim.connections[account].activity \
- and gajim.connections[account].activity['activity'].strip() \
- in ACTIVITIES:
- if 'subactivity' in gajim.connections[account].activity \
- and gajim.connections[account].activity['subactivity'].strip() \
- in ACTIVITIES[gajim.connections[account].activity['activity'].strip()]:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = \
- gtkgui_helpers.load_activity_icon(
- gajim.connections[account].activity['activity'].strip(),
- gajim.connections[account].activity['subactivity'].strip()). \
- get_pixbuf()
- else:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = \
- gtkgui_helpers.load_activity_icon(
- gajim.connections[account].activity['activity'].strip()). \
- get_pixbuf()
- elif gajim.config.get('show_activity_in_roster') \
- and 'activity' in gajim.connections[account].activity:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = \
- gtkgui_helpers.load_activity_icon('unknown'). \
- get_pixbuf()
+ if gajim.config.get('show_activity_in_roster') and 'activity' in pep:
+ self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon()
else:
self.model[child_iter][C_ACTIVITY_PIXBUF] = None
- if gajim.config.get('show_tunes_in_roster') \
- and ('artist' in gajim.connections[account].tune \
- or 'title' in gajim.connections[account].tune):
- path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
- self.model[child_iter][C_TUNE_PIXBUF] = \
- gtk.gdk.pixbuf_new_from_file(path)
+ if gajim.config.get('show_tunes_in_roster') and 'tune' in pep:
+ self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon()
else:
self.model[child_iter][C_TUNE_PIXBUF] = None
-
return False
def draw_group(self, group, account):
@@ -1276,71 +1243,39 @@ class RosterWindow:
return False
-
- def draw_mood(self, jid, account):
+ def _is_pep_shown_in_roster(self, pep_type):
+ if pep_type == 'mood':
+ return gajim.config.get('show_mood_in_roster')
+ elif pep_type == 'activity':
+ return gajim.config.get('show_activity_in_roster')
+ elif pep_type == 'tune':
+ return gajim.config.get('show_tunes_in_roster')
+ else:
+ return False
+
+ def draw_all_pep_types(self, jid, account):
+ for pep_type in self._pep_type_to_model_column:
+ self.draw_pep(jid, account, pep_type)
+
+ def draw_pep(self, jid, account, pep_type):
+ if pep_type not in self._pep_type_to_model_column:
+ return
+ if not self._is_pep_shown_in_roster(pep_type):
+ return
+
+ model_column = self._pep_type_to_model_column[pep_type]
iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_mood_in_roster'):
+ if not iters:
return
jid = self.model[iters[0]][C_JID]
jid = jid.decode('utf-8')
contact = gajim.contacts.get_contact(account, jid)
- if 'mood' in contact.mood and contact.mood['mood'].strip() in MOODS:
- pixbuf = gtkgui_helpers.load_mood_icon(
- contact.mood['mood'].strip()).get_pixbuf()
- elif 'mood' in contact.mood:
- pixbuf = gtkgui_helpers.load_mood_icon(
- 'unknown').get_pixbuf()
+ if pep_type in contact.pep:
+ pixbuf = contact.pep[pep_type].asPixbufIcon()
else:
pixbuf = None
for child_iter in iters:
- self.model[child_iter][C_MOOD_PIXBUF] = pixbuf
- return False
-
-
- def draw_activity(self, jid, account):
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_activity_in_roster'):
- return
- jid = self.model[iters[0]][C_JID]
- jid = jid.decode('utf-8')
- contact = gajim.contacts.get_contact(account, jid)
- if 'activity' in contact.activity \
- and contact.activity['activity'].strip() in ACTIVITIES:
- if 'subactivity' in contact.activity \
- and contact.activity['subactivity'].strip() in \
- ACTIVITIES[contact.activity['activity'].strip()]:
- pixbuf = gtkgui_helpers.load_activity_icon(
- contact.activity['activity'].strip(),
- contact.activity['subactivity'].strip()).get_pixbuf()
- else:
- pixbuf = gtkgui_helpers.load_activity_icon(
- contact.activity['activity'].strip()).get_pixbuf()
- elif 'activity' in contact.activity:
- pixbuf = gtkgui_helpers.load_activity_icon(
- 'unknown').get_pixbuf()
- else:
- pixbuf = None
- for child_iter in iters:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = pixbuf
- return False
-
-
- def draw_tune(self, jid, account):
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_tunes_in_roster'):
- return
- jid = self.model[iters[0]][C_JID]
- jid = jid.decode('utf-8')
- contact = gajim.contacts.get_contact(account, jid)
- if 'artist' in contact.tune or 'title' in contact.tune:
- path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
- pixbuf = gtk.gdk.pixbuf_new_from_file(path)
- else:
- pixbuf = None
- for child_iter in iters:
- self.model[child_iter][C_TUNE_PIXBUF] = pixbuf
- return False
-
+ self.model[child_iter][model_column] = pixbuf
def draw_avatar(self, jid, account):
iters = self._get_contact_iter(jid, account, model=self.model)
@@ -1359,9 +1294,7 @@ class RosterWindow:
def draw_completely(self, jid, account):
self.draw_contact(jid, account)
- self.draw_mood(jid, account)
- self.draw_activity(jid, account)
- self.draw_tune(jid, account)
+ self.draw_all_pep_types(jid, account)
self.draw_avatar(jid, account)
def adjust_and_draw_contact_context(self, jid, account):
@@ -1626,9 +1559,7 @@ class RosterWindow:
account1 = account1.decode('utf-8')
account2 = account2.decode('utf-8')
if type1 == 'account':
- if account1 < account2:
- return -1
- return 1
+ return locale.strcoll(account1, account2)
jid1 = model[iter1][C_JID].decode('utf-8')
jid2 = model[iter2][C_JID].decode('utf-8')
if type1 == 'contact':
@@ -1675,20 +1606,23 @@ class RosterWindow:
elif show1 > show2:
return 1
# We compare names
- if name1.lower() < name2.lower():
+ cmp_result = locale.strcoll(name1.lower(), name2.lower())
+ if cmp_result < 0:
return -1
- if name2.lower() < name1.lower():
+ if cmp_result > 0:
return 1
if type1 == 'contact' and type2 == 'contact':
# We compare account names
- if account1.lower() < account2.lower():
+ cmp_result = locale.strcoll(account1.lower(), account2.lower())
+ if cmp_result < 0:
return -1
- if account2.lower() < account1.lower():
+ if cmp_result > 0:
return 1
# We compare jids
- if jid1.lower() < jid2.lower():
+ cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
+ if cmp_result < 0:
return -1
- if jid2.lower() < jid1.lower():
+ if cmp_result > 0:
return 1
return 0
@@ -1964,36 +1898,36 @@ class RosterWindow:
self.send_status_continue(account, status, txt, auto, to)
- def send_pep(self, account, pep_dict=None):
- '''Sends pep information (activity, mood)'''
- if not pep_dict:
- return
- # activity
- if 'activity' in pep_dict and pep_dict['activity'] in pep.ACTIVITIES:
- activity = pep_dict['activity']
- if 'subactivity' in pep_dict and \
- pep_dict['subactivity'] in pep.ACTIVITIES[activity]:
- subactivity = pep_dict['subactivity']
- else:
- subactivity = 'other'
- if 'activity_text' in pep_dict:
- activity_text = pep_dict['activity_text']
- else:
- activity_text = ''
- pep.user_send_activity(account, activity, subactivity, activity_text)
- else:
- pep.user_send_activity(account, '')
+ def send_pep(self, account, pep_dict):
+ connection = gajim.connections[account]
- # mood
- if 'mood' in pep_dict and pep_dict['mood'] in pep.MOODS:
- mood = pep_dict['mood']
- if 'mood_text' in pep_dict:
- mood_text = pep_dict['mood_text']
- else:
- mood_text = ''
- pep.user_send_mood(account, mood, mood_text)
+ if 'activity' in pep_dict:
+ activity = pep_dict['activity']
+ subactivity = pep_dict.get('subactivity', None)
+ activity_text = pep_dict.get('activity_text', None)
+ connection.send_activity(activity, subactivity, activity_text)
else:
- pep.user_send_mood(account, '')
+ connection.retract_activity()
+
+ if 'mood' in pep_dict:
+ mood = pep_dict['mood']
+ mood_text = pep_dict.get('mood_text', None)
+ connection.send_mood(mood, mood_text)
+ else:
+ connection.retract_mood()
+
+ def delete_pep(self, jid, account):
+ if jid == gajim.get_jid_from_account(account):
+ gajim.connections[account].pep = {}
+ self.draw_account(account)
+
+ for contact in gajim.contacts.get_contacts(account, jid):
+ contact.pep = {}
+
+ self.draw_all_pep_types(jid, account)
+ ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ ctrl.update_all_pep_types()
def send_status_continue(self, account, status, txt, auto, to):
if gajim.account_is_connected(account) and not to:
@@ -2008,8 +1942,7 @@ class RosterWindow:
gajim.connections[account].send_custom_status(status, txt, to)
else:
if status in ('invisible', 'offline'):
- pep.delete_pep(gajim.get_jid_from_account(account), \
- account)
+ self.delete_pep(gajim.get_jid_from_account(account), account)
was_invisible = gajim.connections[account].connected == \
gajim.SHOW_LIST.index('invisible')
gajim.connections[account].change_status(status, txt, auto)
@@ -2087,7 +2020,7 @@ class RosterWindow:
contact_instances)
if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
and not contact.is_groupchat():
- pep.delete_pep(contact.jid, account)
+ self.delete_pep(contact.jid, account)
# Redraw everything and select the sender
self.adjust_and_draw_contact_context(contact.jid, account)
@@ -2509,11 +2442,10 @@ class RosterWindow:
account_name = account
if gajim.account_is_connected(account):
account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
- contact = gajim.contacts.create_contact(jid=jid, account=account, name=account_name,
- show=connection.get_status(), sub='', status=connection.status,
- resource=connection.server_resource,
- priority=connection.priority, mood=connection.mood,
- tune=connection.tune, activity=connection.activity)
+ contact = gajim.contacts.create_self_contact(jid=jid, account=account,
+ name=account_name, show=connection.get_status(),
+ status=connection.status, resource=connection.server_resource,
+ priority=connection.priority)
if gajim.connections[account].gpg:
contact.keyID = gajim.config.get_per('accounts', connection.name,
'keyid')
@@ -3396,23 +3328,19 @@ class RosterWindow:
gajim.interface.instances['preferences'] = config.PreferencesWindow()
def on_publish_tune_toggled(self, widget, account):
- act = widget.get_active()
- gajim.config.set_per('accounts', account, 'publish_tune', act)
- if act:
+ active = widget.get_active()
+ gajim.config.set_per('accounts', account, 'publish_tune', active)
+ if active:
gajim.interface.enable_music_listener()
else:
- # disable it only if no other account use it
- for acct in gajim.connections:
- if gajim.config.get_per('accounts', acct, 'publish_tune'):
+ gajim.connections[account].retract_tune()
+ # disable music listener only if no other account uses it
+ for acc in gajim.connections:
+ if gajim.config.get_per('accounts', acc, 'publish_tune'):
break
else:
gajim.interface.disable_music_listener()
- if gajim.connections[account].pep_supported:
- # As many implementations don't support retracting items, we send a
- # "Stopped" event first
- pep.user_send_tune(account, '')
- pep.user_retract_tune(account)
helpers.update_optional_features(account)
def on_pep_services_menuitem_activate(self, widget, account):
@@ -4489,9 +4417,9 @@ class RosterWindow:
renderer.set_property('xpad', 8)
- def _fill_mood_pixbuf_renderer(self, column, renderer, model, titer,
- data = None):
- '''When a row is added, set properties for avatar renderer'''
+ def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
+ data=None):
+ '''When a row is added, draw the respective pep icon'''
theme = gajim.config.get('roster_theme')
type_ = model[titer][C_TYPE]
if type_ == 'group':
@@ -4499,155 +4427,37 @@ class RosterWindow:
return
# allocate space for the icon only if needed
- if model[titer][C_MOOD_PIXBUF]:
+ if model[titer][data]:
renderer.set_property('visible', True)
else:
renderer.set_property('visible', False)
if type_ == 'account':
- color = gajim.config.get_per('themes', theme,
- 'accountbgcolor')
+ color = gajim.config.get_per('themes', theme, 'accountbgcolor')
if color:
renderer.set_property('cell-background', color)
else:
- self.set_renderer_color(renderer,
- gtk.STATE_ACTIVE)
+ self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
# align pixbuf to the right)
renderer.set_property('xalign', 1)
# prevent type_ = None, see http://trac.gajim.org/ticket/2534
elif type_:
- if not model[titer][C_JID] \
- or not model[titer][C_ACCOUNT]:
+ if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
# This can append at the moment we add the row
return
jid = model[titer][C_JID].decode('utf-8')
account = model[titer][C_ACCOUNT].decode('utf-8')
if jid in gajim.newly_added[account]:
- renderer.set_property('cell-background',
- gajim.config.get(
+ renderer.set_property('cell-background', gajim.config.get(
'just_connected_bg_color'))
elif jid in gajim.to_be_removed[account]:
- renderer.set_property('cell-background',
- gajim.config.get(
+ renderer.set_property('cell-background', gajim.config.get(
'just_disconnected_bg_color'))
else:
- color = gajim.config.get_per('themes',
- theme, 'contactbgcolor')
- if color:
- renderer.set_property(
- 'cell-background', color)
- else:
- renderer.set_property(
- 'cell-background', None)
+ color = gajim.config.get_per('themes', theme, 'contactbgcolor')
+ renderer.set_property('cell-background', color if color else None)
# align pixbuf to the right
renderer.set_property('xalign', 1)
-
- def _fill_activity_pixbuf_renderer(self, column, renderer, model, titer,
- data = None):
- '''When a row is added, set properties for avatar renderer'''
- theme = gajim.config.get('roster_theme')
- type_ = model[titer][C_TYPE]
- if type_ == 'group':
- renderer.set_property('visible', False)
- return
-
- # allocate space for the icon only if needed
- if model[titer][C_ACTIVITY_PIXBUF]:
- renderer.set_property('visible', True)
- else:
- renderer.set_property('visible', False)
- if type_ == 'account':
- color = gajim.config.get_per('themes', theme,
- 'accountbgcolor')
- if color:
- renderer.set_property('cell-background', color)
- else:
- self.set_renderer_color(renderer,
- gtk.STATE_ACTIVE)
- # align pixbuf to the right)
- renderer.set_property('xalign', 1)
- # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- elif type_:
- if not model[titer][C_JID] \
- or not model[titer][C_ACCOUNT]:
- # This can append at the moment we add the row
- return
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- if jid in gajim.newly_added[account]:
- renderer.set_property('cell-background',
- gajim.config.get(
- 'just_connected_bg_color'))
- elif jid in gajim.to_be_removed[account]:
- renderer.set_property('cell-background',
- gajim.config.get(
- 'just_disconnected_bg_color'))
- else:
- color = gajim.config.get_per('themes',
- theme, 'contactbgcolor')
- if color:
- renderer.set_property(
- 'cell-background', color)
- else:
- renderer.set_property(
- 'cell-background', None)
- # align pixbuf to the right
- renderer.set_property('xalign', 1)
-
-
- def _fill_tune_pixbuf_renderer(self, column, renderer, model, titer,
- data = None):
- '''When a row is added, set properties for avatar renderer'''
- theme = gajim.config.get('roster_theme')
- type_ = model[titer][C_TYPE]
- if type_ == 'group':
- renderer.set_property('visible', False)
- return
-
- # allocate space for the icon only if needed
- if model[titer][C_TUNE_PIXBUF]:
- renderer.set_property('visible', True)
- else:
- renderer.set_property('visible', False)
- if type_ == 'account':
- color = gajim.config.get_per('themes', theme,
- 'accountbgcolor')
- if color:
- renderer.set_property('cell-background', color)
- else:
- self.set_renderer_color(renderer,
- gtk.STATE_ACTIVE)
- # align pixbuf to the right)
- renderer.set_property('xalign', 1)
- # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- elif type_:
- if not model[titer][C_JID] \
- or not model[titer][C_ACCOUNT]:
- # This can append at the moment we add the row
- return
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- if jid in gajim.newly_added[account]:
- renderer.set_property('cell-background',
- gajim.config.get(
- 'just_connected_bg_color'))
- elif jid in gajim.to_be_removed[account]:
- renderer.set_property('cell-background',
- gajim.config.get(
- 'just_disconnected_bg_color'))
- else:
- color = gajim.config.get_per('themes',
- theme, 'contactbgcolor')
- if color:
- renderer.set_property(
- 'cell-background', color)
- else:
- renderer.set_property(
- 'cell-background', None)
- # align pixbuf to the right
- renderer.set_property('xalign', 1)
-
-
def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer,
data = None):
'''When a row is added, set properties for avatar renderer'''
@@ -5948,19 +5758,23 @@ class RosterWindow:
col.pack_start(render_pixbuf, expand=False)
col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF)
col.set_cell_data_func(render_pixbuf,
- self._fill_mood_pixbuf_renderer, None)
+ self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF)
render_pixbuf = gtk.CellRendererPixbuf()
col.pack_start(render_pixbuf, expand=False)
col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF)
col.set_cell_data_func(render_pixbuf,
- self._fill_activity_pixbuf_renderer, None)
+ self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF)
render_pixbuf = gtk.CellRendererPixbuf()
col.pack_start(render_pixbuf, expand=False)
col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF)
col.set_cell_data_func(render_pixbuf,
- self._fill_tune_pixbuf_renderer, None)
+ self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF)
+
+ self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF,
+ 'activity': C_ACTIVITY_PIXBUF,
+ 'tune': C_TUNE_PIXBUF}
if gajim.config.get('avatar_position_in_roster') == 'right':
add_avatar_renderer()
@@ -6010,10 +5824,8 @@ class RosterWindow:
if gajim.config.get('show_roster_on_startup'):
self.window.show_all()
else:
- if not gajim.config.get('trayicon') or not \
- gajim.interface.systray_capabilities:
- # cannot happen via GUI, but I put this incase user touches
- # config. without trayicon, he or she should see the roster!
+ if gajim.config.get('trayicon') != 'always':
+ # Without trayicon, user should see the roster!
self.window.show_all()
gajim.config.set('show_roster_on_startup', True)
diff --git a/src/session.py b/src/session.py
index a3693d130..614bb88ef 100644
--- a/src/session.py
+++ b/src/session.py
@@ -119,6 +119,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
log_type = 'chat_msg_recv'
else:
log_type = 'single_msg_recv'
+
if self.is_loggable() and msgtxt:
try:
msg_id = gajim.logger.write(log_type, full_jid_with_resource,
diff --git a/src/statusicon.py b/src/statusicon.py
index 90bdb8c91..e28b67cfd 100644
--- a/src/statusicon.py
+++ b/src/statusicon.py
@@ -25,26 +25,62 @@
import sys
import gtk
-import systray
+import gobject
+import os
+
+import dialogs
+import config
+import tooltips
+import gtkgui_helpers
+import tooltips
from common import gajim
from common import helpers
+from common import pep
-class StatusIcon(systray.Systray):
+class StatusIcon:
'''Class for the notification area icon'''
- #NOTE: gtk api does NOT allow:
- # leave, enter motion notify
- # and can't do cool tooltips we use
def __init__(self):
- systray.Systray.__init__(self)
+ self.single_message_handler_id = None
+ self.new_chat_handler_id = None
+ # click somewhere else does not popdown menu. workaround this.
+ self.added_hide_menuitem = False
+ self.status = 'offline'
+ self.xml = gtkgui_helpers.get_glade('systray_context_menu.glade')
+ self.systray_context_menu = self.xml.get_widget('systray_context_menu')
+ self.xml.signal_autoconnect(self)
+ self.popup_menus = []
self.status_icon = None
+ self.tooltip = tooltips.NotificationAreaTooltip()
+
+ def subscribe_events(self):
+ '''Register listeners to the events class'''
+ gajim.events.event_added_subscribe(self.on_event_added)
+ gajim.events.event_removed_subscribe(self.on_event_removed)
+
+ def unsubscribe_events(self):
+ '''Unregister listeners to the events class'''
+ gajim.events.event_added_unsubscribe(self.on_event_added)
+ gajim.events.event_removed_unsubscribe(self.on_event_removed)
+
+ def on_event_added(self, event):
+ '''Called when an event is added to the event list'''
+ if event.show_in_systray:
+ self.set_img()
+
+ def on_event_removed(self, event_list):
+ '''Called when one or more events are removed from the event list'''
+ self.set_img()
def show_icon(self):
if not self.status_icon:
self.status_icon = gtk.StatusIcon()
+ self.status_icon.set_property('has-tooltip', True)
self.status_icon.connect('activate', self.on_status_icon_left_clicked)
self.status_icon.connect('popup-menu',
self.on_status_icon_right_clicked)
+ self.status_icon.connect('query-tooltip',
+ self.on_status_icon_query_tooltip)
self.set_img()
self.status_icon.set_visible(True)
@@ -53,6 +89,11 @@ class StatusIcon(systray.Systray):
def on_status_icon_right_clicked(self, widget, event_button, event_time):
self.make_menu(event_button, event_time)
+ def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
+ self.tooltip.populate()
+ tooltip.set_custom(self.tooltip.hbox)
+ return True
+
def hide_icon(self):
self.status_icon.set_visible(False)
self.unsubscribe_events()
@@ -64,22 +105,315 @@ class StatusIcon(systray.Systray):
'''apart from image, we also update tooltip text here'''
if not gajim.interface.systray_enabled:
return
- text = helpers.get_notification_icon_tooltip_text()
- self.status_icon.set_tooltip(text)
if gajim.events.get_nb_systray_events():
- state = 'event'
self.status_icon.set_blinking(True)
else:
- state = self.status
self.status_icon.set_blinking(False)
- #FIXME: do not always use 16x16 (ask actually used size and use that)
- image = gajim.interface.jabber_state_images['16'][state]
+ # FIXME: do not always use 16x16 (ask actually used size and use that)
+ image = gajim.interface.jabber_state_images['16'][self.status]
if image.get_storage_type() == gtk.IMAGE_PIXBUF:
self.status_icon.set_from_pixbuf(image.get_pixbuf())
- #FIXME: oops they forgot to support GIF animation?
- #or they were lazy to get it to work under Windows! WTF!
- #elif image.get_storage_type() == gtk.IMAGE_ANIMATION:
+ # FIXME: oops they forgot to support GIF animation?
+ # or they were lazy to get it to work under Windows! WTF!
+ elif image.get_storage_type() == gtk.IMAGE_ANIMATION:
+ self.status_icon.set_from_pixbuf(
+ image.get_animation().get_static_image())
# self.img_tray.set_from_animation(image.get_animation())
+ def change_status(self, global_status):
+ ''' set tray image to 'global_status' '''
+ # change image and status, only if it is different
+ if global_status is not None and self.status != global_status:
+ self.status = global_status
+ self.set_img()
+
+ def start_chat(self, widget, account, jid):
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ if gajim.interface.msg_win_mgr.has_window(jid, account):
+ gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
+ jid, account)
+ elif contact:
+ gajim.interface.new_chat(contact, account)
+ gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
+ jid, account)
+
+ def on_single_message_menuitem_activate(self, widget, account):
+ dialogs.SingleMessageWindow(account, action='send')
+
+ def on_new_chat(self, widget, account):
+ dialogs.NewChatDialog(account)
+
+ def make_menu(self, event_button, event_time):
+ '''create chat with and new message (sub) menus/menuitems'''
+ for m in self.popup_menus:
+ m.destroy()
+
+ chat_with_menuitem = self.xml.get_widget('chat_with_menuitem')
+ single_message_menuitem = self.xml.get_widget(
+ 'single_message_menuitem')
+ status_menuitem = self.xml.get_widget('status_menu')
+ join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
+ sounds_mute_menuitem = self.xml.get_widget('sounds_mute_menuitem')
+
+ if self.single_message_handler_id:
+ single_message_menuitem.handler_disconnect(
+ self.single_message_handler_id)
+ self.single_message_handler_id = None
+ if self.new_chat_handler_id:
+ chat_with_menuitem.disconnect(self.new_chat_handler_id)
+ self.new_chat_handler_id = None
+
+ sub_menu = gtk.Menu()
+ self.popup_menus.append(sub_menu)
+ status_menuitem.set_submenu(sub_menu)
+
+ gc_sub_menu = gtk.Menu() # gc is always a submenu
+ join_gc_menuitem.set_submenu(gc_sub_menu)
+
+ # We need our own set of status icons, let's make 'em!
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ state_images = gtkgui_helpers.load_iconset(path)
+
+ if 'muc_active' in state_images:
+ join_gc_menuitem.set_image(state_images['muc_active'])
+
+ for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+ uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+ item = gtk.ImageMenuItem(uf_show)
+ item.set_image(state_images[show])
+ sub_menu.append(item)
+ item.connect('activate', self.on_show_menuitem_activate, show)
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ item = gtk.ImageMenuItem(_('_Change Status Message...'))
+ path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
+ img = gtk.Image()
+ img.set_from_file(path)
+ item.set_image(img)
+ sub_menu.append(item)
+ item.connect('activate', self.on_change_status_message_activate)
+
+ connected_accounts = gajim.get_number_of_connected_accounts()
+ if connected_accounts < 1:
+ item.set_sensitive(False)
+
+ connected_accounts_with_private_storage = 0
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
+ item = gtk.ImageMenuItem(uf_show)
+ item.set_image(state_images['offline'])
+ sub_menu.append(item)
+ item.connect('activate', self.on_show_menuitem_activate, 'offline')
+
+ iskey = connected_accounts > 0 and not (connected_accounts == 1 and
+ gajim.connections[gajim.connections.keys()[0]].is_zeroconf)
+ chat_with_menuitem.set_sensitive(iskey)
+ single_message_menuitem.set_sensitive(iskey)
+ join_gc_menuitem.set_sensitive(iskey)
+
+ accounts_list = sorted(gajim.contacts.get_accounts())
+ # items that get shown whether an account is zeroconf or not
+ if connected_accounts > 1: # 2 or more connections? make submenus
+ account_menu_for_chat_with = gtk.Menu()
+ chat_with_menuitem.set_submenu(account_menu_for_chat_with)
+ self.popup_menus.append(account_menu_for_chat_with)
+
+ for account in accounts_list:
+ if gajim.account_is_connected(account):
+ # for chat_with
+ item = gtk.MenuItem(_('using account %s') % account)
+ account_menu_for_chat_with.append(item)
+ item.connect('activate', self.on_new_chat, account)
+
+ elif connected_accounts == 1: # one account
+ # one account connected, no need to show 'as jid'
+ for account in gajim.connections:
+ if gajim.connections[account].connected > 1:
+ # for start chat
+ self.new_chat_handler_id = chat_with_menuitem.connect(
+ 'activate', self.on_new_chat, account)
+ break # No other connected account
+
+ # menu items that don't apply to zeroconf connections
+ if connected_accounts == 1 or (connected_accounts == 2 and \
+ gajim.zeroconf_is_connected()):
+ # only one 'real' (non-zeroconf) account is connected, don't need
+ # submenus
+ for account in gajim.connections:
+ if gajim.account_is_connected(account) and \
+ not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ if gajim.connections[account].private_storage_supported:
+ connected_accounts_with_private_storage += 1
+
+ # for single message
+ single_message_menuitem.remove_submenu()
+ self.single_message_handler_id = single_message_menuitem.\
+ connect('activate',
+ self.on_single_message_menuitem_activate, account)
+ # join gc
+ gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
+ account)
+ break # No other account connected
+ else:
+ # 2 or more 'real' accounts are connected, make submenus
+ account_menu_for_single_message = gtk.Menu()
+ single_message_menuitem.set_submenu(
+ account_menu_for_single_message)
+ self.popup_menus.append(account_menu_for_single_message)
+
+ for account in accounts_list:
+ if gajim.connections[account].is_zeroconf or \
+ not gajim.account_is_connected(account):
+ continue
+ if gajim.connections[account].private_storage_supported:
+ connected_accounts_with_private_storage += 1
+ # for single message
+ item = gtk.MenuItem(_('using account %s') % account)
+ item.connect('activate',
+ self.on_single_message_menuitem_activate, account)
+ account_menu_for_single_message.append(item)
+
+ # join gc
+ gc_item = gtk.MenuItem(_('using account %s') % account, False)
+ gc_sub_menu.append(gc_item)
+ gc_menuitem_menu = gtk.Menu()
+ gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu,
+ account)
+ gc_item.set_submenu(gc_menuitem_menu)
+ gc_sub_menu.show_all()
+
+ newitem = gtk.SeparatorMenuItem() # separator
+ gc_sub_menu.append(newitem)
+ newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
+ img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
+ newitem.set_image(img)
+ newitem.connect('activate',
+ gajim.interface.roster.on_manage_bookmarks_menuitem_activate)
+ gc_sub_menu.append(newitem)
+ if connected_accounts_with_private_storage == 0:
+ newitem.set_sensitive(False)
+
+ sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
+
+ if os.name == 'nt':
+ if self.added_hide_menuitem is False:
+ self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
+ item = gtk.MenuItem(_('Hide this menu'))
+ self.systray_context_menu.prepend(item)
+ self.added_hide_menuitem = True
+
+ self.systray_context_menu.show_all()
+ self.systray_context_menu.popup(None, None, None, 0,
+ event_time)
+
+ def on_show_all_events_menuitem_activate(self, widget):
+ events = gajim.events.get_systray_events()
+ for account in events:
+ for jid in events[account]:
+ for event in events[account][jid]:
+ gajim.interface.handle_event(account, jid, event.type_)
+
+ def on_sounds_mute_menuitem_activate(self, widget):
+ gajim.config.set('sounds_on', not widget.get_active())
+ gajim.interface.save_config()
+
+ def on_show_roster_menuitem_activate(self, widget):
+ win = gajim.interface.roster.window
+ win.present()
+
+ def on_preferences_menuitem_activate(self, widget):
+ if 'preferences' in gajim.interface.instances:
+ gajim.interface.instances['preferences'].window.present()
+ else:
+ gajim.interface.instances['preferences'] = config.PreferencesWindow()
+
+ def on_quit_menuitem_activate(self, widget):
+ gajim.interface.roster.on_quit_request()
+
+ def on_left_click(self):
+ win = gajim.interface.roster.window
+ if len(gajim.events.get_systray_events()) == 0:
+ # No pending events, so toggle visible/hidden for roster window
+ if not win.iconify_initially and (win.get_property(
+ 'has-toplevel-focus') or os.name == 'nt'):
+ # visible in ANY virtual desktop?
+
+ # we could be in another VD right now. eg vd2
+ # and we want to show it in vd2
+ if not gtkgui_helpers.possibly_move_window_in_current_desktop(win):
+ win.set_property('skip-taskbar-hint', False)
+ win.iconify() # else we hide it from VD that was visible in
+ win.set_property('skip-taskbar-hint', True)
+ else:
+ win.deiconify()
+ if not gajim.config.get('roster_window_skip_taskbar'):
+ win.set_property('skip-taskbar-hint', False)
+ win.present_with_time(gtk.get_current_event_time())
+ else:
+ self.handle_first_event()
+
+ def handle_first_event(self):
+ account, jid, event = gajim.events.get_first_systray_event()
+ if not event:
+ return
+ gajim.interface.handle_event(account, jid, event.type_)
+
+ def on_middle_click(self):
+ '''middle click raises window to have complete focus (fe. get kbd events)
+ but if already raised, it hides it'''
+ win = gajim.interface.roster.window
+ if win.is_active(): # is it fully raised? (eg does it receive kbd events?)
+ win.hide()
+ else:
+ win.present()
+
+ def on_clicked(self, widget, event):
+ self.on_tray_leave_notify_event(widget, None)
+ if event.type != gtk.gdk.BUTTON_PRESS:
+ return
+ if event.button == 1: # Left click
+ self.on_left_click()
+ elif event.button == 2: # middle click
+ self.on_middle_click()
+ elif event.button == 3: # right click
+ self.make_menu(event.button, event.time)
+
+ def on_show_menuitem_activate(self, widget, show):
+ # we all add some fake (we cannot select those nor have them as show)
+ # but this helps to align with roster's status_combobox index positions
+ l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR',
+ 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline']
+ index = l.index(show)
+ if not helpers.statuses_unified():
+ gajim.interface.roster.status_combobox.set_active(index + 2)
+ return
+ current = gajim.interface.roster.status_combobox.get_active()
+ if index != current:
+ gajim.interface.roster.status_combobox.set_active(index)
+
+ def on_change_status_message_activate(self, widget):
+ model = gajim.interface.roster.status_combobox.get_model()
+ active = gajim.interface.roster.status_combobox.get_active()
+ status = model[active][2].decode('utf-8')
+ def on_response(message, pep_dict):
+ if message is None: # None if user press Cancel
+ return
+ accounts = gajim.connections.keys()
+ for acct in accounts:
+ if not gajim.config.get_per('accounts', acct,
+ 'sync_with_global_status'):
+ continue
+ show = gajim.SHOW_LIST[gajim.connections[acct].connected]
+ gajim.interface.roster.send_status(acct, show, message)
+ gajim.interface.roster.send_pep(acct, pep_dict)
+ dlg = dialogs.ChangeStatusMessageDialog(on_response, status)
+ dlg.dialog.present()
+
# vim: se ts=3:
diff --git a/src/systray.py b/src/systray.py
deleted file mode 100644
index e2c09331b..000000000
--- a/src/systray.py
+++ /dev/null
@@ -1,459 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/systray.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez
-## Copyright (C) 2003-2008 Yann Leboulanger
-## Copyright (C) 2005 Norman Rasmussen
-## Copyright (C) 2005-2006 Dimitur Kirov
-## Travis Shirk
-## Copyright (C) 2005-2007 Nikos Kouremenos
-## Copyright (C) 2006 Stefan Bethge
-## Copyright (C) 2006-2008 Jean-Marie Traissard
-## Copyright (C) 2007 Lukas Petrovicky
-## Julien Pivotto
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see .
-##
-
-import gtk
-import gobject
-import os
-
-import dialogs
-import config
-import tooltips
-import gtkgui_helpers
-
-from common import gajim
-from common import helpers
-from common import pep
-
-HAS_SYSTRAY_CAPABILITIES = True
-
-try:
- import egg.trayicon as trayicon # gnomepythonextras trayicon
-except Exception:
- try:
- import trayicon # our trayicon
- except Exception:
- gajim.log.debug('No trayicon module available')
- HAS_SYSTRAY_CAPABILITIES = False
-
-
-class Systray:
- '''Class for icon in the notification area
- This class is both base class (for statusicon.py) and normal class
- for trayicon in GNU/Linux'''
-
- def __init__(self):
- self.single_message_handler_id = None
- self.new_chat_handler_id = None
- self.t = None
- # click somewhere else does not popdown menu. workaround this.
- self.added_hide_menuitem = False
- self.img_tray = gtk.Image()
- self.status = 'offline'
- self.xml = gtkgui_helpers.get_glade('systray_context_menu.glade')
- self.systray_context_menu = self.xml.get_widget('systray_context_menu')
- self.xml.signal_autoconnect(self)
- self.popup_menus = []
-
- def subscribe_events(self):
- '''Register listeners to the events class'''
- gajim.events.event_added_subscribe(self.on_event_added)
- gajim.events.event_removed_subscribe(self.on_event_removed)
-
- def unsubscribe_events(self):
- '''Unregister listeners to the events class'''
- gajim.events.event_added_unsubscribe(self.on_event_added)
- gajim.events.event_removed_unsubscribe(self.on_event_removed)
-
- def on_event_added(self, event):
- '''Called when an event is added to the event list'''
- if event.show_in_systray:
- self.set_img()
-
- def on_event_removed(self, event_list):
- '''Called when one or more events are removed from the event list'''
- self.set_img()
-
- def set_img(self):
- if not gajim.interface.systray_enabled:
- return
- if gajim.events.get_nb_systray_events():
- state = 'event'
- else:
- state = self.status
- if state != 'event' and gajim.config.get('trayicon') == 'on_event':
- self.t.hide()
- else:
- self.t.show()
- image = gajim.interface.jabber_state_images['16'][state]
- if image.get_storage_type() == gtk.IMAGE_ANIMATION:
- self.img_tray.set_from_animation(image.get_animation())
- elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
- self.img_tray.set_from_pixbuf(image.get_pixbuf())
-
- def change_status(self, global_status):
- ''' set tray image to 'global_status' '''
- # change image and status, only if it is different
- if global_status is not None and self.status != global_status:
- self.status = global_status
- self.set_img()
-
- def start_chat(self, widget, account, jid):
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if gajim.interface.msg_win_mgr.has_window(jid, account):
- gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
- jid, account)
- elif contact:
- gajim.interface.new_chat(contact, account)
- gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
- jid, account)
-
- def on_single_message_menuitem_activate(self, widget, account):
- dialogs.SingleMessageWindow(account, action = 'send')
-
- def on_new_chat(self, widget, account):
- dialogs.NewChatDialog(account)
-
- def make_menu(self, event_button, event_time):
- '''create chat with and new message (sub) menus/menuitems'''
- for m in self.popup_menus:
- m.destroy()
-
- chat_with_menuitem = self.xml.get_widget('chat_with_menuitem')
- single_message_menuitem = self.xml.get_widget(
- 'single_message_menuitem')
- status_menuitem = self.xml.get_widget('status_menu')
- join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
- sounds_mute_menuitem = self.xml.get_widget('sounds_mute_menuitem')
-
- if self.single_message_handler_id:
- single_message_menuitem.handler_disconnect(
- self.single_message_handler_id)
- self.single_message_handler_id = None
- if self.new_chat_handler_id:
- chat_with_menuitem.disconnect(self.new_chat_handler_id)
- self.new_chat_handler_id = None
-
- sub_menu = gtk.Menu()
- self.popup_menus.append(sub_menu)
- status_menuitem.set_submenu(sub_menu)
-
- gc_sub_menu = gtk.Menu() # gc is always a submenu
- join_gc_menuitem.set_submenu(gc_sub_menu)
-
- # We need our own set of status icons, let's make 'em!
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- state_images = gtkgui_helpers.load_iconset(path)
-
- if 'muc_active' in state_images:
- join_gc_menuitem.set_image(state_images['muc_active'])
-
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic = True)
- item = gtk.ImageMenuItem(uf_show)
- item.set_image(state_images[show])
- sub_menu.append(item)
- item.connect('activate', self.on_show_menuitem_activate, show)
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
-
- item = gtk.ImageMenuItem(_('_Change Status Message...'))
- path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
- img = gtk.Image()
- img.set_from_file(path)
- item.set_image(img)
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate)
-
- connected_accounts = gajim.get_number_of_connected_accounts()
- if connected_accounts < 1:
- item.set_sensitive(False)
-
- connected_accounts_with_private_storage = 0
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
-
- uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
- item = gtk.ImageMenuItem(uf_show)
- item.set_image(state_images['offline'])
- sub_menu.append(item)
- item.connect('activate', self.on_show_menuitem_activate, 'offline')
-
- iskey = connected_accounts > 0 and not (connected_accounts == 1 and
- gajim.connections[gajim.connections.keys()[0]].is_zeroconf)
- chat_with_menuitem.set_sensitive(iskey)
- single_message_menuitem.set_sensitive(iskey)
- join_gc_menuitem.set_sensitive(iskey)
-
- accounts_list = sorted(gajim.contacts.get_accounts())
- # items that get shown whether an account is zeroconf or not
- if connected_accounts > 1: # 2 or more connections? make submenus
- account_menu_for_chat_with = gtk.Menu()
- chat_with_menuitem.set_submenu(account_menu_for_chat_with)
- self.popup_menus.append(account_menu_for_chat_with)
-
- for account in accounts_list:
- if gajim.account_is_connected(account):
- # for chat_with
- item = gtk.MenuItem(_('using account %s') % account)
- account_menu_for_chat_with.append(item)
- item.connect('activate', self.on_new_chat, account)
-
- elif connected_accounts == 1: # one account
- # one account connected, no need to show 'as jid'
- for account in gajim.connections:
- if gajim.connections[account].connected > 1:
- # for start chat
- self.new_chat_handler_id = chat_with_menuitem.connect(
- 'activate', self.on_new_chat, account)
- break # No other connected account
-
- # menu items that don't apply to zeroconf connections
- if connected_accounts == 1 or (connected_accounts == 2 and \
- gajim.zeroconf_is_connected()):
- # only one 'real' (non-zeroconf) account is connected, don't need
- # submenus
- for account in gajim.connections:
- if gajim.account_is_connected(account) and \
- not gajim.config.get_per('accounts', account, 'is_zeroconf'):
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
-
- # for single message
- single_message_menuitem.remove_submenu()
- self.single_message_handler_id = single_message_menuitem.\
- connect('activate',
- self.on_single_message_menuitem_activate, account)
- # join gc
- gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
- account)
- break # No other account connected
- else:
- # 2 or more 'real' accounts are connected, make submenus
- account_menu_for_single_message = gtk.Menu()
- single_message_menuitem.set_submenu(
- account_menu_for_single_message)
- self.popup_menus.append(account_menu_for_single_message)
-
- for account in accounts_list:
- if gajim.connections[account].is_zeroconf or \
- not gajim.account_is_connected(account):
- continue
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
- # for single message
- item = gtk.MenuItem(_('using account %s') % account)
- item.connect('activate',
- self.on_single_message_menuitem_activate, account)
- account_menu_for_single_message.append(item)
-
- # join gc
- gc_item = gtk.MenuItem(_('using account %s') % account, False)
- gc_sub_menu.append(gc_item)
- gc_menuitem_menu = gtk.Menu()
- gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu,
- account)
- gc_item.set_submenu(gc_menuitem_menu)
- gc_sub_menu.show_all()
-
- newitem = gtk.SeparatorMenuItem() # separator
- gc_sub_menu.append(newitem)
- newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
- img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
- newitem.set_image(img)
- newitem.connect('activate',
- gajim.interface.roster.on_manage_bookmarks_menuitem_activate)
- gc_sub_menu.append(newitem)
- if connected_accounts_with_private_storage == 0:
- newitem.set_sensitive(False)
-
- sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
-
- if os.name == 'nt':
- if self.added_hide_menuitem is False:
- self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
- item = gtk.MenuItem(_('Hide this menu'))
- self.systray_context_menu.prepend(item)
- self.added_hide_menuitem = True
-
- self.systray_context_menu.show_all()
- self.systray_context_menu.popup(None, None, None, 0,
- event_time)
-
- def on_show_all_events_menuitem_activate(self, widget):
- events = gajim.events.get_systray_events()
- for account in events:
- for jid in events[account]:
- for event in events[account][jid]:
- gajim.interface.handle_event(account, jid, event.type_)
-
- def on_sounds_mute_menuitem_activate(self, widget):
- gajim.config.set('sounds_on', not widget.get_active())
- gajim.interface.save_config()
-
- def on_show_roster_menuitem_activate(self, widget):
- win = gajim.interface.roster.window
- win.present()
-
- def on_preferences_menuitem_activate(self, widget):
- if 'preferences' in gajim.interface.instances:
- gajim.interface.instances['preferences'].window.present()
- else:
- gajim.interface.instances['preferences'] = config.PreferencesWindow()
-
- def on_quit_menuitem_activate(self, widget):
- gajim.interface.roster.on_quit_request()
-
- def on_left_click(self):
- win = gajim.interface.roster.window
- if len(gajim.events.get_systray_events()) == 0:
- # No pending events, so toggle visible/hidden for roster window
- if not win.iconify_initially and (win.get_property(
- 'has-toplevel-focus') or os.name == 'nt'):
- # visible in ANY virtual desktop?
-
- # we could be in another VD right now. eg vd2
- # and we want to show it in vd2
- if not gtkgui_helpers.possibly_move_window_in_current_desktop(win):
- win.set_property('skip-taskbar-hint', False)
- win.iconify() # else we hide it from VD that was visible in
- win.set_property('skip-taskbar-hint', True)
- else:
- win.deiconify()
- if not gajim.config.get('roster_window_skip_taskbar'):
- win.set_property('skip-taskbar-hint', False)
- win.present_with_time(gtk.get_current_event_time())
- else:
- self.handle_first_event()
-
- def handle_first_event(self):
- account, jid, event = gajim.events.get_first_systray_event()
- if not event:
- return
- gajim.interface.handle_event(account, jid, event.type_)
-
- def on_middle_click(self):
- '''middle click raises window to have complete focus (fe. get kbd events)
- but if already raised, it hides it'''
- win = gajim.interface.roster.window
- if win.is_active(): # is it fully raised? (eg does it receive kbd events?)
- win.hide()
- else:
- win.present()
-
- def on_clicked(self, widget, event):
- self.on_tray_leave_notify_event(widget, None)
- if event.type != gtk.gdk.BUTTON_PRESS:
- return
- if event.button == 1: # Left click
- self.on_left_click()
- elif event.button == 2: # middle click
- self.on_middle_click()
- elif event.button == 3: # right click
- self.make_menu(event.button, event.time)
-
- def on_show_menuitem_activate(self, widget, show):
- # we all add some fake (we cannot select those nor have them as show)
- # but this helps to align with roster's status_combobox index positions
- l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR',
- 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline']
- index = l.index(show)
- if not helpers.statuses_unified():
- gajim.interface.roster.status_combobox.set_active(index + 2)
- return
- current = gajim.interface.roster.status_combobox.get_active()
- if index != current:
- gajim.interface.roster.status_combobox.set_active(index)
-
- def on_change_status_message_activate(self, widget):
- model = gajim.interface.roster.status_combobox.get_model()
- active = gajim.interface.roster.status_combobox.get_active()
- status = model[active][2].decode('utf-8')
- def on_response(message, pep_dict):
- if message is None: # None if user press Cancel
- return
- accounts = gajim.connections.keys()
- for acct in accounts:
- if not gajim.config.get_per('accounts', acct,
- 'sync_with_global_status'):
- continue
- show = gajim.SHOW_LIST[gajim.connections[acct].connected]
- gajim.interface.roster.send_status(acct, show, message)
- gajim.interface.roster.send_pep(acct, pep_dict)
- dlg = dialogs.ChangeStatusMessageDialog(on_response, status)
- dlg.dialog.present()
-
- def show_tooltip(self, widget):
- position = widget.window.get_origin()
- if self.tooltip.id == position:
- size = widget.window.get_size()
- self.tooltip.show_tooltip('', size[1], position[1])
-
- def on_tray_motion_notify_event(self, widget, event):
- position = widget.window.get_origin()
- if self.tooltip.timeout > 0:
- if self.tooltip.id != position:
- self.tooltip.hide_tooltip()
- if self.tooltip.timeout == 0 and \
- self.tooltip.id != position:
- self.tooltip.id = position
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, widget)
-
- def on_tray_leave_notify_event(self, widget, event):
- position = widget.window.get_origin()
- if self.tooltip.timeout > 0 and \
- self.tooltip.id == position:
- self.tooltip.hide_tooltip()
-
- def on_tray_destroyed(self, widget):
- '''re-add trayicon when systray is destroyed'''
- self.t = None
- if gajim.interface.systray_enabled:
- self.show_icon()
-
- def show_icon(self):
- if not self.t:
- self.t = trayicon.TrayIcon('Gajim')
- self.t.connect('destroy', self.on_tray_destroyed)
- eb = gtk.EventBox()
- # avoid draw seperate bg color in some gtk themes
- eb.set_visible_window(False)
- eb.set_events(gtk.gdk.POINTER_MOTION_MASK)
- eb.connect('button-press-event', self.on_clicked)
- eb.connect('motion-notify-event', self.on_tray_motion_notify_event)
- eb.connect('leave-notify-event', self.on_tray_leave_notify_event)
- self.tooltip = tooltips.NotificationAreaTooltip()
-
- self.img_tray = gtk.Image()
- eb.add(self.img_tray)
- self.t.add(eb)
- self.set_img()
- self.subscribe_events()
- self.t.show_all()
-
- def hide_icon(self):
- if self.t:
- self.t.destroy()
- self.t = None
- self.unsubscribe_events()
-
-# vim: se ts=3:
diff --git a/src/tooltips.py b/src/tooltips.py
index fa96cbd8c..5e55166c0 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -269,7 +269,7 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
for line in acct['event_lines']:
self.add_text_row(' ' + line, 1)
- def populate(self, data):
+ def populate(self, data=''):
self.create_window()
self.create_table()
@@ -280,7 +280,7 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
self.table.set_property('column-spacing', 1)
self.hbox.add(self.table)
- self.win.add(self.hbox)
+ self.hbox.show_all()
class GCTooltip(BaseTooltip):
''' Tooltip that is shown in the GC treeview '''
@@ -579,65 +579,19 @@ class RosterTooltip(NotificationAreaTooltip):
Append Tune, Mood, Activity information of the specified contact
to the given property list.
'''
- if 'mood' in contact.mood:
- mood = contact.mood['mood'].strip()
- mood = MOODS.get(mood, mood)
- mood = gobject.markup_escape_text(mood)
- mood_string = _('Mood:') + ' %s' % mood
- if 'text' in contact.mood \
- and contact.mood['text'] != '':
- mood_text = contact.mood['text'].strip()
- mood_text = \
- gobject.markup_escape_text(mood_text)
- mood_string += ' (%s)' % mood_text
+ if 'mood' in contact.pep:
+ mood = contact.pep['mood'].asMarkupText()
+ mood_string = _('Mood:') + ' %s' % mood
properties.append((mood_string, None))
- if 'activity' in contact.activity:
- activity = act_plain = \
- contact.activity['activity'].strip()
- activity = gobject.markup_escape_text(activity)
- if act_plain in ACTIVITIES:
- activity = ACTIVITIES[activity]['category']
- activity_string = _('Activity:') + ' %s' % activity
- if 'subactivity' in contact.activity:
- activity_sub = \
- contact.activity['subactivity'].strip()
- if act_plain in ACTIVITIES and activity_sub in \
- ACTIVITIES[act_plain]:
- activity_sub = ACTIVITIES[act_plain][activity_sub]
- activity_sub = \
- gobject.markup_escape_text(activity_sub)
- activity_string += ': %s' % activity_sub
- else:
- activity_string += ''
- if 'text' in contact.activity:
- activity_text = contact.activity['text'].strip()
- activity_text = gobject.markup_escape_text(
- activity_text)
- activity_string += ' (%s)' % activity_text
+ if 'activity' in contact.pep:
+ activity = contact.pep['activity'].asMarkupText()
+ activity_string = _('Activity:') + ' %s' % activity
properties.append((activity_string, None))
- if 'artist' in contact.tune \
- or 'title' in contact.tune:
- if 'artist' in contact.tune:
- artist = contact.tune['artist'].strip()
- artist = gobject.markup_escape_text(artist)
- else:
- artist = _('Unknown Artist')
- if 'title' in contact.tune:
- title = contact.tune['title'].strip()
- title = gobject.markup_escape_text(title)
- else:
- title = _('Unknown Title')
- if 'source' in contact.tune:
- source = contact.tune['source'].strip()
- source = gobject.markup_escape_text(source)
- else:
- source = _('Unknown Source')
- tune_string = _('Tune:') + ' ' + \
- _('"%(title)s" by %(artist)s\n'
- 'from %(source)s') % {'title': title,
- 'artist': artist, 'source': source}
+ if 'tune' in contact.pep:
+ tune = contact.pep['tune'].asMarkupText()
+ tune_string = _('Tune:') + ' %s' % tune
properties.append((tune_string, None))
diff --git a/src/trayicon.defs b/src/trayicon.defs
deleted file mode 100644
index 6d253cf83..000000000
--- a/src/trayicon.defs
+++ /dev/null
@@ -1,59 +0,0 @@
-;; -*- scheme -*-
-
-; object definitions ...
-(define-object TrayIcon
- (in-module "Egg")
- (parent "GtkPlug")
- (c-name "EggTrayIcon")
- (gtype-id "EGG_TYPE_TRAY_ICON")
-)
-
-;; Enumerations and flags ...
-
-
-;; From eggtrayicon.h
-
-(define-function egg_tray_icon_get_type
- (c-name "egg_tray_icon_get_type")
- (return-type "GType")
-)
-
-(define-function egg_tray_icon_new_for_screen
- (c-name "egg_tray_icon_new_for_screen")
- (return-type "EggTrayIcon*")
- (parameters
- '("GdkScreen*" "screen")
- '("const-gchar*" "name")
- )
-)
-
-(define-function egg_tray_icon_new
- (c-name "egg_tray_icon_new")
- (is-constructor-of "EggTrayIcon")
- (return-type "EggTrayIcon*")
- (parameters
- '("const-gchar*" "name")
- )
-)
-
-(define-method send_message
- (of-object "EggTrayIcon")
- (c-name "egg_tray_icon_send_message")
- (return-type "guint")
- (parameters
- '("gint" "timeout")
- '("const-char*" "message")
- '("gint" "len")
- )
-)
-
-(define-method cancel_message
- (of-object "EggTrayIcon")
- (c-name "egg_tray_icon_cancel_message")
- (return-type "none")
- (parameters
- '("guint" "id")
- )
-)
-
-
diff --git a/src/trayicon.override b/src/trayicon.override
deleted file mode 100644
index 75bd7ed8f..000000000
--- a/src/trayicon.override
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4 -*-
- * src/trayicon.override
- *
- * Copyright (C) 2004-2005 Yann Leboulanger
- *
- * This file is part of Gajim.
- *
- * Gajim is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; version 3 only.
- *
- * Gajim is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Gajim. If not, see .
- */
-%%
-headers
-#include
-
-#include "pygobject.h"
-#include "eggtrayicon.h"
-%%
-modulename trayicon
-%%
-import gtk.Plug as PyGtkPlug_Type
-import gtk.gdk.Screen as PyGdkScreen_Type
-%%
-ignore-glob
- *_get_type
-%%
-override egg_tray_icon_send_message kwargs
-static PyObject*
-_wrap_egg_tray_icon_send_message(PyGObject *self, PyObject *args, PyObject *kwargs)
-{
- static char *kwlist[] = {"timeout", "message", NULL};
- int timeout, len, ret;
- char *message;
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "is#:TrayIcon.send_message", kwlist, &timeout, &message, &len))
- return NULL;
- ret = egg_tray_icon_send_message(EGG_TRAY_ICON(self->obj), timeout, message, len);
- return PyInt_FromLong(ret);
-}
diff --git a/src/trayiconmodule.c b/src/trayiconmodule.c
deleted file mode 100644
index 7a2b1206f..000000000
--- a/src/trayiconmodule.c
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4 -*-
- * src/trayiconmodule.c
- *
- * Copyright (C) 2004-2005 Yann Leboulanger
- *
- * This file is part of Gajim.
- *
- * Gajim is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; version 3 only.
- *
- * Gajim is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Gajim. If not, see .
- */
-
-/* include this first, before NO_IMPORT_PYGOBJECT is defined */
-#include
-
-void trayicon_register_classes (PyObject *d);
-
-extern PyMethodDef trayicon_functions[];
-
-DL_EXPORT(void)
-inittrayicon(void)
-{
- PyObject *m, *d;
-
- init_pygobject ();
-
- m = Py_InitModule ("trayicon", trayicon_functions);
- d = PyModule_GetDict (m);
-
- trayicon_register_classes (d);
-
- if (PyErr_Occurred ()) {
- Py_FatalError ("can't initialise module trayicon :(");
- }
-}
diff --git a/test/integration/test_xmpp_transports_nb.py b/test/integration/test_xmpp_transports_nb.py
index 6e56ddf94..ef9908903 100644
--- a/test/integration/test_xmpp_transports_nb.py
+++ b/test/integration/test_xmpp_transports_nb.py
@@ -254,12 +254,12 @@ class TestNonBlockingHTTP(AbstractTransportTest):
def test_receive_http_message_in_chunks(self):
''' Let _on_receive handle some chunked http messages '''
transport = self._get_transport(self.bosh_http_dict)
-
- header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +
- "Content-Length: 88\r\n\r\n")
- payload = "Please don't fail!"
+
+ payload = "Please don't fail!\n\n"
body = "%s" \
% payload
+ header = "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +\
+ "Content-Length: %i\r\n\r\n" % len(body)
message = "%s%s" % (header, body)
chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \
diff --git a/test/lib/gajim_mocks.py b/test/lib/gajim_mocks.py
index d45e488e6..9607a77c9 100644
--- a/test/lib/gajim_mocks.py
+++ b/test/lib/gajim_mocks.py
@@ -14,9 +14,7 @@ class MockConnection(Mock, ConnectionHandlersBase):
self.name = account
self.connected = 2
- self.mood = {}
- self.activity = {}
- self.tune = {}
+ self.pep = {}
self.blocked_contacts = {}
self.blocked_groups = {}
self.sessions = {}