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

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/emoticons/animated/emoticons.py96
-rw-r--r--data/emoticons/static-big/emoticons.py82
-rw-r--r--data/emoticons/static/emoticons.py82
-rw-r--r--data/emoticons/tango/emoticons.py2
-rwxr-xr-xscripts/dev/run-build-test.py15
-rwxr-xr-xscripts/dev/run-pylint.py4
-rw-r--r--setup_win32.py42
-rw-r--r--src/adhoc_commands.py1028
-rw-r--r--src/advanced_configuration_window.py478
-rw-r--r--src/atom_window.py234
-rw-r--r--src/cell_renderer_image.py218
-rw-r--r--src/chat_control.py5546
-rw-r--r--src/common/GnuPG.py426
-rw-r--r--src/common/GnuPGInterface.py2
-rw-r--r--src/common/__init__.py2
-rw-r--r--src/common/account.py20
-rw-r--r--src/common/atom.py260
-rw-r--r--src/common/caps_cache.py638
-rw-r--r--src/common/check_paths.py580
-rw-r--r--src/common/commands.py804
-rw-r--r--src/common/config.py1366
-rw-r--r--src/common/configpaths.py244
-rw-r--r--src/common/connection.py4376
-rw-r--r--src/common/connection_handlers.py4438
-rw-r--r--src/common/contacts.py1550
-rw-r--r--src/common/crypto.py88
-rw-r--r--src/common/dataforms.py1088
-rw-r--r--src/common/dbus_support.py260
-rw-r--r--src/common/defs.py4
-rw-r--r--src/common/dh.py52
-rw-r--r--src/common/events.py608
-rw-r--r--src/common/exceptions.py148
-rwxr-xr-xsrc/common/fuzzyclock.py56
-rw-r--r--src/common/gajim.py436
-rw-r--r--src/common/ged.py50
-rw-r--r--src/common/helpers.py2292
-rw-r--r--src/common/i18n.py92
-rw-r--r--src/common/idle.py116
-rw-r--r--src/common/jingle.py214
-rw-r--r--src/common/jingle_content.py226
-rw-r--r--src/common/jingle_rtp.py584
-rw-r--r--src/common/jingle_session.py1228
-rw-r--r--src/common/jingle_transport.py228
-rw-r--r--src/common/kwalletbinding.py86
-rw-r--r--src/common/latex.py198
-rw-r--r--src/common/location_listener.py196
-rw-r--r--src/common/logger.py2002
-rw-r--r--src/common/logging_helpers.py270
-rw-r--r--src/common/multimedia_helpers.py141
-rw-r--r--src/common/optparser.py1686
-rw-r--r--src/common/passwords.py332
-rw-r--r--src/common/pep.py1172
-rw-r--r--src/common/protocol/__init__.py2
-rw-r--r--src/common/protocol/bytestream.py1186
-rw-r--r--src/common/protocol/caps.py152
-rw-r--r--src/common/proxy65_manager.py838
-rw-r--r--src/common/pubsub.py346
-rw-r--r--src/common/resolver.py584
-rw-r--r--src/common/rst_xhtml_generator.py248
-rw-r--r--src/common/sleepy.py200
-rw-r--r--src/common/socks5.py2204
-rw-r--r--src/common/stanza_session.py1788
-rw-r--r--src/common/xmpp/__init__.py2
-rw-r--r--src/common/xmpp/auth_nb.py1016
-rw-r--r--src/common/xmpp/bosh.py1026
-rw-r--r--src/common/xmpp/c14n.py58
-rw-r--r--src/common/xmpp/client_nb.py1112
-rw-r--r--src/common/xmpp/dispatcher_nb.py1102
-rw-r--r--src/common/xmpp/features_nb.py298
-rw-r--r--src/common/xmpp/idlequeue.py990
-rw-r--r--src/common/xmpp/plugin.py130
-rw-r--r--src/common/xmpp/protocol.py2228
-rw-r--r--src/common/xmpp/proxy_connectors.py410
-rw-r--r--src/common/xmpp/roster_nb.py670
-rw-r--r--src/common/xmpp/simplexml.py1288
-rw-r--r--src/common/xmpp/stringprepare.py322
-rw-r--r--src/common/xmpp/tls_nb.py740
-rw-r--r--src/common/xmpp/transports_nb.py1410
-rw-r--r--src/common/zeroconf/__init__.py2
-rw-r--r--src/common/zeroconf/client_zeroconf.py1506
-rw-r--r--src/common/zeroconf/connection_handlers_zeroconf.py268
-rw-r--r--src/common/zeroconf/connection_zeroconf.py632
-rw-r--r--src/common/zeroconf/roster_zeroconf.py282
-rw-r--r--src/common/zeroconf/zeroconf.py36
-rw-r--r--src/common/zeroconf/zeroconf_avahi.py842
-rw-r--r--src/common/zeroconf/zeroconf_bonjour.py606
-rw-r--r--src/config.py7911
-rw-r--r--src/conversation_textview.py2570
-rw-r--r--src/dataforms_widget.py1150
-rw-r--r--src/dialogs.py9436
-rw-r--r--src/disco.py4292
-rw-r--r--src/features_window.py394
-rw-r--r--src/filetransfers_window.py1830
-rw-r--r--src/gajim-remote.py1084
-rw-r--r--src/gajim.py568
-rw-r--r--src/gajim_themes_window.py748
-rw-r--r--src/groupchat_control.py4676
-rw-r--r--src/groups.py78
-rw-r--r--src/gtkexcepthook.py112
-rw-r--r--src/gtkgui_helpers.py1894
-rw-r--r--src/gtkspell.py1
-rw-r--r--src/gui_interface.py6646
-rw-r--r--src/gui_menu_builder.py880
-rw-r--r--src/history_manager.py1168
-rw-r--r--src/history_window.py1182
-rw-r--r--src/htmltextview.py1816
-rw-r--r--src/ipython_view.py890
-rw-r--r--src/message_control.py410
-rw-r--r--src/message_textview.py589
-rw-r--r--src/message_window.py2320
-rw-r--r--src/music_track_listener.py536
-rw-r--r--src/negotiation.py86
-rw-r--r--src/network_manager_listener.py118
-rw-r--r--src/notify.py1216
-rw-r--r--src/profile_window.py658
-rw-r--r--src/remote_control.py1444
-rw-r--r--src/roster_window.py11720
-rw-r--r--src/search_window.py402
-rw-r--r--src/secrets.py122
-rw-r--r--src/session.py966
-rw-r--r--src/statusicon.py782
-rw-r--r--src/tooltips.py1438
-rw-r--r--src/vcard.py1022
-rw-r--r--test/integration/__init__.py2
-rw-r--r--test/integration/test_gui_event_integration.py234
-rw-r--r--test/integration/test_resolver.py158
-rw-r--r--test/integration/test_roster.py336
-rw-r--r--test/integration/test_xmpp_client_nb.py282
-rw-r--r--test/integration/test_xmpp_transports_nb.py506
-rw-r--r--test/lib/__init__.py40
-rwxr-xr-xtest/lib/data.py124
-rw-r--r--test/lib/gajim_mocks.py200
-rw-r--r--test/lib/mock.py2
-rw-r--r--test/lib/notify.py10
-rw-r--r--test/lib/xmpp_mocks.py156
-rwxr-xr-xtest/runtests.py72
-rw-r--r--test/unit/__init__.py2
-rw-r--r--test/unit/test_account.py14
-rw-r--r--test/unit/test_caps_cache.py178
-rw-r--r--test/unit/test_contacts.py218
-rw-r--r--test/unit/test_gui_interface.py186
-rw-r--r--test/unit/test_protocol_caps.py94
-rw-r--r--test/unit/test_sessions.py272
-rw-r--r--test/unit/test_xmpp_dispatcher_nb.py162
-rw-r--r--test/unit/test_xmpp_transports_nb.py124
145 files changed, 68586 insertions, 68851 deletions
diff --git a/data/emoticons/animated/emoticons.py b/data/emoticons/animated/emoticons.py
index 59591e6cc..222358ef9 100644
--- a/data/emoticons/animated/emoticons.py
+++ b/data/emoticons/animated/emoticons.py
@@ -1,52 +1,50 @@
# coding=utf-8
emoticons = {
- 'smile.png': [':-)', ':)'],
- 'coolglasses.png': ['B-)', '(H)'],
- 'wink.gif': [';-)', ';)'],
- 'biggrin.png': [':-D', ':D'],
- 'unhappy.png': [':-(', ':('],
- 'cry.gif': [":'-(", ":'(", ';-(', ';(', ";'-("],
- 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'],
- 'blush.png': [':-$', ':$'],
- 'angry.png': [':-@', ':@'],
- 'bat.gif': [':-[', ':['],
- 'kiss.png': [':-{}', ':-*', ':*', '(K)'],
- 'stare.png': [':-|', ':|'],
- 'devil.png': [']:->', '>:-)', '>:)', '(6)'],
- 'tongue.png': [':-P', ':P', ':-þ', ':þ'],
- 'oh.png': ['=-O', ':-O', ':O'],
- 'heart.png': ['<3', '(L)', '*IN LOVE*'],
- 'pussy.png': ['(@)'],
- 'cuffs.png': ['(%)'],
- 'moon.png': ['(S)'],
- 'lamp.png': ['(I)'],
- 'music.png': ['(8)'],
- 'beer.png': ['(B)', '*DRINK*'],
- 'brflower.png': ['(W)'],
- 'boy.png': ['(Z)'],
- 'girl.png': ['(X)'],
- 'mail.png': ['(E)'],
- 'thumbdown.png': ['(N)'],
- 'photo.png': ['(P)'],
- 'thumbup.png': ['(Y)', '*THUMBS UP*'],
- 'hugleft.png': ['(})'],
- 'brheart.png': ['</3', '(U)'],
- 'flower.png': ['@}->--', '(F)'],
- 'drink.png': ['(D)'],
- 'phone.png': ['(T)'],
- 'coffee.png': ['(C)'],
- 'hugright.png': ['({)'],
- 'star.png': ['(*)'],
- 'rainbow.png': ['(R)'],
- 'cigarette.gif': ['(ci)'],
- 'cake.gif': ['(^)'],
- 'dontknow.gif': [':^)'],
- 'eyeroll.gif': ['8-)'],
- 'lightning.gif': ['(li)'],
- 'party.gif': ['<:o)'],
- 'sleepy.gif': ['|-)'],
- 'think.gif': ['*-)'],
- 'puke.gif': [':-!'],
+ 'smile.png': [':-)', ':)'],
+ 'coolglasses.png': ['B-)', '(H)'],
+ 'wink.gif': [';-)', ';)'],
+ 'biggrin.png': [':-D', ':D'],
+ 'unhappy.png': [':-(', ':('],
+ 'cry.gif': [":'-(", ":'(", ';-(', ';(', ";'-("],
+ 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'],
+ 'blush.png': [':-$', ':$'],
+ 'angry.png': [':-@', ':@'],
+ 'bat.gif': [':-[', ':['],
+ 'kiss.png': [':-{}', ':-*', ':*', '(K)'],
+ 'stare.png': [':-|', ':|'],
+ 'devil.png': [']:->', '>:-)', '>:)', '(6)'],
+ 'tongue.png': [':-P', ':P', ':-þ', ':þ'],
+ 'oh.png': ['=-O', ':-O', ':O'],
+ 'heart.png': ['<3', '(L)', '*IN LOVE*'],
+ 'pussy.png': ['(@)'],
+ 'cuffs.png': ['(%)'],
+ 'moon.png': ['(S)'],
+ 'lamp.png': ['(I)'],
+ 'music.png': ['(8)'],
+ 'beer.png': ['(B)', '*DRINK*'],
+ 'brflower.png': ['(W)'],
+ 'boy.png': ['(Z)'],
+ 'girl.png': ['(X)'],
+ 'mail.png': ['(E)'],
+ 'thumbdown.png': ['(N)'],
+ 'photo.png': ['(P)'],
+ 'thumbup.png': ['(Y)', '*THUMBS UP*'],
+ 'hugleft.png': ['(})'],
+ 'brheart.png': ['</3', '(U)'],
+ 'flower.png': ['@}->--', '(F)'],
+ 'drink.png': ['(D)'],
+ 'phone.png': ['(T)'],
+ 'coffee.png': ['(C)'],
+ 'hugright.png': ['({)'],
+ 'star.png': ['(*)'],
+ 'rainbow.png': ['(R)'],
+ 'cigarette.gif': ['(ci)'],
+ 'cake.gif': ['(^)'],
+ 'dontknow.gif': [':^)'],
+ 'eyeroll.gif': ['8-)'],
+ 'lightning.gif': ['(li)'],
+ 'party.gif': ['<:o)'],
+ 'sleepy.gif': ['|-)'],
+ 'think.gif': ['*-)'],
+ 'puke.gif': [':-!'],
}
-
-# vim: se ts=3:
diff --git a/data/emoticons/static-big/emoticons.py b/data/emoticons/static-big/emoticons.py
index 3c2d08b72..8ed1da834 100644
--- a/data/emoticons/static-big/emoticons.py
+++ b/data/emoticons/static-big/emoticons.py
@@ -1,45 +1,43 @@
# coding=utf-8
emoticons = {
- 'smile.png': [':-)', ':)'],
- 'coolglasses.png': ['8-)', 'B-)', '(H)'],
- 'wink.png': [';-)', ';)'],
- 'biggrin.png': [':-D', ':D'],
- 'unhappy.png': [':-(', ':('],
- 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("],
- 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'],
- 'blush.png': [':-$', ':$'],
- 'angry.png': [':-@', ':@'],
- 'bat.png': [':-[', ':['],
- 'kiss.png': [':-{}', ':-*', ':*', '(K)'],
- 'stare.png': [':-|', ':|'],
- 'devil.png': [']:->', '>:-)', '>:)', '(6)'],
- 'tongue.png': [':-P', ':P', ':-þ', ':þ'],
- 'oh.png': ['=-O', ':-O', ':O'],
- 'heart.png': ['<3', '(L)', '*IN LOVE*'],
- 'lion.png': [':3', '>:3'],
- 'pussy.png': ['(@)', '=^.^='],
- 'cuffs.png': ['(%)'],
- 'moon.png': ['(S)'],
- 'lamp.png': ['(I)'],
- 'music.png': ['(8)'],
- 'beer.png': ['(B)', '*DRINK*'],
- 'brflower.png': ['(W)'],
- 'boy.png': ['(Z)'],
- 'girl.png': ['(X)'],
- 'mail.png': ['(E)'],
- 'thumbdown.png': ['(N)'],
- 'photo.png': ['(P)'],
- 'thumbup.png': ['(Y)', '*THUMBS UP*'],
- 'hugleft.png': ['(})'],
- 'brheart.png': ['</3', '(U)'],
- 'flower.png': ['@}->--', '(F)'],
- 'drink.png': ['(D)'],
- 'phone.png': ['(T)'],
- 'coffee.png': ['(C)'],
- 'hugright.png': ['({)'],
- 'star.png': ['(*)'],
- 'rainbow.png': ['(R)'],
- 'puke.png': [':-!'],
+ 'smile.png': [':-)', ':)'],
+ 'coolglasses.png': ['8-)', 'B-)', '(H)'],
+ 'wink.png': [';-)', ';)'],
+ 'biggrin.png': [':-D', ':D'],
+ 'unhappy.png': [':-(', ':('],
+ 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("],
+ 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'],
+ 'blush.png': [':-$', ':$'],
+ 'angry.png': [':-@', ':@'],
+ 'bat.png': [':-[', ':['],
+ 'kiss.png': [':-{}', ':-*', ':*', '(K)'],
+ 'stare.png': [':-|', ':|'],
+ 'devil.png': [']:->', '>:-)', '>:)', '(6)'],
+ 'tongue.png': [':-P', ':P', ':-þ', ':þ'],
+ 'oh.png': ['=-O', ':-O', ':O'],
+ 'heart.png': ['<3', '(L)', '*IN LOVE*'],
+ 'lion.png': [':3', '>:3'],
+ 'pussy.png': ['(@)', '=^.^='],
+ 'cuffs.png': ['(%)'],
+ 'moon.png': ['(S)'],
+ 'lamp.png': ['(I)'],
+ 'music.png': ['(8)'],
+ 'beer.png': ['(B)', '*DRINK*'],
+ 'brflower.png': ['(W)'],
+ 'boy.png': ['(Z)'],
+ 'girl.png': ['(X)'],
+ 'mail.png': ['(E)'],
+ 'thumbdown.png': ['(N)'],
+ 'photo.png': ['(P)'],
+ 'thumbup.png': ['(Y)', '*THUMBS UP*'],
+ 'hugleft.png': ['(})'],
+ 'brheart.png': ['</3', '(U)'],
+ 'flower.png': ['@}->--', '(F)'],
+ 'drink.png': ['(D)'],
+ 'phone.png': ['(T)'],
+ 'coffee.png': ['(C)'],
+ 'hugright.png': ['({)'],
+ 'star.png': ['(*)'],
+ 'rainbow.png': ['(R)'],
+ 'puke.png': [':-!'],
}
-
-# vim: se ts=3:
diff --git a/data/emoticons/static/emoticons.py b/data/emoticons/static/emoticons.py
index 3c2d08b72..8ed1da834 100644
--- a/data/emoticons/static/emoticons.py
+++ b/data/emoticons/static/emoticons.py
@@ -1,45 +1,43 @@
# coding=utf-8
emoticons = {
- 'smile.png': [':-)', ':)'],
- 'coolglasses.png': ['8-)', 'B-)', '(H)'],
- 'wink.png': [';-)', ';)'],
- 'biggrin.png': [':-D', ':D'],
- 'unhappy.png': [':-(', ':('],
- 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("],
- 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'],
- 'blush.png': [':-$', ':$'],
- 'angry.png': [':-@', ':@'],
- 'bat.png': [':-[', ':['],
- 'kiss.png': [':-{}', ':-*', ':*', '(K)'],
- 'stare.png': [':-|', ':|'],
- 'devil.png': [']:->', '>:-)', '>:)', '(6)'],
- 'tongue.png': [':-P', ':P', ':-þ', ':þ'],
- 'oh.png': ['=-O', ':-O', ':O'],
- 'heart.png': ['<3', '(L)', '*IN LOVE*'],
- 'lion.png': [':3', '>:3'],
- 'pussy.png': ['(@)', '=^.^='],
- 'cuffs.png': ['(%)'],
- 'moon.png': ['(S)'],
- 'lamp.png': ['(I)'],
- 'music.png': ['(8)'],
- 'beer.png': ['(B)', '*DRINK*'],
- 'brflower.png': ['(W)'],
- 'boy.png': ['(Z)'],
- 'girl.png': ['(X)'],
- 'mail.png': ['(E)'],
- 'thumbdown.png': ['(N)'],
- 'photo.png': ['(P)'],
- 'thumbup.png': ['(Y)', '*THUMBS UP*'],
- 'hugleft.png': ['(})'],
- 'brheart.png': ['</3', '(U)'],
- 'flower.png': ['@}->--', '(F)'],
- 'drink.png': ['(D)'],
- 'phone.png': ['(T)'],
- 'coffee.png': ['(C)'],
- 'hugright.png': ['({)'],
- 'star.png': ['(*)'],
- 'rainbow.png': ['(R)'],
- 'puke.png': [':-!'],
+ 'smile.png': [':-)', ':)'],
+ 'coolglasses.png': ['8-)', 'B-)', '(H)'],
+ 'wink.png': [';-)', ';)'],
+ 'biggrin.png': [':-D', ':D'],
+ 'unhappy.png': [':-(', ':('],
+ 'cry.png': [":'-(", ":'(", ';-(', ';(', ";'-("],
+ 'frowning.png': [':-/', ':/', ':-\\', ':\\', ':-S', ':S'],
+ 'blush.png': [':-$', ':$'],
+ 'angry.png': [':-@', ':@'],
+ 'bat.png': [':-[', ':['],
+ 'kiss.png': [':-{}', ':-*', ':*', '(K)'],
+ 'stare.png': [':-|', ':|'],
+ 'devil.png': [']:->', '>:-)', '>:)', '(6)'],
+ 'tongue.png': [':-P', ':P', ':-þ', ':þ'],
+ 'oh.png': ['=-O', ':-O', ':O'],
+ 'heart.png': ['<3', '(L)', '*IN LOVE*'],
+ 'lion.png': [':3', '>:3'],
+ 'pussy.png': ['(@)', '=^.^='],
+ 'cuffs.png': ['(%)'],
+ 'moon.png': ['(S)'],
+ 'lamp.png': ['(I)'],
+ 'music.png': ['(8)'],
+ 'beer.png': ['(B)', '*DRINK*'],
+ 'brflower.png': ['(W)'],
+ 'boy.png': ['(Z)'],
+ 'girl.png': ['(X)'],
+ 'mail.png': ['(E)'],
+ 'thumbdown.png': ['(N)'],
+ 'photo.png': ['(P)'],
+ 'thumbup.png': ['(Y)', '*THUMBS UP*'],
+ 'hugleft.png': ['(})'],
+ 'brheart.png': ['</3', '(U)'],
+ 'flower.png': ['@}->--', '(F)'],
+ 'drink.png': ['(D)'],
+ 'phone.png': ['(T)'],
+ 'coffee.png': ['(C)'],
+ 'hugright.png': ['({)'],
+ 'star.png': ['(*)'],
+ 'rainbow.png': ['(R)'],
+ 'puke.png': [':-!'],
}
-
-# vim: se ts=3:
diff --git a/data/emoticons/tango/emoticons.py b/data/emoticons/tango/emoticons.py
index 43c7ce37f..db3376b93 100644
--- a/data/emoticons/tango/emoticons.py
+++ b/data/emoticons/tango/emoticons.py
@@ -49,4 +49,4 @@ emoticons = {
'pissed-off.png': ['*WALL*'],
'mail.png': ['*WRITE*', '(E)'],
'tremble.png': ['*SCRATCH*'],
-} \ No newline at end of file
+}
diff --git a/scripts/dev/run-build-test.py b/scripts/dev/run-build-test.py
index daf177e87..c8b3b355f 100755
--- a/scripts/dev/run-build-test.py
+++ b/scripts/dev/run-build-test.py
@@ -4,19 +4,16 @@ import os
import sys
if os.getcwd().endswith('dev'):
- os.chdir('../../') # we were in scripts/dev
+ os.chdir('../../') # we were in scripts/dev
ret = 0
ret += os.system("make clean > " + os.devnull)
-ret += os.system("make > " + os.devnull)
+ret += os.system("make > " + os.devnull)
ret += os.system("make check > " + os.devnull)
if ret == 0:
- print "Build successfull"
- sys.exit(0)
+ print "Build successfull"
+ sys.exit(0)
else:
- print >>sys.stderr, "Build failed"
- sys.exit(1)
-
-# vim: se ts=3:
-
+ print >>sys.stderr, "Build failed"
+ sys.exit(1)
diff --git a/scripts/dev/run-pylint.py b/scripts/dev/run-pylint.py
index c840d8b48..cb611b6bf 100755
--- a/scripts/dev/run-pylint.py
+++ b/scripts/dev/run-pylint.py
@@ -5,9 +5,7 @@ import os
import sys
if os.getcwd().endswith('dev'):
- os.chdir('../../src/') # we were in scripts/dev
+ os.chdir('../../src/') # we were in scripts/dev
os.system("pylint --indent-string='\t' --additional-builtins='_' --disable-msg=C0111,C0103,C0111,C0112 --disable-checker=design " + "".join(sys.argv[1:]))
-
-# vim: se ts=3:
diff --git a/setup_win32.py b/setup_win32.py
index 13096217c..370c567b6 100644
--- a/setup_win32.py
+++ b/setup_win32.py
@@ -54,39 +54,35 @@ opts = {
# ConfigParser,UserString,roman are needed for docutils
'includes': 'pango,atk,gobject,cairo,pangocairo,gtk.keysyms,encodings,encodings.*,ConfigParser,UserString',
'dll_excludes': [
- 'iconv.dll','intl.dll','libatk-1.0-0.dll',
- 'libgdk_pixbuf-2.0-0.dll','libgdk-win32-2.0-0.dll',
- 'libglib-2.0-0.dll','libgmodule-2.0-0.dll',
- 'libgobject-2.0-0.dll','libgthread-2.0-0.dll',
- 'libgtk-win32-2.0-0.dll','libpango-1.0-0.dll',
- 'libpangowin32-1.0-0.dll','libcairo-2.dll',
- 'libpangocairo-1.0-0.dll','libpangoft2-1.0-0.dll',
+ 'iconv.dll', 'intl.dll', 'libatk-1.0-0.dll',
+ 'libgdk_pixbuf-2.0-0.dll', 'libgdk-win32-2.0-0.dll',
+ 'libglib-2.0-0.dll', 'libgmodule-2.0-0.dll',
+ 'libgobject-2.0-0.dll', 'libgthread-2.0-0.dll',
+ 'libgtk-win32-2.0-0.dll', 'libpango-1.0-0.dll',
+ 'libpangowin32-1.0-0.dll', 'libcairo-2.dll',
+ 'libpangocairo-1.0-0.dll', 'libpangoft2-1.0-0.dll',
],
'excludes': [
'docutils'
],
- 'optimize': 2,
+ 'optimize': 2,
}
}
setup(
- name = 'Gajim',
- version = '0.12.1',
- description = 'A full featured Jabber client',
- author = 'Gajim Development Team',
- url = 'http://www.gajim.org/',
- download_url = 'http://www.gajim.org/downloads.php',
- license = 'GPL',
+ name='Gajim',
+ version='0.12.1',
+ description='A full featured Jabber client',
+ author='Gajim Development Team',
+ url='http://www.gajim.org/',
+ download_url='http://www.gajim.org/downloads.php',
+ license='GPL',
- windows = [{'script': 'src/gajim.py',
- 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]},
- {'script': 'src/history_manager.py',
- 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}],
+ windows=[{'script': 'src/gajim.py',
+ 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]},
+ {'script': 'src/history_manager.py',
+ 'icon_resources': [(1, 'data/pixmaps/gajim.ico')]}],
options=opts,
-
data_files=docutils_files,
-
)
-
-# vim: se ts=3:
diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py
index a657b8203..29cc65f2d 100644
--- a/src/adhoc_commands.py
+++ b/src/adhoc_commands.py
@@ -35,537 +35,535 @@ import dialogs
import dataforms_widget
class CommandWindow:
- """
- Class for a window for single ad-hoc commands session
-
- Note, that there might be more than one for one account/jid pair in one
- moment.
-
- TODO: Maybe put this window into MessageWindow? consider this when it will
- be possible to manage more than one window of one.
- TODO: Account/jid pair in MessageWindowMgr.
- TODO: GTK 2.10 has a special wizard-widget, consider using it...
- """
-
- def __init__(self, account, jid, commandnode=None):
- """
- Create new window
- """
-
- # an account object
- self.account = gajim.connections[account]
- self.jid = jid
-
- self.pulse_id=None # to satisfy self.setup_pulsing()
- self.commandlist=None # a list of (commandname, commanddescription)
-
- # command's data
- self.commandnode = commandnode
- self.sessionid = None
- self.dataform = None
- self.allow_stage3_close = False
-
- # retrieving widgets from xml
- self.xml = gtkgui_helpers.get_gtk_builder('adhoc_commands_window.ui')
- self.window = self.xml.get_object('adhoc_commands_window')
- self.window.connect('delete-event', self.on_adhoc_commands_window_delete_event)
- for name in ('back_button', 'forward_button',
- 'execute_button','close_button','stages_notebook',
- 'retrieving_commands_stage_vbox',
- 'command_list_stage_vbox','command_list_vbox',
- 'sending_form_stage_vbox','sending_form_progressbar',
- 'notes_label','no_commands_stage_vbox','error_stage_vbox',
- 'error_description_label'):
- self.__dict__[name] = self.xml.get_object(name)
-
- # creating data forms widget
- self.data_form_widget = dataforms_widget.DataFormWidget()
- self.data_form_widget.show()
- self.sending_form_stage_vbox.pack_start(self.data_form_widget)
-
- if self.commandnode:
- # Execute command
- self.stage3()
- else:
- # setting initial stage
- self.stage1()
-
- # displaying the window
- self.xml.connect_signals(self)
- self.window.show_all()
-
- # These functions are set up by appropriate stageX methods.
- def stage_finish(self, *anything):
- pass
-
- 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)
-
- def on_forward_button_clicked(self, *anything):
- return self.stage_forward_button_clicked(*anything)
-
- def on_execute_button_clicked(self, *anything):
- return self.stage_execute_button_clicked(*anything)
-
- def on_close_button_clicked(self, *anything):
- return self.stage_close_button_clicked(*anything)
-
- def on_adhoc_commands_window_destroy(self, *anything):
- # TODO: do all actions that are needed to remove this object from memory...
- self.remove_pulsing()
-
- def on_adhoc_commands_window_delete_event(self, *anything):
- return self.stage_adhoc_commands_window_delete_event(self.window)
-
- def __del__(self):
- print 'Object has been deleted.'
+ """
+ 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.
+
+ 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
+ """
+
+ # an account object
+ self.account = gajim.connections[account]
+ self.jid = jid
+
+ self.pulse_id=None # to satisfy self.setup_pulsing()
+ self.commandlist=None # a list of (commandname, commanddescription)
+
+ # command's data
+ self.commandnode = commandnode
+ self.sessionid = None
+ self.dataform = None
+ self.allow_stage3_close = False
+
+ # retrieving widgets from xml
+ self.xml = gtkgui_helpers.get_gtk_builder('adhoc_commands_window.ui')
+ self.window = self.xml.get_object('adhoc_commands_window')
+ self.window.connect('delete-event', self.on_adhoc_commands_window_delete_event)
+ for name in ('back_button', 'forward_button',
+ 'execute_button','close_button','stages_notebook',
+ 'retrieving_commands_stage_vbox',
+ 'command_list_stage_vbox','command_list_vbox',
+ 'sending_form_stage_vbox','sending_form_progressbar',
+ 'notes_label','no_commands_stage_vbox','error_stage_vbox',
+ 'error_description_label'):
+ self.__dict__[name] = self.xml.get_object(name)
+
+ # creating data forms widget
+ self.data_form_widget = dataforms_widget.DataFormWidget()
+ self.data_form_widget.show()
+ self.sending_form_stage_vbox.pack_start(self.data_form_widget)
+
+ if self.commandnode:
+ # Execute command
+ self.stage3()
+ else:
+ # setting initial stage
+ self.stage1()
+
+ # displaying the window
+ self.xml.connect_signals(self)
+ self.window.show_all()
+
+ # These functions are set up by appropriate stageX methods.
+ def stage_finish(self, *anything):
+ pass
+
+ 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)
+
+ def on_forward_button_clicked(self, *anything):
+ return self.stage_forward_button_clicked(*anything)
+
+ def on_execute_button_clicked(self, *anything):
+ return self.stage_execute_button_clicked(*anything)
+
+ def on_close_button_clicked(self, *anything):
+ return self.stage_close_button_clicked(*anything)
+
+ def on_adhoc_commands_window_destroy(self, *anything):
+ # TODO: do all actions that are needed to remove this object from memory...
+ self.remove_pulsing()
+
+ def on_adhoc_commands_window_delete_event(self, *anything):
+ return self.stage_adhoc_commands_window_delete_event(self.window)
+
+ def __del__(self):
+ print 'Object has been deleted.'
# stage 1: waiting for command list
- def stage1(self):
- """
- Prepare the first stage. Request command list, set appropriate state of
- widgets
- """
- # close old stage...
- self.stage_finish()
-
- # show the stage
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(
- self.retrieving_commands_stage_vbox))
-
- # set widgets' state
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
-
- # request command list
- self.request_command_list()
- self.setup_pulsing(
- self.xml.get_object('retrieving_commands_progressbar'))
-
- # setup the callbacks
- self.stage_finish = self.stage1_finish
- self.stage_close_button_clicked = self.stage1_close_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event
-
- def stage1_finish(self):
- self.remove_pulsing()
-
- def stage1_close_button_clicked(self, widget):
- # cancelling in this stage is not critical, so we don't
- # show any popups to user
- self.stage1_finish()
- self.window.destroy()
-
- def stage1_adhoc_commands_window_delete_event(self, widget):
- self.stage1_finish()
- return True
+ def stage1(self):
+ """
+ Prepare the first stage. Request command list, set appropriate state of
+ widgets
+ """
+ # close old stage...
+ self.stage_finish()
+
+ # show the stage
+ self.stages_notebook.set_current_page(
+ self.stages_notebook.page_num(
+ self.retrieving_commands_stage_vbox))
+
+ # set widgets' state
+ self.close_button.set_sensitive(True)
+ self.back_button.set_sensitive(False)
+ self.forward_button.set_sensitive(False)
+ self.execute_button.set_sensitive(False)
+
+ # request command list
+ self.request_command_list()
+ self.setup_pulsing(
+ self.xml.get_object('retrieving_commands_progressbar'))
+
+ # setup the callbacks
+ self.stage_finish = self.stage1_finish
+ self.stage_close_button_clicked = self.stage1_close_button_clicked
+ self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event
+
+ def stage1_finish(self):
+ self.remove_pulsing()
+
+ def stage1_close_button_clicked(self, widget):
+ # cancelling in this stage is not critical, so we don't
+ # show any popups to user
+ self.stage1_finish()
+ self.window.destroy()
+
+ def stage1_adhoc_commands_window_delete_event(self, widget):
+ self.stage1_finish()
+ return True
# 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
- """
- # close old stage
- self.stage_finish()
-
- assert len(self.commandlist)>0
-
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(
- self.command_list_stage_vbox))
-
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(True)
- self.execute_button.set_sensitive(False)
-
- # build the commands list radiobuttons
- first_radio = None
- for (commandnode, commandname) in self.commandlist:
- radio = gtk.RadioButton(first_radio, label=commandname)
- radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode)
- if not first_radio:
- first_radio = radio
- self.commandnode = commandnode
- self.command_list_vbox.pack_start(radio, expand=False)
- self.command_list_vbox.show_all()
-
- self.stage_finish = self.stage2_finish
- self.stage_close_button_clicked = self.stage2_close_button_clicked
- self.stage_forward_button_clicked = self.stage2_forward_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.do_nothing
-
- def stage2_finish(self):
- """
- Remove widgets we created. Not needed when the window is destroyed
- """
- def remove_widget(widget):
- self.command_list_vbox.remove(widget)
- self.command_list_vbox.foreach(remove_widget)
-
- def stage2_close_button_clicked(self, widget):
- self.stage_finish()
- self.window.destroy()
-
- def stage2_forward_button_clicked(self, widget):
- self.stage3()
-
- def on_command_radiobutton_toggled(self, widget, commandnode):
- self.commandnode = commandnode
-
- def on_check_commands_1_button_clicked(self, widget):
- self.stage1()
+ def stage2(self):
+ """
+ Populate the command list vbox with radiobuttons
+
+ FIXME: If there is more commands, maybe some kind of list, set widgets
+ state
+ """
+ # close old stage
+ self.stage_finish()
+
+ assert len(self.commandlist)>0
+
+ self.stages_notebook.set_current_page(
+ self.stages_notebook.page_num(
+ self.command_list_stage_vbox))
+
+ self.close_button.set_sensitive(True)
+ self.back_button.set_sensitive(False)
+ self.forward_button.set_sensitive(True)
+ self.execute_button.set_sensitive(False)
+
+ # build the commands list radiobuttons
+ first_radio = None
+ for (commandnode, commandname) in self.commandlist:
+ radio = gtk.RadioButton(first_radio, label=commandname)
+ radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode)
+ if not first_radio:
+ first_radio = radio
+ self.commandnode = commandnode
+ self.command_list_vbox.pack_start(radio, expand=False)
+ self.command_list_vbox.show_all()
+
+ self.stage_finish = self.stage2_finish
+ self.stage_close_button_clicked = self.stage2_close_button_clicked
+ self.stage_forward_button_clicked = self.stage2_forward_button_clicked
+ self.stage_adhoc_commands_window_delete_event = self.do_nothing
+
+ def stage2_finish(self):
+ """
+ Remove widgets we created. Not needed when the window is destroyed
+ """
+ def remove_widget(widget):
+ self.command_list_vbox.remove(widget)
+ self.command_list_vbox.foreach(remove_widget)
+
+ def stage2_close_button_clicked(self, widget):
+ self.stage_finish()
+ self.window.destroy()
+
+ def stage2_forward_button_clicked(self, widget):
+ self.stage3()
+
+ def on_command_radiobutton_toggled(self, widget, commandnode):
+ self.commandnode = commandnode
+
+ def on_check_commands_1_button_clicked(self, widget):
+ self.stage1()
# stage 3: command invocation
- def stage3(self):
- # close old stage
- self.stage_finish()
-
- assert isinstance(self.commandnode, unicode)
-
- self.form_status = None
-
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(
- self.sending_form_stage_vbox))
-
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
-
- self.stage3_submit_form()
-
- self.stage_finish = self.stage3_finish
- self.stage_back_button_clicked = self.stage3_back_button_clicked
- self.stage_forward_button_clicked = self.stage3_forward_button_clicked
- self.stage_execute_button_clicked = self.stage3_execute_button_clicked
- self.stage_close_button_clicked = self.stage3_close_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked
-
- def stage3_finish(self):
- pass
-
- def stage3_close_button_clicked(self, widget):
- """
- We are in the middle of executing command. Ask user if he really want to
- cancel the process, then cancel it
- """
- # this works also as a handler for window_delete_event, so we have to return appropriate
- # values
- if self.form_status == 'completed':
- if widget!=self.window:
- self.window.destroy()
- return False
-
- if self.allow_stage3_close:
- return False
-
- def on_yes(button):
- self.send_cancel()
- self.allow_stage3_close = True
- self.window.destroy()
-
- dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | \
- gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'),
- _('You are in process of executing command. Do you really want to '
- 'cancel it?'), on_response_yes=on_yes)
- dialog.popup()
- return True # Block event, don't close window
-
- def stage3_back_button_clicked(self, widget):
- self.stage3_submit_form('prev')
-
- def stage3_forward_button_clicked(self, widget):
- self.stage3_submit_form('next')
-
- def stage3_execute_button_clicked(self, widget):
- self.stage3_submit_form('execute')
-
- def stage3_submit_form(self, action='execute'):
- self.data_form_widget.set_sensitive(False)
- if self.data_form_widget.get_data_form():
- self.data_form_widget.data_form.type='submit'
- else:
- self.data_form_widget.hide()
-
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
-
- self.sending_form_progressbar.show()
- self.setup_pulsing(self.sending_form_progressbar)
- self.send_command(action)
-
- def stage3_next_form(self, command):
- if not isinstance(command, xmpp.Node):
- self.stage5(error=_('Service sent malformed data'), senderror=True)
- return
-
- self.remove_pulsing()
- self.sending_form_progressbar.hide()
-
- if not self.sessionid:
- self.sessionid = command.getAttr('sessionid')
- elif self.sessionid != command.getAttr('sessionid'):
- self.stage5(error=_('Service changed the session identifier.'),
- senderror=True)
- return
-
- self.form_status = command.getAttr('status')
-
- self.commandnode = command.getAttr('node')
- if command.getTag('x'):
- self.dataform = dataforms.ExtendForm(node=command.getTag('x'))
-
- self.data_form_widget.set_sensitive(True)
- try:
- self.data_form_widget.data_form=self.dataform
- except dataforms.Error:
- self.stage5(error=_('Service sent malformed data'), senderror=True)
- return
- self.data_form_widget.show()
- if self.data_form_widget.title:
- self.window.set_title('%s - Ad-hoc Commands - Gajim' % \
- self.data_form_widget.title)
- else:
- self.data_form_widget.hide()
-
- actions = command.getTag('actions')
- if actions:
- # actions, actions, actions...
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(actions.getTag('prev') is not None)
- self.forward_button.set_sensitive(actions.getTag('next') is not None)
- self.execute_button.set_sensitive(True)
- else:
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(True)
-
- if self.form_status == 'completed':
- self.close_button.set_sensitive(True)
- self.back_button.hide()
- self.forward_button.hide()
- self.execute_button.hide()
- self.close_button.show()
- self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked
-
- note = command.getTag('note')
- if note:
- self.notes_label.set_text(note.getData().decode('utf-8'))
- self.notes_label.set_no_show_all(False)
- self.notes_label.show()
- else:
- self.notes_label.set_no_show_all(True)
- self.notes_label.hide()
+ def stage3(self):
+ # close old stage
+ self.stage_finish()
+
+ assert isinstance(self.commandnode, unicode)
+
+ self.form_status = None
+
+ self.stages_notebook.set_current_page(
+ self.stages_notebook.page_num(
+ self.sending_form_stage_vbox))
+
+ self.close_button.set_sensitive(True)
+ self.back_button.set_sensitive(False)
+ self.forward_button.set_sensitive(False)
+ self.execute_button.set_sensitive(False)
+
+ self.stage3_submit_form()
+
+ self.stage_finish = self.stage3_finish
+ self.stage_back_button_clicked = self.stage3_back_button_clicked
+ self.stage_forward_button_clicked = self.stage3_forward_button_clicked
+ self.stage_execute_button_clicked = self.stage3_execute_button_clicked
+ self.stage_close_button_clicked = self.stage3_close_button_clicked
+ self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked
+
+ def stage3_finish(self):
+ pass
+
+ def stage3_close_button_clicked(self, widget):
+ """
+ We are in the middle of executing command. Ask user if he really want to
+ cancel the process, then cancel it
+ """
+ # this works also as a handler for window_delete_event, so we have to return appropriate
+ # values
+ if self.form_status == 'completed':
+ if widget!=self.window:
+ self.window.destroy()
+ return False
+
+ if self.allow_stage3_close:
+ return False
+
+ def on_yes(button):
+ self.send_cancel()
+ self.allow_stage3_close = True
+ self.window.destroy()
+
+ dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | \
+ gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'),
+ _('You are in process of executing command. Do you really want to '
+ 'cancel it?'), on_response_yes=on_yes)
+ dialog.popup()
+ return True # Block event, don't close window
+
+ def stage3_back_button_clicked(self, widget):
+ self.stage3_submit_form('prev')
+
+ def stage3_forward_button_clicked(self, widget):
+ self.stage3_submit_form('next')
+
+ def stage3_execute_button_clicked(self, widget):
+ self.stage3_submit_form('execute')
+
+ def stage3_submit_form(self, action='execute'):
+ self.data_form_widget.set_sensitive(False)
+ if self.data_form_widget.get_data_form():
+ self.data_form_widget.data_form.type='submit'
+ else:
+ self.data_form_widget.hide()
+
+ self.close_button.set_sensitive(True)
+ self.back_button.set_sensitive(False)
+ self.forward_button.set_sensitive(False)
+ self.execute_button.set_sensitive(False)
+
+ self.sending_form_progressbar.show()
+ self.setup_pulsing(self.sending_form_progressbar)
+ self.send_command(action)
+
+ def stage3_next_form(self, command):
+ if not isinstance(command, xmpp.Node):
+ self.stage5(error=_('Service sent malformed data'), senderror=True)
+ return
+
+ self.remove_pulsing()
+ self.sending_form_progressbar.hide()
+
+ if not self.sessionid:
+ self.sessionid = command.getAttr('sessionid')
+ elif self.sessionid != command.getAttr('sessionid'):
+ self.stage5(error=_('Service changed the session identifier.'),
+ senderror=True)
+ return
+
+ self.form_status = command.getAttr('status')
+
+ self.commandnode = command.getAttr('node')
+ if command.getTag('x'):
+ self.dataform = dataforms.ExtendForm(node=command.getTag('x'))
+
+ self.data_form_widget.set_sensitive(True)
+ try:
+ self.data_form_widget.data_form=self.dataform
+ except dataforms.Error:
+ self.stage5(error=_('Service sent malformed data'), senderror=True)
+ return
+ self.data_form_widget.show()
+ if self.data_form_widget.title:
+ self.window.set_title('%s - Ad-hoc Commands - Gajim' % \
+ self.data_form_widget.title)
+ else:
+ self.data_form_widget.hide()
+
+ actions = command.getTag('actions')
+ if actions:
+ # actions, actions, actions...
+ self.close_button.set_sensitive(True)
+ self.back_button.set_sensitive(actions.getTag('prev') is not None)
+ self.forward_button.set_sensitive(actions.getTag('next') is not None)
+ self.execute_button.set_sensitive(True)
+ else:
+ self.close_button.set_sensitive(True)
+ self.back_button.set_sensitive(False)
+ self.forward_button.set_sensitive(False)
+ self.execute_button.set_sensitive(True)
+
+ if self.form_status == 'completed':
+ self.close_button.set_sensitive(True)
+ self.back_button.hide()
+ self.forward_button.hide()
+ self.execute_button.hide()
+ self.close_button.show()
+ self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked
+
+ note = command.getTag('note')
+ if note:
+ self.notes_label.set_text(note.getData().decode('utf-8'))
+ self.notes_label.set_no_show_all(False)
+ self.notes_label.show()
+ else:
+ self.notes_label.set_no_show_all(True)
+ self.notes_label.hide()
# stage 4: no commands are exposed
- def stage4(self):
- """
- Display the message. Wait for user to close the window
- """
- # close old stage
- self.stage_finish()
+ def stage4(self):
+ """
+ Display the message. Wait for user to close the window
+ """
+ # close old stage
+ self.stage_finish()
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(
- self.no_commands_stage_vbox))
+ self.stages_notebook.set_current_page(
+ self.stages_notebook.page_num(
+ self.no_commands_stage_vbox))
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
+ self.close_button.set_sensitive(True)
+ self.back_button.set_sensitive(False)
+ self.forward_button.set_sensitive(False)
+ self.execute_button.set_sensitive(False)
- self.stage_finish = self.do_nothing
- self.stage_close_button_clicked = self.stage4_close_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.do_nothing
+ self.stage_finish = self.do_nothing
+ self.stage_close_button_clicked = self.stage4_close_button_clicked
+ self.stage_adhoc_commands_window_delete_event = self.do_nothing
- def stage4_close_button_clicked(self, widget):
- self.window.destroy()
+ def stage4_close_button_clicked(self, widget):
+ self.window.destroy()
- def on_check_commands_2_button_clicked(self, widget):
- self.stage1()
+ def on_check_commands_2_button_clicked(self, widget):
+ self.stage1()
# stage 5: an error has occured
- def stage5(self, error=None, errorid=None, senderror=False):
- """
- Display the error message. Wait for user to close the window
- """
- # FIXME: sending error to responder
- # close old stage
- self.stage_finish()
-
- assert errorid or error
-
- if errorid:
- # we've got error code, display appropriate message
- try:
- errorname = xmpp.NS_STANZAS + ' ' + str(errorid)
- errordesc = xmpp.ERRORS[errorname][2]
- error = errordesc.decode('utf-8')
- del errorname, errordesc
- except KeyError: # when stanza doesn't have error description
- error = _('Service returned an error.')
- elif error:
- # we've got error message
- pass
- else:
- # we don't know what's that, bailing out
- assert False
-
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(
- self.error_stage_vbox))
-
- self.close_button.set_sensitive(True)
- self.back_button.hide()
- self.forward_button.hide()
- self.execute_button.hide()
-
- self.error_description_label.set_text(error)
-
- self.stage_finish = self.do_nothing
- self.stage_close_button_clicked = self.stage5_close_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.do_nothing
-
- def stage5_close_button_clicked(self, widget):
- self.window.destroy()
+ def stage5(self, error=None, errorid=None, senderror=False):
+ """
+ Display the error message. Wait for user to close the window
+ """
+ # FIXME: sending error to responder
+ # close old stage
+ self.stage_finish()
+
+ assert errorid or error
+
+ if errorid:
+ # we've got error code, display appropriate message
+ try:
+ errorname = xmpp.NS_STANZAS + ' ' + str(errorid)
+ errordesc = xmpp.ERRORS[errorname][2]
+ error = errordesc.decode('utf-8')
+ del errorname, errordesc
+ except KeyError: # when stanza doesn't have error description
+ error = _('Service returned an error.')
+ elif error:
+ # we've got error message
+ pass
+ else:
+ # we don't know what's that, bailing out
+ assert False
+
+ self.stages_notebook.set_current_page(
+ self.stages_notebook.page_num(
+ self.error_stage_vbox))
+
+ self.close_button.set_sensitive(True)
+ self.back_button.hide()
+ self.forward_button.hide()
+ self.execute_button.hide()
+
+ self.error_description_label.set_text(error)
+
+ self.stage_finish = self.do_nothing
+ self.stage_close_button_clicked = self.stage5_close_button_clicked
+ self.stage_adhoc_commands_window_delete_event = self.do_nothing
+
+ def stage5_close_button_clicked(self, widget):
+ self.window.destroy()
# helpers to handle pulsing in progressbar
- def setup_pulsing(self, progressbar):
- """
- Set the progressbar to pulse. Makes a custom function to repeatedly call
- progressbar.pulse() method
- """
- assert not self.pulse_id
- assert isinstance(progressbar, gtk.ProgressBar)
-
- def callback():
- progressbar.pulse()
- return True # important to keep callback be called back!
-
- # 12 times per second (80 miliseconds)
- self.pulse_id = gobject.timeout_add(80, callback)
-
- def remove_pulsing(self):
- """
- Stop pulsing, useful when especially when removing widget
- """
- if self.pulse_id:
- gobject.source_remove(self.pulse_id)
- self.pulse_id=None
+ def setup_pulsing(self, progressbar):
+ """
+ Set the progressbar to pulse. Makes a custom function to repeatedly call
+ progressbar.pulse() method
+ """
+ assert not self.pulse_id
+ assert isinstance(progressbar, gtk.ProgressBar)
+
+ def callback():
+ progressbar.pulse()
+ return True # important to keep callback be called back!
+
+ # 12 times per second (80 miliseconds)
+ self.pulse_id = gobject.timeout_add(80, callback)
+
+ def remove_pulsing(self):
+ """
+ Stop pulsing, useful when especially when removing widget
+ """
+ if self.pulse_id:
+ gobject.source_remove(self.pulse_id)
+ self.pulse_id=None
# handling xml stanzas
- def request_command_list(self):
- """
- Request the command list. Change stage on delivery
- """
- query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), queryNS=xmpp.NS_DISCO_ITEMS)
- query.setQuerynode(xmpp.NS_COMMANDS)
-
- def callback(response):
- '''Called on response to query.'''
- # FIXME: move to connection_handlers.py
- # is error => error stage
- error = response.getError()
- if error:
- # extracting error description from xmpp/protocol.py
- self.stage5(errorid = error)
- return
-
- # no commands => no commands stage
- # commands => command selection stage
- query = response.getTag('query')
- if query and query.getAttr('node') == xmpp.NS_COMMANDS:
- items = query.getTags('item')
- else:
- items = []
- if len(items)==0:
- self.commandlist = []
- self.stage4()
- else:
- self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items]
- self.stage2()
-
- self.account.connection.SendAndCallForResponse(query, callback)
-
- def send_command(self, action='execute'):
- """
- Send the command with data form. Wait for reply
- """
- # create the stanza
- assert isinstance(self.commandnode, unicode)
- assert action in ('execute', 'prev', 'next', 'complete')
-
- stanza = xmpp.Iq(typ='set', to=self.jid)
- cmdnode = stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={
- 'node':self.commandnode, 'action':action})
-
- if self.sessionid:
- cmdnode.setAttr('sessionid', self.sessionid)
-
- if self.data_form_widget.data_form:
-# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form))
- # FIXME: simplified form to send
-
- cmdnode.addChild(node=self.data_form_widget.data_form)
-
- def callback(response):
- # FIXME: move to connection_handlers.py
- err = response.getError()
- if err:
- self.stage5(errorid = err)
- else:
- self.stage3_next_form(response.getTag('command'))
-
- self.account.connection.SendAndCallForResponse(stanza, callback)
-
- def send_cancel(self):
- """
- Send the command with action='cancel'
- """
- assert self.commandnode
- if self.sessionid and self.account.connection:
- # we already have sessionid, so the service sent at least one reply.
- stanza = xmpp.Iq(typ='set', to=self.jid)
- stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={
- 'node':self.commandnode,
- 'sessionid':self.sessionid,
- 'action':'cancel'
- })
-
- self.account.connection.send(stanza)
- else:
- # we did not received any reply from service; FIXME: we should wait and
- # then send cancel; for now we do nothing
- pass
-
-# vim: se ts=3:
+ def request_command_list(self):
+ """
+ Request the command list. Change stage on delivery
+ """
+ query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), queryNS=xmpp.NS_DISCO_ITEMS)
+ query.setQuerynode(xmpp.NS_COMMANDS)
+
+ def callback(response):
+ '''Called on response to query.'''
+ # FIXME: move to connection_handlers.py
+ # is error => error stage
+ error = response.getError()
+ if error:
+ # extracting error description from xmpp/protocol.py
+ self.stage5(errorid = error)
+ return
+
+ # no commands => no commands stage
+ # commands => command selection stage
+ query = response.getTag('query')
+ if query and query.getAttr('node') == xmpp.NS_COMMANDS:
+ items = query.getTags('item')
+ else:
+ items = []
+ if len(items)==0:
+ self.commandlist = []
+ self.stage4()
+ else:
+ self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items]
+ self.stage2()
+
+ self.account.connection.SendAndCallForResponse(query, callback)
+
+ def send_command(self, action='execute'):
+ """
+ Send the command with data form. Wait for reply
+ """
+ # create the stanza
+ assert isinstance(self.commandnode, unicode)
+ assert action in ('execute', 'prev', 'next', 'complete')
+
+ stanza = xmpp.Iq(typ='set', to=self.jid)
+ cmdnode = stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={
+ 'node':self.commandnode, 'action':action})
+
+ if self.sessionid:
+ cmdnode.setAttr('sessionid', self.sessionid)
+
+ if self.data_form_widget.data_form:
+# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form))
+ # FIXME: simplified form to send
+
+ cmdnode.addChild(node=self.data_form_widget.data_form)
+
+ def callback(response):
+ # FIXME: move to connection_handlers.py
+ err = response.getError()
+ if err:
+ self.stage5(errorid = err)
+ else:
+ self.stage3_next_form(response.getTag('command'))
+
+ self.account.connection.SendAndCallForResponse(stanza, callback)
+
+ def send_cancel(self):
+ """
+ Send the command with action='cancel'
+ """
+ assert self.commandnode
+ if self.sessionid and self.account.connection:
+ # we already have sessionid, so the service sent at least one reply.
+ stanza = xmpp.Iq(typ='set', to=self.jid)
+ stanza.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={
+ 'node':self.commandnode,
+ 'sessionid':self.sessionid,
+ 'action':'cancel'
+ })
+
+ self.account.connection.send(stanza)
+ else:
+ # we did not received any reply from service; FIXME: we should wait and
+ # then send cancel; for now we do nothing
+ pass
diff --git a/src/advanced_configuration_window.py b/src/advanced_configuration_window.py
index d97dac76c..3a1047e86 100644
--- a/src/advanced_configuration_window.py
+++ b/src/advanced_configuration_window.py
@@ -43,250 +43,248 @@ C_TYPE
GTKGUI_GLADE = 'manage_accounts_window.ui'
def rate_limit(rate):
- """
- Call func at most *rate* times per second
- """
- def decorator(func):
- timeout = [None]
- def f(*args, **kwargs):
- if timeout[0] is not None:
- gobject.source_remove(timeout[0])
- timeout[0] = None
- def timeout_func():
- func(*args, **kwargs)
- timeout[0] = None
- timeout[0] = gobject.timeout_add(int(1000.0 / rate), timeout_func)
- return f
- return decorator
+ """
+ Call func at most *rate* times per second
+ """
+ def decorator(func):
+ timeout = [None]
+ def f(*args, **kwargs):
+ if timeout[0] is not None:
+ gobject.source_remove(timeout[0])
+ timeout[0] = None
+ def timeout_func():
+ func(*args, **kwargs)
+ timeout[0] = None
+ timeout[0] = gobject.timeout_add(int(1000.0 / rate), timeout_func)
+ return f
+ return decorator
def tree_model_iter_children(model, treeiter):
- it = model.iter_children(treeiter)
- while it:
- yield it
- it = model.iter_next(it)
+ it = model.iter_children(treeiter)
+ while it:
+ yield it
+ it = model.iter_next(it)
def tree_model_pre_order(model, treeiter):
- yield treeiter
- for childiter in tree_model_iter_children(model, treeiter):
- for it in tree_model_pre_order(model, childiter):
- yield it
+ yield treeiter
+ for childiter in tree_model_iter_children(model, treeiter):
+ for it in tree_model_pre_order(model, childiter):
+ yield it
class AdvancedConfigurationWindow(object):
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('advanced_configuration_window.ui')
- self.window = self.xml.get_object('advanced_configuration_window')
- self.window.set_transient_for(
- gajim.interface.instances['preferences'].window)
- self.entry = self.xml.get_object('advanced_entry')
- self.desc_label = self.xml.get_object('advanced_desc_label')
- self.restart_label = self.xml.get_object('restart_label')
-
- # Format:
- # key = option name (root/subopt/opt separated by \n then)
- # value = array(oldval, newval)
- self.changed_opts = {}
-
- # For i18n
- self.right_true_dict = {True: _('Activated'), False: _('Deactivated')}
- self.types = {
- 'boolean': _('Boolean'),
- 'integer': _('Integer'),
- 'string': _('Text'),
- 'color': _('Color')}
-
- treeview = self.xml.get_object('advanced_treeview')
- self.treeview = treeview
- self.model = gtk.TreeStore(str, str, str)
- self.fill_model()
- self.model.set_sort_column_id(0, gtk.SORT_ASCENDING)
- self.modelfilter = self.model.filter_new()
- self.modelfilter.set_visible_func(self.visible_func)
-
- renderer_text = gtk.CellRendererText()
- col = treeview.insert_column_with_attributes(-1, _('Preference Name'),
- renderer_text, text = 0)
- col.set_resizable(True)
-
- renderer_text = gtk.CellRendererText()
- renderer_text.connect('edited', self.on_config_edited)
- col = treeview.insert_column_with_attributes(-1, _('Value'),
- renderer_text, text = 1)
- col.set_cell_data_func(renderer_text, self.cb_value_column_data)
-
- col.props.resizable = True
- col.set_max_width(250)
-
- renderer_text = gtk.CellRendererText()
- treeview.insert_column_with_attributes(-1, _('Type'),
- renderer_text, text = 2)
-
- treeview.set_model(self.modelfilter)
-
- # connect signal for selection change
- treeview.get_selection().connect('changed',
- self.on_advanced_treeview_selection_changed)
-
- self.xml.connect_signals(self)
- self.window.show_all()
- self.restart_label.hide()
- gajim.interface.instances['advanced_config'] = self
-
- def cb_value_column_data(self, col, cell, model, iter_):
- """
- Check if it's boolen or holds password stuff and if yes make the
- cellrenderertext not editable, else - it's editable
- """
- optname = model[iter_][C_PREFNAME]
- opttype = model[iter_][C_TYPE]
- if opttype == self.types['boolean'] or optname == 'password':
- cell.set_property('editable', False)
- else:
- cell.set_property('editable', True)
-
- def get_option_path(self, model, iter_):
- # It looks like path made from reversed array
- # path[0] is the true one optname
- # path[1] is the key name
- # path[2] is the root of tree
- # last two is optional
- path = [model[iter_][0].decode('utf-8')]
- parent = model.iter_parent(iter_)
- while parent:
- path.append(model[parent][0].decode('utf-8'))
- parent = model.iter_parent(parent)
- return path
-
- def on_advanced_treeview_selection_changed(self, treeselection):
- model, iter_ = treeselection.get_selected()
- # Check for GtkTreeIter
- if iter_:
- opt_path = self.get_option_path(model, iter_)
- # Get text from first column in this row
- desc = None
- if len(opt_path) == 3:
- desc = gajim.config.get_desc_per(opt_path[2], opt_path[1],
- opt_path[0])
- elif len(opt_path) == 1:
- desc = gajim.config.get_desc(opt_path[0])
- if desc:
- self.desc_label.set_text(desc)
- else:
- #we talk about option description in advanced configuration editor
- self.desc_label.set_text(_('(None)'))
-
- def remember_option(self, option, oldval, newval):
- if option in self.changed_opts:
- self.changed_opts[option] = (self.changed_opts[option][0], newval)
- else:
- self.changed_opts[option] = (oldval, newval)
-
- def on_advanced_treeview_row_activated(self, treeview, path, column):
- modelpath = self.modelfilter.convert_path_to_child_path(path)
- modelrow = self.model[modelpath]
- option = modelrow[0].decode('utf-8')
- if modelrow[2] == self.types['boolean']:
- for key in self.right_true_dict.keys():
- if self.right_true_dict[key] == modelrow[1]:
- modelrow[1] = key
- newval = {'False': True, 'True': False}[modelrow[1]]
- if len(modelpath) > 1:
- optnamerow = self.model[modelpath[0]]
- optname = optnamerow[0].decode('utf-8')
- keyrow = self.model[modelpath[:2]]
- key = keyrow[0].decode('utf-8')
- gajim.config.get_desc_per(optname, key, option)
- self.remember_option(option + '\n' + key + '\n' + optname,
- modelrow[1], newval)
- gajim.config.set_per(optname, key, option, newval)
- else:
- self.remember_option(option, modelrow[1], newval)
- gajim.config.set(option, newval)
- gajim.interface.save_config()
- modelrow[1] = self.right_true_dict[newval]
- self.check_for_restart()
-
- def check_for_restart(self):
- self.restart_label.hide()
- for opt in self.changed_opts:
- opt_path = opt.split('\n')
- if len(opt_path)==3:
- restart = gajim.config.get_restart_per(opt_path[2], opt_path[1],
- opt_path[0])
- else:
- restart = gajim.config.get_restart(opt_path[0])
- if restart:
- if self.changed_opts[opt][0] != self.changed_opts[opt][1]:
- self.restart_label.show()
- break
-
- def on_config_edited(self, cell, path, text):
- # convert modelfilter path to model path
- modelpath = self.modelfilter.convert_path_to_child_path(path)
- modelrow = self.model[modelpath]
- option = modelrow[0].decode('utf-8')
- text = text.decode('utf-8')
- if len(modelpath) > 1:
- optnamerow = self.model[modelpath[0]]
- optname = optnamerow[0].decode('utf-8')
- keyrow = self.model[modelpath[:2]]
- key = keyrow[0].decode('utf-8')
- self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1],
- text)
- gajim.config.set_per(optname, key, option, text)
- else:
- self.remember_option(option, modelrow[1], text)
- gajim.config.set(option, text)
- gajim.interface.save_config()
- modelrow[1] = text
- self.check_for_restart()
-
- def on_advanced_configuration_window_destroy(self, widget):
- del gajim.interface.instances['advanced_config']
-
- def on_advanced_close_button_clicked(self, widget):
- self.window.destroy()
-
- def fill_model(self, node=None, parent=None):
- for item, option in gajim.config.get_children(node):
- name = item[-1]
- if option is None: # Node
- newparent = self.model.append(parent, [name, '', ''])
- self.fill_model(item, newparent)
- else: # Leaf
- type_ = self.types[option[OPT_TYPE][0]]
- if name == 'password':
- value = _('Hidden')
- else:
- if type_ == self.types['boolean']:
- value = self.right_true_dict[option[OPT_VAL]]
- else:
- value = option[OPT_VAL]
- self.model.append(parent, [name, value, type_])
-
- def visible_func(self, model, treeiter):
- search_string = self.entry.get_text().decode('utf-8').lower()
- for it in tree_model_pre_order(model,treeiter):
- if model[it][C_TYPE] != '':
- opt_path = self.get_option_path(model, it)
- if len(opt_path) == 3:
- desc = gajim.config.get_desc_per(opt_path[2], opt_path[1],
- opt_path[0])
- elif len(opt_path) == 1:
- desc = gajim.config.get_desc(opt_path[0])
- if search_string in model[it][C_PREFNAME] or (desc and \
- search_string in desc.lower()):
- return True
- return False
-
- @rate_limit(3)
- def on_advanced_entry_changed(self, widget):
- self.modelfilter.refilter()
- if not widget.get_text():
- # Maybe the expanded rows should be remembered here ...
- self.treeview.collapse_all()
- else:
- # ... and be restored correctly here
- self.treeview.expand_all()
-
-# vim: se ts=3:
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('advanced_configuration_window.ui')
+ self.window = self.xml.get_object('advanced_configuration_window')
+ self.window.set_transient_for(
+ gajim.interface.instances['preferences'].window)
+ self.entry = self.xml.get_object('advanced_entry')
+ self.desc_label = self.xml.get_object('advanced_desc_label')
+ self.restart_label = self.xml.get_object('restart_label')
+
+ # Format:
+ # key = option name (root/subopt/opt separated by \n then)
+ # value = array(oldval, newval)
+ self.changed_opts = {}
+
+ # For i18n
+ self.right_true_dict = {True: _('Activated'), False: _('Deactivated')}
+ self.types = {
+ 'boolean': _('Boolean'),
+ 'integer': _('Integer'),
+ 'string': _('Text'),
+ 'color': _('Color')}
+
+ treeview = self.xml.get_object('advanced_treeview')
+ self.treeview = treeview
+ self.model = gtk.TreeStore(str, str, str)
+ self.fill_model()
+ self.model.set_sort_column_id(0, gtk.SORT_ASCENDING)
+ self.modelfilter = self.model.filter_new()
+ self.modelfilter.set_visible_func(self.visible_func)
+
+ renderer_text = gtk.CellRendererText()
+ col = treeview.insert_column_with_attributes(-1, _('Preference Name'),
+ renderer_text, text = 0)
+ col.set_resizable(True)
+
+ renderer_text = gtk.CellRendererText()
+ renderer_text.connect('edited', self.on_config_edited)
+ col = treeview.insert_column_with_attributes(-1, _('Value'),
+ renderer_text, text = 1)
+ col.set_cell_data_func(renderer_text, self.cb_value_column_data)
+
+ col.props.resizable = True
+ col.set_max_width(250)
+
+ renderer_text = gtk.CellRendererText()
+ treeview.insert_column_with_attributes(-1, _('Type'),
+ renderer_text, text = 2)
+
+ treeview.set_model(self.modelfilter)
+
+ # connect signal for selection change
+ treeview.get_selection().connect('changed',
+ self.on_advanced_treeview_selection_changed)
+
+ self.xml.connect_signals(self)
+ self.window.show_all()
+ self.restart_label.hide()
+ gajim.interface.instances['advanced_config'] = self
+
+ def cb_value_column_data(self, col, cell, model, iter_):
+ """
+ Check if it's boolen or holds password stuff and if yes make the
+ cellrenderertext not editable, else - it's editable
+ """
+ optname = model[iter_][C_PREFNAME]
+ opttype = model[iter_][C_TYPE]
+ if opttype == self.types['boolean'] or optname == 'password':
+ cell.set_property('editable', False)
+ else:
+ cell.set_property('editable', True)
+
+ def get_option_path(self, model, iter_):
+ # It looks like path made from reversed array
+ # path[0] is the true one optname
+ # path[1] is the key name
+ # path[2] is the root of tree
+ # last two is optional
+ path = [model[iter_][0].decode('utf-8')]
+ parent = model.iter_parent(iter_)
+ while parent:
+ path.append(model[parent][0].decode('utf-8'))
+ parent = model.iter_parent(parent)
+ return path
+
+ def on_advanced_treeview_selection_changed(self, treeselection):
+ model, iter_ = treeselection.get_selected()
+ # Check for GtkTreeIter
+ if iter_:
+ opt_path = self.get_option_path(model, iter_)
+ # Get text from first column in this row
+ desc = None
+ if len(opt_path) == 3:
+ desc = gajim.config.get_desc_per(opt_path[2], opt_path[1],
+ opt_path[0])
+ elif len(opt_path) == 1:
+ desc = gajim.config.get_desc(opt_path[0])
+ if desc:
+ self.desc_label.set_text(desc)
+ else:
+ #we talk about option description in advanced configuration editor
+ self.desc_label.set_text(_('(None)'))
+
+ def remember_option(self, option, oldval, newval):
+ if option in self.changed_opts:
+ self.changed_opts[option] = (self.changed_opts[option][0], newval)
+ else:
+ self.changed_opts[option] = (oldval, newval)
+
+ def on_advanced_treeview_row_activated(self, treeview, path, column):
+ modelpath = self.modelfilter.convert_path_to_child_path(path)
+ modelrow = self.model[modelpath]
+ option = modelrow[0].decode('utf-8')
+ if modelrow[2] == self.types['boolean']:
+ for key in self.right_true_dict.keys():
+ if self.right_true_dict[key] == modelrow[1]:
+ modelrow[1] = key
+ newval = {'False': True, 'True': False}[modelrow[1]]
+ if len(modelpath) > 1:
+ optnamerow = self.model[modelpath[0]]
+ optname = optnamerow[0].decode('utf-8')
+ keyrow = self.model[modelpath[:2]]
+ key = keyrow[0].decode('utf-8')
+ gajim.config.get_desc_per(optname, key, option)
+ self.remember_option(option + '\n' + key + '\n' + optname,
+ modelrow[1], newval)
+ gajim.config.set_per(optname, key, option, newval)
+ else:
+ self.remember_option(option, modelrow[1], newval)
+ gajim.config.set(option, newval)
+ gajim.interface.save_config()
+ modelrow[1] = self.right_true_dict[newval]
+ self.check_for_restart()
+
+ def check_for_restart(self):
+ self.restart_label.hide()
+ for opt in self.changed_opts:
+ opt_path = opt.split('\n')
+ if len(opt_path)==3:
+ restart = gajim.config.get_restart_per(opt_path[2], opt_path[1],
+ opt_path[0])
+ else:
+ restart = gajim.config.get_restart(opt_path[0])
+ if restart:
+ if self.changed_opts[opt][0] != self.changed_opts[opt][1]:
+ self.restart_label.show()
+ break
+
+ def on_config_edited(self, cell, path, text):
+ # convert modelfilter path to model path
+ modelpath = self.modelfilter.convert_path_to_child_path(path)
+ modelrow = self.model[modelpath]
+ option = modelrow[0].decode('utf-8')
+ text = text.decode('utf-8')
+ if len(modelpath) > 1:
+ optnamerow = self.model[modelpath[0]]
+ optname = optnamerow[0].decode('utf-8')
+ keyrow = self.model[modelpath[:2]]
+ key = keyrow[0].decode('utf-8')
+ self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1],
+ text)
+ gajim.config.set_per(optname, key, option, text)
+ else:
+ self.remember_option(option, modelrow[1], text)
+ gajim.config.set(option, text)
+ gajim.interface.save_config()
+ modelrow[1] = text
+ self.check_for_restart()
+
+ def on_advanced_configuration_window_destroy(self, widget):
+ del gajim.interface.instances['advanced_config']
+
+ def on_advanced_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def fill_model(self, node=None, parent=None):
+ for item, option in gajim.config.get_children(node):
+ name = item[-1]
+ if option is None: # Node
+ newparent = self.model.append(parent, [name, '', ''])
+ self.fill_model(item, newparent)
+ else: # Leaf
+ type_ = self.types[option[OPT_TYPE][0]]
+ if name == 'password':
+ value = _('Hidden')
+ else:
+ if type_ == self.types['boolean']:
+ value = self.right_true_dict[option[OPT_VAL]]
+ else:
+ value = option[OPT_VAL]
+ self.model.append(parent, [name, value, type_])
+
+ def visible_func(self, model, treeiter):
+ search_string = self.entry.get_text().decode('utf-8').lower()
+ for it in tree_model_pre_order(model,treeiter):
+ if model[it][C_TYPE] != '':
+ opt_path = self.get_option_path(model, it)
+ if len(opt_path) == 3:
+ desc = gajim.config.get_desc_per(opt_path[2], opt_path[1],
+ opt_path[0])
+ elif len(opt_path) == 1:
+ desc = gajim.config.get_desc(opt_path[0])
+ if search_string in model[it][C_PREFNAME] or (desc and \
+ search_string in desc.lower()):
+ return True
+ return False
+
+ @rate_limit(3)
+ def on_advanced_entry_changed(self, widget):
+ self.modelfilter.refilter()
+ if not widget.get_text():
+ # Maybe the expanded rows should be remembered here ...
+ self.treeview.collapse_all()
+ else:
+ # ... and be restored correctly here
+ self.treeview.expand_all()
diff --git a/src/atom_window.py b/src/atom_window.py
index 394d4ac27..9052ed1ae 100644
--- a/src/atom_window.py
+++ b/src/atom_window.py
@@ -30,121 +30,119 @@ from common import helpers
from common import i18n
class AtomWindow:
- window = None
- entries = []
-
- @classmethod
- def newAtomEntry(cls, entry):
- """
- Queue new entry, open window if there's no one opened
- """
- cls.entries.append(entry)
-
- if cls.window is None:
- cls.window = AtomWindow()
- else:
- cls.window.updateCounter()
-
- @classmethod
- def windowClosed(cls):
- cls.window = None
-
- def __init__(self):
- """
- Create new window... only if we have anything to show
- """
- assert len(self.__class__.entries)>0
-
- self.entry = None # the entry actually displayed
-
- self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui')
- self.window = self.xml.get_object('atom_entry_window')
- for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox',
- 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox',
- 'last_modified_label', 'close_button', 'next_button'):
- self.__dict__[name] = self.xml.get_object(name)
-
- self.displayNextEntry()
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK)
- self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK)
-
- def displayNextEntry(self):
- """
- Get next entry from the queue and display it in the window
- """
- assert len(self.__class__.entries)>0
-
- newentry = self.__class__.entries.pop(0)
-
- # fill the fields
- if newentry.feed_link is not None:
- self.feed_title_label.set_markup(
- u'<span foreground="blue" underline="single">%s</span>' % \
- gobject.markup_escape_text(newentry.feed_title))
- else:
- self.feed_title_label.set_markup(
- gobject.markup_escape_text(newentry.feed_title))
-
- self.feed_tagline_label.set_markup(
- u'<small>%s</small>' % \
- gobject.markup_escape_text(newentry.feed_tagline))
-
- if newentry.uri is not None:
- self.entry_title_label.set_markup(
- u'<span foreground="blue" underline="single">%s</span>' % \
- gobject.markup_escape_text(newentry.title))
- else:
- self.entry_title_label.set_markup(
- gobject.markup_escape_text(newentry.title))
-
- self.last_modified_label.set_text(newentry.updated)
-
- # update the counters
- self.updateCounter()
-
- self.entry = newentry
-
- def updateCounter(self):
- """
- Display number of events on the top of window, sometimes it needs to be
- changed
- """
- count = len(self.__class__.entries)
- if count>0:
- self.new_entry_label.set_text(i18n.ngettext(
- 'You have received new entries (and %d not displayed):',
- 'You have received new entries (and %d not displayed):', count,
- count, count))
- self.next_button.set_sensitive(True)
- else:
- self.new_entry_label.set_text(_('You have received new entry:'))
- self.next_button.set_sensitive(False)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
- self.windowClosed()
-
- def on_next_button_clicked(self, widget):
- self.displayNextEntry()
-
- def on_entry_title_button_press_event(self, widget, event):
- #FIXME: make it using special gtk2.10 widget
- if event.button == 1: # left click
- uri = self.entry.uri
- if uri is not None:
- helpers.launch_browser_mailer('url', uri)
- return True
-
- def on_feed_title_button_press_event(self, widget, event):
- #FIXME: make it using special gtk2.10 widget
- if event.button == 1: # left click
- uri = self.entry.feed_uri
- if uri is not None:
- helpers.launch_browser_mailer('url', uri)
- return True
-
-# vim: se ts=3:
+ window = None
+ entries = []
+
+ @classmethod
+ def newAtomEntry(cls, entry):
+ """
+ Queue new entry, open window if there's no one opened
+ """
+ cls.entries.append(entry)
+
+ if cls.window is None:
+ cls.window = AtomWindow()
+ else:
+ cls.window.updateCounter()
+
+ @classmethod
+ def windowClosed(cls):
+ cls.window = None
+
+ def __init__(self):
+ """
+ Create new window... only if we have anything to show
+ """
+ assert len(self.__class__.entries)>0
+
+ self.entry = None # the entry actually displayed
+
+ self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui')
+ self.window = self.xml.get_object('atom_entry_window')
+ for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox',
+ 'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox',
+ 'last_modified_label', 'close_button', 'next_button'):
+ self.__dict__[name] = self.xml.get_object(name)
+
+ self.displayNextEntry()
+
+ self.xml.connect_signals(self)
+ self.window.show_all()
+
+ self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK)
+ self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK)
+
+ def displayNextEntry(self):
+ """
+ Get next entry from the queue and display it in the window
+ """
+ assert len(self.__class__.entries)>0
+
+ newentry = self.__class__.entries.pop(0)
+
+ # fill the fields
+ if newentry.feed_link is not None:
+ self.feed_title_label.set_markup(
+ u'<span foreground="blue" underline="single">%s</span>' % \
+ gobject.markup_escape_text(newentry.feed_title))
+ else:
+ self.feed_title_label.set_markup(
+ gobject.markup_escape_text(newentry.feed_title))
+
+ self.feed_tagline_label.set_markup(
+ u'<small>%s</small>' % \
+ gobject.markup_escape_text(newentry.feed_tagline))
+
+ if newentry.uri is not None:
+ self.entry_title_label.set_markup(
+ u'<span foreground="blue" underline="single">%s</span>' % \
+ gobject.markup_escape_text(newentry.title))
+ else:
+ self.entry_title_label.set_markup(
+ gobject.markup_escape_text(newentry.title))
+
+ self.last_modified_label.set_text(newentry.updated)
+
+ # update the counters
+ self.updateCounter()
+
+ self.entry = newentry
+
+ def updateCounter(self):
+ """
+ Display number of events on the top of window, sometimes it needs to be
+ changed
+ """
+ count = len(self.__class__.entries)
+ if count>0:
+ self.new_entry_label.set_text(i18n.ngettext(
+ 'You have received new entries (and %d not displayed):',
+ 'You have received new entries (and %d not displayed):', count,
+ count, count))
+ self.next_button.set_sensitive(True)
+ else:
+ self.new_entry_label.set_text(_('You have received new entry:'))
+ self.next_button.set_sensitive(False)
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+ self.windowClosed()
+
+ def on_next_button_clicked(self, widget):
+ self.displayNextEntry()
+
+ def on_entry_title_button_press_event(self, widget, event):
+ #FIXME: make it using special gtk2.10 widget
+ if event.button == 1: # left click
+ uri = self.entry.uri
+ if uri is not None:
+ helpers.launch_browser_mailer('url', uri)
+ return True
+
+ def on_feed_title_button_press_event(self, widget, event):
+ #FIXME: make it using special gtk2.10 widget
+ if event.button == 1: # left click
+ uri = self.entry.feed_uri
+ if uri is not None:
+ helpers.launch_browser_mailer('url', uri)
+ return True
diff --git a/src/cell_renderer_image.py b/src/cell_renderer_image.py
index e1cdf8c81..0b85e8c44 100644
--- a/src/cell_renderer_image.py
+++ b/src/cell_renderer_image.py
@@ -27,113 +27,111 @@ import gobject
class CellRendererImage(gtk.GenericCellRenderer):
- __gproperties__ = {
- 'image': (gobject.TYPE_OBJECT, 'Image',
- 'Image', gobject.PARAM_READWRITE),
- }
-
- def __init__(self, col_index, tv_index):
- self.__gobject_init__()
- self.image = None
- self.col_index = col_index
- self.tv_index = tv_index
- self.iters = {}
-
- def do_set_property(self, pspec, value):
- setattr(self, pspec.name, value)
-
- def do_get_property(self, pspec):
- return getattr(self, pspec.name)
-
- def func(self, model, path, iter_, image_tree):
- image, tree = image_tree
- if model.get_value(iter_, self.tv_index) != image:
- return
- self.redraw = 1
- col = tree.get_column(self.col_index)
- cell_area = tree.get_cell_area(path, col)
-
- tree.queue_draw_area(cell_area.x, cell_area.y,
- cell_area.width, cell_area.height)
-
- def animation_timeout(self, tree, image):
- if image.get_storage_type() != gtk.IMAGE_ANIMATION:
- return
- self.redraw = 0
- iter_ = self.iters[image]
- iter_.advance()
- model = tree.get_model()
- if model:
- model.foreach(self.func, (image, tree))
- if self.redraw:
- gobject.timeout_add(iter_.get_delay_time(),
- self.animation_timeout, tree, image)
- elif image in self.iters:
- del self.iters[image]
-
- def on_render(self, window, widget, background_area, cell_area,
- expose_area, flags):
- if not self.image:
- return
- pix_rect = gtk.gdk.Rectangle()
- pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = \
- self.on_get_size(widget, cell_area)
-
- pix_rect.x += cell_area.x
- pix_rect.y += cell_area.y
- pix_rect.width -= 2 * self.get_property('xpad')
- pix_rect.height -= 2 * self.get_property('ypad')
-
- draw_rect = cell_area.intersect(pix_rect)
- draw_rect = expose_area.intersect(draw_rect)
-
- if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
- if self.image not in self.iters:
- if not isinstance(widget, gtk.TreeView):
- return
- animation = self.image.get_animation()
- iter_ = animation.get_iter()
- self.iters[self.image] = iter_
- gobject.timeout_add(iter_.get_delay_time(),
- self.animation_timeout, widget, self.image)
-
- pix = self.iters[self.image].get_pixbuf()
- elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
- pix = self.image.get_pixbuf()
- else:
- return
- if draw_rect.x < 1:
- return
- window.draw_pixbuf(widget.style.black_gc, pix,
- draw_rect.x - pix_rect.x,
- draw_rect.y - pix_rect.y,
- draw_rect.x, draw_rect.y,
- draw_rect.width, draw_rect.height,
- gtk.gdk.RGB_DITHER_NONE, 0, 0)
-
- def on_get_size(self, widget, cell_area):
- if not self.image:
- return 0, 0, 0, 0
- if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
- animation = self.image.get_animation()
- pix = animation.get_iter().get_pixbuf()
- elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
- pix = self.image.get_pixbuf()
- else:
- return 0, 0, 0, 0
- pixbuf_width = pix.get_width()
- pixbuf_height = pix.get_height()
- calc_width = self.get_property('xpad') * 2 + pixbuf_width
- calc_height = self.get_property('ypad') * 2 + pixbuf_height
- x_offset = 0
- y_offset = 0
- if cell_area and pixbuf_width > 0 and pixbuf_height > 0:
- x_offset = self.get_property('xalign') * \
- (cell_area.width - calc_width - \
- self.get_property('xpad'))
- y_offset = self.get_property('yalign') * \
- (cell_area.height - calc_height - \
- self.get_property('ypad'))
- return x_offset, y_offset, calc_width, calc_height
-
-# vim: se ts=3:
+ __gproperties__ = {
+ 'image': (gobject.TYPE_OBJECT, 'Image',
+ 'Image', gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self, col_index, tv_index):
+ self.__gobject_init__()
+ self.image = None
+ self.col_index = col_index
+ self.tv_index = tv_index
+ self.iters = {}
+
+ def do_set_property(self, pspec, value):
+ setattr(self, pspec.name, value)
+
+ def do_get_property(self, pspec):
+ return getattr(self, pspec.name)
+
+ def func(self, model, path, iter_, image_tree):
+ image, tree = image_tree
+ if model.get_value(iter_, self.tv_index) != image:
+ return
+ self.redraw = 1
+ col = tree.get_column(self.col_index)
+ cell_area = tree.get_cell_area(path, col)
+
+ tree.queue_draw_area(cell_area.x, cell_area.y,
+ cell_area.width, cell_area.height)
+
+ def animation_timeout(self, tree, image):
+ if image.get_storage_type() != gtk.IMAGE_ANIMATION:
+ return
+ self.redraw = 0
+ iter_ = self.iters[image]
+ iter_.advance()
+ model = tree.get_model()
+ if model:
+ model.foreach(self.func, (image, tree))
+ if self.redraw:
+ gobject.timeout_add(iter_.get_delay_time(),
+ self.animation_timeout, tree, image)
+ elif image in self.iters:
+ del self.iters[image]
+
+ def on_render(self, window, widget, background_area, cell_area,
+ expose_area, flags):
+ if not self.image:
+ return
+ pix_rect = gtk.gdk.Rectangle()
+ pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = \
+ self.on_get_size(widget, cell_area)
+
+ pix_rect.x += cell_area.x
+ pix_rect.y += cell_area.y
+ pix_rect.width -= 2 * self.get_property('xpad')
+ pix_rect.height -= 2 * self.get_property('ypad')
+
+ draw_rect = cell_area.intersect(pix_rect)
+ draw_rect = expose_area.intersect(draw_rect)
+
+ if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
+ if self.image not in self.iters:
+ if not isinstance(widget, gtk.TreeView):
+ return
+ animation = self.image.get_animation()
+ iter_ = animation.get_iter()
+ self.iters[self.image] = iter_
+ gobject.timeout_add(iter_.get_delay_time(),
+ self.animation_timeout, widget, self.image)
+
+ pix = self.iters[self.image].get_pixbuf()
+ elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
+ pix = self.image.get_pixbuf()
+ else:
+ return
+ if draw_rect.x < 1:
+ return
+ window.draw_pixbuf(widget.style.black_gc, pix,
+ draw_rect.x - pix_rect.x,
+ draw_rect.y - pix_rect.y,
+ draw_rect.x, draw_rect.y,
+ draw_rect.width, draw_rect.height,
+ gtk.gdk.RGB_DITHER_NONE, 0, 0)
+
+ def on_get_size(self, widget, cell_area):
+ if not self.image:
+ return 0, 0, 0, 0
+ if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
+ animation = self.image.get_animation()
+ pix = animation.get_iter().get_pixbuf()
+ elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
+ pix = self.image.get_pixbuf()
+ else:
+ return 0, 0, 0, 0
+ pixbuf_width = pix.get_width()
+ pixbuf_height = pix.get_height()
+ calc_width = self.get_property('xpad') * 2 + pixbuf_width
+ calc_height = self.get_property('ypad') * 2 + pixbuf_height
+ x_offset = 0
+ y_offset = 0
+ if cell_area and pixbuf_width > 0 and pixbuf_height > 0:
+ x_offset = self.get_property('xalign') * \
+ (cell_area.width - calc_width - \
+ self.get_property('xpad'))
+ y_offset = self.get_property('yalign') * \
+ (cell_area.height - calc_height - \
+ self.get_property('ypad'))
+ return x_offset, y_offset, calc_width, calc_height
diff --git a/src/chat_control.py b/src/chat_control.py
index f4089906b..845365021 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -62,10 +62,10 @@ from command_system.implementation.hosts import ChatCommands
import command_system.implementation.standard
try:
- import gtkspell
- HAS_GTK_SPELL = True
+ import gtkspell
+ HAS_GTK_SPELL = True
except ImportError:
- HAS_GTK_SPELL = False
+ HAS_GTK_SPELL = False
# the next script, executed in the "po" directory,
# generates the following list.
@@ -75,2780 +75,2778 @@ except ImportError:
langs = {_('English'): 'en', _('Belarusian'): 'be', _('Bulgarian'): 'bg', _('Breton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('British'): 'en_GB', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basque'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norwegian (b)'): 'nb', _('Dutch'): 'nl', _('Norwegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Serbian'): 'sr', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'}
if gajim.config.get('use_speller') and HAS_GTK_SPELL:
- # loop removing non-existent dictionaries
- # iterating on a copy
- tv = gtk.TextView()
- spell = gtkspell.Spell(tv)
- for lang in dict(langs):
- try:
- spell.set_language(langs[lang])
- except OSError:
- del langs[lang]
- if spell:
- spell.detach()
- del tv
+ # loop removing non-existent dictionaries
+ # iterating on a copy
+ tv = gtk.TextView()
+ spell = gtkspell.Spell(tv)
+ for lang in dict(langs):
+ try:
+ spell.set_language(langs[lang])
+ except OSError:
+ del langs[lang]
+ if spell:
+ spell.detach()
+ del tv
################################################################################
class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
- """
- A base class containing a banner, ConversationTextview, MessageTextView
- """
-
- def make_href(self, match):
- url_color = gajim.config.get('urlmsgcolor')
- url = match.group()
- if not '://' in url:
- url = 'http://' + url
- return '<a href="%s"><span color="%s">%s</span></a>' % (url,
- url_color, match.group())
-
- def get_font_attrs(self):
- """
- Get pango font attributes for banner from theme settings
- """
- theme = gajim.config.get('roster_theme')
- bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
- bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
-
- if bannerfont:
- font = pango.FontDescription(bannerfont)
- else:
- font = pango.FontDescription('Normal')
- if bannerfontattrs:
- # B attribute is set by default
- if 'B' in bannerfontattrs:
- font.set_weight(pango.WEIGHT_HEAVY)
- if 'I' in bannerfontattrs:
- font.set_style(pango.STYLE_ITALIC)
-
- font_attrs = 'font_desc="%s"' % font.to_string()
-
- # in case there is no font specified we use x-large font size
- if font.get_size() == 0:
- font_attrs = '%s size="x-large"' % font_attrs
- font.set_weight(pango.WEIGHT_NORMAL)
- font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
- return (font_attrs, font_attrs_small)
-
- def get_nb_unread(self):
- jid = self.contact.jid
- if self.resource:
- jid += '/' + self.resource
- type_ = self.type_id
- return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
- type_]))
-
- def draw_banner(self):
- """
- Draw the fat line at the top of the window that houses the icon, jid, etc
-
- Derived types MAY implement this.
- """
- self.draw_banner_text()
- self._update_banner_state_image()
-
- def draw_banner_text(self):
- """
- Derived types SHOULD implement this
- """
- pass
-
- def update_ui(self):
- """
- Derived types SHOULD implement this
- """
- self.draw_banner()
-
- def repaint_themed_widgets(self):
- """
- Derived types MAY implement this
- """
- self._paint_banner()
- self.draw_banner()
-
- def _update_banner_state_image(self):
- """
- Derived types MAY implement this
- """
- pass
-
- def handle_message_textview_mykey_press(self, widget, event_keyval,
- event_keymod):
- """
- Derives types SHOULD implement this, rather than connection to the even
- itself
- """
- event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
- event.keyval = event_keyval
- event.state = event_keymod
- event.time = 0
-
- _buffer = widget.get_buffer()
- start, end = _buffer.get_bounds()
-
- if event.keyval -- gtk.keysyms.Tab:
- position = _buffer.get_insert()
- end = _buffer.get_iter_at_mark(position)
-
- text = _buffer.get_text(start, end, False)
- text = text.decode('utf8')
-
- splitted = text.split()
-
- if (text.startswith(self.COMMAND_PREFIX) and not
- text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
-
- text = splitted[0]
- bare = text.lstrip(self.COMMAND_PREFIX)
-
- if len(text) == 1:
- self.command_hits = []
- for command in self.list_commands():
- for name in command.names:
- self.command_hits.append(name)
- else:
- if (self.last_key_tabs and self.command_hits and
- self.command_hits[0].startswith(bare)):
- self.command_hits.append(self.command_hits.pop(0))
- else:
- self.command_hits = []
- for command in self.list_commands():
- for name in command.names:
- if name.startswith(bare):
- self.command_hits.append(name)
-
- if self.command_hits:
- _buffer.delete(start, end)
- _buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ')
- self.last_key_tabs = True
-
- return True
-
- self.last_key_tabs = False
-
- def status_url_clicked(self, widget, url):
- helpers.launch_browser_mailer('url', url)
-
- def __init__(self, type_id, parent_win, widget_name, contact, acct,
- resource=None):
- # Undo needs this variable to know if space has been pressed.
- # Initialize it to True so empty textview is saved in undo list
- self.space_pressed = True
-
- if resource is None:
- # We very likely got a contact with a random resource.
- # This is bad, we need the highest for caps etc.
- c = gajim.contacts.get_contact_with_highest_priority(
- acct, contact.jid)
- if c and not isinstance(c, GC_Contact):
- contact = c
-
- MessageControl.__init__(self, type_id, parent_win, widget_name,
- contact, acct, resource=resource)
-
- widget = self.xml.get_object('history_button')
- id_ = widget.connect('clicked', self._on_history_menuitem_activate)
- self.handlers[id_] = widget
-
- # when/if we do XHTML we will put formatting buttons back
- widget = self.xml.get_object('emoticons_button')
- id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
- self.handlers[id_] = widget
-
- # Create banner and connect signals
- widget = self.xml.get_object('banner_eventbox')
- id_ = widget.connect('button-press-event',
- self._on_banner_eventbox_button_press_event)
- self.handlers[id_] = widget
-
- self.urlfinder = re.compile(
- r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
-
- self.banner_status_label = self.xml.get_object('banner_label')
- id_ = self.banner_status_label.connect('populate_popup',
- self.on_banner_label_populate_popup)
- self.handlers[id_] = self.banner_status_label
-
- # Init DND
- self.TARGET_TYPE_URI_LIST = 80
- self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ),
- ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0)]
- id_ = self.widget.connect('drag_data_received',
- self._on_drag_data_received)
- self.handlers[id_] = self.widget
- self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
- gtk.DEST_DEFAULT_HIGHLIGHT |
- gtk.DEST_DEFAULT_DROP,
- self.dnd_list, gtk.gdk.ACTION_COPY)
-
- # Create textviews and connect signals
- self.conv_textview = ConversationTextview(self.account)
- id_ = self.conv_textview.connect('quote', self.on_quote)
- self.handlers[id_] = self.conv_textview.tv
- id_ = self.conv_textview.tv.connect('key_press_event',
- self._conv_textview_key_press_event)
- self.handlers[id_] = self.conv_textview.tv
- # FIXME: DND on non editable TextView, find a better way
- self.drag_entered = False
- id_ = self.conv_textview.tv.connect('drag_data_received',
- self._on_drag_data_received)
- self.handlers[id_] = self.conv_textview.tv
- id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
- self.handlers[id_] = self.conv_textview.tv
- id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
- self.handlers[id_] = self.conv_textview.tv
- self.conv_textview.tv.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
- gtk.DEST_DEFAULT_HIGHLIGHT |
- gtk.DEST_DEFAULT_DROP,
- self.dnd_list, gtk.gdk.ACTION_COPY)
-
- self.conv_scrolledwindow = self.xml.get_object(
- 'conversation_scrolledwindow')
- self.conv_scrolledwindow.add(self.conv_textview.tv)
- widget = self.conv_scrolledwindow.get_vadjustment()
- id_ = widget.connect('value-changed',
- self.on_conversation_vadjustment_value_changed)
- self.handlers[id_] = widget
- id_ = widget.connect('changed',
- self.on_conversation_vadjustment_changed)
- self.handlers[id_] = widget
- self.scroll_to_end_id = None
- self.was_at_the_end = True
-
- # add MessageTextView to UI and connect signals
- self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
- self.msg_textview = MessageTextView()
- id_ = self.msg_textview.connect('mykeypress',
- self._on_message_textview_mykeypress_event)
- self.handlers[id_] = self.msg_textview
- self.msg_scrolledwindow.add(self.msg_textview)
- id_ = self.msg_textview.connect('key_press_event',
- self._on_message_textview_key_press_event)
- self.handlers[id_] = self.msg_textview
- id_ = self.msg_textview.connect('size-request', self.size_request)
- self.handlers[id_] = self.msg_textview
- id_ = self.msg_textview.connect('populate_popup',
- self.on_msg_textview_populate_popup)
- self.handlers[id_] = self.msg_textview
- # Setup DND
- id_ = self.msg_textview.connect('drag_data_received',
- self._on_drag_data_received)
- self.handlers[id_] = self.msg_textview
- self.msg_textview.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
- gtk.DEST_DEFAULT_HIGHLIGHT,
- self.dnd_list, gtk.gdk.ACTION_COPY)
-
- self.update_font()
-
- # Hook up send button
- widget = self.xml.get_object('send_button')
- id_ = widget.connect('clicked', self._on_send_button_clicked)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('formattings_button')
- id_ = widget.connect('clicked', self.on_formattings_button_clicked)
- self.handlers[id_] = widget
-
- # the following vars are used to keep history of user's messages
- self.sent_history = []
- self.sent_history_pos = 0
- self.orig_msg = None
-
- # Emoticons menu
- # set image no matter if user wants at this time emoticons or not
- # (so toggle works ok)
- img = self.xml.get_object('emoticons_button_image')
- img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
- 'smile.png'))
- self.toggle_emoticons()
-
- # Attach speller
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
- self.set_speller()
- self.conv_textview.tv.show()
- self._paint_banner()
-
- # For XEP-0172
- self.user_nick = None
-
- self.smooth = True
- self.msg_textview.grab_focus()
-
- self.command_hits = []
- self.last_key_tabs = False
-
- def set_speller(self):
- # now set the one the user selected
- per_type = 'contacts'
- if self.type_id == message_control.TYPE_GC:
- per_type = 'rooms'
- lang = gajim.config.get_per(per_type, self.contact.jid,
- 'speller_language')
- if not lang:
- # use the default one
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- if lang:
- try:
- gtkspell.Spell(self.msg_textview, lang)
- self.msg_textview.lang = lang
- except (gobject.GError, RuntimeError, TypeError, OSError):
- dialogs.AspellDictError(lang)
-
- def on_banner_label_populate_popup(self, label, menu):
- """
- Override the default context menu and add our own menutiems
- """
- item = gtk.SeparatorMenuItem()
- menu.prepend(item)
-
- menu2 = self.prepare_context_menu()
- i = 0
- for item in menu2:
- menu2.remove(item)
- menu.prepend(item)
- menu.reorder_child(item, i)
- i += 1
- menu.show_all()
-
- def on_msg_textview_populate_popup(self, textview, menu):
- """
- Override the default context menu and we prepend an option to switch
- languages
- """
- def _on_select_dictionary(widget, lang):
- per_type = 'contacts'
- if self.type_id == message_control.TYPE_GC:
- per_type = 'rooms'
- if not gajim.config.get_per(per_type, self.contact.jid):
- gajim.config.add_per(per_type, self.contact.jid)
- gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
- lang)
- spell = gtkspell.get_from_text_view(self.msg_textview)
- self.msg_textview.lang = lang
- spell.set_language(lang)
- widget.set_active(True)
-
- item = gtk.ImageMenuItem(gtk.STOCK_UNDO)
- menu.prepend(item)
- id_ = item.connect('activate', self.msg_textview.undo)
- self.handlers[id_] = item
-
- item = gtk.SeparatorMenuItem()
- menu.prepend(item)
-
- item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
- menu.prepend(item)
- id_ = item.connect('activate', self.msg_textview.clear)
- self.handlers[id_] = item
-
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
- item = gtk.MenuItem(_('Spelling language'))
- menu.prepend(item)
- submenu = gtk.Menu()
- item.set_submenu(submenu)
- for lang in sorted(langs):
- item = gtk.CheckMenuItem(lang)
- if langs[lang] == self.msg_textview.lang:
- item.set_active(True)
- submenu.append(item)
- id_ = item.connect('activate', _on_select_dictionary, langs[lang])
- self.handlers[id_] = item
-
- menu.show_all()
-
- def on_quote(self, widget, text):
- text = '>' + text.replace('\n', '\n>') + '\n'
- message_buffer = self.msg_textview.get_buffer()
- message_buffer.insert_at_cursor(text)
-
- # moved from ChatControl
- def _on_banner_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- self.parent_win.popup_menu(event)
-
- def _on_send_button_clicked(self, widget):
- """
- When send button is pressed: send the current message
- """
- if gajim.connections[self.account].connected < 2: # we are not connected
- dialogs.ErrorDialog(_('A connection is not available'),
- _('Your message can not be sent until you are connected.'))
- return
- message_buffer = self.msg_textview.get_buffer()
- start_iter = message_buffer.get_start_iter()
- end_iter = message_buffer.get_end_iter()
- message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8')
- xhtml = self.msg_textview.get_xhtml()
-
- # send the message
- self.send_message(message, xhtml=xhtml)
-
- def _paint_banner(self):
- """
- Repaint banner with theme color
- """
- theme = gajim.config.get('roster_theme')
- bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
- textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
- # the backgrounds are colored by using an eventbox by
- # setting the bg color of the eventbox and the fg of the name_label
- banner_eventbox = self.xml.get_object('banner_eventbox')
- banner_name_label = self.xml.get_object('banner_name_label')
- self.disconnect_style_event(banner_name_label)
- self.disconnect_style_event(self.banner_status_label)
- if bgcolor:
- banner_eventbox.modify_bg(gtk.STATE_NORMAL,
- gtk.gdk.color_parse(bgcolor))
- default_bg = False
- else:
- default_bg = True
- if textcolor:
- banner_name_label.modify_fg(gtk.STATE_NORMAL,
- gtk.gdk.color_parse(textcolor))
- self.banner_status_label.modify_fg(gtk.STATE_NORMAL,
- gtk.gdk.color_parse(textcolor))
- default_fg = False
- else:
- default_fg = True
- if default_bg or default_fg:
- self._on_style_set_event(banner_name_label, None, default_fg,
- default_bg)
- if self.banner_status_label.flags() & gtk.REALIZED:
- # Widget is realized
- self._on_style_set_event(self.banner_status_label, None, default_fg,
- default_bg)
-
- def disconnect_style_event(self, widget):
- # Try to find the event_id
- for id_ in self.handlers.keys():
- if self.handlers[id_] == widget:
- widget.disconnect(id_)
- del self.handlers[id_]
- break
-
- def connect_style_event(self, widget, set_fg = False, set_bg = False):
- self.disconnect_style_event(widget)
- id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
- set_bg)
- self.handlers[id_] = widget
-
- def _on_style_set_event(self, widget, style, *opts):
- """
- Set style of widget from style class *.Frame.Eventbox
- opts[0] == True -> set fg color
- opts[1] == True -> set bg color
- """
- banner_eventbox = self.xml.get_object('banner_eventbox')
- self.disconnect_style_event(widget)
- if opts[1]:
- bg_color = widget.style.bg[gtk.STATE_SELECTED]
- banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
- if opts[0]:
- fg_color = widget.style.fg[gtk.STATE_SELECTED]
- widget.modify_fg(gtk.STATE_NORMAL, fg_color)
- self.connect_style_event(widget, opts[0], opts[1])
-
- def _conv_textview_key_press_event(self, widget, event):
- if (event.state & gtk.gdk.CONTROL_MASK and event.keyval in (gtk.keysyms.c,
- gtk.keysyms.Insert)) or (event.state & gtk.gdk.SHIFT_MASK and \
- event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)):
- return False
- self.parent_win.notebook.emit('key_press_event', event)
- return True
-
- def show_emoticons_menu(self):
- if not gajim.config.get('emoticons_theme'):
- return
- def set_emoticons_menu_position(w, msg_tv = self.msg_textview):
- window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET)
- # get the window position
- origin = window.get_origin()
- size = window.get_size()
- buf = msg_tv.get_buffer()
- # get the cursor position
- cursor = msg_tv.get_iter_location(buf.get_iter_at_mark(
- buf.get_insert()))
- cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
- cursor.x, cursor.y)
- x = origin[0] + cursor[0]
- y = origin[1] + size[1]
- menu_height = gajim.interface.emoticons_menu.size_request()[1]
- #FIXME: get_line_count is not so good
- #get the iter of cursor, then tv.get_line_yrange
- # so we know in which y we are typing (not how many lines we have
- # then go show just above the current cursor line for up
- # or just below the current cursor line for down
- #TEST with having 3 lines and writing in the 2nd
- if y + menu_height > gtk.gdk.screen_height():
- # move menu just above cursor
- y -= menu_height + (msg_tv.allocation.height / buf.get_line_count())
- #else: # move menu just below cursor
- # y -= (msg_tv.allocation.height / buf.get_line_count())
- return (x, y, True) # push_in True
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
- gajim.interface.emoticons_menu.popup(None, None,
- set_emoticons_menu_position, 1, 0)
-
- def _on_message_textview_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.space:
- self.space_pressed = True
-
- elif (self.space_pressed or self.msg_textview.undo_pressed) and \
- event.keyval not in (gtk.keysyms.Control_L, gtk.keysyms.Control_R) and \
- not (event.keyval == gtk.keysyms.z and event.state & gtk.gdk.CONTROL_MASK):
- # If the space key has been pressed and now it hasnt,
- # we save the buffer into the undo list. But be carefull we're not
- # pressiong Control again (as in ctrl+z)
- _buffer = widget.get_buffer()
- start_iter, end_iter = _buffer.get_bounds()
- self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter))
- self.space_pressed = False
-
- # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
- if self.widget_name == 'groupchat_control':
- if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab):
- self.last_key_tabs = False
- if event.state & gtk.gdk.SHIFT_MASK:
- # CTRL + SHIFT + TAB
- if event.state & gtk.gdk.CONTROL_MASK and \
- event.keyval == gtk.keysyms.ISO_Left_Tab:
- self.parent_win.move_to_next_unread_tab(False)
- return True
- # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
- elif event.keyval == gtk.keysyms.Page_Down or \
- event.keyval == gtk.keysyms.Page_Up:
- self.conv_textview.tv.emit('key_press_event', event)
- return True
- elif event.state & gtk.gdk.CONTROL_MASK:
- if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
- self.parent_win.move_to_next_unread_tab(True)
- return True
- return False
-
- def _on_message_textview_mykeypress_event(self, widget, event_keyval,
- event_keymod):
- """
- When a key is pressed: if enter is pressed without the shift key, message
- (if not empty) is sent and printed in the conversation
- """
- # NOTE: handles mykeypress which is custom signal connected to this
- # CB in new_tab(). for this singal see message_textview.py
- message_textview = widget
- message_buffer = message_textview.get_buffer()
- start_iter, end_iter = message_buffer.get_bounds()
- message = message_buffer.get_text(start_iter, end_iter, False).decode(
- 'utf-8')
- xhtml = self.msg_textview.get_xhtml()
-
- # construct event instance from binding
- event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
- event.keyval = event_keyval
- event.state = event_keymod
- event.time = 0 # assign current time
-
- if event.keyval == gtk.keysyms.Up:
- if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
- self.sent_messages_scroll('up', widget.get_buffer())
- elif event.keyval == gtk.keysyms.Down:
- if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down
- self.sent_messages_scroll('down', widget.get_buffer())
- elif event.keyval == gtk.keysyms.Return or \
- event.keyval == gtk.keysyms.KP_Enter: # ENTER
- # NOTE: SHIFT + ENTER is not needed to be emulated as it is not
- # binding at all (textview's default action is newline)
-
- if gajim.config.get('send_on_ctrl_enter'):
- # here, we emulate GTK default action on ENTER (add new line)
- # normally I would add in keypress but it gets way to complex
- # to get instant result on changing this advanced setting
- if event.state == 0: # no ctrl, no shift just ENTER add newline
- end_iter = message_buffer.get_end_iter()
- message_buffer.insert_at_cursor('\n')
- send_message = False
- elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER
- send_message = True
- else: # send on Enter, do newline on Ctrl Enter
- if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER
- end_iter = message_buffer.get_end_iter()
- message_buffer.insert_at_cursor('\n')
- send_message = False
- else: # ENTER
- send_message = True
-
- if gajim.connections[self.account].connected < 2 and send_message:
- # we are not connected
- dialogs.ErrorDialog(_('A connection is not available'),
- _('Your message can not be sent until you are connected.'))
- send_message = False
-
- if send_message:
- self.send_message(message, xhtml=xhtml) # send the message
- elif event.keyval == gtk.keysyms.z: # CTRL+z
- if event.state & gtk.gdk.CONTROL_MASK:
- self.msg_textview.undo()
- else:
- # Give the control itself a chance to process
- self.handle_message_textview_mykey_press(widget, event_keyval,
- event_keymod)
-
- def _on_drag_data_received(self, widget, context, x, y, selection,
- target_type, timestamp):
- """
- Derived types SHOULD implement this
- """
- pass
-
- def _on_drag_leave(self, widget, context, time):
- # FIXME: DND on non editable TextView, find a better way
- self.drag_entered = False
- self.conv_textview.tv.set_editable(False)
-
- def _on_drag_motion(self, widget, context, x, y, time):
- # FIXME: DND on non editable TextView, find a better way
- if not self.drag_entered:
- # We drag new data over the TextView, make it editable to catch dnd
- self.drag_entered_conv = True
- self.conv_textview.tv.set_editable(True)
-
- def send_message(self, message, keyID='', type_='chat', chatstate=None,
- msg_id=None, composing_xep=None, resource=None, xhtml=None,
- callback=None, callback_args=[], process_commands=True):
- """
- Send the given message to the active tab. Doesn't return None if error
- """
- if not message or message == '\n':
- return None
-
- if process_commands and self.process_as_command(message):
- return
-
- MessageControl.send_message(self, message, keyID, type_=type_,
- chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
- resource=resource, user_nick=self.user_nick, xhtml=xhtml,
- callback=callback, callback_args=callback_args)
-
- # Record message history
- self.save_sent_message(message)
-
- # Be sure to send user nickname only once according to JEP-0172
- self.user_nick = None
-
- # Clear msg input
- message_buffer = self.msg_textview.get_buffer()
- message_buffer.set_text('') # clear message buffer (and tv of course)
-
- def save_sent_message(self, message):
- # save the message, so user can scroll though the list with key up/down
- size = len(self.sent_history)
- # we don't want size of the buffer to grow indefinately
- max_size = gajim.config.get('key_up_lines')
- if size >= max_size:
- for i in xrange(0, size - 1):
- self.sent_history[i] = self.sent_history[i + 1]
- self.sent_history[max_size - 1] = message
- # self.sent_history_pos has changed if we browsed sent_history,
- # reset to real value
- self.sent_history_pos = max_size
- else:
- self.sent_history.append(message)
- self.sent_history_pos = size + 1
- self.orig_msg = None
-
- def print_conversation_line(self, text, kind, name, tim,
- other_tags_for_name=[], other_tags_for_time=[],
- other_tags_for_text=[], count_as_new=True, subject=None,
- old_kind=None, xhtml=None, simple=False, xep0184_id=None,
- graphics=True):
- """
- Print 'chat' type messages
- """
- jid = self.contact.jid
- full_jid = self.get_full_jid()
- textview = self.conv_textview
- end = False
- if self.was_at_the_end or kind == 'outgoing':
- end = True
- textview.print_conversation_line(text, jid, kind, name, tim,
- other_tags_for_name, other_tags_for_time, other_tags_for_text,
- subject, old_kind, xhtml, simple=simple, graphics=graphics)
-
- if xep0184_id is not None:
- textview.show_xep0184_warning(xep0184_id)
-
- if not count_as_new:
- return
- if kind == 'incoming':
- if not self.type_id == message_control.TYPE_GC or \
- gajim.config.get('notify_on_all_muc_messages') or \
- 'marked' in other_tags_for_text:
- # it's a normal message, or a muc message with want to be
- # notified about if quitting just after
- # other_tags_for_text == ['marked'] --> highlighted gc message
- gajim.last_message_time[self.account][full_jid] = time.time()
-
- if kind in ('incoming', 'incoming_queue', 'error'):
- gc_message = False
- if self.type_id == message_control.TYPE_GC:
- gc_message = True
-
- if ((self.parent_win and (not self.parent_win.get_active_control() or \
- self != self.parent_win.get_active_control() or \
- not self.parent_win.is_active() or not end)) or \
- (gc_message and \
- jid in gajim.interface.minimized_controls[self.account])) and \
- kind in ('incoming', 'incoming_queue', 'error'):
- # we want to have save this message in events list
- # other_tags_for_text == ['marked'] --> highlighted gc message
- if gc_message:
- if 'marked' in other_tags_for_text:
- type_ = 'printed_marked_gc_msg'
- else:
- type_ = 'printed_gc_msg'
- event = 'gc_message_received'
- else:
- type_ = 'printed_' + self.type_id
- event = 'message_received'
- show_in_roster = notify.get_show_in_roster(event,
- self.account, self.contact, self.session)
- show_in_systray = notify.get_show_in_systray(event,
- self.account, self.contact, type_)
-
- event = gajim.events.create_event(type_, (self,),
- show_in_roster = show_in_roster,
- show_in_systray = show_in_systray)
- gajim.events.add_event(self.account, full_jid, event)
- # We need to redraw contact if we show in roster
- if show_in_roster:
- gajim.interface.roster.draw_contact(self.contact.jid,
- self.account)
-
- if not self.parent_win:
- return
-
- if (not self.parent_win.get_active_control() or \
- self != self.parent_win.get_active_control() or \
- not self.parent_win.is_active() or not end) and \
- kind in ('incoming', 'incoming_queue', 'error'):
- self.parent_win.redraw_tab(self)
- if not self.parent_win.is_active():
- self.parent_win.show_title(True, self) # Enabled Urgent hint
- else:
- self.parent_win.show_title(False, self) # Disabled Urgent hint
-
- def toggle_emoticons(self):
- """
- Hide show emoticons_button and make sure emoticons_menu is always there
- when needed
- """
- emoticons_button = self.xml.get_object('emoticons_button')
- if gajim.config.get('emoticons_theme'):
- emoticons_button.show()
- emoticons_button.set_no_show_all(False)
- else:
- emoticons_button.hide()
- emoticons_button.set_no_show_all(True)
-
- def append_emoticon(self, str_):
- buffer_ = self.msg_textview.get_buffer()
- if buffer_.get_char_count():
- buffer_.insert_at_cursor(' %s ' % str_)
- else: # we are the beginning of buffer
- buffer_.insert_at_cursor('%s ' % str_)
- self.msg_textview.grab_focus()
-
- def on_emoticons_button_clicked(self, widget):
- """
- Popup emoticons menu
- """
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
- gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
-
- def on_formattings_button_clicked(self, widget):
- """
- Popup formattings menu
- """
- menu = gtk.Menu()
-
- menuitems = ((_('Bold'), 'bold'),
- (_('Italic'), 'italic'),
- (_('Underline'), 'underline'),
- (_('Strike'), 'strike'))
-
- active_tags = self.msg_textview.get_active_tags()
-
- for menuitem in menuitems:
- item = gtk.CheckMenuItem(menuitem[0])
- if menuitem[1] in active_tags:
- item.set_active(True)
- else:
- item.set_active(False)
- item.connect('activate', self.msg_textview.set_tag,
- menuitem[1])
- menu.append(item)
-
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- item = gtk.ImageMenuItem(_('Color'))
- icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- item.connect('activate', self.on_color_menuitem_activale)
- menu.append(item)
-
- item = gtk.ImageMenuItem(_('Font'))
- icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_FONT, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- item.connect('activate', self.on_font_menuitem_activale)
- menu.append(item)
-
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- item = gtk.ImageMenuItem(_('Clear formating'))
- icon = gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- item.connect('activate', self.msg_textview.clear_tags)
- menu.append(item)
-
- menu.show_all()
- gtkgui_helpers.popup_emoticons_under_button(menu, widget,
- self.parent_win)
-
- def on_color_menuitem_activale(self, widget):
- color_dialog = gtk.ColorSelectionDialog('Select a color')
- color_dialog.connect('response', self.msg_textview.color_set,
- color_dialog.colorsel)
- color_dialog.show_all()
-
- def on_font_menuitem_activale(self, widget):
- font_dialog = gtk.FontSelectionDialog('Select a font')
- font_dialog.connect('response', self.msg_textview.font_set,
- font_dialog.fontsel)
- font_dialog.show_all()
-
-
- def on_actions_button_clicked(self, widget):
- """
- Popup action menu
- """
- menu = self.prepare_context_menu(hide_buttonbar_items=True)
- menu.show_all()
- gtkgui_helpers.popup_emoticons_under_button(menu, widget,
- self.parent_win)
-
- def update_font(self):
- font = pango.FontDescription(gajim.config.get('conversation_font'))
- self.conv_textview.tv.modify_font(font)
- self.msg_textview.modify_font(font)
-
- def update_tags(self):
- self.conv_textview.update_tags()
-
- def clear(self, tv):
- buffer_ = tv.get_buffer()
- start, end = buffer_.get_bounds()
- buffer_.delete(start, end)
-
- def _on_history_menuitem_activate(self, widget = None, jid = None):
- """
- When history menuitem is pressed: call history window
- """
- if not jid:
- jid = self.contact.jid
-
- if 'logs' in gajim.interface.instances:
- gajim.interface.instances['logs'].window.present()
- gajim.interface.instances['logs'].open_history(jid, self.account)
- else:
- gajim.interface.instances['logs'] = \
- history_window.HistoryWindow(jid, self.account)
-
- def _on_send_file(self, gc_contact=None):
- """
- gc_contact can be set when we are in a groupchat control
- """
- def _on_ok(c):
- gajim.interface.instances['file_transfers'].show_file_send_request(
- self.account, c)
- if self.TYPE_ID == message_control.TYPE_PM:
- gc_contact = self.gc_contact
- if gc_contact:
- # gc or pm
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(
- gc_contact.room_jid, self.account)
- self_contact = gajim.contacts.get_gc_contact(self.account,
- gc_control.room_jid, gc_control.nick)
- if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
- 'owner'] and self_contact.affiliation in ['admin', 'owner']:
- contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
- if not contact or contact.sub not in ('both', 'to'):
- prim_text = _('Really send file?')
- sec_text = _('If you send a file to %s, he/she will know your '
- 'real Jabber ID.') % gc_contact.name
- dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
- on_response_ok = (_on_ok, gc_contact))
- dialog.popup()
- return
- _on_ok(gc_contact)
- return
- _on_ok(self.contact)
-
- def on_minimize_menuitem_toggled(self, widget):
- """
- When a grouchat is minimized, unparent the tab, put it in roster etc
- """
- old_value = False
- minimized_gc = gajim.config.get_per('accounts', self.account,
- 'minimized_gc').split()
- if self.contact.jid in minimized_gc:
- old_value = True
- minimize = widget.get_active()
- if minimize and not self.contact.jid in minimized_gc:
- minimized_gc.append(self.contact.jid)
- if not minimize and self.contact.jid in minimized_gc:
- minimized_gc.remove(self.contact.jid)
- if old_value != minimize:
- gajim.config.set_per('accounts', self.account, 'minimized_gc',
- ' '.join(minimized_gc))
-
- def set_control_active(self, state):
- if state:
- jid = self.contact.jid
- if self.was_at_the_end:
- # we are at the end
- type_ = ['printed_' + self.type_id]
- if self.type_id == message_control.TYPE_GC:
- type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
- if not gajim.events.remove_events(self.account, self.get_full_jid(),
- types = type_):
- # There were events to remove
- self.redraw_after_event_removed(jid)
-
-
- def bring_scroll_to_end(self, textview, diff_y = 0):
- """
- Scroll to the end of textview if end is not visible
- """
- if self.scroll_to_end_id:
- # a scroll is already planned
- return
- buffer_ = textview.get_buffer()
- end_iter = buffer_.get_end_iter()
- end_rect = textview.get_iter_location(end_iter)
- visible_rect = textview.get_visible_rect()
- # scroll only if expected end is not visible
- if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
- self.scroll_to_end_id = gobject.idle_add(self.scroll_to_end_iter,
- textview)
-
- def scroll_to_end_iter(self, textview):
- buffer_ = textview.get_buffer()
- end_iter = buffer_.get_end_iter()
- textview.scroll_to_iter(end_iter, 0, False, 1, 1)
- self.scroll_to_end_id = None
- return False
-
- def size_request(self, msg_textview , requisition):
- """
- When message_textview changes its size: if the new height will enlarge
- the window, enable the scrollbar automatic policy. Also enable scrollbar
- automatic policy for horizontal scrollbar if message we have in
- message_textview is too big
- """
- if msg_textview.window is None:
- return
-
- min_height = self.conv_scrolledwindow.get_property('height-request')
- conversation_height = self.conv_textview.tv.window.get_size()[1]
- message_height = msg_textview.window.get_size()[1]
- message_width = msg_textview.window.get_size()[0]
- # new tab is not exposed yet
- if conversation_height < 2:
- return
-
- if conversation_height < min_height:
- min_height = conversation_height
-
- # we don't want to always resize in height the message_textview
- # so we have minimum on conversation_textview's scrolled window
- # but we also want to avoid window resizing so if we reach that
- # minimum for conversation_textview and maximum for message_textview
- # we set to automatic the scrollbar policy
- diff_y = message_height - requisition.height
- if diff_y != 0:
- if conversation_height + diff_y < min_height:
- if message_height + conversation_height - min_height > min_height:
- policy = self.msg_scrolledwindow.get_property(
- 'vscrollbar-policy')
- # scroll only when scrollbar appear
- if policy != gtk.POLICY_AUTOMATIC:
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
- gtk.POLICY_AUTOMATIC)
- self.msg_scrolledwindow.set_property('height-request',
- message_height + conversation_height - min_height)
- self.bring_scroll_to_end(msg_textview)
- else:
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
- gtk.POLICY_NEVER)
- self.msg_scrolledwindow.set_property('height-request', -1)
- self.conv_textview.bring_scroll_to_end(diff_y - 18, False)
- else:
- self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth)
- self.smooth = True # reinit the flag
- # enable scrollbar automatic policy for horizontal scrollbar
- # if message we have in message_textview is too big
- if requisition.width > message_width:
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
- gtk.POLICY_AUTOMATIC)
- else:
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
- gtk.POLICY_NEVER)
-
- return True
-
- def on_conversation_vadjustment_changed(self, adjustment):
- # used to stay at the end of the textview when we shrink conversation
- # textview.
- if self.was_at_the_end:
- self.conv_textview.bring_scroll_to_end(-18)
- self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18
-
- def on_conversation_vadjustment_value_changed(self, adjustment):
- # stop automatic scroll when we manually scroll
- if not self.conv_textview.auto_scrolling:
- self.conv_textview.stop_scrolling()
- self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18
- if self.resource:
- jid = self.contact.get_full_jid()
- else:
- jid = self.contact.jid
- types_list = []
- type_ = self.type_id
- if type_ == message_control.TYPE_GC:
- type_ = 'gc_msg'
- types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
- else: # Not a GC
- types_list = ['printed_' + type_, type_]
-
- if not len(gajim.events.get_events(self.account, jid, types_list)):
- return
- if not self.parent_win:
- return
- if self.conv_textview.at_the_end() and \
- self.parent_win.get_active_control() == self and \
- self.parent_win.window.is_active():
- # we are at the end
- if self.type_id == message_control.TYPE_GC:
- if not gajim.events.remove_events(self.account, jid,
- types=types_list):
- self.redraw_after_event_removed(jid)
- elif self.session and self.session.remove_events(types_list):
- # There were events to remove
- self.redraw_after_event_removed(jid)
-
- def redraw_after_event_removed(self, jid):
- """
- We just removed a 'printed_*' event, redraw contact in roster or
- gc_roster and titles in roster and msg_win
- """
- self.parent_win.redraw_tab(self)
- self.parent_win.show_title()
- # TODO : get the contact and check notify.get_show_in_roster()
- if self.type_id == message_control.TYPE_PM:
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
- groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
- room_jid, self.account)
- if room_jid in gajim.interface.minimized_controls[self.account]:
- groupchat_control = \
- gajim.interface.minimized_controls[self.account][room_jid]
- contact = \
- gajim.contacts.get_contact_with_highest_priority(self.account, \
- room_jid)
- if contact:
- gajim.interface.roster.draw_contact(room_jid, self.account)
- if groupchat_control:
- groupchat_control.draw_contact(nick)
- if groupchat_control.parent_win:
- groupchat_control.parent_win.redraw_tab(groupchat_control)
- else:
- gajim.interface.roster.draw_contact(jid, self.account)
- gajim.interface.roster.show_title()
-
- def sent_messages_scroll(self, direction, conv_buf):
- size = len(self.sent_history)
- if self.orig_msg is None:
- # user was typing something and then went into history, so save
- # whatever is already typed
- start_iter = conv_buf.get_start_iter()
- end_iter = conv_buf.get_end_iter()
- self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode(
- 'utf-8')
- if direction == 'up':
- if self.sent_history_pos == 0:
- return
- self.sent_history_pos = self.sent_history_pos - 1
- self.smooth = False
- conv_buf.set_text(self.sent_history[self.sent_history_pos])
- elif direction == 'down':
- if self.sent_history_pos >= size - 1:
- conv_buf.set_text(self.orig_msg)
- self.orig_msg = None
- self.sent_history_pos = size
- return
-
- self.sent_history_pos = self.sent_history_pos + 1
- self.smooth = False
- conv_buf.set_text(self.sent_history[self.sent_history_pos])
-
- def lighten_color(self, color):
- p = 0.4
- mask = 0
- color.red = int((color.red * p) + (mask * (1 - p)))
- color.green = int((color.green * p) + (mask * (1 - p)))
- color.blue = int((color.blue * p) + (mask * (1 - p)))
- return color
-
- def widget_set_visible(self, widget, state):
- """
- Show or hide a widget
- """
- # make the last message visible, when changing to "full view"
- if not state:
- gobject.idle_add(self.conv_textview.scroll_to_end_iter)
-
- widget.set_no_show_all(state)
- if state:
- widget.hide()
- else:
- widget.show_all()
-
- def chat_buttons_set_visible(self, state):
- """
- Toggle chat buttons
- """
- MessageControl.chat_buttons_set_visible(self, state)
- self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
-
- def got_connected(self):
- self.msg_textview.set_sensitive(True)
- self.msg_textview.set_editable(True)
- # FIXME: Set sensitivity for toolbar
-
- def got_disconnected(self):
- self.msg_textview.set_sensitive(False)
- self.msg_textview.set_editable(False)
- self.conv_textview.tv.grab_focus()
-
- self.no_autonegotiation = False
- # FIXME: Set sensitivity for toolbar
+ """
+ A base class containing a banner, ConversationTextview, MessageTextView
+ """
+
+ def make_href(self, match):
+ url_color = gajim.config.get('urlmsgcolor')
+ url = match.group()
+ if not '://' in url:
+ url = 'http://' + url
+ return '<a href="%s"><span color="%s">%s</span></a>' % (url,
+ url_color, match.group())
+
+ def get_font_attrs(self):
+ """
+ Get pango font attributes for banner from theme settings
+ """
+ theme = gajim.config.get('roster_theme')
+ bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
+ bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
+
+ if bannerfont:
+ font = pango.FontDescription(bannerfont)
+ else:
+ font = pango.FontDescription('Normal')
+ if bannerfontattrs:
+ # B attribute is set by default
+ if 'B' in bannerfontattrs:
+ font.set_weight(pango.WEIGHT_HEAVY)
+ if 'I' in bannerfontattrs:
+ font.set_style(pango.STYLE_ITALIC)
+
+ font_attrs = 'font_desc="%s"' % font.to_string()
+
+ # in case there is no font specified we use x-large font size
+ if font.get_size() == 0:
+ font_attrs = '%s size="x-large"' % font_attrs
+ font.set_weight(pango.WEIGHT_NORMAL)
+ font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
+ return (font_attrs, font_attrs_small)
+
+ def get_nb_unread(self):
+ jid = self.contact.jid
+ if self.resource:
+ jid += '/' + self.resource
+ type_ = self.type_id
+ return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
+ type_]))
+
+ def draw_banner(self):
+ """
+ Draw the fat line at the top of the window that houses the icon, jid, etc
+
+ Derived types MAY implement this.
+ """
+ self.draw_banner_text()
+ self._update_banner_state_image()
+
+ def draw_banner_text(self):
+ """
+ Derived types SHOULD implement this
+ """
+ pass
+
+ def update_ui(self):
+ """
+ Derived types SHOULD implement this
+ """
+ self.draw_banner()
+
+ def repaint_themed_widgets(self):
+ """
+ Derived types MAY implement this
+ """
+ self._paint_banner()
+ self.draw_banner()
+
+ def _update_banner_state_image(self):
+ """
+ Derived types MAY implement this
+ """
+ pass
+
+ def handle_message_textview_mykey_press(self, widget, event_keyval,
+ event_keymod):
+ """
+ Derives types SHOULD implement this, rather than connection to the even
+ itself
+ """
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
+ event.keyval = event_keyval
+ event.state = event_keymod
+ event.time = 0
+
+ _buffer = widget.get_buffer()
+ start, end = _buffer.get_bounds()
+
+ if event.keyval -- gtk.keysyms.Tab:
+ position = _buffer.get_insert()
+ end = _buffer.get_iter_at_mark(position)
+
+ text = _buffer.get_text(start, end, False)
+ text = text.decode('utf8')
+
+ splitted = text.split()
+
+ if (text.startswith(self.COMMAND_PREFIX) and not
+ text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
+
+ text = splitted[0]
+ bare = text.lstrip(self.COMMAND_PREFIX)
+
+ if len(text) == 1:
+ self.command_hits = []
+ for command in self.list_commands():
+ for name in command.names:
+ self.command_hits.append(name)
+ else:
+ if (self.last_key_tabs and self.command_hits and
+ self.command_hits[0].startswith(bare)):
+ self.command_hits.append(self.command_hits.pop(0))
+ else:
+ self.command_hits = []
+ for command in self.list_commands():
+ for name in command.names:
+ if name.startswith(bare):
+ self.command_hits.append(name)
+
+ if self.command_hits:
+ _buffer.delete(start, end)
+ _buffer.insert_at_cursor(self.COMMAND_PREFIX + self.command_hits[0] + ' ')
+ self.last_key_tabs = True
+
+ return True
+
+ self.last_key_tabs = False
+
+ def status_url_clicked(self, widget, url):
+ helpers.launch_browser_mailer('url', url)
+
+ def __init__(self, type_id, parent_win, widget_name, contact, acct,
+ resource=None):
+ # Undo needs this variable to know if space has been pressed.
+ # Initialize it to True so empty textview is saved in undo list
+ self.space_pressed = True
+
+ if resource is None:
+ # We very likely got a contact with a random resource.
+ # This is bad, we need the highest for caps etc.
+ c = gajim.contacts.get_contact_with_highest_priority(
+ acct, contact.jid)
+ if c and not isinstance(c, GC_Contact):
+ contact = c
+
+ MessageControl.__init__(self, type_id, parent_win, widget_name,
+ contact, acct, resource=resource)
+
+ widget = self.xml.get_object('history_button')
+ id_ = widget.connect('clicked', self._on_history_menuitem_activate)
+ self.handlers[id_] = widget
+
+ # when/if we do XHTML we will put formatting buttons back
+ widget = self.xml.get_object('emoticons_button')
+ id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
+ self.handlers[id_] = widget
+
+ # Create banner and connect signals
+ widget = self.xml.get_object('banner_eventbox')
+ id_ = widget.connect('button-press-event',
+ self._on_banner_eventbox_button_press_event)
+ self.handlers[id_] = widget
+
+ self.urlfinder = re.compile(
+ r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
+
+ self.banner_status_label = self.xml.get_object('banner_label')
+ id_ = self.banner_status_label.connect('populate_popup',
+ self.on_banner_label_populate_popup)
+ self.handlers[id_] = self.banner_status_label
+
+ # Init DND
+ self.TARGET_TYPE_URI_LIST = 80
+ self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ),
+ ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0)]
+ id_ = self.widget.connect('drag_data_received',
+ self._on_drag_data_received)
+ self.handlers[id_] = self.widget
+ self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
+ gtk.DEST_DEFAULT_HIGHLIGHT |
+ gtk.DEST_DEFAULT_DROP,
+ self.dnd_list, gtk.gdk.ACTION_COPY)
+
+ # Create textviews and connect signals
+ self.conv_textview = ConversationTextview(self.account)
+ id_ = self.conv_textview.connect('quote', self.on_quote)
+ self.handlers[id_] = self.conv_textview.tv
+ id_ = self.conv_textview.tv.connect('key_press_event',
+ self._conv_textview_key_press_event)
+ self.handlers[id_] = self.conv_textview.tv
+ # FIXME: DND on non editable TextView, find a better way
+ self.drag_entered = False
+ id_ = self.conv_textview.tv.connect('drag_data_received',
+ self._on_drag_data_received)
+ self.handlers[id_] = self.conv_textview.tv
+ id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
+ self.handlers[id_] = self.conv_textview.tv
+ id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
+ self.handlers[id_] = self.conv_textview.tv
+ self.conv_textview.tv.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
+ gtk.DEST_DEFAULT_HIGHLIGHT |
+ gtk.DEST_DEFAULT_DROP,
+ self.dnd_list, gtk.gdk.ACTION_COPY)
+
+ self.conv_scrolledwindow = self.xml.get_object(
+ 'conversation_scrolledwindow')
+ self.conv_scrolledwindow.add(self.conv_textview.tv)
+ widget = self.conv_scrolledwindow.get_vadjustment()
+ id_ = widget.connect('value-changed',
+ self.on_conversation_vadjustment_value_changed)
+ self.handlers[id_] = widget
+ id_ = widget.connect('changed',
+ self.on_conversation_vadjustment_changed)
+ self.handlers[id_] = widget
+ self.scroll_to_end_id = None
+ self.was_at_the_end = True
+
+ # add MessageTextView to UI and connect signals
+ self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
+ self.msg_textview = MessageTextView()
+ id_ = self.msg_textview.connect('mykeypress',
+ self._on_message_textview_mykeypress_event)
+ self.handlers[id_] = self.msg_textview
+ self.msg_scrolledwindow.add(self.msg_textview)
+ id_ = self.msg_textview.connect('key_press_event',
+ self._on_message_textview_key_press_event)
+ self.handlers[id_] = self.msg_textview
+ id_ = self.msg_textview.connect('size-request', self.size_request)
+ self.handlers[id_] = self.msg_textview
+ id_ = self.msg_textview.connect('populate_popup',
+ self.on_msg_textview_populate_popup)
+ self.handlers[id_] = self.msg_textview
+ # Setup DND
+ id_ = self.msg_textview.connect('drag_data_received',
+ self._on_drag_data_received)
+ self.handlers[id_] = self.msg_textview
+ self.msg_textview.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
+ gtk.DEST_DEFAULT_HIGHLIGHT,
+ self.dnd_list, gtk.gdk.ACTION_COPY)
+
+ self.update_font()
+
+ # Hook up send button
+ widget = self.xml.get_object('send_button')
+ id_ = widget.connect('clicked', self._on_send_button_clicked)
+ self.handlers[id_] = widget
+
+ widget = self.xml.get_object('formattings_button')
+ id_ = widget.connect('clicked', self.on_formattings_button_clicked)
+ self.handlers[id_] = widget
+
+ # the following vars are used to keep history of user's messages
+ self.sent_history = []
+ self.sent_history_pos = 0
+ self.orig_msg = None
+
+ # Emoticons menu
+ # set image no matter if user wants at this time emoticons or not
+ # (so toggle works ok)
+ img = self.xml.get_object('emoticons_button_image')
+ img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
+ 'smile.png'))
+ self.toggle_emoticons()
+
+ # Attach speller
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
+ self.set_speller()
+ self.conv_textview.tv.show()
+ self._paint_banner()
+
+ # For XEP-0172
+ self.user_nick = None
+
+ self.smooth = True
+ self.msg_textview.grab_focus()
+
+ self.command_hits = []
+ self.last_key_tabs = False
+
+ def set_speller(self):
+ # now set the one the user selected
+ per_type = 'contacts'
+ if self.type_id == message_control.TYPE_GC:
+ per_type = 'rooms'
+ lang = gajim.config.get_per(per_type, self.contact.jid,
+ 'speller_language')
+ if not lang:
+ # use the default one
+ lang = gajim.config.get('speller_language')
+ if not lang:
+ lang = gajim.LANG
+ if lang:
+ try:
+ gtkspell.Spell(self.msg_textview, lang)
+ self.msg_textview.lang = lang
+ except (gobject.GError, RuntimeError, TypeError, OSError):
+ dialogs.AspellDictError(lang)
+
+ def on_banner_label_populate_popup(self, label, menu):
+ """
+ Override the default context menu and add our own menutiems
+ """
+ item = gtk.SeparatorMenuItem()
+ menu.prepend(item)
+
+ menu2 = self.prepare_context_menu()
+ i = 0
+ for item in menu2:
+ menu2.remove(item)
+ menu.prepend(item)
+ menu.reorder_child(item, i)
+ i += 1
+ menu.show_all()
+
+ def on_msg_textview_populate_popup(self, textview, menu):
+ """
+ Override the default context menu and we prepend an option to switch
+ languages
+ """
+ def _on_select_dictionary(widget, lang):
+ per_type = 'contacts'
+ if self.type_id == message_control.TYPE_GC:
+ per_type = 'rooms'
+ if not gajim.config.get_per(per_type, self.contact.jid):
+ gajim.config.add_per(per_type, self.contact.jid)
+ gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
+ lang)
+ spell = gtkspell.get_from_text_view(self.msg_textview)
+ self.msg_textview.lang = lang
+ spell.set_language(lang)
+ widget.set_active(True)
+
+ item = gtk.ImageMenuItem(gtk.STOCK_UNDO)
+ menu.prepend(item)
+ id_ = item.connect('activate', self.msg_textview.undo)
+ self.handlers[id_] = item
+
+ item = gtk.SeparatorMenuItem()
+ menu.prepend(item)
+
+ item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+ menu.prepend(item)
+ id_ = item.connect('activate', self.msg_textview.clear)
+ self.handlers[id_] = item
+
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
+ item = gtk.MenuItem(_('Spelling language'))
+ menu.prepend(item)
+ submenu = gtk.Menu()
+ item.set_submenu(submenu)
+ for lang in sorted(langs):
+ item = gtk.CheckMenuItem(lang)
+ if langs[lang] == self.msg_textview.lang:
+ item.set_active(True)
+ submenu.append(item)
+ id_ = item.connect('activate', _on_select_dictionary, langs[lang])
+ self.handlers[id_] = item
+
+ menu.show_all()
+
+ def on_quote(self, widget, text):
+ text = '>' + text.replace('\n', '\n>') + '\n'
+ message_buffer = self.msg_textview.get_buffer()
+ message_buffer.insert_at_cursor(text)
+
+ # moved from ChatControl
+ def _on_banner_eventbox_button_press_event(self, widget, event):
+ """
+ If right-clicked, show popup
+ """
+ if event.button == 3: # right click
+ self.parent_win.popup_menu(event)
+
+ def _on_send_button_clicked(self, widget):
+ """
+ When send button is pressed: send the current message
+ """
+ if gajim.connections[self.account].connected < 2: # we are not connected
+ dialogs.ErrorDialog(_('A connection is not available'),
+ _('Your message can not be sent until you are connected.'))
+ return
+ message_buffer = self.msg_textview.get_buffer()
+ start_iter = message_buffer.get_start_iter()
+ end_iter = message_buffer.get_end_iter()
+ message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8')
+ xhtml = self.msg_textview.get_xhtml()
+
+ # send the message
+ self.send_message(message, xhtml=xhtml)
+
+ def _paint_banner(self):
+ """
+ Repaint banner with theme color
+ """
+ theme = gajim.config.get('roster_theme')
+ bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
+ textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
+ # the backgrounds are colored by using an eventbox by
+ # setting the bg color of the eventbox and the fg of the name_label
+ banner_eventbox = self.xml.get_object('banner_eventbox')
+ banner_name_label = self.xml.get_object('banner_name_label')
+ self.disconnect_style_event(banner_name_label)
+ self.disconnect_style_event(self.banner_status_label)
+ if bgcolor:
+ banner_eventbox.modify_bg(gtk.STATE_NORMAL,
+ gtk.gdk.color_parse(bgcolor))
+ default_bg = False
+ else:
+ default_bg = True
+ if textcolor:
+ banner_name_label.modify_fg(gtk.STATE_NORMAL,
+ gtk.gdk.color_parse(textcolor))
+ self.banner_status_label.modify_fg(gtk.STATE_NORMAL,
+ gtk.gdk.color_parse(textcolor))
+ default_fg = False
+ else:
+ default_fg = True
+ if default_bg or default_fg:
+ self._on_style_set_event(banner_name_label, None, default_fg,
+ default_bg)
+ if self.banner_status_label.flags() & gtk.REALIZED:
+ # Widget is realized
+ self._on_style_set_event(self.banner_status_label, None, default_fg,
+ default_bg)
+
+ def disconnect_style_event(self, widget):
+ # Try to find the event_id
+ for id_ in self.handlers.keys():
+ if self.handlers[id_] == widget:
+ widget.disconnect(id_)
+ del self.handlers[id_]
+ break
+
+ def connect_style_event(self, widget, set_fg = False, set_bg = False):
+ self.disconnect_style_event(widget)
+ id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
+ set_bg)
+ self.handlers[id_] = widget
+
+ def _on_style_set_event(self, widget, style, *opts):
+ """
+ Set style of widget from style class *.Frame.Eventbox
+ opts[0] == True -> set fg color
+ opts[1] == True -> set bg color
+ """
+ banner_eventbox = self.xml.get_object('banner_eventbox')
+ self.disconnect_style_event(widget)
+ if opts[1]:
+ bg_color = widget.style.bg[gtk.STATE_SELECTED]
+ banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
+ if opts[0]:
+ fg_color = widget.style.fg[gtk.STATE_SELECTED]
+ widget.modify_fg(gtk.STATE_NORMAL, fg_color)
+ self.connect_style_event(widget, opts[0], opts[1])
+
+ def _conv_textview_key_press_event(self, widget, event):
+ if (event.state & gtk.gdk.CONTROL_MASK and event.keyval in (gtk.keysyms.c,
+ gtk.keysyms.Insert)) or (event.state & gtk.gdk.SHIFT_MASK and \
+ event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)):
+ return False
+ self.parent_win.notebook.emit('key_press_event', event)
+ return True
+
+ def show_emoticons_menu(self):
+ if not gajim.config.get('emoticons_theme'):
+ return
+ def set_emoticons_menu_position(w, msg_tv = self.msg_textview):
+ window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET)
+ # get the window position
+ origin = window.get_origin()
+ size = window.get_size()
+ buf = msg_tv.get_buffer()
+ # get the cursor position
+ cursor = msg_tv.get_iter_location(buf.get_iter_at_mark(
+ buf.get_insert()))
+ cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
+ cursor.x, cursor.y)
+ x = origin[0] + cursor[0]
+ y = origin[1] + size[1]
+ menu_height = gajim.interface.emoticons_menu.size_request()[1]
+ #FIXME: get_line_count is not so good
+ #get the iter of cursor, then tv.get_line_yrange
+ # so we know in which y we are typing (not how many lines we have
+ # then go show just above the current cursor line for up
+ # or just below the current cursor line for down
+ #TEST with having 3 lines and writing in the 2nd
+ if y + menu_height > gtk.gdk.screen_height():
+ # move menu just above cursor
+ y -= menu_height + (msg_tv.allocation.height / buf.get_line_count())
+ #else: # move menu just below cursor
+ # y -= (msg_tv.allocation.height / buf.get_line_count())
+ return (x, y, True) # push_in True
+ gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
+ gajim.interface.emoticons_menu.popup(None, None,
+ set_emoticons_menu_position, 1, 0)
+
+ def _on_message_textview_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.space:
+ self.space_pressed = True
+
+ elif (self.space_pressed or self.msg_textview.undo_pressed) and \
+ event.keyval not in (gtk.keysyms.Control_L, gtk.keysyms.Control_R) and \
+ not (event.keyval == gtk.keysyms.z and event.state & gtk.gdk.CONTROL_MASK):
+ # If the space key has been pressed and now it hasnt,
+ # we save the buffer into the undo list. But be carefull we're not
+ # pressiong Control again (as in ctrl+z)
+ _buffer = widget.get_buffer()
+ start_iter, end_iter = _buffer.get_bounds()
+ self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter))
+ self.space_pressed = False
+
+ # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
+ if self.widget_name == 'groupchat_control':
+ if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab):
+ self.last_key_tabs = False
+ if event.state & gtk.gdk.SHIFT_MASK:
+ # CTRL + SHIFT + TAB
+ if event.state & gtk.gdk.CONTROL_MASK and \
+ event.keyval == gtk.keysyms.ISO_Left_Tab:
+ self.parent_win.move_to_next_unread_tab(False)
+ return True
+ # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
+ elif event.keyval == gtk.keysyms.Page_Down or \
+ event.keyval == gtk.keysyms.Page_Up:
+ self.conv_textview.tv.emit('key_press_event', event)
+ return True
+ elif event.state & gtk.gdk.CONTROL_MASK:
+ if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
+ self.parent_win.move_to_next_unread_tab(True)
+ return True
+ return False
+
+ def _on_message_textview_mykeypress_event(self, widget, event_keyval,
+ event_keymod):
+ """
+ When a key is pressed: if enter is pressed without the shift key, message
+ (if not empty) is sent and printed in the conversation
+ """
+ # NOTE: handles mykeypress which is custom signal connected to this
+ # CB in new_tab(). for this singal see message_textview.py
+ message_textview = widget
+ message_buffer = message_textview.get_buffer()
+ start_iter, end_iter = message_buffer.get_bounds()
+ message = message_buffer.get_text(start_iter, end_iter, False).decode(
+ 'utf-8')
+ xhtml = self.msg_textview.get_xhtml()
+
+ # construct event instance from binding
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
+ event.keyval = event_keyval
+ event.state = event_keymod
+ event.time = 0 # assign current time
+
+ if event.keyval == gtk.keysyms.Up:
+ if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
+ self.sent_messages_scroll('up', widget.get_buffer())
+ elif event.keyval == gtk.keysyms.Down:
+ if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down
+ self.sent_messages_scroll('down', widget.get_buffer())
+ elif event.keyval == gtk.keysyms.Return or \
+ event.keyval == gtk.keysyms.KP_Enter: # ENTER
+ # NOTE: SHIFT + ENTER is not needed to be emulated as it is not
+ # binding at all (textview's default action is newline)
+
+ if gajim.config.get('send_on_ctrl_enter'):
+ # here, we emulate GTK default action on ENTER (add new line)
+ # normally I would add in keypress but it gets way to complex
+ # to get instant result on changing this advanced setting
+ if event.state == 0: # no ctrl, no shift just ENTER add newline
+ end_iter = message_buffer.get_end_iter()
+ message_buffer.insert_at_cursor('\n')
+ send_message = False
+ elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER
+ send_message = True
+ else: # send on Enter, do newline on Ctrl Enter
+ if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER
+ end_iter = message_buffer.get_end_iter()
+ message_buffer.insert_at_cursor('\n')
+ send_message = False
+ else: # ENTER
+ send_message = True
+
+ if gajim.connections[self.account].connected < 2 and send_message:
+ # we are not connected
+ dialogs.ErrorDialog(_('A connection is not available'),
+ _('Your message can not be sent until you are connected.'))
+ send_message = False
+
+ if send_message:
+ self.send_message(message, xhtml=xhtml) # send the message
+ elif event.keyval == gtk.keysyms.z: # CTRL+z
+ if event.state & gtk.gdk.CONTROL_MASK:
+ self.msg_textview.undo()
+ else:
+ # Give the control itself a chance to process
+ self.handle_message_textview_mykey_press(widget, event_keyval,
+ event_keymod)
+
+ def _on_drag_data_received(self, widget, context, x, y, selection,
+ target_type, timestamp):
+ """
+ Derived types SHOULD implement this
+ """
+ pass
+
+ def _on_drag_leave(self, widget, context, time):
+ # FIXME: DND on non editable TextView, find a better way
+ self.drag_entered = False
+ self.conv_textview.tv.set_editable(False)
+
+ def _on_drag_motion(self, widget, context, x, y, time):
+ # FIXME: DND on non editable TextView, find a better way
+ if not self.drag_entered:
+ # We drag new data over the TextView, make it editable to catch dnd
+ self.drag_entered_conv = True
+ self.conv_textview.tv.set_editable(True)
+
+ def send_message(self, message, keyID='', type_='chat', chatstate=None,
+ msg_id=None, composing_xep=None, resource=None, xhtml=None,
+ callback=None, callback_args=[], process_commands=True):
+ """
+ Send the given message to the active tab. Doesn't return None if error
+ """
+ if not message or message == '\n':
+ return None
+
+ if process_commands and self.process_as_command(message):
+ return
+
+ MessageControl.send_message(self, message, keyID, type_=type_,
+ chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
+ resource=resource, user_nick=self.user_nick, xhtml=xhtml,
+ callback=callback, callback_args=callback_args)
+
+ # Record message history
+ self.save_sent_message(message)
+
+ # Be sure to send user nickname only once according to JEP-0172
+ self.user_nick = None
+
+ # Clear msg input
+ message_buffer = self.msg_textview.get_buffer()
+ message_buffer.set_text('') # clear message buffer (and tv of course)
+
+ def save_sent_message(self, message):
+ # save the message, so user can scroll though the list with key up/down
+ size = len(self.sent_history)
+ # we don't want size of the buffer to grow indefinately
+ max_size = gajim.config.get('key_up_lines')
+ if size >= max_size:
+ for i in xrange(0, size - 1):
+ self.sent_history[i] = self.sent_history[i + 1]
+ self.sent_history[max_size - 1] = message
+ # self.sent_history_pos has changed if we browsed sent_history,
+ # reset to real value
+ self.sent_history_pos = max_size
+ else:
+ self.sent_history.append(message)
+ self.sent_history_pos = size + 1
+ self.orig_msg = None
+
+ def print_conversation_line(self, text, kind, name, tim,
+ other_tags_for_name=[], other_tags_for_time=[],
+ other_tags_for_text=[], count_as_new=True, subject=None,
+ old_kind=None, xhtml=None, simple=False, xep0184_id=None,
+ graphics=True):
+ """
+ Print 'chat' type messages
+ """
+ jid = self.contact.jid
+ full_jid = self.get_full_jid()
+ textview = self.conv_textview
+ end = False
+ if self.was_at_the_end or kind == 'outgoing':
+ end = True
+ textview.print_conversation_line(text, jid, kind, name, tim,
+ other_tags_for_name, other_tags_for_time, other_tags_for_text,
+ subject, old_kind, xhtml, simple=simple, graphics=graphics)
+
+ if xep0184_id is not None:
+ textview.show_xep0184_warning(xep0184_id)
+
+ if not count_as_new:
+ return
+ if kind == 'incoming':
+ if not self.type_id == message_control.TYPE_GC or \
+ gajim.config.get('notify_on_all_muc_messages') or \
+ 'marked' in other_tags_for_text:
+ # it's a normal message, or a muc message with want to be
+ # notified about if quitting just after
+ # other_tags_for_text == ['marked'] --> highlighted gc message
+ gajim.last_message_time[self.account][full_jid] = time.time()
+
+ if kind in ('incoming', 'incoming_queue', 'error'):
+ gc_message = False
+ if self.type_id == message_control.TYPE_GC:
+ gc_message = True
+
+ if ((self.parent_win and (not self.parent_win.get_active_control() or \
+ self != self.parent_win.get_active_control() or \
+ not self.parent_win.is_active() or not end)) or \
+ (gc_message and \
+ jid in gajim.interface.minimized_controls[self.account])) and \
+ kind in ('incoming', 'incoming_queue', 'error'):
+ # we want to have save this message in events list
+ # other_tags_for_text == ['marked'] --> highlighted gc message
+ if gc_message:
+ if 'marked' in other_tags_for_text:
+ type_ = 'printed_marked_gc_msg'
+ else:
+ type_ = 'printed_gc_msg'
+ event = 'gc_message_received'
+ else:
+ type_ = 'printed_' + self.type_id
+ event = 'message_received'
+ show_in_roster = notify.get_show_in_roster(event,
+ self.account, self.contact, self.session)
+ show_in_systray = notify.get_show_in_systray(event,
+ self.account, self.contact, type_)
+
+ event = gajim.events.create_event(type_, (self,),
+ show_in_roster = show_in_roster,
+ show_in_systray = show_in_systray)
+ gajim.events.add_event(self.account, full_jid, event)
+ # We need to redraw contact if we show in roster
+ if show_in_roster:
+ gajim.interface.roster.draw_contact(self.contact.jid,
+ self.account)
+
+ if not self.parent_win:
+ return
+
+ if (not self.parent_win.get_active_control() or \
+ self != self.parent_win.get_active_control() or \
+ not self.parent_win.is_active() or not end) and \
+ kind in ('incoming', 'incoming_queue', 'error'):
+ self.parent_win.redraw_tab(self)
+ if not self.parent_win.is_active():
+ self.parent_win.show_title(True, self) # Enabled Urgent hint
+ else:
+ self.parent_win.show_title(False, self) # Disabled Urgent hint
+
+ def toggle_emoticons(self):
+ """
+ Hide show emoticons_button and make sure emoticons_menu is always there
+ when needed
+ """
+ emoticons_button = self.xml.get_object('emoticons_button')
+ if gajim.config.get('emoticons_theme'):
+ emoticons_button.show()
+ emoticons_button.set_no_show_all(False)
+ else:
+ emoticons_button.hide()
+ emoticons_button.set_no_show_all(True)
+
+ def append_emoticon(self, str_):
+ buffer_ = self.msg_textview.get_buffer()
+ if buffer_.get_char_count():
+ buffer_.insert_at_cursor(' %s ' % str_)
+ else: # we are the beginning of buffer
+ buffer_.insert_at_cursor('%s ' % str_)
+ self.msg_textview.grab_focus()
+
+ def on_emoticons_button_clicked(self, widget):
+ """
+ Popup emoticons menu
+ """
+ gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
+ gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
+
+ def on_formattings_button_clicked(self, widget):
+ """
+ Popup formattings menu
+ """
+ menu = gtk.Menu()
+
+ menuitems = ((_('Bold'), 'bold'),
+ (_('Italic'), 'italic'),
+ (_('Underline'), 'underline'),
+ (_('Strike'), 'strike'))
+
+ active_tags = self.msg_textview.get_active_tags()
+
+ for menuitem in menuitems:
+ item = gtk.CheckMenuItem(menuitem[0])
+ if menuitem[1] in active_tags:
+ item.set_active(True)
+ else:
+ item.set_active(False)
+ item.connect('activate', self.msg_textview.set_tag,
+ menuitem[1])
+ menu.append(item)
+
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ item = gtk.ImageMenuItem(_('Color'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_COLOR, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ item.connect('activate', self.on_color_menuitem_activale)
+ menu.append(item)
+
+ item = gtk.ImageMenuItem(_('Font'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_SELECT_FONT, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ item.connect('activate', self.on_font_menuitem_activale)
+ menu.append(item)
+
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ item = gtk.ImageMenuItem(_('Clear formating'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ item.connect('activate', self.msg_textview.clear_tags)
+ menu.append(item)
+
+ menu.show_all()
+ gtkgui_helpers.popup_emoticons_under_button(menu, widget,
+ self.parent_win)
+
+ def on_color_menuitem_activale(self, widget):
+ color_dialog = gtk.ColorSelectionDialog('Select a color')
+ color_dialog.connect('response', self.msg_textview.color_set,
+ color_dialog.colorsel)
+ color_dialog.show_all()
+
+ def on_font_menuitem_activale(self, widget):
+ font_dialog = gtk.FontSelectionDialog('Select a font')
+ font_dialog.connect('response', self.msg_textview.font_set,
+ font_dialog.fontsel)
+ font_dialog.show_all()
+
+
+ def on_actions_button_clicked(self, widget):
+ """
+ Popup action menu
+ """
+ menu = self.prepare_context_menu(hide_buttonbar_items=True)
+ menu.show_all()
+ gtkgui_helpers.popup_emoticons_under_button(menu, widget,
+ self.parent_win)
+
+ def update_font(self):
+ font = pango.FontDescription(gajim.config.get('conversation_font'))
+ self.conv_textview.tv.modify_font(font)
+ self.msg_textview.modify_font(font)
+
+ def update_tags(self):
+ self.conv_textview.update_tags()
+
+ def clear(self, tv):
+ buffer_ = tv.get_buffer()
+ start, end = buffer_.get_bounds()
+ buffer_.delete(start, end)
+
+ def _on_history_menuitem_activate(self, widget = None, jid = None):
+ """
+ When history menuitem is pressed: call history window
+ """
+ if not jid:
+ jid = self.contact.jid
+
+ if 'logs' in gajim.interface.instances:
+ gajim.interface.instances['logs'].window.present()
+ gajim.interface.instances['logs'].open_history(jid, self.account)
+ else:
+ gajim.interface.instances['logs'] = \
+ history_window.HistoryWindow(jid, self.account)
+
+ def _on_send_file(self, gc_contact=None):
+ """
+ gc_contact can be set when we are in a groupchat control
+ """
+ def _on_ok(c):
+ gajim.interface.instances['file_transfers'].show_file_send_request(
+ self.account, c)
+ if self.TYPE_ID == message_control.TYPE_PM:
+ gc_contact = self.gc_contact
+ if gc_contact:
+ # gc or pm
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(
+ gc_contact.room_jid, self.account)
+ self_contact = gajim.contacts.get_gc_contact(self.account,
+ gc_control.room_jid, gc_control.nick)
+ if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
+ 'owner'] and self_contact.affiliation in ['admin', 'owner']:
+ contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
+ if not contact or contact.sub not in ('both', 'to'):
+ prim_text = _('Really send file?')
+ sec_text = _('If you send a file to %s, he/she will know your '
+ 'real Jabber ID.') % gc_contact.name
+ dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
+ on_response_ok = (_on_ok, gc_contact))
+ dialog.popup()
+ return
+ _on_ok(gc_contact)
+ return
+ _on_ok(self.contact)
+
+ def on_minimize_menuitem_toggled(self, widget):
+ """
+ When a grouchat is minimized, unparent the tab, put it in roster etc
+ """
+ old_value = False
+ minimized_gc = gajim.config.get_per('accounts', self.account,
+ 'minimized_gc').split()
+ if self.contact.jid in minimized_gc:
+ old_value = True
+ minimize = widget.get_active()
+ if minimize and not self.contact.jid in minimized_gc:
+ minimized_gc.append(self.contact.jid)
+ if not minimize and self.contact.jid in minimized_gc:
+ minimized_gc.remove(self.contact.jid)
+ if old_value != minimize:
+ gajim.config.set_per('accounts', self.account, 'minimized_gc',
+ ' '.join(minimized_gc))
+
+ def set_control_active(self, state):
+ if state:
+ jid = self.contact.jid
+ if self.was_at_the_end:
+ # we are at the end
+ type_ = ['printed_' + self.type_id]
+ if self.type_id == message_control.TYPE_GC:
+ type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
+ if not gajim.events.remove_events(self.account, self.get_full_jid(),
+ types = type_):
+ # There were events to remove
+ self.redraw_after_event_removed(jid)
+
+
+ def bring_scroll_to_end(self, textview, diff_y = 0):
+ """
+ Scroll to the end of textview if end is not visible
+ """
+ if self.scroll_to_end_id:
+ # a scroll is already planned
+ return
+ buffer_ = textview.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ end_rect = textview.get_iter_location(end_iter)
+ visible_rect = textview.get_visible_rect()
+ # scroll only if expected end is not visible
+ if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
+ self.scroll_to_end_id = gobject.idle_add(self.scroll_to_end_iter,
+ textview)
+
+ def scroll_to_end_iter(self, textview):
+ buffer_ = textview.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ textview.scroll_to_iter(end_iter, 0, False, 1, 1)
+ self.scroll_to_end_id = None
+ return False
+
+ def size_request(self, msg_textview , requisition):
+ """
+ When message_textview changes its size: if the new height will enlarge
+ the window, enable the scrollbar automatic policy. Also enable scrollbar
+ automatic policy for horizontal scrollbar if message we have in
+ message_textview is too big
+ """
+ if msg_textview.window is None:
+ return
+
+ min_height = self.conv_scrolledwindow.get_property('height-request')
+ conversation_height = self.conv_textview.tv.window.get_size()[1]
+ message_height = msg_textview.window.get_size()[1]
+ message_width = msg_textview.window.get_size()[0]
+ # new tab is not exposed yet
+ if conversation_height < 2:
+ return
+
+ if conversation_height < min_height:
+ min_height = conversation_height
+
+ # we don't want to always resize in height the message_textview
+ # so we have minimum on conversation_textview's scrolled window
+ # but we also want to avoid window resizing so if we reach that
+ # minimum for conversation_textview and maximum for message_textview
+ # we set to automatic the scrollbar policy
+ diff_y = message_height - requisition.height
+ if diff_y != 0:
+ if conversation_height + diff_y < min_height:
+ if message_height + conversation_height - min_height > min_height:
+ policy = self.msg_scrolledwindow.get_property(
+ 'vscrollbar-policy')
+ # scroll only when scrollbar appear
+ if policy != gtk.POLICY_AUTOMATIC:
+ self.msg_scrolledwindow.set_property('vscrollbar-policy',
+ gtk.POLICY_AUTOMATIC)
+ self.msg_scrolledwindow.set_property('height-request',
+ message_height + conversation_height - min_height)
+ self.bring_scroll_to_end(msg_textview)
+ else:
+ self.msg_scrolledwindow.set_property('vscrollbar-policy',
+ gtk.POLICY_NEVER)
+ self.msg_scrolledwindow.set_property('height-request', -1)
+ self.conv_textview.bring_scroll_to_end(diff_y - 18, False)
+ else:
+ self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth)
+ self.smooth = True # reinit the flag
+ # enable scrollbar automatic policy for horizontal scrollbar
+ # if message we have in message_textview is too big
+ if requisition.width > message_width:
+ self.msg_scrolledwindow.set_property('hscrollbar-policy',
+ gtk.POLICY_AUTOMATIC)
+ else:
+ self.msg_scrolledwindow.set_property('hscrollbar-policy',
+ gtk.POLICY_NEVER)
+
+ return True
+
+ def on_conversation_vadjustment_changed(self, adjustment):
+ # used to stay at the end of the textview when we shrink conversation
+ # textview.
+ if self.was_at_the_end:
+ self.conv_textview.bring_scroll_to_end(-18)
+ self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18
+
+ def on_conversation_vadjustment_value_changed(self, adjustment):
+ # stop automatic scroll when we manually scroll
+ if not self.conv_textview.auto_scrolling:
+ self.conv_textview.stop_scrolling()
+ self.was_at_the_end = (adjustment.upper - adjustment.value - adjustment.page_size) < 18
+ if self.resource:
+ jid = self.contact.get_full_jid()
+ else:
+ jid = self.contact.jid
+ types_list = []
+ type_ = self.type_id
+ if type_ == message_control.TYPE_GC:
+ type_ = 'gc_msg'
+ types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
+ else: # Not a GC
+ types_list = ['printed_' + type_, type_]
+
+ if not len(gajim.events.get_events(self.account, jid, types_list)):
+ return
+ if not self.parent_win:
+ return
+ if self.conv_textview.at_the_end() and \
+ self.parent_win.get_active_control() == self and \
+ self.parent_win.window.is_active():
+ # we are at the end
+ if self.type_id == message_control.TYPE_GC:
+ if not gajim.events.remove_events(self.account, jid,
+ types=types_list):
+ self.redraw_after_event_removed(jid)
+ elif self.session and self.session.remove_events(types_list):
+ # There were events to remove
+ self.redraw_after_event_removed(jid)
+
+ def redraw_after_event_removed(self, jid):
+ """
+ We just removed a 'printed_*' event, redraw contact in roster or
+ gc_roster and titles in roster and msg_win
+ """
+ self.parent_win.redraw_tab(self)
+ self.parent_win.show_title()
+ # TODO : get the contact and check notify.get_show_in_roster()
+ if self.type_id == message_control.TYPE_PM:
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+ groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
+ room_jid, self.account)
+ if room_jid in gajim.interface.minimized_controls[self.account]:
+ groupchat_control = \
+ gajim.interface.minimized_controls[self.account][room_jid]
+ contact = \
+ gajim.contacts.get_contact_with_highest_priority(self.account, \
+ room_jid)
+ if contact:
+ gajim.interface.roster.draw_contact(room_jid, self.account)
+ if groupchat_control:
+ groupchat_control.draw_contact(nick)
+ if groupchat_control.parent_win:
+ groupchat_control.parent_win.redraw_tab(groupchat_control)
+ else:
+ gajim.interface.roster.draw_contact(jid, self.account)
+ gajim.interface.roster.show_title()
+
+ def sent_messages_scroll(self, direction, conv_buf):
+ size = len(self.sent_history)
+ if self.orig_msg is None:
+ # user was typing something and then went into history, so save
+ # whatever is already typed
+ start_iter = conv_buf.get_start_iter()
+ end_iter = conv_buf.get_end_iter()
+ self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode(
+ 'utf-8')
+ if direction == 'up':
+ if self.sent_history_pos == 0:
+ return
+ self.sent_history_pos = self.sent_history_pos - 1
+ self.smooth = False
+ conv_buf.set_text(self.sent_history[self.sent_history_pos])
+ elif direction == 'down':
+ if self.sent_history_pos >= size - 1:
+ conv_buf.set_text(self.orig_msg)
+ self.orig_msg = None
+ self.sent_history_pos = size
+ return
+
+ self.sent_history_pos = self.sent_history_pos + 1
+ self.smooth = False
+ conv_buf.set_text(self.sent_history[self.sent_history_pos])
+
+ def lighten_color(self, color):
+ p = 0.4
+ mask = 0
+ color.red = int((color.red * p) + (mask * (1 - p)))
+ color.green = int((color.green * p) + (mask * (1 - p)))
+ color.blue = int((color.blue * p) + (mask * (1 - p)))
+ return color
+
+ def widget_set_visible(self, widget, state):
+ """
+ Show or hide a widget
+ """
+ # make the last message visible, when changing to "full view"
+ if not state:
+ gobject.idle_add(self.conv_textview.scroll_to_end_iter)
+
+ widget.set_no_show_all(state)
+ if state:
+ widget.hide()
+ else:
+ widget.show_all()
+
+ def chat_buttons_set_visible(self, state):
+ """
+ Toggle chat buttons
+ """
+ MessageControl.chat_buttons_set_visible(self, state)
+ self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
+
+ def got_connected(self):
+ self.msg_textview.set_sensitive(True)
+ self.msg_textview.set_editable(True)
+ # FIXME: Set sensitivity for toolbar
+
+ def got_disconnected(self):
+ self.msg_textview.set_sensitive(False)
+ self.msg_textview.set_editable(False)
+ self.conv_textview.tv.grab_focus()
+
+ self.no_autonegotiation = False
+ # FIXME: Set sensitivity for toolbar
################################################################################
class ChatControl(ChatControlBase):
- """
- A control for standard 1-1 chat
- """
- (
- JINGLE_STATE_NOT_AVAILABLE,
- JINGLE_STATE_AVAILABLE,
- JINGLE_STATE_CONNECTING,
- JINGLE_STATE_CONNECTION_RECEIVED,
- JINGLE_STATE_CONNECTED,
- JINGLE_STATE_ERROR
- ) = range(6)
-
- TYPE_ID = message_control.TYPE_CHAT
- old_msg_kind = None # last kind of the printed message
-
- # Set a command host to bound to. Every command given through a chat will be
- # processed with this command host.
- COMMAND_HOST = ChatCommands
-
- def __init__(self, parent_win, contact, acct, session, resource = None):
- ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
- 'chat_control', contact, acct, resource)
-
- self.gpg_is_active = False
- # for muc use:
- # widget = self.xml.get_object('muc_window_actions_button')
- self.actions_button = self.xml.get_object('message_window_actions_button')
- id_ = self.actions_button.connect('clicked',
- self.on_actions_button_clicked)
- self.handlers[id_] = self.actions_button
-
- self._formattings_button = self.xml.get_object('formattings_button')
-
- self._add_to_roster_button = self.xml.get_object(
- 'add_to_roster_button')
- id_ = self._add_to_roster_button.connect('clicked',
- self._on_add_to_roster_menuitem_activate)
- self.handlers[id_] = self._add_to_roster_button
-
- self._audio_button = self.xml.get_object('audio_togglebutton')
- id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled)
- self.handlers[id_] = self._audio_button
- # add a special img
- gtkgui_helpers.add_image_to_button(self._audio_button,
- 'gajim-mic_inactive')
-
- self._video_button = self.xml.get_object('video_togglebutton')
- id_ = self._video_button.connect('toggled', self.on_video_button_toggled)
- self.handlers[id_] = self._video_button
- # add a special img
- gtkgui_helpers.add_image_to_button(self._video_button,
- 'gajim-cam_inactive')
-
- self._send_file_button = self.xml.get_object('send_file_button')
- # add a special img for send file button
- path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
- img = gtk.Image()
- img.set_from_file(path_to_upload_img)
- self._send_file_button.set_image(img)
- id_ = self._send_file_button.connect('clicked',
- self._on_send_file_menuitem_activate)
- self.handlers[id_] = self._send_file_button
-
- self._convert_to_gc_button = self.xml.get_object(
- 'convert_to_gc_button')
- id_ = self._convert_to_gc_button.connect('clicked',
- self._on_convert_to_gc_menuitem_activate)
- self.handlers[id_] = self._convert_to_gc_button
-
- contact_information_button = self.xml.get_object(
- 'contact_information_button')
- id_ = contact_information_button.connect('clicked',
- self._on_contact_information_menuitem_activate)
- self.handlers[id_] = contact_information_button
-
- compact_view = gajim.config.get('compact_view')
- self.chat_buttons_set_visible(compact_view)
- self.widget_set_visible(self.xml.get_object('banner_eventbox'),
- gajim.config.get('hide_chat_banner'))
-
- self.authentication_button = self.xml.get_object(
- 'authentication_button')
- id_ = self.authentication_button.connect('clicked',
- self._on_authentication_button_clicked)
- self.handlers[id_] = self.authentication_button
-
- # Add lock image to show chat encryption
- self.lock_image = self.xml.get_object('lock_image')
-
- # Convert to GC icon
- img = self.xml.get_object('convert_to_gc_button_image')
- img.set_from_pixbuf(gtkgui_helpers.load_icon(
- 'muc_active').get_pixbuf())
-
- self._audio_banner_image = self.xml.get_object('audio_banner_image')
- self._video_banner_image = self.xml.get_object('video_banner_image')
- self.audio_sid = None
- self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE
- self.video_sid = None
- self.video_state = self.JINGLE_STATE_NOT_AVAILABLE
-
- self.update_toolbar()
-
- self._pep_images = {}
- self._pep_images['mood'] = self.xml.get_object('mood_image')
- self._pep_images['activity'] = self.xml.get_object('activity_image')
- self._pep_images['tune'] = self.xml.get_object('tune_image')
- self._pep_images['location'] = self.xml.get_object('location_image')
- self.update_all_pep_types()
-
- # keep timeout id and window obj for possible big avatar
- # it is on enter-notify and leave-notify so no need to be
- # per jid
- self.show_bigger_avatar_timeout_id = None
- self.bigger_avatar_window = None
- self.show_avatar()
-
- # chatstate timers and state
- self.reset_kbd_mouse_timeout_vars()
- self._schedule_activity_timers()
-
- # Hook up signals
- id_ = self.parent_win.window.connect('motion-notify-event',
- self._on_window_motion_notify)
- self.handlers[id_] = self.parent_win.window
- message_tv_buffer = self.msg_textview.get_buffer()
- id_ = message_tv_buffer.connect('changed',
- self._on_message_tv_buffer_changed)
- self.handlers[id_] = message_tv_buffer
-
- widget = self.xml.get_object('avatar_eventbox')
- widget.set_property('height-request', gajim.config.get(
- 'chat_avatar_height'))
- id_ = widget.connect('enter-notify-event',
- self.on_avatar_eventbox_enter_notify_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('leave-notify-event',
- self.on_avatar_eventbox_leave_notify_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('button-press-event',
- self.on_avatar_eventbox_button_press_event)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('location_eventbox')
- id_ = widget.connect('button-release-event',
- self.on_location_eventbox_button_release_event)
- self.handlers[id_] = widget
-
- for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'):
- widget = self.xml.get_object(key + '_button')
- id_ = widget.connect('pressed', self.on_num_button_pressed, key)
- self.handlers[id_] = widget
- id_ = widget.connect('released', self.on_num_button_released)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('mic_hscale')
- id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('sound_hscale')
- id_ = widget.connect('value_changed', self.on_sound_hscale_value_changed)
- self.handlers[id_] = widget
-
- if not session:
- # Don't use previous session if we want to a specific resource
- # and it's not the same
- if not resource:
- resource = contact.resource
- session = gajim.connections[self.account].find_controlless_session(
- self.contact.jid, resource)
-
- if session:
- session.control = self
- self.session = session
-
- if session.enable_encryption:
- self.print_esession_details()
-
- # Enable encryption if needed
- self.no_autonegotiation = False
- e2e_is_active = self.session and self.session.enable_encryption
- gpg_pref = gajim.config.get_per('contacts', contact.jid,
- 'gpg_enabled')
-
- # try GPG first
- if not e2e_is_active and gpg_pref and \
- gajim.config.get_per('accounts', self.account, 'keyid') and \
- gajim.connections[self.account].USE_GPG:
- self.gpg_is_active = True
- gajim.encrypted_chats[self.account].append(contact.jid)
- msg = _('GPG encryption enabled')
- ChatControlBase.print_conversation_line(self, msg,
- 'status', '', None)
-
- if self.session:
- self.session.loggable = gajim.config.get_per('accounts',
- self.account, 'log_encrypted_sessions')
- # GPG is always authenticated as we use GPG's WoT
- self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active,
- self.session and self.session.is_loggable(), True)
-
- self.update_ui()
- # restore previous conversation
- self.restore_conversation()
- self.msg_textview.grab_focus()
-
- def update_toolbar(self):
- # Formatting
- if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active:
- self._formattings_button.set_sensitive(True)
- else:
- self._formattings_button.set_sensitive(False)
-
- # Add to roster
- if not isinstance(self.contact, GC_Contact) \
- and _('Not in Roster') in self.contact.groups:
- self._add_to_roster_button.show()
- else:
- self._add_to_roster_button.hide()
-
- # Jingle detection
- if self.contact.supports(NS_JINGLE_ICE_UDP) and \
- gajim.HAVE_FARSIGHT and self.contact.resource:
- if self.contact.supports(NS_JINGLE_RTP_AUDIO):
- if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
- self.set_audio_state('available')
- else:
- self.set_audio_state('not_available')
-
- if self.contact.supports(NS_JINGLE_RTP_VIDEO):
- if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
- self.set_video_state('available')
- else:
- self.set_video_state('not_available')
- else:
- if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE:
- self.set_audio_state('not_available')
- if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE:
- self.set_video_state('not_available')
-
- # Audio buttons
- if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
- self._audio_button.set_sensitive(False)
- else:
- self._audio_button.set_sensitive(True)
-
- # Video buttons
- if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
- self._video_button.set_sensitive(False)
- else:
- self._video_button.set_sensitive(True)
-
- # Send file
- if self.contact.supports(NS_FILE) and self.contact.resource:
- self._send_file_button.set_sensitive(True)
- else:
- self._send_file_button.set_sensitive(False)
- if not self.contact.supports(NS_FILE):
- self._send_file_button.set_tooltip_text(_(
- "This contact does not support file transfer."))
- else:
- self._send_file_button.set_tooltip_text(
- _("You need to know the real JID of the contact to send him or "
- "her a file."))
-
- # Convert to GC
- if self.contact.supports(NS_MUC):
- self._convert_to_gc_button.set_sensitive(True)
- else:
- self._convert_to_gc_button.set_sensitive(False)
-
- def update_all_pep_types(self):
- for pep_type in self._pep_images:
- self.update_pep(pep_type)
-
- def update_pep(self, pep_type):
- if isinstance(self.contact, GC_Contact):
- return
- if pep_type not in self._pep_images:
- return
- pep = self.contact.pep
- img = self._pep_images[pep_type]
- if pep_type in pep:
- img.set_from_pixbuf(pep[pep_type].asPixbufIcon())
- img.set_tooltip_markup(pep[pep_type].asMarkupText())
- img.show()
- else:
- img.hide()
-
- def _update_jingle(self, jingle_type):
- if jingle_type not in ('audio', 'video'):
- return
- banner_image = getattr(self, '_' + jingle_type + '_banner_image')
- state = getattr(self, jingle_type + '_state')
- if state in (self.JINGLE_STATE_NOT_AVAILABLE,
- self.JINGLE_STATE_AVAILABLE):
- banner_image.hide()
- else:
- banner_image.show()
- if state == self.JINGLE_STATE_CONNECTING:
- banner_image.set_from_stock(
- gtk.STOCK_CONVERT, 1)
- elif state == self.JINGLE_STATE_CONNECTION_RECEIVED:
- banner_image.set_from_stock(
- gtk.STOCK_NETWORK, 1)
- elif state == self.JINGLE_STATE_CONNECTED:
- banner_image.set_from_stock(
- gtk.STOCK_CONNECT, 1)
- elif state == self.JINGLE_STATE_ERROR:
- banner_image.set_from_stock(
- gtk.STOCK_DIALOG_WARNING, 1)
- self.update_toolbar()
-
- def update_audio(self):
- self._update_jingle('audio')
- vbox = self.xml.get_object('audio_vbox')
- if self.audio_state == self.JINGLE_STATE_CONNECTED:
- # Set volume from config
- input_vol = gajim.config.get('audio_input_volume')
- output_vol = gajim.config.get('audio_output_volume')
- input_vol = max(min(input_vol, 100), 0)
- output_vol = max(min(output_vol, 100), 0)
- self.xml.get_object('mic_hscale').set_value(input_vol)
- self.xml.get_object('sound_hscale').set_value(output_vol)
- # Show vbox
- vbox.set_no_show_all(False)
- vbox.show_all()
- elif not self.audio_sid:
- vbox.set_no_show_all(True)
- vbox.hide()
-
- def update_video(self):
- self._update_jingle('video')
-
- def change_resource(self, resource):
- old_full_jid = self.get_full_jid()
- self.resource = resource
- new_full_jid = self.get_full_jid()
- # update gajim.last_message_time
- if old_full_jid in gajim.last_message_time[self.account]:
- gajim.last_message_time[self.account][new_full_jid] = \
- gajim.last_message_time[self.account][old_full_jid]
- # update events
- gajim.events.change_jid(self.account, old_full_jid, new_full_jid)
- # update MessageWindow._controls
- self.parent_win.change_jid(self.account, old_full_jid, new_full_jid)
-
- def _set_jingle_state(self, jingle_type, state, sid=None, reason=None):
- if jingle_type not in ('audio', 'video'):
- return
- if state in ('connecting', 'connected', 'stop') and reason:
- str = _('%(type)s state : %(state)s, reason: %(reason)s') % {
- 'type': jingle_type.capitalize(), 'state': state, 'reason': reason}
- self.print_conversation(str, 'info')
-
- states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE,
- 'available': self.JINGLE_STATE_AVAILABLE,
- 'connecting': self.JINGLE_STATE_CONNECTING,
- 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED,
- 'connected': self.JINGLE_STATE_CONNECTED,
- 'stop': self.JINGLE_STATE_AVAILABLE,
- 'error': self.JINGLE_STATE_ERROR}
-
- if state in states:
- jingle_state = states[state]
- if getattr(self, jingle_type + '_state') == jingle_state:
- return
- setattr(self, jingle_type + '_state', jingle_state)
-
- # Destroy existing session with the user when he signs off
- # We need to do that before modifying the sid
- if state == 'not_available':
- gajim.connections[self.account].delete_jingle_session(
- self.contact.get_full_jid(), getattr(self, jingle_type + '_sid'))
-
- if state in ('not_available', 'available', 'stop'):
- setattr(self, jingle_type + '_sid', None)
- if state in ('connection_received', 'connecting'):
- setattr(self, jingle_type + '_sid', sid)
-
- if state in ('connecting', 'connected', 'connection_received'):
- getattr(self, '_' + jingle_type + '_button').set_active(True)
- elif state in ('not_available', 'stop'):
- getattr(self, '_' + jingle_type + '_button').set_active(False)
-
- getattr(self, 'update_' + jingle_type)()
-
- def set_audio_state(self, state, sid=None, reason=None):
- self._set_jingle_state('audio', state, sid=sid, reason=reason)
-
- def set_video_state(self, state, sid=None, reason=None):
- self._set_jingle_state('video', state, sid=sid, reason=reason)
-
- def _get_audio_content(self):
- session = gajim.connections[self.account].get_jingle_session(
- self.contact.get_full_jid(), self.audio_sid)
- return session.get_content('audio')
-
- def on_num_button_pressed(self, widget, num):
- self._get_audio_content()._start_dtmf(num)
-
- def on_num_button_released(self, released):
- self._get_audio_content()._stop_dtmf()
-
- def on_mic_hscale_value_changed(self, widget):
- value = widget.get_value()
- self._get_audio_content().set_mic_volume(value / 100)
- # Save volume to config
- # FIXME: Putting it here is maybe not the right thing to do?
- gajim.config.set('audio_input_volume', value)
-
-
- def on_sound_hscale_value_changed(self, widget):
- value = widget.get_value()
- self._get_audio_content().set_out_volume(value / 100)
- # Save volume to config
- # FIXME: Putting it here is maybe not the right thing to do?
- gajim.config.set('audio_output_volume', value)
-
- def on_avatar_eventbox_enter_notify_event(self, widget, event):
- """
- Enter the eventbox area so we under conditions add a timeout to show a
- bigger avatar after 0.5 sec
- """
- jid = self.contact.jid
- avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
- if avatar_pixbuf in ('ask', None):
- return
- avatar_w = avatar_pixbuf.get_width()
- avatar_h = avatar_pixbuf.get_height()
-
- scaled_buf = self.xml.get_object('avatar_image').get_pixbuf()
- scaled_buf_w = scaled_buf.get_width()
- scaled_buf_h = scaled_buf.get_height()
-
- # do we have something bigger to show?
- if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h:
- # wait for 0.5 sec in case we leave earlier
- self.show_bigger_avatar_timeout_id = gobject.timeout_add(500,
- self.show_bigger_avatar, widget)
-
- def on_avatar_eventbox_leave_notify_event(self, widget, event):
- """
- Left the eventbox area that holds the avatar img
- """
- # did we add a timeout? if yes remove it
- if self.show_bigger_avatar_timeout_id is not None:
- gobject.source_remove(self.show_bigger_avatar_timeout_id)
-
- def on_avatar_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- menu = gtk.Menu()
- menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
- id_ = menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.contact.jid, self.account, self.contact.get_shown_name())
- self.handlers[id_] = menuitem
- menu.append(menuitem)
- menu.show_all()
- menu.connect('selection-done', lambda w:w.destroy())
- # show the menu
- menu.show_all()
- menu.popup(None, None, None, event.button, event.time)
- return True
-
- def on_location_eventbox_button_release_event(self, widget, event):
- if 'location' in self.contact.pep:
- location = self.contact.pep['location']._pep_specific_data
- if ('lat' in location) and ('lon' in location):
- uri = 'http://www.openstreetmap.org/?' + \
- 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'],
- 'lon': location['lon']}
- helpers.launch_browser_mailer('url', uri)
-
- def _on_window_motion_notify(self, widget, event):
- """
- It gets called no matter if it is the active window or not
- """
- if self.parent_win.get_active_jid() == self.contact.jid:
- # if window is the active one, change vars assisting chatstate
- self.mouse_over_in_last_5_secs = True
- self.mouse_over_in_last_30_secs = True
-
- def _schedule_activity_timers(self):
- self.possible_paused_timeout_id = gobject.timeout_add_seconds(5,
- self.check_for_possible_paused_chatstate, None)
- self.possible_inactive_timeout_id = gobject.timeout_add_seconds(30,
- self.check_for_possible_inactive_chatstate, None)
-
- def update_ui(self):
- # The name banner is drawn here
- ChatControlBase.update_ui(self)
- self.update_toolbar()
-
- def _update_banner_state_image(self):
- contact = gajim.contacts.get_contact_with_highest_priority(self.account,
- self.contact.jid)
- if not contact or self.resource:
- # For transient contacts
- contact = self.contact
- show = contact.show
- jid = contact.jid
-
- # Set banner image
- img_32 = gajim.interface.roster.get_appropriate_state_images(jid,
- size = '32', icon_name = show)
- img_16 = gajim.interface.roster.get_appropriate_state_images(jid,
- icon_name = show)
- if show in img_32 and img_32[show].get_pixbuf():
- # we have 32x32! use it!
- banner_image = img_32[show]
- use_size_32 = True
- else:
- banner_image = img_16[show]
- use_size_32 = False
-
- banner_status_img = self.xml.get_object('banner_status_image')
- if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION:
- banner_status_img.set_from_animation(banner_image.get_animation())
- else:
- pix = banner_image.get_pixbuf()
- if pix is not None:
- if use_size_32:
- banner_status_img.set_from_pixbuf(pix)
- else: # we need to scale 16x16 to 32x32
- scaled_pix = pix.scale_simple(32, 32,
- gtk.gdk.INTERP_BILINEAR)
- banner_status_img.set_from_pixbuf(scaled_pix)
-
- def draw_banner_text(self):
- """
- Draw the text in the fat line at the top of the window that houses the
- name, jid
- """
- contact = self.contact
- jid = contact.jid
-
- banner_name_label = self.xml.get_object('banner_name_label')
-
- name = contact.get_shown_name()
- if self.resource:
- name += '/' + self.resource
- if self.TYPE_ID == message_control.TYPE_PM:
- name = _('%(nickname)s from group chat %(room_name)s') %\
- {'nickname': name, 'room_name': self.room_name}
- name = gobject.markup_escape_text(name)
-
- # We know our contacts nick, but if another contact has the same nick
- # in another account we need to also display the account.
- # except if we are talking to two different resources of the same contact
- acct_info = ''
- for account in gajim.contacts.get_accounts():
- if account == self.account:
- continue
- if acct_info: # We already found a contact with same nick
- break
- for jid in gajim.contacts.get_jid_list(account):
- other_contact_ = \
- gajim.contacts.get_first_contact_from_jid(account, jid)
- if other_contact_.get_shown_name() == self.contact.get_shown_name():
- acct_info = ' (%s)' % \
- gobject.markup_escape_text(self.account)
- break
-
- status = contact.status
- if status is not None:
- banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
- self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
- status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1)
- status_escaped = gobject.markup_escape_text(status_reduced)
-
- font_attrs, font_attrs_small = self.get_font_attrs()
- st = gajim.config.get('displayed_chat_state_notifications')
- cs = contact.chatstate
- if cs and st in ('composing_only', 'all'):
- if contact.show == 'offline':
- chatstate = ''
- elif contact.composing_xep == 'XEP-0085':
- if st == 'all' or cs == 'composing':
- chatstate = helpers.get_uf_chatstate(cs)
- else:
- chatstate = ''
- elif contact.composing_xep == 'XEP-0022':
- if cs in ('composing', 'paused'):
- # only print composing, paused
- chatstate = helpers.get_uf_chatstate(cs)
- else:
- chatstate = ''
- else:
- # When does that happen ? See [7797] and [7804]
- chatstate = helpers.get_uf_chatstate(cs)
-
- label_text = '<span %s>%s</span><span %s>%s %s</span>' \
- % (font_attrs, name, font_attrs_small,
- acct_info, chatstate)
- if acct_info:
- acct_info = ' ' + acct_info
- label_tooltip = '%s%s %s' % (name, acct_info, chatstate)
- else:
- # weight="heavy" size="x-large"
- label_text = '<span %s>%s</span><span %s>%s</span>' % \
- (font_attrs, name, font_attrs_small, acct_info)
- if acct_info:
- acct_info = ' ' + acct_info
- label_tooltip = '%s%s' % (name, acct_info)
-
- if status_escaped:
- status_text = self.urlfinder.sub(self.make_href, status_escaped)
- status_text = '<span %s>%s</span>' % (font_attrs_small, status_escaped)
- self.banner_status_label.set_tooltip_text(status)
- self.banner_status_label.set_no_show_all(False)
- self.banner_status_label.show()
- else:
- status_text = ''
- self.banner_status_label.hide()
- self.banner_status_label.set_no_show_all(True)
-
- self.banner_status_label.set_markup(status_text)
- # setup the label that holds name and jid
- banner_name_label.set_markup(label_text)
- banner_name_label.set_tooltip_text(label_tooltip)
-
- def close_jingle_content(self, jingle_type):
- sid = getattr(self, jingle_type + '_sid')
- if not sid:
- return
- session = gajim.connections[self.account].get_jingle_session(
- self.contact.get_full_jid(), sid)
- if session:
- content = session.get_content(jingle_type)
- if content:
- session.remove_content(content.creator, content.name)
-
- def on_jingle_button_toggled(self, widget, jingle_type):
- img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type],
- {True: 'active', False: 'inactive'}[widget.get_active()])
- path_to_img = gtkgui_helpers.get_icon_path(img_name)
-
- if widget.get_active():
- if getattr(self, jingle_type + '_state') == \
- self.JINGLE_STATE_AVAILABLE:
- sid = getattr(gajim.connections[self.account],
- 'start_' + jingle_type)(self.contact.get_full_jid())
- getattr(self, 'set_' + jingle_type + '_state')('connecting', sid)
- else:
- self.close_jingle_content(jingle_type)
-
- img = getattr(self, '_' + jingle_type + '_button').get_property('image')
- img.set_from_file(path_to_img)
-
- def on_audio_button_toggled(self, widget):
- self.on_jingle_button_toggled(widget, 'audio')
-
- def on_video_button_toggled(self, widget):
- self.on_jingle_button_toggled(widget, 'video')
-
- def _toggle_gpg(self):
- if not self.gpg_is_active and not self.contact.keyID:
- dialogs.ErrorDialog(_('No GPG key assigned'),
- _('No GPG key is assigned to this contact. So you cannot '
- 'encrypt messages with GPG.'))
- return
- ec = gajim.encrypted_chats[self.account]
- if self.gpg_is_active:
- # Disable encryption
- ec.remove(self.contact.jid)
- self.gpg_is_active = False
- loggable = False
- msg = _('GPG encryption disabled')
- ChatControlBase.print_conversation_line(self, msg,
- 'status', '', None)
- if self.session:
- self.session.loggable = True
-
- else:
- # Enable encryption
- ec.append(self.contact.jid)
- self.gpg_is_active = True
- msg = _('GPG encryption enabled')
- ChatControlBase.print_conversation_line(self, msg,
- 'status', '', None)
-
- loggable = gajim.config.get_per('accounts', self.account,
- 'log_encrypted_sessions')
-
- if self.session:
- self.session.loggable = loggable
-
- loggable = self.session.is_loggable()
- else:
- loggable = loggable and gajim.config.should_log(self.account,
- self.contact.jid)
-
- if loggable:
- msg = _('Session WILL be logged')
- else:
- msg = _('Session WILL NOT be logged')
-
- ChatControlBase.print_conversation_line(self, msg,
- 'status', '', None)
-
- gajim.config.set_per('contacts', self.contact.jid,
- 'gpg_enabled', self.gpg_is_active)
-
- self._show_lock_image(self.gpg_is_active, 'GPG',
- self.gpg_is_active, loggable, True)
-
- def _show_lock_image(self, visible, enc_type = '', enc_enabled = False,
- chat_logged = False, authenticated = False):
- """
- Set lock icon visibility and create tooltip
- """
- #encryption %s active
- status_string = enc_enabled and _('is') or _('is NOT')
- #chat session %s be logged
- logged_string = chat_logged and _('will') or _('will NOT')
-
- if authenticated:
- #About encrypted chat session
- authenticated_string = _('and authenticated')
- img_path = gtkgui_helpers.get_icon_path('gajim-security_high')
- else:
- #About encrypted chat session
- authenticated_string = _('and NOT authenticated')
- img_path = gtkgui_helpers.get_icon_path('gajim-security_low')
- self.lock_image.set_from_file(img_path)
-
- #status will become 'is' or 'is not', authentificaed will become
- #'and authentificated' or 'and not authentificated', logged will become
- #'will' or 'will not'
- tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n'
- 'Your chat session %(logged)s be logged.') % {'type': enc_type,
- 'status': status_string, 'authenticated': authenticated_string,
- 'logged': logged_string}
-
- self.authentication_button.set_tooltip_text(tooltip)
- self.widget_set_visible(self.authentication_button, not visible)
- self.lock_image.set_sensitive(enc_enabled)
-
- def _on_authentication_button_clicked(self, widget):
- if self.gpg_is_active:
- dialogs.GPGInfoWindow(self)
- elif self.session and self.session.enable_encryption:
- dialogs.ESessionInfoWindow(self.session)
-
- def send_message(self, message, keyID='', chatstate=None, xhtml=None,
- process_commands=True):
- """
- Send a message to contact
- """
- if message in ('', None, '\n'):
- return None
-
- # refresh timers
- self.reset_kbd_mouse_timeout_vars()
-
- contact = self.contact
-
- encrypted = bool(self.session) and self.session.enable_encryption
-
- keyID = ''
- if self.gpg_is_active:
- keyID = contact.keyID
- encrypted = True
- if not keyID:
- keyID = 'UNKNOWN'
-
- chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
- 'disabled'
- composing_xep = contact.composing_xep
- chatstate_to_send = None
- if chatstates_on and contact is not None:
- if composing_xep is None:
- # no info about peer
- # send active to discover chat state capabilities
- # this is here (and not in send_chatstate)
- # because we want it sent with REAL message
- # (not standlone) eg. one that has body
-
- if contact.our_chatstate:
- # We already asked for xep 85, don't ask it twice
- composing_xep = 'asked_once'
-
- chatstate_to_send = 'active'
- contact.our_chatstate = 'ask' # pseudo state
- # if peer supports jep85 and we are not 'ask', send 'active'
- # NOTE: first active and 'ask' is set in gajim.py
- elif composing_xep is not False:
- # send active chatstate on every message (as XEP says)
- chatstate_to_send = 'active'
- contact.our_chatstate = 'active'
-
- gobject.source_remove(self.possible_paused_timeout_id)
- gobject.source_remove(self.possible_inactive_timeout_id)
- self._schedule_activity_timers()
-
- def _on_sent(id_, contact, message, encrypted, xhtml):
- if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts',
- self.account, 'request_receipt'):
- xep0184_id = id_
- else:
- xep0184_id = None
-
- self.print_conversation(message, self.contact.jid, encrypted=encrypted,
- xep0184_id=xep0184_id, xhtml=xhtml)
-
- ChatControlBase.send_message(self, message, keyID, type_='chat',
- chatstate=chatstate_to_send, composing_xep=composing_xep,
- xhtml=xhtml, callback=_on_sent,
- callback_args=[contact, message, encrypted, xhtml],
- process_commands=process_commands)
-
- def check_for_possible_paused_chatstate(self, arg):
- """
- Did we move mouse of that window or write something in message textview
- in the last 5 seconds? If yes - we go active for mouse, composing for
- kbd. If not - we go paused if we were previously composing
- """
- contact = self.contact
- jid = contact.jid
- current_state = contact.our_chatstate
- if current_state is False: # jid doesn't support chatstates
- return False # stop looping
-
- message_buffer = self.msg_textview.get_buffer()
- if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count():
- # Only composing if the keyboard activity was in text entry
- self.send_chatstate('composing')
- elif self.mouse_over_in_last_5_secs and\
- jid == self.parent_win.get_active_jid():
- self.send_chatstate('active')
- else:
- if current_state == 'composing':
- self.send_chatstate('paused') # pause composing
-
- # assume no activity and let the motion-notify or 'insert-text' make them
- # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
- self.reset_kbd_mouse_timeout_vars()
- return True # loop forever
-
- def check_for_possible_inactive_chatstate(self, arg):
- """
- Did we move mouse over that window or wrote something in message textview
- in the last 30 seconds? if yes - we go active. If no - we go inactive
- """
- contact = self.contact
-
- current_state = contact.our_chatstate
- if current_state is False: # jid doesn't support chatstates
- return False # stop looping
-
- if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
- return True # loop forever
-
- if not self.mouse_over_in_last_30_secs or \
- self.kbd_activity_in_last_30_secs:
- self.send_chatstate('inactive', contact)
-
- # assume no activity and let the motion-notify or 'insert-text' make them
- # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
- self.reset_kbd_mouse_timeout_vars()
- return True # loop forever
-
- def reset_kbd_mouse_timeout_vars(self):
- self.kbd_activity_in_last_5_secs = False
- self.mouse_over_in_last_5_secs = False
- self.mouse_over_in_last_30_secs = False
- self.kbd_activity_in_last_30_secs = False
-
- def on_cancel_session_negotiation(self):
- msg = _('Session negotiation cancelled')
- ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
-
- def print_esession_details(self):
- """
- Print esession settings to textview
- """
- e2e_is_active = bool(self.session) and self.session.enable_encryption
- if e2e_is_active:
- msg = _('This session is encrypted')
-
- if self.session.is_loggable():
- msg += _(' and WILL be logged')
- else:
- msg += _(' and WILL NOT be logged')
-
- ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
-
- if not self.session.verified_identity:
- ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None)
- else:
- msg = _('E2E encryption disabled')
- ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
-
- self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
- self.session.is_loggable(), self.session and self.session.verified_identity)
-
- def print_conversation(self, text, frm='', tim=None, encrypted=False,
- subject=None, xhtml=None, simple=False, xep0184_id=None):
- """
- Print a line in the conversation
-
- If frm is set to status: it's a status message.
- if frm is set to error: it's an error message. The difference between
- status and error is mainly that with error, msg count as a new message
- (in systray and in control).
- If frm is set to info: it's a information message.
- If frm is set to print_queue: it is incomming from queue.
- If frm is set to another value: it's an outgoing message.
- If frm is not set: it's an incomming message.
- """
- contact = self.contact
-
- if frm == 'status':
- if not gajim.config.get('print_status_in_chats'):
- return
- kind = 'status'
- name = ''
- elif frm == 'error':
- kind = 'error'
- name = ''
- elif frm == 'info':
- kind = 'info'
- name = ''
- else:
- if self.session and self.session.enable_encryption:
- # ESessions
- if not encrypted:
- msg = _('The following message was NOT encrypted')
- ChatControlBase.print_conversation_line(self, msg, 'status', '',
- tim)
- else:
- # GPG encryption
- if encrypted and not self.gpg_is_active:
- msg = _('The following message was encrypted')
- ChatControlBase.print_conversation_line(self, msg, 'status', '',
- tim)
- # turn on OpenPGP if this was in fact a XEP-0027 encrypted message
- if encrypted == 'xep27':
- self._toggle_gpg()
- elif not encrypted and self.gpg_is_active:
- msg = _('The following message was NOT encrypted')
- ChatControlBase.print_conversation_line(self, msg, 'status', '',
- tim)
- if not frm:
- kind = 'incoming'
- name = contact.get_shown_name()
- elif frm == 'print_queue': # incoming message, but do not update time
- kind = 'incoming_queue'
- name = contact.get_shown_name()
- else:
- kind = 'outgoing'
- name = gajim.nicks[self.account]
- if not xhtml and not (encrypted and self.gpg_is_active) and \
- gajim.config.get('rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- xhtml = create_xhtml(text)
- if xhtml:
- xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml)
- ChatControlBase.print_conversation_line(self, text, kind, name, tim,
- subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml,
- simple=simple, xep0184_id=xep0184_id)
- if text.startswith('/me ') or text.startswith('/me\n'):
- self.old_msg_kind = None
- else:
- self.old_msg_kind = kind
-
- def get_tab_label(self, chatstate):
- unread = ''
- if self.resource:
- jid = self.contact.get_full_jid()
- else:
- jid = self.contact.jid
- num_unread = len(gajim.events.get_events(self.account, jid,
- ['printed_' + self.type_id, self.type_id]))
- if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'):
- unread = '*'
- elif num_unread > 1:
- unread = '[' + unicode(num_unread) + ']'
-
- # Draw tab label using chatstate
- theme = gajim.config.get('roster_theme')
- color = None
- if not chatstate:
- chatstate = self.contact.chatstate
- if chatstate is not None:
- if chatstate == 'composing':
- color = gajim.config.get_per('themes', theme,
- 'state_composing_color')
- elif chatstate == 'inactive':
- color = gajim.config.get_per('themes', theme,
- 'state_inactive_color')
- elif chatstate == 'gone':
- color = gajim.config.get_per('themes', theme,
- 'state_gone_color')
- elif chatstate == 'paused':
- color = gajim.config.get_per('themes', theme,
- 'state_paused_color')
- if color:
- # We set the color for when it's the current tab or not
- color = gtk.gdk.colormap_get_system().alloc_color(color)
- # In inactive tab color to be lighter against the darker inactive
- # background
- if chatstate in ('inactive', 'gone') and\
- self.parent_win.get_active_control() != self:
- color = self.lighten_color(color)
- else: # active or not chatstate, get color from gtk
- color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
-
-
- name = self.contact.get_shown_name()
- if self.resource:
- name += '/' + self.resource
- label_str = gobject.markup_escape_text(name)
- if num_unread: # if unread, text in the label becomes bold
- label_str = '<b>' + unread + label_str + '</b>'
- return (label_str, color)
-
- def get_tab_image(self, count_unread=True):
- if self.resource:
- jid = self.contact.get_full_jid()
- else:
- jid = self.contact.jid
- if count_unread:
- num_unread = len(gajim.events.get_events(self.account, jid,
- ['printed_' + self.type_id, self.type_id]))
- else:
- num_unread = 0
- # Set tab image (always 16x16); unread messages show the 'event' image
- tab_img = None
-
- if num_unread and gajim.config.get('show_unread_tab_icon'):
- img_16 = gajim.interface.roster.get_appropriate_state_images(
- self.contact.jid, icon_name = 'event')
- tab_img = img_16['event']
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, self.contact.jid)
- if not contact or self.resource:
- # For transient contacts
- contact = self.contact
- img_16 = gajim.interface.roster.get_appropriate_state_images(
- self.contact.jid, icon_name=contact.show)
- tab_img = img_16[contact.show]
-
- return tab_img
-
- def prepare_context_menu(self, hide_buttonbar_items=False):
- """
- Set compact view menuitem active state sets active and sensitivity state
- for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for
- tranasports) and file_transfer_menuitem and hide()/show() for
- add_to_roster_menuitem
- """
- menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
- use_multiple_contacts=False, show_start_chat=False,
- show_encryption=True, control=self,
- show_buttonbar_items=not hide_buttonbar_items)
- return menu
-
- def send_chatstate(self, state, contact = None):
- """
- Send OUR chatstate as STANDLONE chat state message (eg. no body)
- to contact only if new chatstate is different from the previous one
- if jid is not specified, send to active tab
- """
- # JEP 85 does not allow resending the same chatstate
- # this function checks for that and just returns so it's safe to call it
- # with same state.
-
- # This functions also checks for violation in state transitions
- # and raises RuntimeException with appropriate message
- # more on that http://www.jabber.org/jeps/jep-0085.html#statechart
-
- # do not send nothing if we have chat state notifications disabled
- # that means we won't reply to the <active/> from other peer
- # so we do not broadcast jep85 capabalities
- chatstate_setting = gajim.config.get('outgoing_chat_state_notifications')
- if chatstate_setting == 'disabled':
- return
- elif chatstate_setting == 'composing_only' and state != 'active' and\
- state != 'composing':
- return
-
- if contact is None:
- contact = self.parent_win.get_active_contact()
- if contact is None:
- # contact was from pm in MUC, and left the room so contact is None
- # so we cannot send chatstate anymore
- return
-
- # Don't send chatstates to offline contacts
- if contact.show == 'offline':
- return
-
- if contact.composing_xep is False: # jid cannot do xep85 nor xep22
- return
-
- # if the new state we wanna send (state) equals
- # the current state (contact.our_chatstate) then return
- if contact.our_chatstate == state:
- return
-
- if contact.composing_xep is None:
- # we don't know anything about jid, so return
- # NOTE:
- # send 'active', set current state to 'ask' and return is done
- # in self.send_message() because we need REAL message (with <body>)
- # for that procedure so return to make sure we send only once
- # 'active' until we know peer supports jep85
- return
-
- if contact.our_chatstate == 'ask':
- return
-
- # in JEP22, when we already sent stop composing
- # notification on paused, don't resend it
- if contact.composing_xep == 'XEP-0022' and \
- contact.our_chatstate in ('paused', 'active', 'inactive') and \
- state is not 'composing': # not composing == in (active, inactive, gone)
- contact.our_chatstate = 'active'
- self.reset_kbd_mouse_timeout_vars()
- return
-
- # prevent going paused if we we were not composing (JEP violation)
- if state == 'paused' and not contact.our_chatstate == 'composing':
- # go active before
- MessageControl.send_message(self, None, chatstate = 'active')
- contact.our_chatstate = 'active'
- self.reset_kbd_mouse_timeout_vars()
-
- # if we're inactive prevent composing (JEP violation)
- elif contact.our_chatstate == 'inactive' and state == 'composing':
- # go active before
- MessageControl.send_message(self, None, chatstate = 'active')
- contact.our_chatstate = 'active'
- self.reset_kbd_mouse_timeout_vars()
-
- MessageControl.send_message(self, None, chatstate = state,
- msg_id = contact.msg_id, composing_xep = contact.composing_xep)
- contact.our_chatstate = state
- if contact.our_chatstate == 'active':
- self.reset_kbd_mouse_timeout_vars()
-
- def shutdown(self):
- # Send 'gone' chatstate
- self.send_chatstate('gone', self.contact)
- self.contact.chatstate = None
- self.contact.our_chatstate = None
-
- for jingle_type in ('audio', 'video'):
- self.close_jingle_content(jingle_type)
-
- # disconnect self from session
- if self.session:
- self.session.control = None
-
- # Disconnect timer callbacks
- gobject.source_remove(self.possible_paused_timeout_id)
- gobject.source_remove(self.possible_inactive_timeout_id)
- # Remove bigger avatar window
- if self.bigger_avatar_window:
- self.bigger_avatar_window.destroy()
- # Clean events
- gajim.events.remove_events(self.account, self.get_full_jid(),
- types = ['printed_' + self.type_id, self.type_id])
- # Remove contact instance if contact has been removed
- key = (self.contact.jid, self.account)
- roster = gajim.interface.roster
- if key in roster.contacts_to_be_removed.keys() and \
- not roster.contact_has_pending_roster_events(self.contact, self.account):
- backend = roster.contacts_to_be_removed[key]['backend']
- del roster.contacts_to_be_removed[key]
- roster.remove_contact(self.contact.jid, self.account, force=True,
- backend=backend)
- # remove all register handlers on widgets, created by self.xml
- # to prevent circular references among objects
- for i in self.handlers.keys():
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers[i]
- self.conv_textview.del_handlers()
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
- spell_obj = gtkspell.get_from_text_view(self.msg_textview)
- if spell_obj:
- spell_obj.detach()
- self.msg_textview.destroy()
-
- def minimizable(self):
- return False
-
- def safe_shutdown(self):
- return False
-
- def allow_shutdown(self, method, on_yes, on_no, on_minimize):
- if time.time() - gajim.last_message_time[self.account]\
- [self.get_full_jid()] < 2:
- # 2 seconds
- def on_ok():
- on_yes(self)
-
- def on_cancel():
- on_no(self)
-
- dialogs.ConfirmationDialog(
- # %s is being replaced in the code with JID
- _('You just received a new message from "%s"') % self.contact.jid,
- _('If you close this tab and you have history disabled, '\
- 'this message will be lost.'), on_response_ok=on_ok,
- on_response_cancel=on_cancel)
- return
- on_yes(self)
-
- def handle_incoming_chatstate(self):
- """
- Handle incoming chatstate that jid SENT TO us
- """
- self.draw_banner_text()
- # update chatstate in tab for this chat
- self.parent_win.redraw_tab(self, self.contact.chatstate)
-
- def set_control_active(self, state):
- ChatControlBase.set_control_active(self, state)
- # send chatstate inactive to the one we're leaving
- # and active to the one we visit
- if state:
- self.send_chatstate('active', self.contact)
- else:
- self.send_chatstate('inactive', self.contact)
- # Hide bigger avatar window
- if self.bigger_avatar_window:
- self.bigger_avatar_window.destroy()
- self.bigger_avatar_window = None
- # Re-show the small avatar
- self.show_avatar()
-
- def show_avatar(self):
- if not gajim.config.get('show_avatar_in_chat'):
- return
-
- jid_with_resource = self.contact.get_full_jid()
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid_with_resource)
- if pixbuf == 'ask':
- # we don't have the vcard
- if self.TYPE_ID == message_control.TYPE_PM:
- if self.gc_contact.jid:
- # We know the real jid of this contact
- real_jid = self.gc_contact.jid
- if self.gc_contact.resource:
- real_jid += '/' + self.gc_contact.resource
- else:
- real_jid = jid_with_resource
- gajim.connections[self.account].request_vcard(real_jid,
- jid_with_resource)
- else:
- gajim.connections[self.account].request_vcard(jid_with_resource)
- return
- elif pixbuf:
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat')
- else:
- scaled_pixbuf = None
-
- image = self.xml.get_object('avatar_image')
- image.set_from_pixbuf(scaled_pixbuf)
- image.show_all()
-
- def _on_drag_data_received(self, widget, context, x, y, selection,
- target_type, timestamp):
- if not selection.data:
- return
- if self.TYPE_ID == message_control.TYPE_PM:
- c = self.gc_contact
- else:
- c = self.contact
- if target_type == self.TARGET_TYPE_URI_LIST:
- if not c.resource: # If no resource is known, we can't send a file
- return
- uri = selection.data.strip()
- uri_splitted = uri.split() # we may have more than one file dropped
- for uri in uri_splitted:
- path = helpers.get_file_path_from_dnd_dropped_uri(uri)
- if os.path.isfile(path): # is it file?
- ft = gajim.interface.instances['file_transfers']
- ft.send_file(self.account, c, path)
- return
-
- # chat2muc
- treeview = gajim.interface.roster.tree
- model = treeview.get_model()
- data = selection.data
- path = treeview.get_selection().get_selected_rows()[1][0]
- iter_ = model.get_iter(path)
- type_ = model[iter_][2]
- if type_ != 'contact': # source is not a contact
- return
- dropped_jid = data.decode('utf-8')
-
- dropped_transport = gajim.get_transport_name_from_jid(dropped_jid)
- c_transport = gajim.get_transport_name_from_jid(c.jid)
- if dropped_transport or c_transport:
- return # transport contacts cannot be invited
-
- dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid])
-
- def _on_message_tv_buffer_changed(self, textbuffer):
- self.kbd_activity_in_last_5_secs = True
- self.kbd_activity_in_last_30_secs = True
- if textbuffer.get_char_count():
- self.send_chatstate('composing', self.contact)
-
- e2e_is_active = self.session and \
- self.session.enable_encryption
- e2e_pref = gajim.config.get_per('accounts', self.account,
- 'enable_esessions') and gajim.config.get_per('accounts',
- self.account, 'autonegotiate_esessions') and gajim.config.get_per(
- 'contacts', self.contact.jid, 'autonegotiate_esessions')
- want_e2e = not e2e_is_active and not self.gpg_is_active \
- and e2e_pref
-
- if want_e2e and not self.no_autonegotiation \
- and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION):
- self.begin_e2e_negotiation()
- else:
- self.send_chatstate('active', self.contact)
-
- def restore_conversation(self):
- jid = self.contact.jid
- # don't restore lines if it's a transport
- if gajim.jid_is_transport(jid):
- return
-
- # How many lines to restore and when to time them out
- restore_how_many = gajim.config.get('restore_lines')
- if restore_how_many <= 0:
- return
- timeout = gajim.config.get('restore_timeout') # in minutes
-
- # number of messages that are in queue and are already logged, we want
- # to avoid duplication
- pending_how_many = len(gajim.events.get_events(self.account, jid,
- ['chat', 'pm']))
- if self.resource:
- pending_how_many += len(gajim.events.get_events(self.account,
- self.contact.get_full_jid(), ['chat', 'pm']))
-
- try:
- rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
- pending_how_many, timeout, self.account)
- except exceptions.DatabaseMalformed:
- import common.logger
- dialogs.ErrorDialog(_('Database Error'),
- _('The database file (%s) cannot be read. Try to repair it or remove it (all history will be lost).') % common.logger.LOG_DB_PATH)
- rows = []
- local_old_kind = None
- for row in rows: # row[0] time, row[1] has kind, row[2] the message
- if not row[2]: # message is empty, we don't print it
- continue
- if row[1] in (constants.KIND_CHAT_MSG_SENT,
- constants.KIND_SINGLE_MSG_SENT):
- kind = 'outgoing'
- name = gajim.nicks[self.account]
- elif row[1] in (constants.KIND_SINGLE_MSG_RECV,
- constants.KIND_CHAT_MSG_RECV):
- kind = 'incoming'
- name = self.contact.get_shown_name()
- elif row[1] == constants.KIND_ERROR:
- kind = 'status'
- name = self.contact.get_shown_name()
-
- tim = time.localtime(float(row[0]))
-
- if gajim.config.get('restored_messages_small'):
- small_attr = ['small']
- else:
- small_attr = []
- ChatControlBase.print_conversation_line(self, row[2], kind, name, tim,
- small_attr,
- small_attr + ['restored_message'],
- small_attr + ['restored_message'],
- False, old_kind = local_old_kind)
- if row[2].startswith('/me ') or row[2].startswith('/me\n'):
- local_old_kind = None
- else:
- local_old_kind = kind
- if len(rows):
- self.conv_textview.print_empty_line()
-
- def read_queue(self):
- """
- Read queue and print messages containted in it
- """
- jid = self.contact.jid
- jid_with_resource = jid
- if self.resource:
- jid_with_resource += '/' + self.resource
- events = gajim.events.get_events(self.account, jid_with_resource)
-
- # list of message ids which should be marked as read
- message_ids = []
- for event in events:
- if event.type_ != self.type_id:
- continue
- data = event.parameters
- kind = data[2]
- if kind == 'error':
- kind = 'info'
- else:
- kind = 'print_queue'
- self.print_conversation(data[0], kind, tim = data[3],
- encrypted = data[4], subject = data[1], xhtml = data[7])
- if len(data) > 6 and isinstance(data[6], int):
- message_ids.append(data[6])
-
- if len(data) > 8:
- self.set_session(data[8])
- if message_ids:
- gajim.logger.set_read_messages(message_ids)
- gajim.events.remove_events(self.account, jid_with_resource,
- types = [self.type_id])
-
- typ = 'chat' # Is it a normal chat or a pm ?
-
- # reset to status image in gc if it is a pm
- # Is it a pm ?
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
- control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- self.account)
- if control and control.type_id == message_control.TYPE_GC:
- control.update_ui()
- control.parent_win.show_title()
- typ = 'pm'
-
- self.redraw_after_event_removed(jid)
- if (self.contact.show in ('offline', 'error')):
- show_offline = gajim.config.get('showoffline')
- show_transports = gajim.config.get('show_transports_group')
- if (not show_transports and gajim.jid_is_transport(jid)) or \
- (not show_offline and typ == 'chat' and \
- len(gajim.contacts.get_contacts(self.account, jid)) < 2):
- gajim.interface.roster.remove_to_be_removed(self.contact.jid,
- self.account)
- elif typ == 'pm':
- control.remove_contact(nick)
-
- def show_bigger_avatar(self, small_avatar):
- """
- Resize the avatar, if needed, so it has at max half the screen size and
- shows it
- """
- if not small_avatar.window:
- # Tab has been closed since we hovered the avatar
- return
- avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(
- self.contact.jid)
- if avatar_pixbuf in ('ask', None):
- return
- # Hide the small avatar
- # this code hides the small avatar when we show a bigger one in case
- # the avatar has a transparency hole in the middle
- # so when we show the big one we avoid seeing the small one behind.
- # It's why I set it transparent.
- image = self.xml.get_object('avatar_image')
- pixbuf = image.get_pixbuf()
- pixbuf.fill(0xffffff00L) # RGBA
- image.queue_draw()
-
- screen_w = gtk.gdk.screen_width()
- screen_h = gtk.gdk.screen_height()
- avatar_w = avatar_pixbuf.get_width()
- avatar_h = avatar_pixbuf.get_height()
- half_scr_w = screen_w / 2
- half_scr_h = screen_h / 2
- if avatar_w > half_scr_w:
- avatar_w = half_scr_w
- if avatar_h > half_scr_h:
- avatar_h = half_scr_h
- window = gtk.Window(gtk.WINDOW_POPUP)
- self.bigger_avatar_window = window
- pixmap, mask = avatar_pixbuf.render_pixmap_and_mask()
- window.set_size_request(avatar_w, avatar_h)
- # we should make the cursor visible
- # gtk+ doesn't make use of the motion notify on gtkwindow by default
- # so this line adds that
- window.set_events(gtk.gdk.POINTER_MOTION_MASK)
- window.set_app_paintable(True)
- window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)
-
- window.realize()
- window.window.set_back_pixmap(pixmap, False) # make it transparent
- window.window.shape_combine_mask(mask, 0, 0)
-
- # make the bigger avatar window show up centered
- x0, y0 = small_avatar.window.get_origin()
- x0 += small_avatar.allocation.x
- y0 += small_avatar.allocation.y
- center_x= x0 + (small_avatar.allocation.width / 2)
- center_y = y0 + (small_avatar.allocation.height / 2)
- pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2)
- window.move(pos_x, pos_y)
- # make the cursor invisible so we can see the image
- invisible_cursor = gtkgui_helpers.get_invisible_cursor()
- window.window.set_cursor(invisible_cursor)
-
- # we should hide the window
- window.connect('leave_notify_event',
- self._on_window_avatar_leave_notify_event)
- window.connect('motion-notify-event',
- self._on_window_motion_notify_event)
-
- window.show_all()
-
- def _on_window_avatar_leave_notify_event(self, widget, event):
- """
- Just left the popup window that holds avatar
- """
- self.bigger_avatar_window.destroy()
- self.bigger_avatar_window = None
- # Re-show the small avatar
- self.show_avatar()
-
- def _on_window_motion_notify_event(self, widget, event):
- """
- Just moved the mouse so show the cursor
- """
- cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
- self.bigger_avatar_window.window.set_cursor(cursor)
-
- def _on_send_file_menuitem_activate(self, widget):
- self._on_send_file()
-
- def _on_add_to_roster_menuitem_activate(self, widget):
- dialogs.AddNewContactWindow(self.account, self.contact.jid)
-
- def _on_contact_information_menuitem_activate(self, widget):
- gajim.interface.roster.on_info(widget, self.contact, self.account)
-
- def _on_toggle_gpg_menuitem_activate(self, widget):
- self._toggle_gpg()
-
- def _on_convert_to_gc_menuitem_activate(self, widget):
- """
- User wants to invite some friends to chat
- """
- dialogs.TransformChatToMUC(self.account, [self.contact.jid])
-
- def _on_toggle_e2e_menuitem_activate(self, widget):
- if self.session and self.session.enable_encryption:
- # e2e was enabled, disable it
- jid = str(self.session.jid)
- thread_id = self.session.thread_id
-
- self.session.terminate_e2e()
-
- gajim.connections[self.account].delete_session(jid, thread_id)
-
- # presumably the user had a good reason to shut it off, so
- # disable autonegotiation too
- self.no_autonegotiation = True
- else:
- self.begin_e2e_negotiation()
-
- def begin_e2e_negotiation(self):
- self.no_autonegotiation = True
-
- if not self.session:
- fjid = self.contact.get_full_jid()
- new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id)
- self.set_session(new_sess)
-
- self.session.negotiate_e2e(False)
-
- def got_connected(self):
- ChatControlBase.got_connected(self)
- # Refreshing contact
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, self.contact.jid)
- if isinstance(contact, GC_Contact):
- contact = contact.as_contact()
- if contact:
- self.contact = contact
- self.draw_banner()
-
- def update_status_display(self, name, uf_show, status):
- """
- Print the contact's status and update the status/GPG image
- """
- self.update_ui()
- self.parent_win.redraw_tab(self)
-
- self.print_conversation(_('%(name)s is now %(status)s') % {'name': name,
- 'status': uf_show}, 'status')
-
- if status:
- self.print_conversation(' (', 'status', simple=True)
- self.print_conversation('%s' % (status), 'status', simple=True)
- self.print_conversation(')', 'status', simple=True)
-
-# vim: se ts=3:
+ """
+ A control for standard 1-1 chat
+ """
+ (
+ JINGLE_STATE_NOT_AVAILABLE,
+ JINGLE_STATE_AVAILABLE,
+ JINGLE_STATE_CONNECTING,
+ JINGLE_STATE_CONNECTION_RECEIVED,
+ JINGLE_STATE_CONNECTED,
+ JINGLE_STATE_ERROR
+ ) = range(6)
+
+ TYPE_ID = message_control.TYPE_CHAT
+ old_msg_kind = None # last kind of the printed message
+
+ # Set a command host to bound to. Every command given through a chat will be
+ # processed with this command host.
+ COMMAND_HOST = ChatCommands
+
+ def __init__(self, parent_win, contact, acct, session, resource = None):
+ ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
+ 'chat_control', contact, acct, resource)
+
+ self.gpg_is_active = False
+ # for muc use:
+ # widget = self.xml.get_object('muc_window_actions_button')
+ self.actions_button = self.xml.get_object('message_window_actions_button')
+ id_ = self.actions_button.connect('clicked',
+ self.on_actions_button_clicked)
+ self.handlers[id_] = self.actions_button
+
+ self._formattings_button = self.xml.get_object('formattings_button')
+
+ self._add_to_roster_button = self.xml.get_object(
+ 'add_to_roster_button')
+ id_ = self._add_to_roster_button.connect('clicked',
+ self._on_add_to_roster_menuitem_activate)
+ self.handlers[id_] = self._add_to_roster_button
+
+ self._audio_button = self.xml.get_object('audio_togglebutton')
+ id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled)
+ self.handlers[id_] = self._audio_button
+ # add a special img
+ gtkgui_helpers.add_image_to_button(self._audio_button,
+ 'gajim-mic_inactive')
+
+ self._video_button = self.xml.get_object('video_togglebutton')
+ id_ = self._video_button.connect('toggled', self.on_video_button_toggled)
+ self.handlers[id_] = self._video_button
+ # add a special img
+ gtkgui_helpers.add_image_to_button(self._video_button,
+ 'gajim-cam_inactive')
+
+ self._send_file_button = self.xml.get_object('send_file_button')
+ # add a special img for send file button
+ path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
+ img = gtk.Image()
+ img.set_from_file(path_to_upload_img)
+ self._send_file_button.set_image(img)
+ id_ = self._send_file_button.connect('clicked',
+ self._on_send_file_menuitem_activate)
+ self.handlers[id_] = self._send_file_button
+
+ self._convert_to_gc_button = self.xml.get_object(
+ 'convert_to_gc_button')
+ id_ = self._convert_to_gc_button.connect('clicked',
+ self._on_convert_to_gc_menuitem_activate)
+ self.handlers[id_] = self._convert_to_gc_button
+
+ contact_information_button = self.xml.get_object(
+ 'contact_information_button')
+ id_ = contact_information_button.connect('clicked',
+ self._on_contact_information_menuitem_activate)
+ self.handlers[id_] = contact_information_button
+
+ compact_view = gajim.config.get('compact_view')
+ self.chat_buttons_set_visible(compact_view)
+ self.widget_set_visible(self.xml.get_object('banner_eventbox'),
+ gajim.config.get('hide_chat_banner'))
+
+ self.authentication_button = self.xml.get_object(
+ 'authentication_button')
+ id_ = self.authentication_button.connect('clicked',
+ self._on_authentication_button_clicked)
+ self.handlers[id_] = self.authentication_button
+
+ # Add lock image to show chat encryption
+ self.lock_image = self.xml.get_object('lock_image')
+
+ # Convert to GC icon
+ img = self.xml.get_object('convert_to_gc_button_image')
+ img.set_from_pixbuf(gtkgui_helpers.load_icon(
+ 'muc_active').get_pixbuf())
+
+ self._audio_banner_image = self.xml.get_object('audio_banner_image')
+ self._video_banner_image = self.xml.get_object('video_banner_image')
+ self.audio_sid = None
+ self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE
+ self.video_sid = None
+ self.video_state = self.JINGLE_STATE_NOT_AVAILABLE
+
+ self.update_toolbar()
+
+ self._pep_images = {}
+ self._pep_images['mood'] = self.xml.get_object('mood_image')
+ self._pep_images['activity'] = self.xml.get_object('activity_image')
+ self._pep_images['tune'] = self.xml.get_object('tune_image')
+ self._pep_images['location'] = self.xml.get_object('location_image')
+ self.update_all_pep_types()
+
+ # keep timeout id and window obj for possible big avatar
+ # it is on enter-notify and leave-notify so no need to be
+ # per jid
+ self.show_bigger_avatar_timeout_id = None
+ self.bigger_avatar_window = None
+ self.show_avatar()
+
+ # chatstate timers and state
+ self.reset_kbd_mouse_timeout_vars()
+ self._schedule_activity_timers()
+
+ # Hook up signals
+ id_ = self.parent_win.window.connect('motion-notify-event',
+ self._on_window_motion_notify)
+ self.handlers[id_] = self.parent_win.window
+ message_tv_buffer = self.msg_textview.get_buffer()
+ id_ = message_tv_buffer.connect('changed',
+ self._on_message_tv_buffer_changed)
+ self.handlers[id_] = message_tv_buffer
+
+ widget = self.xml.get_object('avatar_eventbox')
+ widget.set_property('height-request', gajim.config.get(
+ 'chat_avatar_height'))
+ id_ = widget.connect('enter-notify-event',
+ self.on_avatar_eventbox_enter_notify_event)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('leave-notify-event',
+ self.on_avatar_eventbox_leave_notify_event)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('button-press-event',
+ self.on_avatar_eventbox_button_press_event)
+ self.handlers[id_] = widget
+
+ widget = self.xml.get_object('location_eventbox')
+ id_ = widget.connect('button-release-event',
+ self.on_location_eventbox_button_release_event)
+ self.handlers[id_] = widget
+
+ for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'):
+ widget = self.xml.get_object(key + '_button')
+ id_ = widget.connect('pressed', self.on_num_button_pressed, key)
+ self.handlers[id_] = widget
+ id_ = widget.connect('released', self.on_num_button_released)
+ self.handlers[id_] = widget
+
+ widget = self.xml.get_object('mic_hscale')
+ id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed)
+ self.handlers[id_] = widget
+
+ widget = self.xml.get_object('sound_hscale')
+ id_ = widget.connect('value_changed', self.on_sound_hscale_value_changed)
+ self.handlers[id_] = widget
+
+ if not session:
+ # Don't use previous session if we want to a specific resource
+ # and it's not the same
+ if not resource:
+ resource = contact.resource
+ session = gajim.connections[self.account].find_controlless_session(
+ self.contact.jid, resource)
+
+ if session:
+ session.control = self
+ self.session = session
+
+ if session.enable_encryption:
+ self.print_esession_details()
+
+ # Enable encryption if needed
+ self.no_autonegotiation = False
+ e2e_is_active = self.session and self.session.enable_encryption
+ gpg_pref = gajim.config.get_per('contacts', contact.jid,
+ 'gpg_enabled')
+
+ # try GPG first
+ if not e2e_is_active and gpg_pref and \
+ gajim.config.get_per('accounts', self.account, 'keyid') and \
+ gajim.connections[self.account].USE_GPG:
+ self.gpg_is_active = True
+ gajim.encrypted_chats[self.account].append(contact.jid)
+ msg = _('GPG encryption enabled')
+ ChatControlBase.print_conversation_line(self, msg,
+ 'status', '', None)
+
+ if self.session:
+ self.session.loggable = gajim.config.get_per('accounts',
+ self.account, 'log_encrypted_sessions')
+ # GPG is always authenticated as we use GPG's WoT
+ self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active,
+ self.session and self.session.is_loggable(), True)
+
+ self.update_ui()
+ # restore previous conversation
+ self.restore_conversation()
+ self.msg_textview.grab_focus()
+
+ def update_toolbar(self):
+ # Formatting
+ if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active:
+ self._formattings_button.set_sensitive(True)
+ else:
+ self._formattings_button.set_sensitive(False)
+
+ # Add to roster
+ if not isinstance(self.contact, GC_Contact) \
+ and _('Not in Roster') in self.contact.groups:
+ self._add_to_roster_button.show()
+ else:
+ self._add_to_roster_button.hide()
+
+ # Jingle detection
+ if self.contact.supports(NS_JINGLE_ICE_UDP) and \
+ gajim.HAVE_FARSIGHT and self.contact.resource:
+ if self.contact.supports(NS_JINGLE_RTP_AUDIO):
+ if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
+ self.set_audio_state('available')
+ else:
+ self.set_audio_state('not_available')
+
+ if self.contact.supports(NS_JINGLE_RTP_VIDEO):
+ if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
+ self.set_video_state('available')
+ else:
+ self.set_video_state('not_available')
+ else:
+ if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE:
+ self.set_audio_state('not_available')
+ if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE:
+ self.set_video_state('not_available')
+
+ # Audio buttons
+ if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
+ self._audio_button.set_sensitive(False)
+ else:
+ self._audio_button.set_sensitive(True)
+
+ # Video buttons
+ if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
+ self._video_button.set_sensitive(False)
+ else:
+ self._video_button.set_sensitive(True)
+
+ # Send file
+ if self.contact.supports(NS_FILE) and self.contact.resource:
+ self._send_file_button.set_sensitive(True)
+ else:
+ self._send_file_button.set_sensitive(False)
+ if not self.contact.supports(NS_FILE):
+ self._send_file_button.set_tooltip_text(_(
+ "This contact does not support file transfer."))
+ else:
+ self._send_file_button.set_tooltip_text(
+ _("You need to know the real JID of the contact to send him or "
+ "her a file."))
+
+ # Convert to GC
+ if self.contact.supports(NS_MUC):
+ self._convert_to_gc_button.set_sensitive(True)
+ else:
+ self._convert_to_gc_button.set_sensitive(False)
+
+ def update_all_pep_types(self):
+ for pep_type in self._pep_images:
+ self.update_pep(pep_type)
+
+ def update_pep(self, pep_type):
+ if isinstance(self.contact, GC_Contact):
+ return
+ if pep_type not in self._pep_images:
+ return
+ pep = self.contact.pep
+ img = self._pep_images[pep_type]
+ if pep_type in pep:
+ img.set_from_pixbuf(pep[pep_type].asPixbufIcon())
+ img.set_tooltip_markup(pep[pep_type].asMarkupText())
+ img.show()
+ else:
+ img.hide()
+
+ def _update_jingle(self, jingle_type):
+ if jingle_type not in ('audio', 'video'):
+ return
+ banner_image = getattr(self, '_' + jingle_type + '_banner_image')
+ state = getattr(self, jingle_type + '_state')
+ if state in (self.JINGLE_STATE_NOT_AVAILABLE,
+ self.JINGLE_STATE_AVAILABLE):
+ banner_image.hide()
+ else:
+ banner_image.show()
+ if state == self.JINGLE_STATE_CONNECTING:
+ banner_image.set_from_stock(
+ gtk.STOCK_CONVERT, 1)
+ elif state == self.JINGLE_STATE_CONNECTION_RECEIVED:
+ banner_image.set_from_stock(
+ gtk.STOCK_NETWORK, 1)
+ elif state == self.JINGLE_STATE_CONNECTED:
+ banner_image.set_from_stock(
+ gtk.STOCK_CONNECT, 1)
+ elif state == self.JINGLE_STATE_ERROR:
+ banner_image.set_from_stock(
+ gtk.STOCK_DIALOG_WARNING, 1)
+ self.update_toolbar()
+
+ def update_audio(self):
+ self._update_jingle('audio')
+ vbox = self.xml.get_object('audio_vbox')
+ if self.audio_state == self.JINGLE_STATE_CONNECTED:
+ # Set volume from config
+ input_vol = gajim.config.get('audio_input_volume')
+ output_vol = gajim.config.get('audio_output_volume')
+ input_vol = max(min(input_vol, 100), 0)
+ output_vol = max(min(output_vol, 100), 0)
+ self.xml.get_object('mic_hscale').set_value(input_vol)
+ self.xml.get_object('sound_hscale').set_value(output_vol)
+ # Show vbox
+ vbox.set_no_show_all(False)
+ vbox.show_all()
+ elif not self.audio_sid:
+ vbox.set_no_show_all(True)
+ vbox.hide()
+
+ def update_video(self):
+ self._update_jingle('video')
+
+ def change_resource(self, resource):
+ old_full_jid = self.get_full_jid()
+ self.resource = resource
+ new_full_jid = self.get_full_jid()
+ # update gajim.last_message_time
+ if old_full_jid in gajim.last_message_time[self.account]:
+ gajim.last_message_time[self.account][new_full_jid] = \
+ gajim.last_message_time[self.account][old_full_jid]
+ # update events
+ gajim.events.change_jid(self.account, old_full_jid, new_full_jid)
+ # update MessageWindow._controls
+ self.parent_win.change_jid(self.account, old_full_jid, new_full_jid)
+
+ def _set_jingle_state(self, jingle_type, state, sid=None, reason=None):
+ if jingle_type not in ('audio', 'video'):
+ return
+ if state in ('connecting', 'connected', 'stop') and reason:
+ str = _('%(type)s state : %(state)s, reason: %(reason)s') % {
+ 'type': jingle_type.capitalize(), 'state': state, 'reason': reason}
+ self.print_conversation(str, 'info')
+
+ states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE,
+ 'available': self.JINGLE_STATE_AVAILABLE,
+ 'connecting': self.JINGLE_STATE_CONNECTING,
+ 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED,
+ 'connected': self.JINGLE_STATE_CONNECTED,
+ 'stop': self.JINGLE_STATE_AVAILABLE,
+ 'error': self.JINGLE_STATE_ERROR}
+
+ if state in states:
+ jingle_state = states[state]
+ if getattr(self, jingle_type + '_state') == jingle_state:
+ return
+ setattr(self, jingle_type + '_state', jingle_state)
+
+ # Destroy existing session with the user when he signs off
+ # We need to do that before modifying the sid
+ if state == 'not_available':
+ gajim.connections[self.account].delete_jingle_session(
+ self.contact.get_full_jid(), getattr(self, jingle_type + '_sid'))
+
+ if state in ('not_available', 'available', 'stop'):
+ setattr(self, jingle_type + '_sid', None)
+ if state in ('connection_received', 'connecting'):
+ setattr(self, jingle_type + '_sid', sid)
+
+ if state in ('connecting', 'connected', 'connection_received'):
+ getattr(self, '_' + jingle_type + '_button').set_active(True)
+ elif state in ('not_available', 'stop'):
+ getattr(self, '_' + jingle_type + '_button').set_active(False)
+
+ getattr(self, 'update_' + jingle_type)()
+
+ def set_audio_state(self, state, sid=None, reason=None):
+ self._set_jingle_state('audio', state, sid=sid, reason=reason)
+
+ def set_video_state(self, state, sid=None, reason=None):
+ self._set_jingle_state('video', state, sid=sid, reason=reason)
+
+ def _get_audio_content(self):
+ session = gajim.connections[self.account].get_jingle_session(
+ self.contact.get_full_jid(), self.audio_sid)
+ return session.get_content('audio')
+
+ def on_num_button_pressed(self, widget, num):
+ self._get_audio_content()._start_dtmf(num)
+
+ def on_num_button_released(self, released):
+ self._get_audio_content()._stop_dtmf()
+
+ def on_mic_hscale_value_changed(self, widget):
+ value = widget.get_value()
+ self._get_audio_content().set_mic_volume(value / 100)
+ # Save volume to config
+ # FIXME: Putting it here is maybe not the right thing to do?
+ gajim.config.set('audio_input_volume', value)
+
+
+ def on_sound_hscale_value_changed(self, widget):
+ value = widget.get_value()
+ self._get_audio_content().set_out_volume(value / 100)
+ # Save volume to config
+ # FIXME: Putting it here is maybe not the right thing to do?
+ gajim.config.set('audio_output_volume', value)
+
+ def on_avatar_eventbox_enter_notify_event(self, widget, event):
+ """
+ Enter the eventbox area so we under conditions add a timeout to show a
+ bigger avatar after 0.5 sec
+ """
+ jid = self.contact.jid
+ avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
+ if avatar_pixbuf in ('ask', None):
+ return
+ avatar_w = avatar_pixbuf.get_width()
+ avatar_h = avatar_pixbuf.get_height()
+
+ scaled_buf = self.xml.get_object('avatar_image').get_pixbuf()
+ scaled_buf_w = scaled_buf.get_width()
+ scaled_buf_h = scaled_buf.get_height()
+
+ # do we have something bigger to show?
+ if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h:
+ # wait for 0.5 sec in case we leave earlier
+ self.show_bigger_avatar_timeout_id = gobject.timeout_add(500,
+ self.show_bigger_avatar, widget)
+
+ def on_avatar_eventbox_leave_notify_event(self, widget, event):
+ """
+ Left the eventbox area that holds the avatar img
+ """
+ # did we add a timeout? if yes remove it
+ if self.show_bigger_avatar_timeout_id is not None:
+ gobject.source_remove(self.show_bigger_avatar_timeout_id)
+
+ def on_avatar_eventbox_button_press_event(self, widget, event):
+ """
+ If right-clicked, show popup
+ """
+ if event.button == 3: # right click
+ menu = gtk.Menu()
+ menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
+ id_ = menuitem.connect('activate',
+ gtkgui_helpers.on_avatar_save_as_menuitem_activate,
+ self.contact.jid, self.account, self.contact.get_shown_name())
+ self.handlers[id_] = menuitem
+ menu.append(menuitem)
+ menu.show_all()
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+ return True
+
+ def on_location_eventbox_button_release_event(self, widget, event):
+ if 'location' in self.contact.pep:
+ location = self.contact.pep['location']._pep_specific_data
+ if ('lat' in location) and ('lon' in location):
+ uri = 'http://www.openstreetmap.org/?' + \
+ 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'],
+ 'lon': location['lon']}
+ helpers.launch_browser_mailer('url', uri)
+
+ def _on_window_motion_notify(self, widget, event):
+ """
+ It gets called no matter if it is the active window or not
+ """
+ if self.parent_win.get_active_jid() == self.contact.jid:
+ # if window is the active one, change vars assisting chatstate
+ self.mouse_over_in_last_5_secs = True
+ self.mouse_over_in_last_30_secs = True
+
+ def _schedule_activity_timers(self):
+ self.possible_paused_timeout_id = gobject.timeout_add_seconds(5,
+ self.check_for_possible_paused_chatstate, None)
+ self.possible_inactive_timeout_id = gobject.timeout_add_seconds(30,
+ self.check_for_possible_inactive_chatstate, None)
+
+ def update_ui(self):
+ # The name banner is drawn here
+ ChatControlBase.update_ui(self)
+ self.update_toolbar()
+
+ def _update_banner_state_image(self):
+ contact = gajim.contacts.get_contact_with_highest_priority(self.account,
+ self.contact.jid)
+ if not contact or self.resource:
+ # For transient contacts
+ contact = self.contact
+ show = contact.show
+ jid = contact.jid
+
+ # Set banner image
+ img_32 = gajim.interface.roster.get_appropriate_state_images(jid,
+ size = '32', icon_name = show)
+ img_16 = gajim.interface.roster.get_appropriate_state_images(jid,
+ icon_name = show)
+ if show in img_32 and img_32[show].get_pixbuf():
+ # we have 32x32! use it!
+ banner_image = img_32[show]
+ use_size_32 = True
+ else:
+ banner_image = img_16[show]
+ use_size_32 = False
+
+ banner_status_img = self.xml.get_object('banner_status_image')
+ if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION:
+ banner_status_img.set_from_animation(banner_image.get_animation())
+ else:
+ pix = banner_image.get_pixbuf()
+ if pix is not None:
+ if use_size_32:
+ banner_status_img.set_from_pixbuf(pix)
+ else: # we need to scale 16x16 to 32x32
+ scaled_pix = pix.scale_simple(32, 32,
+ gtk.gdk.INTERP_BILINEAR)
+ banner_status_img.set_from_pixbuf(scaled_pix)
+
+ def draw_banner_text(self):
+ """
+ Draw the text in the fat line at the top of the window that houses the
+ name, jid
+ """
+ contact = self.contact
+ jid = contact.jid
+
+ banner_name_label = self.xml.get_object('banner_name_label')
+
+ name = contact.get_shown_name()
+ if self.resource:
+ name += '/' + self.resource
+ if self.TYPE_ID == message_control.TYPE_PM:
+ name = _('%(nickname)s from group chat %(room_name)s') %\
+ {'nickname': name, 'room_name': self.room_name}
+ name = gobject.markup_escape_text(name)
+
+ # We know our contacts nick, but if another contact has the same nick
+ # in another account we need to also display the account.
+ # except if we are talking to two different resources of the same contact
+ acct_info = ''
+ for account in gajim.contacts.get_accounts():
+ if account == self.account:
+ continue
+ if acct_info: # We already found a contact with same nick
+ break
+ for jid in gajim.contacts.get_jid_list(account):
+ other_contact_ = \
+ gajim.contacts.get_first_contact_from_jid(account, jid)
+ if other_contact_.get_shown_name() == self.contact.get_shown_name():
+ acct_info = ' (%s)' % \
+ gobject.markup_escape_text(self.account)
+ break
+
+ status = contact.status
+ if status is not None:
+ banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
+ self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
+ status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1)
+ status_escaped = gobject.markup_escape_text(status_reduced)
+
+ font_attrs, font_attrs_small = self.get_font_attrs()
+ st = gajim.config.get('displayed_chat_state_notifications')
+ cs = contact.chatstate
+ if cs and st in ('composing_only', 'all'):
+ if contact.show == 'offline':
+ chatstate = ''
+ elif contact.composing_xep == 'XEP-0085':
+ if st == 'all' or cs == 'composing':
+ chatstate = helpers.get_uf_chatstate(cs)
+ else:
+ chatstate = ''
+ elif contact.composing_xep == 'XEP-0022':
+ if cs in ('composing', 'paused'):
+ # only print composing, paused
+ chatstate = helpers.get_uf_chatstate(cs)
+ else:
+ chatstate = ''
+ else:
+ # When does that happen ? See [7797] and [7804]
+ chatstate = helpers.get_uf_chatstate(cs)
+
+ label_text = '<span %s>%s</span><span %s>%s %s</span>' \
+ % (font_attrs, name, font_attrs_small,
+ acct_info, chatstate)
+ if acct_info:
+ acct_info = ' ' + acct_info
+ label_tooltip = '%s%s %s' % (name, acct_info, chatstate)
+ else:
+ # weight="heavy" size="x-large"
+ label_text = '<span %s>%s</span><span %s>%s</span>' % \
+ (font_attrs, name, font_attrs_small, acct_info)
+ if acct_info:
+ acct_info = ' ' + acct_info
+ label_tooltip = '%s%s' % (name, acct_info)
+
+ if status_escaped:
+ status_text = self.urlfinder.sub(self.make_href, status_escaped)
+ status_text = '<span %s>%s</span>' % (font_attrs_small, status_escaped)
+ self.banner_status_label.set_tooltip_text(status)
+ self.banner_status_label.set_no_show_all(False)
+ self.banner_status_label.show()
+ else:
+ status_text = ''
+ self.banner_status_label.hide()
+ self.banner_status_label.set_no_show_all(True)
+
+ self.banner_status_label.set_markup(status_text)
+ # setup the label that holds name and jid
+ banner_name_label.set_markup(label_text)
+ banner_name_label.set_tooltip_text(label_tooltip)
+
+ def close_jingle_content(self, jingle_type):
+ sid = getattr(self, jingle_type + '_sid')
+ if not sid:
+ return
+ session = gajim.connections[self.account].get_jingle_session(
+ self.contact.get_full_jid(), sid)
+ if session:
+ content = session.get_content(jingle_type)
+ if content:
+ session.remove_content(content.creator, content.name)
+
+ def on_jingle_button_toggled(self, widget, jingle_type):
+ img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type],
+ {True: 'active', False: 'inactive'}[widget.get_active()])
+ path_to_img = gtkgui_helpers.get_icon_path(img_name)
+
+ if widget.get_active():
+ if getattr(self, jingle_type + '_state') == \
+ self.JINGLE_STATE_AVAILABLE:
+ sid = getattr(gajim.connections[self.account],
+ 'start_' + jingle_type)(self.contact.get_full_jid())
+ getattr(self, 'set_' + jingle_type + '_state')('connecting', sid)
+ else:
+ self.close_jingle_content(jingle_type)
+
+ img = getattr(self, '_' + jingle_type + '_button').get_property('image')
+ img.set_from_file(path_to_img)
+
+ def on_audio_button_toggled(self, widget):
+ self.on_jingle_button_toggled(widget, 'audio')
+
+ def on_video_button_toggled(self, widget):
+ self.on_jingle_button_toggled(widget, 'video')
+
+ def _toggle_gpg(self):
+ if not self.gpg_is_active and not self.contact.keyID:
+ dialogs.ErrorDialog(_('No GPG key assigned'),
+ _('No GPG key is assigned to this contact. So you cannot '
+ 'encrypt messages with GPG.'))
+ return
+ ec = gajim.encrypted_chats[self.account]
+ if self.gpg_is_active:
+ # Disable encryption
+ ec.remove(self.contact.jid)
+ self.gpg_is_active = False
+ loggable = False
+ msg = _('GPG encryption disabled')
+ ChatControlBase.print_conversation_line(self, msg,
+ 'status', '', None)
+ if self.session:
+ self.session.loggable = True
+
+ else:
+ # Enable encryption
+ ec.append(self.contact.jid)
+ self.gpg_is_active = True
+ msg = _('GPG encryption enabled')
+ ChatControlBase.print_conversation_line(self, msg,
+ 'status', '', None)
+
+ loggable = gajim.config.get_per('accounts', self.account,
+ 'log_encrypted_sessions')
+
+ if self.session:
+ self.session.loggable = loggable
+
+ loggable = self.session.is_loggable()
+ else:
+ loggable = loggable and gajim.config.should_log(self.account,
+ self.contact.jid)
+
+ if loggable:
+ msg = _('Session WILL be logged')
+ else:
+ msg = _('Session WILL NOT be logged')
+
+ ChatControlBase.print_conversation_line(self, msg,
+ 'status', '', None)
+
+ gajim.config.set_per('contacts', self.contact.jid,
+ 'gpg_enabled', self.gpg_is_active)
+
+ self._show_lock_image(self.gpg_is_active, 'GPG',
+ self.gpg_is_active, loggable, True)
+
+ def _show_lock_image(self, visible, enc_type = '', enc_enabled = False,
+ chat_logged = False, authenticated = False):
+ """
+ Set lock icon visibility and create tooltip
+ """
+ #encryption %s active
+ status_string = enc_enabled and _('is') or _('is NOT')
+ #chat session %s be logged
+ logged_string = chat_logged and _('will') or _('will NOT')
+
+ if authenticated:
+ #About encrypted chat session
+ authenticated_string = _('and authenticated')
+ img_path = gtkgui_helpers.get_icon_path('gajim-security_high')
+ else:
+ #About encrypted chat session
+ authenticated_string = _('and NOT authenticated')
+ img_path = gtkgui_helpers.get_icon_path('gajim-security_low')
+ self.lock_image.set_from_file(img_path)
+
+ #status will become 'is' or 'is not', authentificaed will become
+ #'and authentificated' or 'and not authentificated', logged will become
+ #'will' or 'will not'
+ tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n'
+ 'Your chat session %(logged)s be logged.') % {'type': enc_type,
+ 'status': status_string, 'authenticated': authenticated_string,
+ 'logged': logged_string}
+
+ self.authentication_button.set_tooltip_text(tooltip)
+ self.widget_set_visible(self.authentication_button, not visible)
+ self.lock_image.set_sensitive(enc_enabled)
+
+ def _on_authentication_button_clicked(self, widget):
+ if self.gpg_is_active:
+ dialogs.GPGInfoWindow(self)
+ elif self.session and self.session.enable_encryption:
+ dialogs.ESessionInfoWindow(self.session)
+
+ def send_message(self, message, keyID='', chatstate=None, xhtml=None,
+ process_commands=True):
+ """
+ Send a message to contact
+ """
+ if message in ('', None, '\n'):
+ return None
+
+ # refresh timers
+ self.reset_kbd_mouse_timeout_vars()
+
+ contact = self.contact
+
+ encrypted = bool(self.session) and self.session.enable_encryption
+
+ keyID = ''
+ if self.gpg_is_active:
+ keyID = contact.keyID
+ encrypted = True
+ if not keyID:
+ keyID = 'UNKNOWN'
+
+ chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
+ 'disabled'
+ composing_xep = contact.composing_xep
+ chatstate_to_send = None
+ if chatstates_on and contact is not None:
+ if composing_xep is None:
+ # no info about peer
+ # send active to discover chat state capabilities
+ # this is here (and not in send_chatstate)
+ # because we want it sent with REAL message
+ # (not standlone) eg. one that has body
+
+ if contact.our_chatstate:
+ # We already asked for xep 85, don't ask it twice
+ composing_xep = 'asked_once'
+
+ chatstate_to_send = 'active'
+ contact.our_chatstate = 'ask' # pseudo state
+ # if peer supports jep85 and we are not 'ask', send 'active'
+ # NOTE: first active and 'ask' is set in gajim.py
+ elif composing_xep is not False:
+ # send active chatstate on every message (as XEP says)
+ chatstate_to_send = 'active'
+ contact.our_chatstate = 'active'
+
+ gobject.source_remove(self.possible_paused_timeout_id)
+ gobject.source_remove(self.possible_inactive_timeout_id)
+ self._schedule_activity_timers()
+
+ def _on_sent(id_, contact, message, encrypted, xhtml):
+ if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts',
+ self.account, 'request_receipt'):
+ xep0184_id = id_
+ else:
+ xep0184_id = None
+
+ self.print_conversation(message, self.contact.jid, encrypted=encrypted,
+ xep0184_id=xep0184_id, xhtml=xhtml)
+
+ ChatControlBase.send_message(self, message, keyID, type_='chat',
+ chatstate=chatstate_to_send, composing_xep=composing_xep,
+ xhtml=xhtml, callback=_on_sent,
+ callback_args=[contact, message, encrypted, xhtml],
+ process_commands=process_commands)
+
+ def check_for_possible_paused_chatstate(self, arg):
+ """
+ Did we move mouse of that window or write something in message textview
+ in the last 5 seconds? If yes - we go active for mouse, composing for
+ kbd. If not - we go paused if we were previously composing
+ """
+ contact = self.contact
+ jid = contact.jid
+ current_state = contact.our_chatstate
+ if current_state is False: # jid doesn't support chatstates
+ return False # stop looping
+
+ message_buffer = self.msg_textview.get_buffer()
+ if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count():
+ # Only composing if the keyboard activity was in text entry
+ self.send_chatstate('composing')
+ elif self.mouse_over_in_last_5_secs and\
+ jid == self.parent_win.get_active_jid():
+ self.send_chatstate('active')
+ else:
+ if current_state == 'composing':
+ self.send_chatstate('paused') # pause composing
+
+ # assume no activity and let the motion-notify or 'insert-text' make them
+ # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
+ self.reset_kbd_mouse_timeout_vars()
+ return True # loop forever
+
+ def check_for_possible_inactive_chatstate(self, arg):
+ """
+ Did we move mouse over that window or wrote something in message textview
+ in the last 30 seconds? if yes - we go active. If no - we go inactive
+ """
+ contact = self.contact
+
+ current_state = contact.our_chatstate
+ if current_state is False: # jid doesn't support chatstates
+ return False # stop looping
+
+ if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
+ return True # loop forever
+
+ if not self.mouse_over_in_last_30_secs or \
+ self.kbd_activity_in_last_30_secs:
+ self.send_chatstate('inactive', contact)
+
+ # assume no activity and let the motion-notify or 'insert-text' make them
+ # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
+ self.reset_kbd_mouse_timeout_vars()
+ return True # loop forever
+
+ def reset_kbd_mouse_timeout_vars(self):
+ self.kbd_activity_in_last_5_secs = False
+ self.mouse_over_in_last_5_secs = False
+ self.mouse_over_in_last_30_secs = False
+ self.kbd_activity_in_last_30_secs = False
+
+ def on_cancel_session_negotiation(self):
+ msg = _('Session negotiation cancelled')
+ ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
+
+ def print_esession_details(self):
+ """
+ Print esession settings to textview
+ """
+ e2e_is_active = bool(self.session) and self.session.enable_encryption
+ if e2e_is_active:
+ msg = _('This session is encrypted')
+
+ if self.session.is_loggable():
+ msg += _(' and WILL be logged')
+ else:
+ msg += _(' and WILL NOT be logged')
+
+ ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
+
+ if not self.session.verified_identity:
+ ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None)
+ else:
+ msg = _('E2E encryption disabled')
+ ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
+
+ self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
+ self.session.is_loggable(), self.session and self.session.verified_identity)
+
+ def print_conversation(self, text, frm='', tim=None, encrypted=False,
+ subject=None, xhtml=None, simple=False, xep0184_id=None):
+ """
+ Print a line in the conversation
+
+ If frm is set to status: it's a status message.
+ if frm is set to error: it's an error message. The difference between
+ status and error is mainly that with error, msg count as a new message
+ (in systray and in control).
+ If frm is set to info: it's a information message.
+ If frm is set to print_queue: it is incomming from queue.
+ If frm is set to another value: it's an outgoing message.
+ If frm is not set: it's an incomming message.
+ """
+ contact = self.contact
+
+ if frm == 'status':
+ if not gajim.config.get('print_status_in_chats'):
+ return
+ kind = 'status'
+ name = ''
+ elif frm == 'error':
+ kind = 'error'
+ name = ''
+ elif frm == 'info':
+ kind = 'info'
+ name = ''
+ else:
+ if self.session and self.session.enable_encryption:
+ # ESessions
+ if not encrypted:
+ msg = _('The following message was NOT encrypted')
+ ChatControlBase.print_conversation_line(self, msg, 'status', '',
+ tim)
+ else:
+ # GPG encryption
+ if encrypted and not self.gpg_is_active:
+ msg = _('The following message was encrypted')
+ ChatControlBase.print_conversation_line(self, msg, 'status', '',
+ tim)
+ # turn on OpenPGP if this was in fact a XEP-0027 encrypted message
+ if encrypted == 'xep27':
+ self._toggle_gpg()
+ elif not encrypted and self.gpg_is_active:
+ msg = _('The following message was NOT encrypted')
+ ChatControlBase.print_conversation_line(self, msg, 'status', '',
+ tim)
+ if not frm:
+ kind = 'incoming'
+ name = contact.get_shown_name()
+ elif frm == 'print_queue': # incoming message, but do not update time
+ kind = 'incoming_queue'
+ name = contact.get_shown_name()
+ else:
+ kind = 'outgoing'
+ name = gajim.nicks[self.account]
+ if not xhtml and not (encrypted and self.gpg_is_active) and \
+ gajim.config.get('rst_formatting_outgoing_messages'):
+ from common.rst_xhtml_generator import create_xhtml
+ xhtml = create_xhtml(text)
+ if xhtml:
+ xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml)
+ ChatControlBase.print_conversation_line(self, text, kind, name, tim,
+ subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml,
+ simple=simple, xep0184_id=xep0184_id)
+ if text.startswith('/me ') or text.startswith('/me\n'):
+ self.old_msg_kind = None
+ else:
+ self.old_msg_kind = kind
+
+ def get_tab_label(self, chatstate):
+ unread = ''
+ if self.resource:
+ jid = self.contact.get_full_jid()
+ else:
+ jid = self.contact.jid
+ num_unread = len(gajim.events.get_events(self.account, jid,
+ ['printed_' + self.type_id, self.type_id]))
+ if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'):
+ unread = '*'
+ elif num_unread > 1:
+ unread = '[' + unicode(num_unread) + ']'
+
+ # Draw tab label using chatstate
+ theme = gajim.config.get('roster_theme')
+ color = None
+ if not chatstate:
+ chatstate = self.contact.chatstate
+ if chatstate is not None:
+ if chatstate == 'composing':
+ color = gajim.config.get_per('themes', theme,
+ 'state_composing_color')
+ elif chatstate == 'inactive':
+ color = gajim.config.get_per('themes', theme,
+ 'state_inactive_color')
+ elif chatstate == 'gone':
+ color = gajim.config.get_per('themes', theme,
+ 'state_gone_color')
+ elif chatstate == 'paused':
+ color = gajim.config.get_per('themes', theme,
+ 'state_paused_color')
+ if color:
+ # We set the color for when it's the current tab or not
+ color = gtk.gdk.colormap_get_system().alloc_color(color)
+ # In inactive tab color to be lighter against the darker inactive
+ # background
+ if chatstate in ('inactive', 'gone') and\
+ self.parent_win.get_active_control() != self:
+ color = self.lighten_color(color)
+ else: # active or not chatstate, get color from gtk
+ color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
+
+
+ name = self.contact.get_shown_name()
+ if self.resource:
+ name += '/' + self.resource
+ label_str = gobject.markup_escape_text(name)
+ if num_unread: # if unread, text in the label becomes bold
+ label_str = '<b>' + unread + label_str + '</b>'
+ return (label_str, color)
+
+ def get_tab_image(self, count_unread=True):
+ if self.resource:
+ jid = self.contact.get_full_jid()
+ else:
+ jid = self.contact.jid
+ if count_unread:
+ num_unread = len(gajim.events.get_events(self.account, jid,
+ ['printed_' + self.type_id, self.type_id]))
+ else:
+ num_unread = 0
+ # Set tab image (always 16x16); unread messages show the 'event' image
+ tab_img = None
+
+ if num_unread and gajim.config.get('show_unread_tab_icon'):
+ img_16 = gajim.interface.roster.get_appropriate_state_images(
+ self.contact.jid, icon_name = 'event')
+ tab_img = img_16['event']
+ else:
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ self.account, self.contact.jid)
+ if not contact or self.resource:
+ # For transient contacts
+ contact = self.contact
+ img_16 = gajim.interface.roster.get_appropriate_state_images(
+ self.contact.jid, icon_name=contact.show)
+ tab_img = img_16[contact.show]
+
+ return tab_img
+
+ def prepare_context_menu(self, hide_buttonbar_items=False):
+ """
+ Set compact view menuitem active state sets active and sensitivity state
+ for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for
+ tranasports) and file_transfer_menuitem and hide()/show() for
+ add_to_roster_menuitem
+ """
+ menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
+ use_multiple_contacts=False, show_start_chat=False,
+ show_encryption=True, control=self,
+ show_buttonbar_items=not hide_buttonbar_items)
+ return menu
+
+ def send_chatstate(self, state, contact = None):
+ """
+ Send OUR chatstate as STANDLONE chat state message (eg. no body)
+ to contact only if new chatstate is different from the previous one
+ if jid is not specified, send to active tab
+ """
+ # JEP 85 does not allow resending the same chatstate
+ # this function checks for that and just returns so it's safe to call it
+ # with same state.
+
+ # This functions also checks for violation in state transitions
+ # and raises RuntimeException with appropriate message
+ # more on that http://www.jabber.org/jeps/jep-0085.html#statechart
+
+ # do not send nothing if we have chat state notifications disabled
+ # that means we won't reply to the <active/> from other peer
+ # so we do not broadcast jep85 capabalities
+ chatstate_setting = gajim.config.get('outgoing_chat_state_notifications')
+ if chatstate_setting == 'disabled':
+ return
+ elif chatstate_setting == 'composing_only' and state != 'active' and\
+ state != 'composing':
+ return
+
+ if contact is None:
+ contact = self.parent_win.get_active_contact()
+ if contact is None:
+ # contact was from pm in MUC, and left the room so contact is None
+ # so we cannot send chatstate anymore
+ return
+
+ # Don't send chatstates to offline contacts
+ if contact.show == 'offline':
+ return
+
+ if contact.composing_xep is False: # jid cannot do xep85 nor xep22
+ return
+
+ # if the new state we wanna send (state) equals
+ # the current state (contact.our_chatstate) then return
+ if contact.our_chatstate == state:
+ return
+
+ if contact.composing_xep is None:
+ # we don't know anything about jid, so return
+ # NOTE:
+ # send 'active', set current state to 'ask' and return is done
+ # in self.send_message() because we need REAL message (with <body>)
+ # for that procedure so return to make sure we send only once
+ # 'active' until we know peer supports jep85
+ return
+
+ if contact.our_chatstate == 'ask':
+ return
+
+ # in JEP22, when we already sent stop composing
+ # notification on paused, don't resend it
+ if contact.composing_xep == 'XEP-0022' and \
+ contact.our_chatstate in ('paused', 'active', 'inactive') and \
+ state is not 'composing': # not composing == in (active, inactive, gone)
+ contact.our_chatstate = 'active'
+ self.reset_kbd_mouse_timeout_vars()
+ return
+
+ # prevent going paused if we we were not composing (JEP violation)
+ if state == 'paused' and not contact.our_chatstate == 'composing':
+ # go active before
+ MessageControl.send_message(self, None, chatstate = 'active')
+ contact.our_chatstate = 'active'
+ self.reset_kbd_mouse_timeout_vars()
+
+ # if we're inactive prevent composing (JEP violation)
+ elif contact.our_chatstate == 'inactive' and state == 'composing':
+ # go active before
+ MessageControl.send_message(self, None, chatstate = 'active')
+ contact.our_chatstate = 'active'
+ self.reset_kbd_mouse_timeout_vars()
+
+ MessageControl.send_message(self, None, chatstate = state,
+ msg_id = contact.msg_id, composing_xep = contact.composing_xep)
+ contact.our_chatstate = state
+ if contact.our_chatstate == 'active':
+ self.reset_kbd_mouse_timeout_vars()
+
+ def shutdown(self):
+ # Send 'gone' chatstate
+ self.send_chatstate('gone', self.contact)
+ self.contact.chatstate = None
+ self.contact.our_chatstate = None
+
+ for jingle_type in ('audio', 'video'):
+ self.close_jingle_content(jingle_type)
+
+ # disconnect self from session
+ if self.session:
+ self.session.control = None
+
+ # Disconnect timer callbacks
+ gobject.source_remove(self.possible_paused_timeout_id)
+ gobject.source_remove(self.possible_inactive_timeout_id)
+ # Remove bigger avatar window
+ if self.bigger_avatar_window:
+ self.bigger_avatar_window.destroy()
+ # Clean events
+ gajim.events.remove_events(self.account, self.get_full_jid(),
+ types = ['printed_' + self.type_id, self.type_id])
+ # Remove contact instance if contact has been removed
+ key = (self.contact.jid, self.account)
+ roster = gajim.interface.roster
+ if key in roster.contacts_to_be_removed.keys() and \
+ not roster.contact_has_pending_roster_events(self.contact, self.account):
+ backend = roster.contacts_to_be_removed[key]['backend']
+ del roster.contacts_to_be_removed[key]
+ roster.remove_contact(self.contact.jid, self.account, force=True,
+ backend=backend)
+ # remove all register handlers on widgets, created by self.xml
+ # to prevent circular references among objects
+ for i in self.handlers.keys():
+ if self.handlers[i].handler_is_connected(i):
+ self.handlers[i].disconnect(i)
+ del self.handlers[i]
+ self.conv_textview.del_handlers()
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL:
+ spell_obj = gtkspell.get_from_text_view(self.msg_textview)
+ if spell_obj:
+ spell_obj.detach()
+ self.msg_textview.destroy()
+
+ def minimizable(self):
+ return False
+
+ def safe_shutdown(self):
+ return False
+
+ def allow_shutdown(self, method, on_yes, on_no, on_minimize):
+ if time.time() - gajim.last_message_time[self.account]\
+ [self.get_full_jid()] < 2:
+ # 2 seconds
+ def on_ok():
+ on_yes(self)
+
+ def on_cancel():
+ on_no(self)
+
+ dialogs.ConfirmationDialog(
+ # %s is being replaced in the code with JID
+ _('You just received a new message from "%s"') % self.contact.jid,
+ _('If you close this tab and you have history disabled, '\
+ 'this message will be lost.'), on_response_ok=on_ok,
+ on_response_cancel=on_cancel)
+ return
+ on_yes(self)
+
+ def handle_incoming_chatstate(self):
+ """
+ Handle incoming chatstate that jid SENT TO us
+ """
+ self.draw_banner_text()
+ # update chatstate in tab for this chat
+ self.parent_win.redraw_tab(self, self.contact.chatstate)
+
+ def set_control_active(self, state):
+ ChatControlBase.set_control_active(self, state)
+ # send chatstate inactive to the one we're leaving
+ # and active to the one we visit
+ if state:
+ self.send_chatstate('active', self.contact)
+ else:
+ self.send_chatstate('inactive', self.contact)
+ # Hide bigger avatar window
+ if self.bigger_avatar_window:
+ self.bigger_avatar_window.destroy()
+ self.bigger_avatar_window = None
+ # Re-show the small avatar
+ self.show_avatar()
+
+ def show_avatar(self):
+ if not gajim.config.get('show_avatar_in_chat'):
+ return
+
+ jid_with_resource = self.contact.get_full_jid()
+ pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid_with_resource)
+ if pixbuf == 'ask':
+ # we don't have the vcard
+ if self.TYPE_ID == message_control.TYPE_PM:
+ if self.gc_contact.jid:
+ # We know the real jid of this contact
+ real_jid = self.gc_contact.jid
+ if self.gc_contact.resource:
+ real_jid += '/' + self.gc_contact.resource
+ else:
+ real_jid = jid_with_resource
+ gajim.connections[self.account].request_vcard(real_jid,
+ jid_with_resource)
+ else:
+ gajim.connections[self.account].request_vcard(jid_with_resource)
+ return
+ elif pixbuf:
+ scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat')
+ else:
+ scaled_pixbuf = None
+
+ image = self.xml.get_object('avatar_image')
+ image.set_from_pixbuf(scaled_pixbuf)
+ image.show_all()
+
+ def _on_drag_data_received(self, widget, context, x, y, selection,
+ target_type, timestamp):
+ if not selection.data:
+ return
+ if self.TYPE_ID == message_control.TYPE_PM:
+ c = self.gc_contact
+ else:
+ c = self.contact
+ if target_type == self.TARGET_TYPE_URI_LIST:
+ if not c.resource: # If no resource is known, we can't send a file
+ return
+ uri = selection.data.strip()
+ uri_splitted = uri.split() # we may have more than one file dropped
+ for uri in uri_splitted:
+ path = helpers.get_file_path_from_dnd_dropped_uri(uri)
+ if os.path.isfile(path): # is it file?
+ ft = gajim.interface.instances['file_transfers']
+ ft.send_file(self.account, c, path)
+ return
+
+ # chat2muc
+ treeview = gajim.interface.roster.tree
+ model = treeview.get_model()
+ data = selection.data
+ path = treeview.get_selection().get_selected_rows()[1][0]
+ iter_ = model.get_iter(path)
+ type_ = model[iter_][2]
+ if type_ != 'contact': # source is not a contact
+ return
+ dropped_jid = data.decode('utf-8')
+
+ dropped_transport = gajim.get_transport_name_from_jid(dropped_jid)
+ c_transport = gajim.get_transport_name_from_jid(c.jid)
+ if dropped_transport or c_transport:
+ return # transport contacts cannot be invited
+
+ dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid])
+
+ def _on_message_tv_buffer_changed(self, textbuffer):
+ self.kbd_activity_in_last_5_secs = True
+ self.kbd_activity_in_last_30_secs = True
+ if textbuffer.get_char_count():
+ self.send_chatstate('composing', self.contact)
+
+ e2e_is_active = self.session and \
+ self.session.enable_encryption
+ e2e_pref = gajim.config.get_per('accounts', self.account,
+ 'enable_esessions') and gajim.config.get_per('accounts',
+ self.account, 'autonegotiate_esessions') and gajim.config.get_per(
+ 'contacts', self.contact.jid, 'autonegotiate_esessions')
+ want_e2e = not e2e_is_active and not self.gpg_is_active \
+ and e2e_pref
+
+ if want_e2e and not self.no_autonegotiation \
+ and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION):
+ self.begin_e2e_negotiation()
+ else:
+ self.send_chatstate('active', self.contact)
+
+ def restore_conversation(self):
+ jid = self.contact.jid
+ # don't restore lines if it's a transport
+ if gajim.jid_is_transport(jid):
+ return
+
+ # How many lines to restore and when to time them out
+ restore_how_many = gajim.config.get('restore_lines')
+ if restore_how_many <= 0:
+ return
+ timeout = gajim.config.get('restore_timeout') # in minutes
+
+ # number of messages that are in queue and are already logged, we want
+ # to avoid duplication
+ pending_how_many = len(gajim.events.get_events(self.account, jid,
+ ['chat', 'pm']))
+ if self.resource:
+ pending_how_many += len(gajim.events.get_events(self.account,
+ self.contact.get_full_jid(), ['chat', 'pm']))
+
+ try:
+ rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
+ pending_how_many, timeout, self.account)
+ except exceptions.DatabaseMalformed:
+ import common.logger
+ dialogs.ErrorDialog(_('Database Error'),
+ _('The database file (%s) cannot be read. Try to repair it or remove it (all history will be lost).') % common.logger.LOG_DB_PATH)
+ rows = []
+ local_old_kind = None
+ for row in rows: # row[0] time, row[1] has kind, row[2] the message
+ if not row[2]: # message is empty, we don't print it
+ continue
+ if row[1] in (constants.KIND_CHAT_MSG_SENT,
+ constants.KIND_SINGLE_MSG_SENT):
+ kind = 'outgoing'
+ name = gajim.nicks[self.account]
+ elif row[1] in (constants.KIND_SINGLE_MSG_RECV,
+ constants.KIND_CHAT_MSG_RECV):
+ kind = 'incoming'
+ name = self.contact.get_shown_name()
+ elif row[1] == constants.KIND_ERROR:
+ kind = 'status'
+ name = self.contact.get_shown_name()
+
+ tim = time.localtime(float(row[0]))
+
+ if gajim.config.get('restored_messages_small'):
+ small_attr = ['small']
+ else:
+ small_attr = []
+ ChatControlBase.print_conversation_line(self, row[2], kind, name, tim,
+ small_attr,
+ small_attr + ['restored_message'],
+ small_attr + ['restored_message'],
+ False, old_kind = local_old_kind)
+ if row[2].startswith('/me ') or row[2].startswith('/me\n'):
+ local_old_kind = None
+ else:
+ local_old_kind = kind
+ if len(rows):
+ self.conv_textview.print_empty_line()
+
+ def read_queue(self):
+ """
+ Read queue and print messages containted in it
+ """
+ jid = self.contact.jid
+ jid_with_resource = jid
+ if self.resource:
+ jid_with_resource += '/' + self.resource
+ events = gajim.events.get_events(self.account, jid_with_resource)
+
+ # list of message ids which should be marked as read
+ message_ids = []
+ for event in events:
+ if event.type_ != self.type_id:
+ continue
+ data = event.parameters
+ kind = data[2]
+ if kind == 'error':
+ kind = 'info'
+ else:
+ kind = 'print_queue'
+ self.print_conversation(data[0], kind, tim = data[3],
+ encrypted = data[4], subject = data[1], xhtml = data[7])
+ if len(data) > 6 and isinstance(data[6], int):
+ message_ids.append(data[6])
+
+ if len(data) > 8:
+ self.set_session(data[8])
+ if message_ids:
+ gajim.logger.set_read_messages(message_ids)
+ gajim.events.remove_events(self.account, jid_with_resource,
+ types = [self.type_id])
+
+ typ = 'chat' # Is it a normal chat or a pm ?
+
+ # reset to status image in gc if it is a pm
+ # Is it a pm ?
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+ control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
+ self.account)
+ if control and control.type_id == message_control.TYPE_GC:
+ control.update_ui()
+ control.parent_win.show_title()
+ typ = 'pm'
+
+ self.redraw_after_event_removed(jid)
+ if (self.contact.show in ('offline', 'error')):
+ show_offline = gajim.config.get('showoffline')
+ show_transports = gajim.config.get('show_transports_group')
+ if (not show_transports and gajim.jid_is_transport(jid)) or \
+ (not show_offline and typ == 'chat' and \
+ len(gajim.contacts.get_contacts(self.account, jid)) < 2):
+ gajim.interface.roster.remove_to_be_removed(self.contact.jid,
+ self.account)
+ elif typ == 'pm':
+ control.remove_contact(nick)
+
+ def show_bigger_avatar(self, small_avatar):
+ """
+ Resize the avatar, if needed, so it has at max half the screen size and
+ shows it
+ """
+ if not small_avatar.window:
+ # Tab has been closed since we hovered the avatar
+ return
+ avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(
+ self.contact.jid)
+ if avatar_pixbuf in ('ask', None):
+ return
+ # Hide the small avatar
+ # this code hides the small avatar when we show a bigger one in case
+ # the avatar has a transparency hole in the middle
+ # so when we show the big one we avoid seeing the small one behind.
+ # It's why I set it transparent.
+ image = self.xml.get_object('avatar_image')
+ pixbuf = image.get_pixbuf()
+ pixbuf.fill(0xffffff00L) # RGBA
+ image.queue_draw()
+
+ screen_w = gtk.gdk.screen_width()
+ screen_h = gtk.gdk.screen_height()
+ avatar_w = avatar_pixbuf.get_width()
+ avatar_h = avatar_pixbuf.get_height()
+ half_scr_w = screen_w / 2
+ half_scr_h = screen_h / 2
+ if avatar_w > half_scr_w:
+ avatar_w = half_scr_w
+ if avatar_h > half_scr_h:
+ avatar_h = half_scr_h
+ window = gtk.Window(gtk.WINDOW_POPUP)
+ self.bigger_avatar_window = window
+ pixmap, mask = avatar_pixbuf.render_pixmap_and_mask()
+ window.set_size_request(avatar_w, avatar_h)
+ # we should make the cursor visible
+ # gtk+ doesn't make use of the motion notify on gtkwindow by default
+ # so this line adds that
+ window.set_events(gtk.gdk.POINTER_MOTION_MASK)
+ window.set_app_paintable(True)
+ window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)
+
+ window.realize()
+ window.window.set_back_pixmap(pixmap, False) # make it transparent
+ window.window.shape_combine_mask(mask, 0, 0)
+
+ # make the bigger avatar window show up centered
+ x0, y0 = small_avatar.window.get_origin()
+ x0 += small_avatar.allocation.x
+ y0 += small_avatar.allocation.y
+ center_x= x0 + (small_avatar.allocation.width / 2)
+ center_y = y0 + (small_avatar.allocation.height / 2)
+ pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2)
+ window.move(pos_x, pos_y)
+ # make the cursor invisible so we can see the image
+ invisible_cursor = gtkgui_helpers.get_invisible_cursor()
+ window.window.set_cursor(invisible_cursor)
+
+ # we should hide the window
+ window.connect('leave_notify_event',
+ self._on_window_avatar_leave_notify_event)
+ window.connect('motion-notify-event',
+ self._on_window_motion_notify_event)
+
+ window.show_all()
+
+ def _on_window_avatar_leave_notify_event(self, widget, event):
+ """
+ Just left the popup window that holds avatar
+ """
+ self.bigger_avatar_window.destroy()
+ self.bigger_avatar_window = None
+ # Re-show the small avatar
+ self.show_avatar()
+
+ def _on_window_motion_notify_event(self, widget, event):
+ """
+ Just moved the mouse so show the cursor
+ """
+ cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
+ self.bigger_avatar_window.window.set_cursor(cursor)
+
+ def _on_send_file_menuitem_activate(self, widget):
+ self._on_send_file()
+
+ def _on_add_to_roster_menuitem_activate(self, widget):
+ dialogs.AddNewContactWindow(self.account, self.contact.jid)
+
+ def _on_contact_information_menuitem_activate(self, widget):
+ gajim.interface.roster.on_info(widget, self.contact, self.account)
+
+ def _on_toggle_gpg_menuitem_activate(self, widget):
+ self._toggle_gpg()
+
+ def _on_convert_to_gc_menuitem_activate(self, widget):
+ """
+ User wants to invite some friends to chat
+ """
+ dialogs.TransformChatToMUC(self.account, [self.contact.jid])
+
+ def _on_toggle_e2e_menuitem_activate(self, widget):
+ if self.session and self.session.enable_encryption:
+ # e2e was enabled, disable it
+ jid = str(self.session.jid)
+ thread_id = self.session.thread_id
+
+ self.session.terminate_e2e()
+
+ gajim.connections[self.account].delete_session(jid, thread_id)
+
+ # presumably the user had a good reason to shut it off, so
+ # disable autonegotiation too
+ self.no_autonegotiation = True
+ else:
+ self.begin_e2e_negotiation()
+
+ def begin_e2e_negotiation(self):
+ self.no_autonegotiation = True
+
+ if not self.session:
+ fjid = self.contact.get_full_jid()
+ new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id)
+ self.set_session(new_sess)
+
+ self.session.negotiate_e2e(False)
+
+ def got_connected(self):
+ ChatControlBase.got_connected(self)
+ # Refreshing contact
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ self.account, self.contact.jid)
+ if isinstance(contact, GC_Contact):
+ contact = contact.as_contact()
+ if contact:
+ self.contact = contact
+ self.draw_banner()
+
+ def update_status_display(self, name, uf_show, status):
+ """
+ Print the contact's status and update the status/GPG image
+ """
+ self.update_ui()
+ self.parent_win.redraw_tab(self)
+
+ self.print_conversation(_('%(name)s is now %(status)s') % {'name': name,
+ 'status': uf_show}, 'status')
+
+ if status:
+ self.print_conversation(' (', 'status', simple=True)
+ self.print_conversation('%s' % (status), 'status', simple=True)
+ self.print_conversation(')', 'status', simple=True)
diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py
index 64abe75ce..a5c0c0559 100644
--- a/src/common/GnuPG.py
+++ b/src/common/GnuPG.py
@@ -28,217 +28,215 @@ from os import tmpfile
from common import helpers
if gajim.HAVE_GPG:
- import GnuPGInterface
-
- class GnuPG(GnuPGInterface.GnuPG):
- def __init__(self, use_agent = False):
- GnuPGInterface.GnuPG.__init__(self)
- self.use_agent = use_agent
- self._setup_my_options()
- self.always_trust = False
-
- def _setup_my_options(self):
- self.options.armor = 1
- self.options.meta_interactive = 0
- self.options.extra_args.append('--no-secmem-warning')
- # disable photo viewer when verifying keys
- self.options.extra_args.append('--verify-options')
- self.options.extra_args.append('no-show-photo')
- if self.use_agent:
- self.options.extra_args.append('--use-agent')
-
- def _read_response(self, child_stdout):
- # Internal method: reads all the output from GPG, taking notice
- # only of lines that begin with the magic [GNUPG:] prefix.
- # (See doc/DETAILS in the GPG distribution for info on GPG's
- # output when --status-fd is specified.)
- #
- # Returns a dictionary, mapping GPG's keywords to the arguments
- # for that keyword.
-
- resp = {}
- while True:
- line = helpers.temp_failure_retry(child_stdout.readline)
- if line == "": break
- line = line.rstrip()
- if line[0:9] == '[GNUPG:] ':
- # Chop off the prefix
- line = line[9:]
- L = line.split(None, 1)
- keyword = L[0]
- if len(L) > 1:
- resp[ keyword ] = L[1]
- else:
- resp[ keyword ] = ""
- return resp
-
- def encrypt(self, str_, recipients, always_trust=False):
- self.options.recipients = recipients # a list!
-
- opt = ['--encrypt']
- if always_trust or self.always_trust:
- opt.append('--always-trust')
- proc = self.run(opt, create_fhs=['stdin', 'stdout', 'status',
- 'stderr'])
- proc.handles['stdin'].write(str_)
- try:
- proc.handles['stdin'].close()
- except IOError:
- pass
-
- output = proc.handles['stdout'].read()
- try:
- proc.handles['stdout'].close()
- except IOError:
- pass
-
- stat = proc.handles['status']
- resp = self._read_response(stat)
- try:
- proc.handles['status'].close()
- except IOError:
- pass
-
- error = proc.handles['stderr'].read()
- proc.handles['stderr'].close()
-
- try: proc.wait()
- except IOError: pass
- if 'INV_RECP' in resp and resp['INV_RECP'].split()[0] == '10':
- # unusable recipient "Key not trusted"
- return '', 'NOT_TRUSTED'
- if 'BEGIN_ENCRYPTION' in resp and 'END_ENCRYPTION' in resp:
- # Encryption succeeded, even if there is output on stderr. Maybe
- # verbose is on
- error = ''
- return self._stripHeaderFooter(output), helpers.decode_string(error)
-
- def decrypt(self, str_, keyID):
- proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout'])
- enc = self._addHeaderFooter(str_, 'MESSAGE')
- proc.handles['stdin'].write(enc)
- proc.handles['stdin'].close()
-
- output = proc.handles['stdout'].read()
- proc.handles['stdout'].close()
-
- try: proc.wait()
- except IOError: pass
- return output
-
- def sign(self, str_, keyID):
- proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr'])
- proc.handles['stdin'].write(str_)
- try:
- proc.handles['stdin'].close()
- except IOError:
- pass
-
- output = proc.handles['stdout'].read()
- try:
- proc.handles['stdout'].close()
- proc.handles['stderr'].close()
- except IOError:
- pass
-
- stat = proc.handles['status']
- resp = self._read_response(stat)
- try:
- proc.handles['status'].close()
- except IOError:
- pass
-
- try: proc.wait()
- except IOError: pass
- if 'GOOD_PASSPHRASE' in resp or 'SIG_CREATED' in resp:
- return self._stripHeaderFooter(output)
- if 'KEYEXPIRED' in resp:
- return 'KEYEXPIRED'
- return 'BAD_PASSPHRASE'
-
- def verify(self, str_, sign):
- if str_ is None:
- return ''
- f = tmpfile()
- fd = f.fileno()
- f.write(str_)
- f.seek(0)
-
- proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr'])
-
- f.close()
- sign = self._addHeaderFooter(sign, 'SIGNATURE')
- proc.handles['stdin'].write(sign)
- proc.handles['stdin'].close()
- proc.handles['stderr'].close()
-
- stat = proc.handles['status']
- resp = self._read_response(stat)
- proc.handles['status'].close()
-
- try: proc.wait()
- except IOError: pass
-
- keyid = ''
- if 'GOODSIG' in resp:
- keyid = resp['GOODSIG'].split()[0]
- return keyid
-
- def get_keys(self, secret = False):
- if secret:
- opt = '--list-secret-keys'
- else:
- opt = '--list-keys'
- proc = self.run(['--with-colons', opt],
- create_fhs=['stdout'])
- output = proc.handles['stdout'].read()
- proc.handles['stdout'].close()
-
- try: proc.wait()
- except IOError: pass
-
- keys = {}
- lines = output.split('\n')
- for line in lines:
- sline = line.split(':')
- if (sline[0] == 'sec' and secret) or \
- (sline[0] == 'pub' and not secret):
- # decode escaped chars
- name = eval('"' + sline[9].replace('"', '\\"') + '"')
- # make it unicode instance
- keys[sline[4][8:]] = helpers.decode_string(name)
- return keys
-
- def get_secret_keys(self):
- return self.get_keys(True)
-
- def _stripHeaderFooter(self, data):
- """
- Remove header and footer from data
- """
- if not data: return ''
- lines = data.split('\n')
- while lines[0] != '':
- lines.remove(lines[0])
- while lines[0] == '':
- lines.remove(lines[0])
- i = 0
- for line in lines:
- if line:
- if line[0] == '-': break
- i = i+1
- line = '\n'.join(lines[0:i])
- return line
-
- def _addHeaderFooter(self, data, type_):
- """
- Add header and footer from data
- """
- out = "-----BEGIN PGP %s-----\n" % type_
- out = out + "Version: PGP\n"
- out = out + "\n"
- out = out + data + "\n"
- out = out + "-----END PGP %s-----\n" % type_
- return out
-
-# vim: set ts=3:
+ import GnuPGInterface
+
+ class GnuPG(GnuPGInterface.GnuPG):
+ def __init__(self, use_agent = False):
+ GnuPGInterface.GnuPG.__init__(self)
+ self.use_agent = use_agent
+ self._setup_my_options()
+ self.always_trust = False
+
+ def _setup_my_options(self):
+ self.options.armor = 1
+ self.options.meta_interactive = 0
+ self.options.extra_args.append('--no-secmem-warning')
+ # disable photo viewer when verifying keys
+ self.options.extra_args.append('--verify-options')
+ self.options.extra_args.append('no-show-photo')
+ if self.use_agent:
+ self.options.extra_args.append('--use-agent')
+
+ def _read_response(self, child_stdout):
+ # Internal method: reads all the output from GPG, taking notice
+ # only of lines that begin with the magic [GNUPG:] prefix.
+ # (See doc/DETAILS in the GPG distribution for info on GPG's
+ # output when --status-fd is specified.)
+ #
+ # Returns a dictionary, mapping GPG's keywords to the arguments
+ # for that keyword.
+
+ resp = {}
+ while True:
+ line = helpers.temp_failure_retry(child_stdout.readline)
+ if line == "": break
+ line = line.rstrip()
+ if line[0:9] == '[GNUPG:] ':
+ # Chop off the prefix
+ line = line[9:]
+ L = line.split(None, 1)
+ keyword = L[0]
+ if len(L) > 1:
+ resp[ keyword ] = L[1]
+ else:
+ resp[ keyword ] = ""
+ return resp
+
+ def encrypt(self, str_, recipients, always_trust=False):
+ self.options.recipients = recipients # a list!
+
+ opt = ['--encrypt']
+ if always_trust or self.always_trust:
+ opt.append('--always-trust')
+ proc = self.run(opt, create_fhs=['stdin', 'stdout', 'status',
+ 'stderr'])
+ proc.handles['stdin'].write(str_)
+ try:
+ proc.handles['stdin'].close()
+ except IOError:
+ pass
+
+ output = proc.handles['stdout'].read()
+ try:
+ proc.handles['stdout'].close()
+ except IOError:
+ pass
+
+ stat = proc.handles['status']
+ resp = self._read_response(stat)
+ try:
+ proc.handles['status'].close()
+ except IOError:
+ pass
+
+ error = proc.handles['stderr'].read()
+ proc.handles['stderr'].close()
+
+ try: proc.wait()
+ except IOError: pass
+ if 'INV_RECP' in resp and resp['INV_RECP'].split()[0] == '10':
+ # unusable recipient "Key not trusted"
+ return '', 'NOT_TRUSTED'
+ if 'BEGIN_ENCRYPTION' in resp and 'END_ENCRYPTION' in resp:
+ # Encryption succeeded, even if there is output on stderr. Maybe
+ # verbose is on
+ error = ''
+ return self._stripHeaderFooter(output), helpers.decode_string(error)
+
+ def decrypt(self, str_, keyID):
+ proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout'])
+ enc = self._addHeaderFooter(str_, 'MESSAGE')
+ proc.handles['stdin'].write(enc)
+ proc.handles['stdin'].close()
+
+ output = proc.handles['stdout'].read()
+ proc.handles['stdout'].close()
+
+ try: proc.wait()
+ except IOError: pass
+ return output
+
+ def sign(self, str_, keyID):
+ proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr'])
+ proc.handles['stdin'].write(str_)
+ try:
+ proc.handles['stdin'].close()
+ except IOError:
+ pass
+
+ output = proc.handles['stdout'].read()
+ try:
+ proc.handles['stdout'].close()
+ proc.handles['stderr'].close()
+ except IOError:
+ pass
+
+ stat = proc.handles['status']
+ resp = self._read_response(stat)
+ try:
+ proc.handles['status'].close()
+ except IOError:
+ pass
+
+ try: proc.wait()
+ except IOError: pass
+ if 'GOOD_PASSPHRASE' in resp or 'SIG_CREATED' in resp:
+ return self._stripHeaderFooter(output)
+ if 'KEYEXPIRED' in resp:
+ return 'KEYEXPIRED'
+ return 'BAD_PASSPHRASE'
+
+ def verify(self, str_, sign):
+ if str_ is None:
+ return ''
+ f = tmpfile()
+ fd = f.fileno()
+ f.write(str_)
+ f.seek(0)
+
+ proc = self.run(['--verify', '--enable-special-filenames', '-', '-&%s'%fd], create_fhs=['stdin', 'status', 'stderr'])
+
+ f.close()
+ sign = self._addHeaderFooter(sign, 'SIGNATURE')
+ proc.handles['stdin'].write(sign)
+ proc.handles['stdin'].close()
+ proc.handles['stderr'].close()
+
+ stat = proc.handles['status']
+ resp = self._read_response(stat)
+ proc.handles['status'].close()
+
+ try: proc.wait()
+ except IOError: pass
+
+ keyid = ''
+ if 'GOODSIG' in resp:
+ keyid = resp['GOODSIG'].split()[0]
+ return keyid
+
+ def get_keys(self, secret = False):
+ if secret:
+ opt = '--list-secret-keys'
+ else:
+ opt = '--list-keys'
+ proc = self.run(['--with-colons', opt],
+ create_fhs=['stdout'])
+ output = proc.handles['stdout'].read()
+ proc.handles['stdout'].close()
+
+ try: proc.wait()
+ except IOError: pass
+
+ keys = {}
+ lines = output.split('\n')
+ for line in lines:
+ sline = line.split(':')
+ if (sline[0] == 'sec' and secret) or \
+ (sline[0] == 'pub' and not secret):
+ # decode escaped chars
+ name = eval('"' + sline[9].replace('"', '\\"') + '"')
+ # make it unicode instance
+ keys[sline[4][8:]] = helpers.decode_string(name)
+ return keys
+
+ def get_secret_keys(self):
+ return self.get_keys(True)
+
+ def _stripHeaderFooter(self, data):
+ """
+ Remove header and footer from data
+ """
+ if not data: return ''
+ lines = data.split('\n')
+ while lines[0] != '':
+ lines.remove(lines[0])
+ while lines[0] == '':
+ lines.remove(lines[0])
+ i = 0
+ for line in lines:
+ if line:
+ if line[0] == '-': break
+ i = i+1
+ line = '\n'.join(lines[0:i])
+ return line
+
+ def _addHeaderFooter(self, data, type_):
+ """
+ Add header and footer from data
+ """
+ out = "-----BEGIN PGP %s-----\n" % type_
+ out = out + "Version: PGP\n"
+ out = out + "\n"
+ out = out + data + "\n"
+ out = out + "-----END PGP %s-----\n" % type_
+ return out
diff --git a/src/common/GnuPGInterface.py b/src/common/GnuPGInterface.py
index aa4a9eeee..f7f5cb522 100644
--- a/src/common/GnuPGInterface.py
+++ b/src/common/GnuPGInterface.py
@@ -671,5 +671,3 @@ GnuPGInterface = GnuPG
if __name__ == '__main__':
_run_doctests()
-
-# vim: se ts=3:
diff --git a/src/common/__init__.py b/src/common/__init__.py
index de18c8159..e69de29bb 100644
--- a/src/common/__init__.py
+++ b/src/common/__init__.py
@@ -1,2 +0,0 @@
-
-# vim: se ts=3:
diff --git a/src/common/account.py b/src/common/account.py
index c8a4c4604..976b6bf43 100644
--- a/src/common/account.py
+++ b/src/common/account.py
@@ -20,16 +20,16 @@
class Account(object):
- def __init__(self, name, contacts, gc_contacts):
- self.name = name
- self.contacts = contacts
- self.gc_contacts = gc_contacts
+ def __init__(self, name, contacts, gc_contacts):
+ self.name = name
+ self.contacts = contacts
+ self.gc_contacts = gc_contacts
- def change_contact_jid(self, old_jid, new_jid):
- self.contacts.change_contact_jid(old_jid, new_jid)
+ def change_contact_jid(self, old_jid, new_jid):
+ self.contacts.change_contact_jid(old_jid, new_jid)
- def __repr__(self):
- return self.name
+ def __repr__(self):
+ return self.name
- def __hash__(self):
- return hash(self.name)
+ def __hash__(self):
+ return hash(self.name)
diff --git a/src/common/atom.py b/src/common/atom.py
index 689df5cee..e9913cf15 100644
--- a/src/common/atom.py
+++ b/src/common/atom.py
@@ -33,146 +33,144 @@ import xmpp
import time
class PersonConstruct(xmpp.Node, object):
- """
- Not used for now, as we don't need authors/contributors in pubsub.com feeds.
- They rarely exist there
- """
+ """
+ Not used for now, as we don't need authors/contributors in pubsub.com feeds.
+ They rarely exist there
+ """
- def __init__(self, node):
- ''' Create person construct from node. '''
- xmpp.Node.__init__(self, node=node)
+ def __init__(self, node):
+ ''' Create person construct from node. '''
+ xmpp.Node.__init__(self, node=node)
- def get_name(self):
- return self.getTagData('name')
+ def get_name(self):
+ return self.getTagData('name')
- name = property(get_name, None, None,
- '''Conveys a human-readable name for the person. Should not be None,
- although some badly generated atom feeds don't put anything here
- (this is non-standard behavior, still pubsub.com sometimes does that.)''')
+ name = property(get_name, None, None,
+ '''Conveys a human-readable name for the person. Should not be None,
+ although some badly generated atom feeds don't put anything here
+ (this is non-standard behavior, still pubsub.com sometimes does that.)''')
- def get_uri(self):
- return self.getTagData('uri')
+ def get_uri(self):
+ return self.getTagData('uri')
- uri = property(get_uri, None, None,
- '''Conveys an IRI associated with the person. Might be None when not set.''')
+ uri = property(get_uri, None, None,
+ '''Conveys an IRI associated with the person. Might be None when not set.''')
- def get_email(self):
- return self.getTagData('email')
+ def get_email(self):
+ return self.getTagData('email')
- email = property(get_email, None, None,
- '''Conveys an e-mail address associated with the person. Might be None when
- not set.''')
+ email = property(get_email, None, None,
+ '''Conveys an e-mail address associated with the person. Might be None when
+ not set.''')
class Entry(xmpp.Node, object):
- def __init__(self, node=None):
- xmpp.Node.__init__(self, 'entry', node=node)
+ def __init__(self, node=None):
+ xmpp.Node.__init__(self, 'entry', node=node)
- def __repr__(self):
- return '<Atom:Entry object of id="%r">' % self.getAttr('id')
+ def __repr__(self):
+ return '<Atom:Entry object of id="%r">' % self.getAttr('id')
class OldEntry(xmpp.Node, object):
- """
- Parser for feeds from pubsub.com. They use old Atom 0.3 format with their
- extensions
- """
-
- def __init__(self, node=None):
- ''' Create new Atom 0.3 entry object. '''
- xmpp.Node.__init__(self, 'entry', node=node)
-
- def __repr__(self):
- return '<Atom0.3:Entry object of id="%r">' % self.getAttr('id')
-
- def get_feed_title(self):
- """
- Return title of feed, where the entry was created. The result is the feed
- name concatenated with source-feed title
- """
- if self.parent is not None:
- main_feed = self.parent.getTagData('title')
- else:
- main_feed = None
-
- if self.getTag('feed') is not None:
- source_feed = self.getTag('feed').getTagData('title')
- else:
- source_feed = None
-
-
- if main_feed is not None and source_feed is not None:
- return u'%s: %s' % (main_feed, source_feed)
- elif main_feed is not None:
- return main_feed
- elif source_feed is not None:
- return source_feed
- else:
- return u''
-
- feed_title = property(get_feed_title, None, None,
- ''' Title of feed. It is built from entry''s original feed title and title of feed
- which delivered this entry. ''')
-
- def get_feed_link(self):
- """
- Get source link
- """
- try:
- return self.getTag('feed').getTags('link',{'rel':'alternate'})[1].getData()
- except Exception:
- return None
-
- feed_link = property(get_feed_link, None, None,
- ''' Link to main webpage of the feed. ''')
-
- def get_title(self):
- """
- Get an entry's title
- """
- return self.getTagData('title')
-
- title = property(get_title, None, None,
- ''' Entry's title. ''')
-
- def get_uri(self):
- """
- Get the uri the entry points to (entry's first link element with
- rel='alternate' or without rel attribute)
- """
- for element in self.getTags('link'):
- if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue
- try:
- return element.attrs['href']
- except AttributeError:
- pass
- return None
-
- uri = property(get_uri, None, None,
- ''' URI that is pointed by the entry. ''')
-
- def get_updated(self):
- """
- Get the time the entry was updated last time
-
- This should be standarized, but pubsub.com sends it in human-readable
- format. We won't try to parse it. (Atom 0.3 uses the word «modified» for
- that).
-
- If there's no time given in the entry, we try with <published>
- and <issued> elements.
- """
- for name in ('updated', 'modified', 'published', 'issued'):
- date = self.getTagData(name)
- if date is not None: break
-
- if date is None:
- # it is not in the standard format
- return time.asctime()
-
- return date
-
- updated = property(get_updated, None, None,
- ''' Last significant modification time. ''')
-
- feed_tagline = u''
-
-# vim: se ts=3:
+ """
+ Parser for feeds from pubsub.com. They use old Atom 0.3 format with their
+ extensions
+ """
+
+ def __init__(self, node=None):
+ ''' Create new Atom 0.3 entry object. '''
+ xmpp.Node.__init__(self, 'entry', node=node)
+
+ def __repr__(self):
+ return '<Atom0.3:Entry object of id="%r">' % self.getAttr('id')
+
+ def get_feed_title(self):
+ """
+ Return title of feed, where the entry was created. The result is the feed
+ name concatenated with source-feed title
+ """
+ if self.parent is not None:
+ main_feed = self.parent.getTagData('title')
+ else:
+ main_feed = None
+
+ if self.getTag('feed') is not None:
+ source_feed = self.getTag('feed').getTagData('title')
+ else:
+ source_feed = None
+
+
+ if main_feed is not None and source_feed is not None:
+ return u'%s: %s' % (main_feed, source_feed)
+ elif main_feed is not None:
+ return main_feed
+ elif source_feed is not None:
+ return source_feed
+ else:
+ return u''
+
+ feed_title = property(get_feed_title, None, None,
+ ''' Title of feed. It is built from entry''s original feed title and title of feed
+ which delivered this entry. ''')
+
+ def get_feed_link(self):
+ """
+ Get source link
+ """
+ try:
+ return self.getTag('feed').getTags('link',{'rel':'alternate'})[1].getData()
+ except Exception:
+ return None
+
+ feed_link = property(get_feed_link, None, None,
+ ''' Link to main webpage of the feed. ''')
+
+ def get_title(self):
+ """
+ Get an entry's title
+ """
+ return self.getTagData('title')
+
+ title = property(get_title, None, None,
+ ''' Entry's title. ''')
+
+ def get_uri(self):
+ """
+ Get the uri the entry points to (entry's first link element with
+ rel='alternate' or without rel attribute)
+ """
+ for element in self.getTags('link'):
+ if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue
+ try:
+ return element.attrs['href']
+ except AttributeError:
+ pass
+ return None
+
+ uri = property(get_uri, None, None,
+ ''' URI that is pointed by the entry. ''')
+
+ def get_updated(self):
+ """
+ Get the time the entry was updated last time
+
+ This should be standarized, but pubsub.com sends it in human-readable
+ format. We won't try to parse it. (Atom 0.3 uses the word «modified» for
+ that).
+
+ If there's no time given in the entry, we try with <published>
+ and <issued> elements.
+ """
+ for name in ('updated', 'modified', 'published', 'issued'):
+ date = self.getTagData(name)
+ if date is not None: break
+
+ if date is None:
+ # it is not in the standard format
+ return time.asctime()
+
+ return date
+
+ updated = property(get_updated, None, None,
+ ''' Last significant modification time. ''')
+
+ feed_tagline = u''
diff --git a/src/common/caps_cache.py b/src/common/caps_cache.py
index 603c348cb..23c54c279 100644
--- a/src/common/caps_cache.py
+++ b/src/common/caps_cache.py
@@ -38,10 +38,10 @@ import logging
log = logging.getLogger('gajim.c.caps_cache')
from common.xmpp import (NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES,
- NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS)
+ NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_CAPS)
# Features where we cannot safely assume that the other side supports them
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION,
- NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO]
+ NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO]
# Query entry status codes
NEW = 0
@@ -55,109 +55,109 @@ FAKED = 3 # allow NullClientCaps to behave as it has a cached item
capscache = None
def initialize(logger):
- """
- Initialize this module
- """
- global capscache
- capscache = CapsCache(logger)
+ """
+ Initialize this module
+ """
+ global capscache
+ capscache = CapsCache(logger)
def client_supports(client_caps, requested_feature):
- lookup_item = client_caps.get_cache_lookup_strategy()
- cache_item = lookup_item(capscache)
-
- supported_features = cache_item.features
- if requested_feature in supported_features:
- return True
- elif not supported_features and cache_item.status in (NEW, QUERIED, FAKED):
- # assume feature is supported, if we don't know yet, what the client
- # is capable of
- return requested_feature not in FEATURE_BLACKLIST
- else:
- return False
-
+ lookup_item = client_caps.get_cache_lookup_strategy()
+ cache_item = lookup_item(capscache)
+
+ supported_features = cache_item.features
+ if requested_feature in supported_features:
+ return True
+ elif not supported_features and cache_item.status in (NEW, QUERIED, FAKED):
+ # assume feature is supported, if we don't know yet, what the client
+ # is capable of
+ return requested_feature not in FEATURE_BLACKLIST
+ else:
+ return False
+
def create_suitable_client_caps(node, caps_hash, hash_method):
- """
- Create and return a suitable ClientCaps object for the given node,
- caps_hash, hash_method combination.
- """
- if not node or not caps_hash:
- # improper caps, ignore client capabilities.
- client_caps = NullClientCaps()
- elif not hash_method:
- client_caps = OldClientCaps(caps_hash, node)
- else:
- client_caps = ClientCaps(caps_hash, node, hash_method)
- return client_caps
+ """
+ Create and return a suitable ClientCaps object for the given node,
+ caps_hash, hash_method combination.
+ """
+ if not node or not caps_hash:
+ # improper caps, ignore client capabilities.
+ client_caps = NullClientCaps()
+ elif not hash_method:
+ client_caps = OldClientCaps(caps_hash, node)
+ else:
+ client_caps = ClientCaps(caps_hash, node, hash_method)
+ return client_caps
def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
- """
- Compute caps hash according to XEP-0115, V1.5
-
- dataforms are xmpp.DataForms objects as common.dataforms don't allow several
- values without a field type list-multi
- """
- def sort_identities_func(i1, i2):
- cat1 = i1['category']
- cat2 = i2['category']
- if cat1 < cat2:
- return -1
- if cat1 > cat2:
- return 1
- type1 = i1.get('type', '')
- type2 = i2.get('type', '')
- if type1 < type2:
- return -1
- if type1 > type2:
- return 1
- lang1 = i1.get('xml:lang', '')
- lang2 = i2.get('xml:lang', '')
- if lang1 < lang2:
- return -1
- if lang1 > lang2:
- return 1
- return 0
-
- def sort_dataforms_func(d1, d2):
- f1 = d1.getField('FORM_TYPE')
- f2 = d2.getField('FORM_TYPE')
- if f1 and f2 and (f1.getValue() < f2.getValue()):
- return -1
- return 1
-
- S = ''
- identities.sort(cmp=sort_identities_func)
- for i in identities:
- c = i['category']
- type_ = i.get('type', '')
- lang = i.get('xml:lang', '')
- name = i.get('name', '')
- S += '%s/%s/%s/%s<' % (c, type_, lang, name)
- features.sort()
- for f in features:
- S += '%s<' % f
- dataforms.sort(cmp=sort_dataforms_func)
- for dataform in dataforms:
- # fields indexed by var
- fields = {}
- for f in dataform.getChildren():
- fields[f.getVar()] = f
- form_type = fields.get('FORM_TYPE')
- if form_type:
- S += form_type.getValue() + '<'
- del fields['FORM_TYPE']
- for var in sorted(fields.keys()):
- S += '%s<' % var
- values = sorted(fields[var].getValues())
- for value in values:
- S += '%s<' % value
-
- if hash_method == 'sha-1':
- hash_ = hashlib.sha1(S)
- elif hash_method == 'md5':
- hash_ = hashlib.md5(S)
- else:
- return ''
- return base64.b64encode(hash_.digest())
+ """
+ Compute caps hash according to XEP-0115, V1.5
+
+ dataforms are xmpp.DataForms objects as common.dataforms don't allow several
+ values without a field type list-multi
+ """
+ def sort_identities_func(i1, i2):
+ cat1 = i1['category']
+ cat2 = i2['category']
+ if cat1 < cat2:
+ return -1
+ if cat1 > cat2:
+ return 1
+ type1 = i1.get('type', '')
+ type2 = i2.get('type', '')
+ if type1 < type2:
+ return -1
+ if type1 > type2:
+ return 1
+ lang1 = i1.get('xml:lang', '')
+ lang2 = i2.get('xml:lang', '')
+ if lang1 < lang2:
+ return -1
+ if lang1 > lang2:
+ return 1
+ return 0
+
+ def sort_dataforms_func(d1, d2):
+ f1 = d1.getField('FORM_TYPE')
+ f2 = d2.getField('FORM_TYPE')
+ if f1 and f2 and (f1.getValue() < f2.getValue()):
+ return -1
+ return 1
+
+ S = ''
+ identities.sort(cmp=sort_identities_func)
+ for i in identities:
+ c = i['category']
+ type_ = i.get('type', '')
+ lang = i.get('xml:lang', '')
+ name = i.get('name', '')
+ S += '%s/%s/%s/%s<' % (c, type_, lang, name)
+ features.sort()
+ for f in features:
+ S += '%s<' % f
+ dataforms.sort(cmp=sort_dataforms_func)
+ for dataform in dataforms:
+ # fields indexed by var
+ fields = {}
+ for f in dataform.getChildren():
+ fields[f.getVar()] = f
+ form_type = fields.get('FORM_TYPE')
+ if form_type:
+ S += form_type.getValue() + '<'
+ del fields['FORM_TYPE']
+ for var in sorted(fields.keys()):
+ S += '%s<' % var
+ values = sorted(fields[var].getValues())
+ for value in values:
+ S += '%s<' % value
+
+ if hash_method == 'sha-1':
+ hash_ = hashlib.sha1(S)
+ elif hash_method == 'md5':
+ hash_ = hashlib.md5(S)
+ else:
+ return ''
+ return base64.b64encode(hash_.digest())
################################################################################
@@ -165,241 +165,239 @@ def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
################################################################################
class AbstractClientCaps(object):
- """
- Base class representing a client and its capabilities as advertised by a
- caps tag in a presence
- """
- def __init__(self, caps_hash, node):
- self._hash = caps_hash
- self._node = node
-
- def get_discover_strategy(self):
- return self._discover
-
- def _discover(self, connection, jid):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
- def get_cache_lookup_strategy(self):
- return self._lookup_in_cache
-
- def _lookup_in_cache(self, caps_cache):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
- def get_hash_validation_strategy(self):
- return self._is_hash_valid
-
- def _is_hash_valid(self, identities, features, dataforms):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
+ """
+ Base class representing a client and its capabilities as advertised by a
+ caps tag in a presence
+ """
+ def __init__(self, caps_hash, node):
+ self._hash = caps_hash
+ self._node = node
+
+ def get_discover_strategy(self):
+ return self._discover
+
+ def _discover(self, connection, jid):
+ """
+ To be implemented by subclassess
+ """
+ raise NotImplementedError
+
+ def get_cache_lookup_strategy(self):
+ return self._lookup_in_cache
+
+ def _lookup_in_cache(self, caps_cache):
+ """
+ To be implemented by subclassess
+ """
+ raise NotImplementedError
+
+ def get_hash_validation_strategy(self):
+ return self._is_hash_valid
+
+ def _is_hash_valid(self, identities, features, dataforms):
+ """
+ To be implemented by subclassess
+ """
+ raise NotImplementedError
class ClientCaps(AbstractClientCaps):
- """
- The current XEP-115 implementation
- """
- def __init__(self, caps_hash, node, hash_method):
- AbstractClientCaps.__init__(self, caps_hash, node)
- assert hash_method != 'old'
- self._hash_method = hash_method
+ """
+ The current XEP-115 implementation
+ """
+ def __init__(self, caps_hash, node, hash_method):
+ AbstractClientCaps.__init__(self, caps_hash, node)
+ assert hash_method != 'old'
+ self._hash_method = hash_method
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[(self._hash_method, self._hash)]
+ def _lookup_in_cache(self, caps_cache):
+ return caps_cache[(self._hash_method, self._hash)]
- def _discover(self, connection, jid):
- connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
+ def _discover(self, connection, jid):
+ connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
- def _is_hash_valid(self, identities, features, dataforms):
- computed_hash = compute_caps_hash(identities, features,
- dataforms=dataforms, hash_method=self._hash_method)
- return computed_hash == self._hash
+ def _is_hash_valid(self, identities, features, dataforms):
+ computed_hash = compute_caps_hash(identities, features,
+ dataforms=dataforms, hash_method=self._hash_method)
+ return computed_hash == self._hash
class OldClientCaps(AbstractClientCaps):
- """
- Old XEP-115 implemtation. Kept around for background competability
- """
- def __init__(self, caps_hash, node):
- AbstractClientCaps.__init__(self, caps_hash, node)
+ """
+ Old XEP-115 implemtation. Kept around for background competability
+ """
+ def __init__(self, caps_hash, node):
+ AbstractClientCaps.__init__(self, caps_hash, node)
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[('old', self._node + '#' + self._hash)]
+ def _lookup_in_cache(self, caps_cache):
+ return caps_cache[('old', self._node + '#' + self._hash)]
- def _discover(self, connection, jid):
- connection.discoverInfo(jid)
+ def _discover(self, connection, jid):
+ connection.discoverInfo(jid)
- def _is_hash_valid(self, identities, features, dataforms):
- return True
+ def _is_hash_valid(self, identities, features, dataforms):
+ return True
class NullClientCaps(AbstractClientCaps):
- """
- This is a NULL-Object to streamline caps handling if a client has not
- advertised any caps or has advertised them in an improper way
-
- Assumes (almost) everything is supported.
- """
- _instance = None
- def __new__(cls, *args, **kwargs):
- """
- Make it a singleton.
- """
- if not cls._instance:
- cls._instance = super(NullClientCaps, cls).__new__(
- cls, *args, **kwargs)
- return cls._instance
-
- def __init__(self):
- AbstractClientCaps.__init__(self, None, None)
-
- def _lookup_in_cache(self, caps_cache):
- # lookup something which does not exist to get a new CacheItem created
- cache_item = caps_cache[('dummy', '')]
- # Mark the item as cached so that protocol/caps.py does not update it
- cache_item.status = FAKED
- return cache_item
-
- def _discover(self, connection, jid):
- pass
-
- def _is_hash_valid(self, identities, features, dataforms):
- return False
+ """
+ This is a NULL-Object to streamline caps handling if a client has not
+ advertised any caps or has advertised them in an improper way
+
+ Assumes (almost) everything is supported.
+ """
+ _instance = None
+ def __new__(cls, *args, **kwargs):
+ """
+ Make it a singleton.
+ """
+ if not cls._instance:
+ cls._instance = super(NullClientCaps, cls).__new__(
+ cls, *args, **kwargs)
+ return cls._instance
+
+ def __init__(self):
+ AbstractClientCaps.__init__(self, None, None)
+
+ def _lookup_in_cache(self, caps_cache):
+ # lookup something which does not exist to get a new CacheItem created
+ cache_item = caps_cache[('dummy', '')]
+ # Mark the item as cached so that protocol/caps.py does not update it
+ cache_item.status = FAKED
+ return cache_item
+
+ def _discover(self, connection, jid):
+ pass
+
+ def _is_hash_valid(self, identities, features, dataforms):
+ return False
class CapsCache(object):
- """
- This object keeps the mapping between caps data and real disco features they
- represent, and provides simple way to query that info
- """
- def __init__(self, logger=None):
- # our containers:
- # __cache is a dictionary mapping: pair of hash method and hash maps
- # to CapsCacheItem object
- # __CacheItem is a class that stores data about particular
- # client (hash method/hash pair)
- self.__cache = {}
-
- class CacheItem(object):
- # __names is a string cache; every string long enough is given
- # another object, and we will have plenty of identical long
- # strings. therefore we can cache them
- __names = {}
-
- def __init__(self, hash_method, hash_, logger):
- # cached into db
- self.hash_method = hash_method
- self.hash = hash_
- self._features = []
- self._identities = []
- self._logger = logger
-
- self.status = NEW
- self._recently_seen = False
-
- def _get_features(self):
- return self._features
-
- def _set_features(self, value):
- self._features = []
- for feature in value:
- self._features.append(self.__names.setdefault(feature, feature))
-
- features = property(_get_features, _set_features)
-
- def _get_identities(self):
- list_ = []
- for i in self._identities:
- # transforms it back in a dict
- d = dict()
- d['category'] = i[0]
- if i[1]:
- d['type'] = i[1]
- if i[2]:
- d['xml:lang'] = i[2]
- if i[3]:
- d['name'] = i[3]
- list_.append(d)
- return list_
-
- def _set_identities(self, value):
- self._identities = []
- for identity in value:
- # dict are not hashable, so transform it into a tuple
- t = (identity['category'], identity.get('type'),
- identity.get('xml:lang'), identity.get('name'))
- self._identities.append(self.__names.setdefault(t, t))
-
- identities = property(_get_identities, _set_identities)
-
- def set_and_store(self, identities, features):
- self.identities = identities
- self.features = features
- self._logger.add_caps_entry(self.hash_method, self.hash,
- identities, features)
- self.status = CACHED
-
- def update_last_seen(self):
- if not self._recently_seen:
- self._recently_seen = True
- self._logger.update_caps_time(self.hash_method, self.hash)
-
- def is_valid(self):
- """
- Returns True if identities and features for this cache item
- are known.
- """
- return self.status in (CACHED, FAKED)
-
- self.__CacheItem = CacheItem
- self.logger = logger
-
- def initialize_from_db(self):
- self._remove_outdated_caps()
- for hash_method, hash_, identities, features in \
- self.logger.iter_caps_data():
- x = self[(hash_method, hash_)]
- x.identities = identities
- x.features = features
- x.status = CACHED
-
- def _remove_outdated_caps(self):
- """
- Remove outdated values from the db
- """
- self.logger.clean_caps_table()
-
- def __getitem__(self, caps):
- if caps in self.__cache:
- return self.__cache[caps]
-
- hash_method, hash_ = caps
-
- x = self.__CacheItem(hash_method, hash_, self.logger)
- self.__cache[(hash_method, hash_)] = x
- return x
-
- def query_client_of_jid_if_unknown(self, connection, jid, client_caps):
- """
- Start a disco query to determine caps (node, ver, exts). Won't query if
- the data is already in cache
- """
- lookup_cache_item = client_caps.get_cache_lookup_strategy()
- q = lookup_cache_item(self)
-
- if q.status == NEW:
- # do query for bare node+hash pair
- # this will create proper object
- q.status = QUERIED
- discover = client_caps.get_discover_strategy()
- discover(connection, jid)
- else:
- q.update_last_seen()
-
-# vim: se ts=3:
+ """
+ This object keeps the mapping between caps data and real disco features they
+ represent, and provides simple way to query that info
+ """
+ def __init__(self, logger=None):
+ # our containers:
+ # __cache is a dictionary mapping: pair of hash method and hash maps
+ # to CapsCacheItem object
+ # __CacheItem is a class that stores data about particular
+ # client (hash method/hash pair)
+ self.__cache = {}
+
+ class CacheItem(object):
+ # __names is a string cache; every string long enough is given
+ # another object, and we will have plenty of identical long
+ # strings. therefore we can cache them
+ __names = {}
+
+ def __init__(self, hash_method, hash_, logger):
+ # cached into db
+ self.hash_method = hash_method
+ self.hash = hash_
+ self._features = []
+ self._identities = []
+ self._logger = logger
+
+ self.status = NEW
+ self._recently_seen = False
+
+ def _get_features(self):
+ return self._features
+
+ def _set_features(self, value):
+ self._features = []
+ for feature in value:
+ self._features.append(self.__names.setdefault(feature, feature))
+
+ features = property(_get_features, _set_features)
+
+ def _get_identities(self):
+ list_ = []
+ for i in self._identities:
+ # transforms it back in a dict
+ d = dict()
+ d['category'] = i[0]
+ if i[1]:
+ d['type'] = i[1]
+ if i[2]:
+ d['xml:lang'] = i[2]
+ if i[3]:
+ d['name'] = i[3]
+ list_.append(d)
+ return list_
+
+ def _set_identities(self, value):
+ self._identities = []
+ for identity in value:
+ # dict are not hashable, so transform it into a tuple
+ t = (identity['category'], identity.get('type'),
+ identity.get('xml:lang'), identity.get('name'))
+ self._identities.append(self.__names.setdefault(t, t))
+
+ identities = property(_get_identities, _set_identities)
+
+ def set_and_store(self, identities, features):
+ self.identities = identities
+ self.features = features
+ self._logger.add_caps_entry(self.hash_method, self.hash,
+ identities, features)
+ self.status = CACHED
+
+ def update_last_seen(self):
+ if not self._recently_seen:
+ self._recently_seen = True
+ self._logger.update_caps_time(self.hash_method, self.hash)
+
+ def is_valid(self):
+ """
+ Returns True if identities and features for this cache item
+ are known.
+ """
+ return self.status in (CACHED, FAKED)
+
+ self.__CacheItem = CacheItem
+ self.logger = logger
+
+ def initialize_from_db(self):
+ self._remove_outdated_caps()
+ for hash_method, hash_, identities, features in \
+ self.logger.iter_caps_data():
+ x = self[(hash_method, hash_)]
+ x.identities = identities
+ x.features = features
+ x.status = CACHED
+
+ def _remove_outdated_caps(self):
+ """
+ Remove outdated values from the db
+ """
+ self.logger.clean_caps_table()
+
+ def __getitem__(self, caps):
+ if caps in self.__cache:
+ return self.__cache[caps]
+
+ hash_method, hash_ = caps
+
+ x = self.__CacheItem(hash_method, hash_, self.logger)
+ self.__cache[(hash_method, hash_)] = x
+ return x
+
+ def query_client_of_jid_if_unknown(self, connection, jid, client_caps):
+ """
+ Start a disco query to determine caps (node, ver, exts). Won't query if
+ the data is already in cache
+ """
+ lookup_cache_item = client_caps.get_cache_lookup_strategy()
+ q = lookup_cache_item(self)
+
+ if q.status == NEW:
+ # do query for bare node+hash pair
+ # this will create proper object
+ q.status = QUERIED
+ discover = client_caps.get_discover_strategy()
+ discover(connection, jid)
+ else:
+ q.update_last_seen()
diff --git a/src/common/check_paths.py b/src/common/check_paths.py
index f025c082f..ebda5ed8b 100644
--- a/src/common/check_paths.py
+++ b/src/common/check_paths.py
@@ -34,304 +34,302 @@ import logger
import sqlite3 as sqlite
def create_log_db():
- print _('creating logs database')
- con = sqlite.connect(logger.LOG_DB_PATH)
- os.chmod(logger.LOG_DB_PATH, 0600) # rw only for us
- cur = con.cursor()
- # create the tables
- # kind can be
- # status, gcstatus, gc_msg, (we only recv for those 3),
- # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
- # to meet all our needs
- # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code
- # jids.jid text column will be JID if TC-related, room_jid if GC-related,
- # ROOM_JID/nick if pm-related.
- # also check optparser.py, which updates databases on gajim updates
- cur.executescript(
- '''
- CREATE TABLE jids(
- jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid TEXT UNIQUE,
- type INTEGER
- );
-
- CREATE TABLE unread_messages(
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER,
- shown BOOLEAN default 0
- );
-
- CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
-
- CREATE TABLE logs(
- log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER,
- contact_name TEXT,
- time INTEGER,
- kind INTEGER,
- show INTEGER,
- message TEXT,
- subject TEXT
- );
-
- CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC);
- '''
- )
-
- con.commit()
- con.close()
+ print _('creating logs database')
+ con = sqlite.connect(logger.LOG_DB_PATH)
+ os.chmod(logger.LOG_DB_PATH, 0600) # rw only for us
+ cur = con.cursor()
+ # create the tables
+ # kind can be
+ # status, gcstatus, gc_msg, (we only recv for those 3),
+ # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
+ # to meet all our needs
+ # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code
+ # jids.jid text column will be JID if TC-related, room_jid if GC-related,
+ # ROOM_JID/nick if pm-related.
+ # also check optparser.py, which updates databases on gajim updates
+ cur.executescript(
+ '''
+ CREATE TABLE jids(
+ jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+ jid TEXT UNIQUE,
+ type INTEGER
+ );
+
+ CREATE TABLE unread_messages(
+ message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+ jid_id INTEGER,
+ shown BOOLEAN default 0
+ );
+
+ CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
+
+ CREATE TABLE logs(
+ log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+ jid_id INTEGER,
+ contact_name TEXT,
+ time INTEGER,
+ kind INTEGER,
+ show INTEGER,
+ message TEXT,
+ subject TEXT
+ );
+
+ CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC);
+ '''
+ )
+
+ con.commit()
+ con.close()
def create_cache_db():
- print _('creating cache database')
- con = sqlite.connect(logger.CACHE_DB_PATH)
- os.chmod(logger.CACHE_DB_PATH, 0600) # rw only for us
- cur = con.cursor()
- cur.executescript(
- '''
- CREATE TABLE transports_cache (
- transport TEXT UNIQUE,
- type INTEGER
- );
-
- CREATE TABLE caps_cache (
- hash_method TEXT,
- hash TEXT,
- data BLOB,
- last_seen INTEGER);
-
- CREATE TABLE rooms_last_message_time(
- jid_id INTEGER PRIMARY KEY UNIQUE,
- time INTEGER
- );
-
- CREATE TABLE IF NOT EXISTS roster_entry(
- account_jid_id INTEGER,
- jid_id INTEGER,
- name TEXT,
- subscription INTEGER,
- ask BOOLEAN,
- PRIMARY KEY (account_jid_id, jid_id)
- );
-
- CREATE TABLE IF NOT EXISTS roster_group(
- account_jid_id INTEGER,
- jid_id INTEGER,
- group_name TEXT,
- PRIMARY KEY (account_jid_id, jid_id, group_name)
- );
- '''
- )
-
- con.commit()
- con.close()
+ print _('creating cache database')
+ con = sqlite.connect(logger.CACHE_DB_PATH)
+ os.chmod(logger.CACHE_DB_PATH, 0600) # rw only for us
+ cur = con.cursor()
+ cur.executescript(
+ '''
+ CREATE TABLE transports_cache (
+ transport TEXT UNIQUE,
+ type INTEGER
+ );
+
+ CREATE TABLE caps_cache (
+ hash_method TEXT,
+ hash TEXT,
+ data BLOB,
+ last_seen INTEGER);
+
+ CREATE TABLE rooms_last_message_time(
+ jid_id INTEGER PRIMARY KEY UNIQUE,
+ time INTEGER
+ );
+
+ CREATE TABLE IF NOT EXISTS roster_entry(
+ account_jid_id INTEGER,
+ jid_id INTEGER,
+ name TEXT,
+ subscription INTEGER,
+ ask BOOLEAN,
+ PRIMARY KEY (account_jid_id, jid_id)
+ );
+
+ CREATE TABLE IF NOT EXISTS roster_group(
+ account_jid_id INTEGER,
+ jid_id INTEGER,
+ group_name TEXT,
+ PRIMARY KEY (account_jid_id, jid_id, group_name)
+ );
+ '''
+ )
+
+ con.commit()
+ con.close()
def split_db():
- print 'spliting database'
- if os.name == 'nt':
- try:
- OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim')
- except KeyError:
- OLD_LOG_DB_FOLDER = u'.'
- else:
- OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim')
-
- tmp = logger.CACHE_DB_PATH
- logger.CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db')
- create_cache_db()
- back = os.getcwd()
- os.chdir(OLD_LOG_DB_FOLDER)
- con = sqlite.connect('logs.db')
- os.chdir(back)
- cur = con.cursor()
- cur.execute('''SELECT name FROM sqlite_master WHERE type = 'table';''')
- tables = cur.fetchall() # we get [(u'jids',), (u'unread_messages',), ...
- tables = [t[0] for t in tables]
- cur.execute("ATTACH DATABASE '%s' AS cache" % logger.CACHE_DB_PATH)
- for table in ('caps_cache', 'rooms_last_message_time', 'roster_entry',
- 'roster_group', 'transports_cache'):
- if table not in tables:
- continue
- try:
- cur.executescript(
- 'INSERT INTO cache.%s SELECT * FROM %s;' % (table, table))
- con.commit()
- cur.executescript('DROP TABLE %s;' % table)
- con.commit()
- except sqlite.OperationalError, e:
- print >> sys.stderr, 'error moving table %s to cache.db: %s' % \
- (table, str(e))
- con.close()
- logger.CACHE_DB_PATH = tmp
+ print 'spliting database'
+ if os.name == 'nt':
+ try:
+ OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim')
+ except KeyError:
+ OLD_LOG_DB_FOLDER = u'.'
+ else:
+ OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim')
+
+ tmp = logger.CACHE_DB_PATH
+ logger.CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db')
+ create_cache_db()
+ back = os.getcwd()
+ os.chdir(OLD_LOG_DB_FOLDER)
+ con = sqlite.connect('logs.db')
+ os.chdir(back)
+ cur = con.cursor()
+ cur.execute('''SELECT name FROM sqlite_master WHERE type = 'table';''')
+ tables = cur.fetchall() # we get [(u'jids',), (u'unread_messages',), ...
+ tables = [t[0] for t in tables]
+ cur.execute("ATTACH DATABASE '%s' AS cache" % logger.CACHE_DB_PATH)
+ for table in ('caps_cache', 'rooms_last_message_time', 'roster_entry',
+ 'roster_group', 'transports_cache'):
+ if table not in tables:
+ continue
+ try:
+ cur.executescript(
+ 'INSERT INTO cache.%s SELECT * FROM %s;' % (table, table))
+ con.commit()
+ cur.executescript('DROP TABLE %s;' % table)
+ con.commit()
+ except sqlite.OperationalError, e:
+ print >> sys.stderr, 'error moving table %s to cache.db: %s' % \
+ (table, str(e))
+ con.close()
+ logger.CACHE_DB_PATH = tmp
def check_and_possibly_move_config():
- LOG_DB_PATH = logger.LOG_DB_PATH
- CACHE_DB_PATH = logger.CACHE_DB_PATH
- vars = {}
- vars['VCARD_PATH'] = gajim.VCARD_PATH
- vars['AVATAR_PATH'] = gajim.AVATAR_PATH
- vars['MY_EMOTS_PATH'] = gajim.MY_EMOTS_PATH
- vars['MY_ICONSETS_PATH'] = gajim.MY_ICONSETS_PATH
- vars['MY_MOOD_ICONSETS_PATH'] = gajim.MY_MOOD_ICONSETS_PATH
- vars['MY_ACTIVITY_ICONSETS_PATH'] = gajim.MY_ACTIVITY_ICONSETS_PATH
- import configpaths
- MY_DATA = configpaths.gajimpaths['MY_DATA']
- MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
- MY_CACHE = configpaths.gajimpaths['MY_CACHE']
-
- if os.path.exists(LOG_DB_PATH):
- # File already exists
- return
-
- if os.name == 'nt':
- try:
- OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim')
- except KeyError:
- OLD_LOG_DB_FOLDER = u'.'
- else:
- OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim')
- if not os.path.exists(OLD_LOG_DB_FOLDER):
- return
- OLD_LOG_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'logs.db')
- OLD_CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'cache.db')
- vars['OLD_VCARD_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'vcards')
- vars['OLD_AVATAR_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'avatars')
- vars['OLD_MY_EMOTS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'emoticons')
- vars['OLD_MY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'iconsets')
- vars['OLD_MY_MOOD_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'moods')
- vars['OLD_MY_ACTIVITY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER,
- u'activities')
- OLD_CONFIG_FILES = []
- OLD_DATA_FILES = []
- for f in os.listdir(OLD_LOG_DB_FOLDER):
- if f == 'config' or f.startswith('config.'):
- OLD_CONFIG_FILES.append(f)
- if f == 'secrets' or f.startswith('secrets.'):
- OLD_DATA_FILES.append(f)
- if f == 'cacerts.pem':
- OLD_DATA_FILES.append(f)
-
- if not os.path.exists(OLD_LOG_DB_PATH):
- return
-
- if not os.path.exists(OLD_CACHE_DB_PATH):
- # split database
- split_db()
-
- to_move = {}
- to_move[OLD_LOG_DB_PATH] = LOG_DB_PATH
- to_move[OLD_CACHE_DB_PATH] = CACHE_DB_PATH
-
- for folder in ('VCARD_PATH', 'AVATAR_PATH', 'MY_EMOTS_PATH',
- 'MY_ICONSETS_PATH', 'MY_MOOD_ICONSETS_PATH', 'MY_ACTIVITY_ICONSETS_PATH'):
- src = vars['OLD_' + folder]
- dst = vars[folder]
- to_move[src] = dst
-
- # move config files
- for f in OLD_CONFIG_FILES:
- src = os.path.join(OLD_LOG_DB_FOLDER, f)
- dst = os.path.join(MY_CONFIG, f)
- to_move[src] = dst
-
- # Move data files (secrets, cacert.pem)
- for f in OLD_DATA_FILES:
- src = os.path.join(OLD_LOG_DB_FOLDER, f)
- dst = os.path.join(MY_DATA, f)
- to_move[src] = dst
-
- for src, dst in to_move.items():
- if os.path.exists(dst):
- continue
- if not os.path.exists(src):
- continue
- print 'moving %s to %s' % (src, dst)
- os.renames(src, dst)
- gajim.logger.init_vars()
- gajim.logger.attach_cache_database()
+ LOG_DB_PATH = logger.LOG_DB_PATH
+ CACHE_DB_PATH = logger.CACHE_DB_PATH
+ vars = {}
+ vars['VCARD_PATH'] = gajim.VCARD_PATH
+ vars['AVATAR_PATH'] = gajim.AVATAR_PATH
+ vars['MY_EMOTS_PATH'] = gajim.MY_EMOTS_PATH
+ vars['MY_ICONSETS_PATH'] = gajim.MY_ICONSETS_PATH
+ vars['MY_MOOD_ICONSETS_PATH'] = gajim.MY_MOOD_ICONSETS_PATH
+ vars['MY_ACTIVITY_ICONSETS_PATH'] = gajim.MY_ACTIVITY_ICONSETS_PATH
+ import configpaths
+ MY_DATA = configpaths.gajimpaths['MY_DATA']
+ MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
+ MY_CACHE = configpaths.gajimpaths['MY_CACHE']
+
+ if os.path.exists(LOG_DB_PATH):
+ # File already exists
+ return
+
+ if os.name == 'nt':
+ try:
+ OLD_LOG_DB_FOLDER = os.path.join(fse(os.environ[u'appdata']), u'Gajim')
+ except KeyError:
+ OLD_LOG_DB_FOLDER = u'.'
+ else:
+ OLD_LOG_DB_FOLDER = os.path.expanduser(u'~/.gajim')
+ if not os.path.exists(OLD_LOG_DB_FOLDER):
+ return
+ OLD_LOG_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'logs.db')
+ OLD_CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, u'cache.db')
+ vars['OLD_VCARD_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'vcards')
+ vars['OLD_AVATAR_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'avatars')
+ vars['OLD_MY_EMOTS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'emoticons')
+ vars['OLD_MY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'iconsets')
+ vars['OLD_MY_MOOD_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, u'moods')
+ vars['OLD_MY_ACTIVITY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER,
+ u'activities')
+ OLD_CONFIG_FILES = []
+ OLD_DATA_FILES = []
+ for f in os.listdir(OLD_LOG_DB_FOLDER):
+ if f == 'config' or f.startswith('config.'):
+ OLD_CONFIG_FILES.append(f)
+ if f == 'secrets' or f.startswith('secrets.'):
+ OLD_DATA_FILES.append(f)
+ if f == 'cacerts.pem':
+ OLD_DATA_FILES.append(f)
+
+ if not os.path.exists(OLD_LOG_DB_PATH):
+ return
+
+ if not os.path.exists(OLD_CACHE_DB_PATH):
+ # split database
+ split_db()
+
+ to_move = {}
+ to_move[OLD_LOG_DB_PATH] = LOG_DB_PATH
+ to_move[OLD_CACHE_DB_PATH] = CACHE_DB_PATH
+
+ for folder in ('VCARD_PATH', 'AVATAR_PATH', 'MY_EMOTS_PATH',
+ 'MY_ICONSETS_PATH', 'MY_MOOD_ICONSETS_PATH', 'MY_ACTIVITY_ICONSETS_PATH'):
+ src = vars['OLD_' + folder]
+ dst = vars[folder]
+ to_move[src] = dst
+
+ # move config files
+ for f in OLD_CONFIG_FILES:
+ src = os.path.join(OLD_LOG_DB_FOLDER, f)
+ dst = os.path.join(MY_CONFIG, f)
+ to_move[src] = dst
+
+ # Move data files (secrets, cacert.pem)
+ for f in OLD_DATA_FILES:
+ src = os.path.join(OLD_LOG_DB_FOLDER, f)
+ dst = os.path.join(MY_DATA, f)
+ to_move[src] = dst
+
+ for src, dst in to_move.items():
+ if os.path.exists(dst):
+ continue
+ if not os.path.exists(src):
+ continue
+ print 'moving %s to %s' % (src, dst)
+ os.renames(src, dst)
+ gajim.logger.init_vars()
+ gajim.logger.attach_cache_database()
def check_and_possibly_create_paths():
- check_and_possibly_move_config()
-
- LOG_DB_PATH = logger.LOG_DB_PATH
- LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
-
- CACHE_DB_PATH = logger.CACHE_DB_PATH
- CACHE_DB_FOLDER, CACHE_DB_FILE = os.path.split(CACHE_DB_PATH)
-
- VCARD_PATH = gajim.VCARD_PATH
- AVATAR_PATH = gajim.AVATAR_PATH
- import configpaths
- MY_DATA = configpaths.gajimpaths['MY_DATA']
- MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
- MY_CACHE = configpaths.gajimpaths['MY_CACHE']
-
- if not os.path.exists(MY_DATA):
- create_path(MY_DATA)
- elif os.path.isfile(MY_DATA):
- print _('%s is a file but it should be a directory') % MY_DATA
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(MY_CONFIG):
- create_path(MY_CONFIG)
- elif os.path.isfile(MY_CONFIG):
- print _('%s is a file but it should be a directory') % MY_CONFIG
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(MY_CACHE):
- create_path(MY_CACHE)
- elif os.path.isfile(MY_CACHE):
- print _('%s is a file but it should be a directory') % MY_CACHE
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(VCARD_PATH):
- create_path(VCARD_PATH)
- elif os.path.isfile(VCARD_PATH):
- print _('%s is a file but it should be a directory') % VCARD_PATH
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(AVATAR_PATH):
- create_path(AVATAR_PATH)
- elif os.path.isfile(AVATAR_PATH):
- print _('%s is a file but it should be a directory') % AVATAR_PATH
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(LOG_DB_FOLDER):
- create_path(LOG_DB_FOLDER)
- elif os.path.isfile(LOG_DB_FOLDER):
- print _('%s is a file but it should be a directory') % LOG_DB_FOLDER
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(LOG_DB_PATH):
- create_log_db()
- gajim.logger.init_vars()
- elif os.path.isdir(LOG_DB_PATH):
- print _('%s is a directory but should be a file') % LOG_DB_PATH
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(CACHE_DB_FOLDER):
- create_path(CACHE_DB_FOLDER)
- elif os.path.isfile(CACHE_DB_FOLDER):
- print _('%s is a file but it should be a directory') % CACHE_DB_FOLDER
- print _('Gajim will now exit')
- sys.exit()
-
- if not os.path.exists(CACHE_DB_PATH):
- create_cache_db()
- gajim.logger.attach_cache_database()
- elif os.path.isdir(CACHE_DB_PATH):
- print _('%s is a directory but should be a file') % CACHE_DB_PATH
- print _('Gajim will now exit')
- sys.exit()
+ check_and_possibly_move_config()
+
+ LOG_DB_PATH = logger.LOG_DB_PATH
+ LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
+
+ CACHE_DB_PATH = logger.CACHE_DB_PATH
+ CACHE_DB_FOLDER, CACHE_DB_FILE = os.path.split(CACHE_DB_PATH)
+
+ VCARD_PATH = gajim.VCARD_PATH
+ AVATAR_PATH = gajim.AVATAR_PATH
+ import configpaths
+ MY_DATA = configpaths.gajimpaths['MY_DATA']
+ MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
+ MY_CACHE = configpaths.gajimpaths['MY_CACHE']
+
+ if not os.path.exists(MY_DATA):
+ create_path(MY_DATA)
+ elif os.path.isfile(MY_DATA):
+ print _('%s is a file but it should be a directory') % MY_DATA
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(MY_CONFIG):
+ create_path(MY_CONFIG)
+ elif os.path.isfile(MY_CONFIG):
+ print _('%s is a file but it should be a directory') % MY_CONFIG
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(MY_CACHE):
+ create_path(MY_CACHE)
+ elif os.path.isfile(MY_CACHE):
+ print _('%s is a file but it should be a directory') % MY_CACHE
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(VCARD_PATH):
+ create_path(VCARD_PATH)
+ elif os.path.isfile(VCARD_PATH):
+ print _('%s is a file but it should be a directory') % VCARD_PATH
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(AVATAR_PATH):
+ create_path(AVATAR_PATH)
+ elif os.path.isfile(AVATAR_PATH):
+ print _('%s is a file but it should be a directory') % AVATAR_PATH
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(LOG_DB_FOLDER):
+ create_path(LOG_DB_FOLDER)
+ elif os.path.isfile(LOG_DB_FOLDER):
+ print _('%s is a file but it should be a directory') % LOG_DB_FOLDER
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(LOG_DB_PATH):
+ create_log_db()
+ gajim.logger.init_vars()
+ elif os.path.isdir(LOG_DB_PATH):
+ print _('%s is a directory but should be a file') % LOG_DB_PATH
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(CACHE_DB_FOLDER):
+ create_path(CACHE_DB_FOLDER)
+ elif os.path.isfile(CACHE_DB_FOLDER):
+ print _('%s is a file but it should be a directory') % CACHE_DB_FOLDER
+ print _('Gajim will now exit')
+ sys.exit()
+
+ if not os.path.exists(CACHE_DB_PATH):
+ create_cache_db()
+ gajim.logger.attach_cache_database()
+ elif os.path.isdir(CACHE_DB_PATH):
+ print _('%s is a directory but should be a file') % CACHE_DB_PATH
+ print _('Gajim will now exit')
+ sys.exit()
def create_path(directory):
- print _('creating %s directory') % directory
- os.mkdir(directory, 0700)
-
-# vim: se ts=3:
+ print _('creating %s directory') % directory
+ os.mkdir(directory, 0700)
diff --git a/src/common/commands.py b/src/common/commands.py
index 07c866194..88f9f3332 100644
--- a/src/common/commands.py
+++ b/src/common/commands.py
@@ -28,418 +28,416 @@ import dataforms
import gajim
class AdHocCommand:
- commandnode = 'command'
- commandname = 'The Command'
- commandfeatures = (xmpp.NS_DATA,)
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- This returns True if that command should be visible and invokable for
- others
-
- samejid - True when command is invoked by an entity with the same bare
- jid.
- """
- return True
-
- def __init__(self, conn, jid, sessionid):
- self.connection = conn
- self.jid = jid
- self.sessionid = sessionid
-
- def buildResponse(self, request, status = 'executing', defaultaction = None,
- actions = None):
- assert status in ('executing', 'completed', 'canceled')
-
- response = request.buildReply('result')
- cmd = response.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={
- 'sessionid': self.sessionid,
- 'node': self.commandnode,
- 'status': status})
- if defaultaction is not None or actions is not None:
- if defaultaction is not None:
- assert defaultaction in ('cancel', 'execute', 'prev', 'next',
- 'complete')
- attrs = {'action': defaultaction}
- else:
- attrs = {}
-
- cmd.addChild('actions', attrs, actions)
- return response, cmd
-
- def badRequest(self, stanza):
- self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \
- ' bad-request'))
-
- def cancel(self, request):
- response = self.buildResponse(request, status = 'canceled')[0]
- self.connection.connection.send(response)
- return False # finish the session
+ commandnode = 'command'
+ commandname = 'The Command'
+ commandfeatures = (xmpp.NS_DATA,)
+
+ @staticmethod
+ def isVisibleFor(samejid):
+ """
+ This returns True if that command should be visible and invokable for
+ others
+
+ samejid - True when command is invoked by an entity with the same bare
+ jid.
+ """
+ return True
+
+ def __init__(self, conn, jid, sessionid):
+ self.connection = conn
+ self.jid = jid
+ self.sessionid = sessionid
+
+ def buildResponse(self, request, status = 'executing', defaultaction = None,
+ actions = None):
+ assert status in ('executing', 'completed', 'canceled')
+
+ response = request.buildReply('result')
+ cmd = response.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={
+ 'sessionid': self.sessionid,
+ 'node': self.commandnode,
+ 'status': status})
+ if defaultaction is not None or actions is not None:
+ if defaultaction is not None:
+ assert defaultaction in ('cancel', 'execute', 'prev', 'next',
+ 'complete')
+ attrs = {'action': defaultaction}
+ else:
+ attrs = {}
+
+ cmd.addChild('actions', attrs, actions)
+ return response, cmd
+
+ def badRequest(self, stanza):
+ self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \
+ ' bad-request'))
+
+ def cancel(self, request):
+ response = self.buildResponse(request, status = 'canceled')[0]
+ self.connection.connection.send(response)
+ return False # finish the session
class ChangeStatusCommand(AdHocCommand):
- commandnode = 'change-status'
- commandname = _('Change status information')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions = ['execute'])
-
- cmd.addChild(node = dataforms.SimpleDataForm(
- title = _('Change status'),
- instructions = _('Set the presence type and description'),
- fields = [
- dataforms.Field('list-single',
- var = 'presence-type',
- label = 'Type of presence:',
- options = [
- (u'chat', _('Free for chat')),
- (u'online', _('Online')),
- (u'away', _('Away')),
- (u'xa', _('Extended away')),
- (u'dnd', _('Do not disturb')),
- (u'offline', _('Offline - disconnect'))],
- value = 'online',
- required = True),
- dataforms.Field('text-multi',
- var = 'presence-desc',
- label = _('Presence description:'))]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.execute = self.changestatus
-
- return True # keep the session
-
- def changestatus(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- presencetype = form['presence-type'].value
- if not presencetype in \
- ('chat', 'online', 'away', 'xa', 'dnd', 'offline'):
- self.badRequest(request)
- return False
- except Exception: # KeyError if there's no presence-type field in form or
- # AttributeError if that field is of wrong type
- self.badRequest(request)
- return False
-
- try:
- presencedesc = form['presence-desc'].value
- except Exception: # same exceptions as in last comment
- presencedesc = u''
-
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('The status has been changed.'))
-
- # if going offline, we need to push response so it won't go into
- # queue and disappear
- self.connection.connection.send(response, now = presencetype == 'offline')
-
- # send new status
- gajim.interface.roster.send_status(self.connection.name, presencetype,
- presencedesc)
-
- return False # finish the session
+ commandnode = 'change-status'
+ commandname = _('Change status information')
+
+ @staticmethod
+ def isVisibleFor(samejid):
+ """
+ Change status is visible only if the entity has the same bare jid
+ """
+ return samejid
+
+ def execute(self, request):
+ # first query...
+ response, cmd = self.buildResponse(request, defaultaction = 'execute',
+ actions = ['execute'])
+
+ cmd.addChild(node = dataforms.SimpleDataForm(
+ title = _('Change status'),
+ instructions = _('Set the presence type and description'),
+ fields = [
+ dataforms.Field('list-single',
+ var = 'presence-type',
+ label = 'Type of presence:',
+ options = [
+ (u'chat', _('Free for chat')),
+ (u'online', _('Online')),
+ (u'away', _('Away')),
+ (u'xa', _('Extended away')),
+ (u'dnd', _('Do not disturb')),
+ (u'offline', _('Offline - disconnect'))],
+ value = 'online',
+ required = True),
+ dataforms.Field('text-multi',
+ var = 'presence-desc',
+ label = _('Presence description:'))]))
+
+ self.connection.connection.send(response)
+
+ # for next invocation
+ self.execute = self.changestatus
+
+ return True # keep the session
+
+ def changestatus(self, request):
+ # check if the data is correct
+ try:
+ form = dataforms.SimpleDataForm(extend = request.getTag('command').\
+ getTag('x'))
+ except Exception:
+ self.badRequest(request)
+ return False
+
+ try:
+ presencetype = form['presence-type'].value
+ if not presencetype in \
+ ('chat', 'online', 'away', 'xa', 'dnd', 'offline'):
+ self.badRequest(request)
+ return False
+ except Exception: # KeyError if there's no presence-type field in form or
+ # AttributeError if that field is of wrong type
+ self.badRequest(request)
+ return False
+
+ try:
+ presencedesc = form['presence-desc'].value
+ except Exception: # same exceptions as in last comment
+ presencedesc = u''
+
+ response, cmd = self.buildResponse(request, status = 'completed')
+ cmd.addChild('note', {}, _('The status has been changed.'))
+
+ # if going offline, we need to push response so it won't go into
+ # queue and disappear
+ self.connection.connection.send(response, now = presencetype == 'offline')
+
+ # send new status
+ gajim.interface.roster.send_status(self.connection.name, presencetype,
+ presencedesc)
+
+ return False # finish the session
def find_current_groupchats(account):
- import message_control
- rooms = []
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\
- values():
- acct = gc_control.account
- # check if account is the good one
- if acct != account:
- continue
- room_jid = gc_control.room_jid
- nick = gc_control.nick
- if room_jid in gajim.gc_connected[acct] and \
- gajim.gc_connected[acct][room_jid]:
- rooms.append((room_jid, nick,))
- return rooms
+ import message_control
+ rooms = []
+ for gc_control in gajim.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\
+ values():
+ acct = gc_control.account
+ # check if account is the good one
+ if acct != account:
+ continue
+ room_jid = gc_control.room_jid
+ nick = gc_control.nick
+ if room_jid in gajim.gc_connected[acct] and \
+ gajim.gc_connected[acct][room_jid]:
+ rooms.append((room_jid, nick,))
+ return rooms
class LeaveGroupchatsCommand(AdHocCommand):
- commandnode = 'leave-groupchats'
- commandname = _('Leave Groupchats')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions=['execute'])
- options = []
- account = self.connection.name
- for gc in find_current_groupchats(account):
- options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \
- {'nickname': gc[1], 'room_jid': gc[0]}))
- if not len(options):
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('You have not joined a groupchat.'))
-
- self.connection.connection.send(response)
- return False
-
- cmd.addChild(node=dataforms.SimpleDataForm(
- title = _('Leave Groupchats'),
- instructions = _('Choose the groupchats you want to leave'),
- fields=[
- dataforms.Field('list-multi',
- var = 'groupchats',
- label = _('Groupchats'),
- options = options,
- required = True)]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.execute = self.leavegroupchats
-
- return True # keep the session
-
- def leavegroupchats(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- gc = form['groupchats'].values
- except Exception: # KeyError if there's no groupchats in form
- self.badRequest(request)
- return False
- account = self.connection.name
- try:
- for room_jid in gc:
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- account)
- if not gc_control:
- gc_control = gajim.interface.minimized_controls[account]\
- [room_jid]
- gc_control.shutdown()
- gajim.interface.roster.remove_groupchat(room_jid, account)
- continue
- gc_control.parent_win.remove_tab(gc_control, None, force = True)
- except Exception: # KeyError if there's no such room opened
- self.badRequest(request)
- return False
- response, cmd = self.buildResponse(request, status = 'completed')
- note = _('You left the following groupchats:')
- for room_jid in gc:
- note += '\n\t' + room_jid
- cmd.addChild('note', {}, note)
-
- self.connection.connection.send(response)
- return False
+ commandnode = 'leave-groupchats'
+ commandname = _('Leave Groupchats')
+
+ @staticmethod
+ def isVisibleFor(samejid):
+ """
+ Change status is visible only if the entity has the same bare jid
+ """
+ return samejid
+
+ def execute(self, request):
+ # first query...
+ response, cmd = self.buildResponse(request, defaultaction = 'execute',
+ actions=['execute'])
+ options = []
+ account = self.connection.name
+ for gc in find_current_groupchats(account):
+ options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \
+ {'nickname': gc[1], 'room_jid': gc[0]}))
+ if not len(options):
+ response, cmd = self.buildResponse(request, status = 'completed')
+ cmd.addChild('note', {}, _('You have not joined a groupchat.'))
+
+ self.connection.connection.send(response)
+ return False
+
+ cmd.addChild(node=dataforms.SimpleDataForm(
+ title = _('Leave Groupchats'),
+ instructions = _('Choose the groupchats you want to leave'),
+ fields=[
+ dataforms.Field('list-multi',
+ var = 'groupchats',
+ label = _('Groupchats'),
+ options = options,
+ required = True)]))
+
+ self.connection.connection.send(response)
+
+ # for next invocation
+ self.execute = self.leavegroupchats
+
+ return True # keep the session
+
+ def leavegroupchats(self, request):
+ # check if the data is correct
+ try:
+ form = dataforms.SimpleDataForm(extend = request.getTag('command').\
+ getTag('x'))
+ except Exception:
+ self.badRequest(request)
+ return False
+
+ try:
+ gc = form['groupchats'].values
+ except Exception: # KeyError if there's no groupchats in form
+ self.badRequest(request)
+ return False
+ account = self.connection.name
+ try:
+ for room_jid in gc:
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
+ account)
+ if not gc_control:
+ gc_control = gajim.interface.minimized_controls[account]\
+ [room_jid]
+ gc_control.shutdown()
+ gajim.interface.roster.remove_groupchat(room_jid, account)
+ continue
+ gc_control.parent_win.remove_tab(gc_control, None, force = True)
+ except Exception: # KeyError if there's no such room opened
+ self.badRequest(request)
+ return False
+ response, cmd = self.buildResponse(request, status = 'completed')
+ note = _('You left the following groupchats:')
+ for room_jid in gc:
+ note += '\n\t' + room_jid
+ cmd.addChild('note', {}, note)
+
+ self.connection.connection.send(response)
+ return False
class ForwardMessagesCommand(AdHocCommand):
- # http://www.xmpp.org/extensions/xep-0146.html#forward
- commandnode = 'forward-messages'
- commandname = _('Forward unread messages')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- account = self.connection.name
- # Forward messages
- events = gajim.events.get_events(account, types=['chat', 'normal'])
- j, resource = gajim.get_room_and_nick_from_fjid(self.jid)
- for jid in events:
- for event in events[jid]:
- self.connection.send_message(j, event.parameters[0], '',
- type_=event.type_, subject=event.parameters[1],
- resource=resource, forward_from=jid, delayed=event.time_)
-
- # Inform other client of completion
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('All unread messages have been forwarded.'))
-
- self.connection.connection.send(response)
-
- return False # finish the session
+ # http://www.xmpp.org/extensions/xep-0146.html#forward
+ commandnode = 'forward-messages'
+ commandname = _('Forward unread messages')
+
+ @staticmethod
+ def isVisibleFor(samejid):
+ """
+ Change status is visible only if the entity has the same bare jid
+ """
+ return samejid
+
+ def execute(self, request):
+ account = self.connection.name
+ # Forward messages
+ events = gajim.events.get_events(account, types=['chat', 'normal'])
+ j, resource = gajim.get_room_and_nick_from_fjid(self.jid)
+ for jid in events:
+ for event in events[jid]:
+ self.connection.send_message(j, event.parameters[0], '',
+ type_=event.type_, subject=event.parameters[1],
+ resource=resource, forward_from=jid, delayed=event.time_)
+
+ # Inform other client of completion
+ response, cmd = self.buildResponse(request, status = 'completed')
+ cmd.addChild('note', {}, _('All unread messages have been forwarded.'))
+
+ self.connection.connection.send(response)
+
+ return False # finish the session
class ConnectionCommands:
- """
- This class depends on that it is a part of Connection() class
- """
-
- def __init__(self):
- # a list of all commands exposed: node -> command class
- self.__commands = {}
- for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand,
- LeaveGroupchatsCommand):
- self.__commands[cmdobj.commandnode] = cmdobj
-
- # a list of sessions; keys are tuples (jid, sessionid, node)
- self.__sessions = {}
-
- def getOurBareJID(self):
- return gajim.get_jid_from_account(self.name)
-
- def isSameJID(self, jid):
- """
- Test if the bare jid given is the same as our bare jid
- """
- return xmpp.JID(jid).getStripped() == self.getOurBareJID()
-
- def commandListQuery(self, con, iq_obj):
- iq = iq_obj.buildReply('result')
- jid = helpers.get_full_jid_from_iq(iq_obj)
- q = iq.getTag('query')
- # buildReply don't copy the node attribute. Re-add it
- q.setAttr('node', xmpp.NS_COMMANDS)
-
- for node, cmd in self.__commands.iteritems():
- if cmd.isVisibleFor(self.isSameJID(jid)):
- q.addChild('item', {
- # TODO: find the jid
- 'jid': self.getOurBareJID() + u'/' + self.server_resource,
- 'node': node,
- 'name': cmd.commandname})
-
- self.connection.send(iq)
-
- def commandInfoQuery(self, con, iq_obj):
- """
- Send disco#info result for query for command (JEP-0050, example 6.).
- Return True if the result was sent, False if not
- """
- jid = helpers.get_full_jid_from_iq(iq_obj)
- node = iq_obj.getTagAttr('query', 'node')
-
- if node not in self.__commands: return False
-
- cmd = self.__commands[node]
- if cmd.isVisibleFor(self.isSameJID(jid)):
- iq = iq_obj.buildReply('result')
- q = iq.getTag('query')
- q.addChild('identity', attrs = {'type': 'command-node',
- 'category': 'automation',
- 'name': cmd.commandname})
- q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS})
- for feature in cmd.commandfeatures:
- q.addChild('feature', attrs = {'var': feature})
-
- self.connection.send(iq)
- return True
-
- return False
-
- def commandItemsQuery(self, con, iq_obj):
- """
- Send disco#items result for query for command. Return True if the result
- was sent, False if not.
- """
- jid = helpers.get_full_jid_from_iq(iq_obj)
- node = iq_obj.getTagAttr('query', 'node')
-
- if node not in self.__commands: return False
-
- cmd = self.__commands[node]
- if cmd.isVisibleFor(self.isSameJID(jid)):
- iq = iq_obj.buildReply('result')
- self.connection.send(iq)
- return True
-
- return False
-
- def _CommandExecuteCB(self, con, iq_obj):
- jid = helpers.get_full_jid_from_iq(iq_obj)
-
- cmd = iq_obj.getTag('command')
- if cmd is None: return
-
- node = cmd.getAttr('node')
- if node is None: return
-
- sessionid = cmd.getAttr('sessionid')
- if sessionid is None:
- # we start a new command session... only if we are visible for the jid
- # and command exist
- if node not in self.__commands.keys():
- self.connection.send(
- xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found'))
- raise xmpp.NodeProcessed
-
- newcmd = self.__commands[node]
- if not newcmd.isVisibleFor(self.isSameJID(jid)):
- return
-
- # generate new sessionid
- sessionid = self.connection.getAnID()
-
- # create new instance and run it
- obj = newcmd(conn = self, jid = jid, sessionid = sessionid)
- rc = obj.execute(iq_obj)
- if rc:
- self.__sessions[(jid, sessionid, node)] = obj
- raise xmpp.NodeProcessed
- else:
- # the command is already running, check for it
- magictuple = (jid, sessionid, node)
- if magictuple not in self.__sessions:
- # we don't have this session... ha!
- return
-
- action = cmd.getAttr('action')
- obj = self.__sessions[magictuple]
-
- try:
- if action == 'cancel':
- rc = obj.cancel(iq_obj)
- elif action == 'prev':
- rc = obj.prev(iq_obj)
- elif action == 'next':
- rc = obj.next(iq_obj)
- elif action == 'execute' or action is None:
- rc = obj.execute(iq_obj)
- elif action == 'complete':
- rc = obj.complete(iq_obj)
- else:
- # action is wrong. stop the session, send error
- raise AttributeError
- except AttributeError:
- # the command probably doesn't handle invoked action...
- # stop the session, return error
- del self.__sessions[magictuple]
- return
-
- # delete the session if rc is False
- if not rc:
- del self.__sessions[magictuple]
-
- raise xmpp.NodeProcessed
-
-# vim: se ts=3:
+ """
+ This class depends on that it is a part of Connection() class
+ """
+
+ def __init__(self):
+ # a list of all commands exposed: node -> command class
+ self.__commands = {}
+ for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand,
+ LeaveGroupchatsCommand):
+ self.__commands[cmdobj.commandnode] = cmdobj
+
+ # a list of sessions; keys are tuples (jid, sessionid, node)
+ self.__sessions = {}
+
+ def getOurBareJID(self):
+ return gajim.get_jid_from_account(self.name)
+
+ def isSameJID(self, jid):
+ """
+ Test if the bare jid given is the same as our bare jid
+ """
+ return xmpp.JID(jid).getStripped() == self.getOurBareJID()
+
+ def commandListQuery(self, con, iq_obj):
+ iq = iq_obj.buildReply('result')
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ q = iq.getTag('query')
+ # buildReply don't copy the node attribute. Re-add it
+ q.setAttr('node', xmpp.NS_COMMANDS)
+
+ for node, cmd in self.__commands.iteritems():
+ if cmd.isVisibleFor(self.isSameJID(jid)):
+ q.addChild('item', {
+ # TODO: find the jid
+ 'jid': self.getOurBareJID() + u'/' + self.server_resource,
+ 'node': node,
+ 'name': cmd.commandname})
+
+ self.connection.send(iq)
+
+ def commandInfoQuery(self, con, iq_obj):
+ """
+ Send disco#info result for query for command (JEP-0050, example 6.).
+ Return True if the result was sent, False if not
+ """
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ node = iq_obj.getTagAttr('query', 'node')
+
+ if node not in self.__commands: return False
+
+ cmd = self.__commands[node]
+ if cmd.isVisibleFor(self.isSameJID(jid)):
+ iq = iq_obj.buildReply('result')
+ q = iq.getTag('query')
+ q.addChild('identity', attrs = {'type': 'command-node',
+ 'category': 'automation',
+ 'name': cmd.commandname})
+ q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS})
+ for feature in cmd.commandfeatures:
+ q.addChild('feature', attrs = {'var': feature})
+
+ self.connection.send(iq)
+ return True
+
+ return False
+
+ def commandItemsQuery(self, con, iq_obj):
+ """
+ Send disco#items result for query for command. Return True if the result
+ was sent, False if not.
+ """
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ node = iq_obj.getTagAttr('query', 'node')
+
+ if node not in self.__commands: return False
+
+ cmd = self.__commands[node]
+ if cmd.isVisibleFor(self.isSameJID(jid)):
+ iq = iq_obj.buildReply('result')
+ self.connection.send(iq)
+ return True
+
+ return False
+
+ def _CommandExecuteCB(self, con, iq_obj):
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+
+ cmd = iq_obj.getTag('command')
+ if cmd is None: return
+
+ node = cmd.getAttr('node')
+ if node is None: return
+
+ sessionid = cmd.getAttr('sessionid')
+ if sessionid is None:
+ # we start a new command session... only if we are visible for the jid
+ # and command exist
+ if node not in self.__commands.keys():
+ self.connection.send(
+ xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found'))
+ raise xmpp.NodeProcessed
+
+ newcmd = self.__commands[node]
+ if not newcmd.isVisibleFor(self.isSameJID(jid)):
+ return
+
+ # generate new sessionid
+ sessionid = self.connection.getAnID()
+
+ # create new instance and run it
+ obj = newcmd(conn = self, jid = jid, sessionid = sessionid)
+ rc = obj.execute(iq_obj)
+ if rc:
+ self.__sessions[(jid, sessionid, node)] = obj
+ raise xmpp.NodeProcessed
+ else:
+ # the command is already running, check for it
+ magictuple = (jid, sessionid, node)
+ if magictuple not in self.__sessions:
+ # we don't have this session... ha!
+ return
+
+ action = cmd.getAttr('action')
+ obj = self.__sessions[magictuple]
+
+ try:
+ if action == 'cancel':
+ rc = obj.cancel(iq_obj)
+ elif action == 'prev':
+ rc = obj.prev(iq_obj)
+ elif action == 'next':
+ rc = obj.next(iq_obj)
+ elif action == 'execute' or action is None:
+ rc = obj.execute(iq_obj)
+ elif action == 'complete':
+ rc = obj.complete(iq_obj)
+ else:
+ # action is wrong. stop the session, send error
+ raise AttributeError
+ except AttributeError:
+ # the command probably doesn't handle invoked action...
+ # stop the session, return error
+ del self.__sessions[magictuple]
+ return
+
+ # delete the session if rc is False
+ if not rc:
+ del self.__sessions[magictuple]
+
+ raise xmpp.NodeProcessed
diff --git a/src/common/config.py b/src/common/config.py
index 82c1b9b3c..2b052857c 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -56,688 +56,686 @@ opt_treat_incoming_messages = ['', 'chat', 'normal']
class Config:
- DEFAULT_ICONSET = 'dcraven'
- DEFAULT_MOOD_ICONSET = 'default'
- DEFAULT_ACTIVITY_ICONSET = 'default'
- DEFAULT_OPENWITH = 'gnome-open'
- DEFAULT_BROWSER = 'firefox'
- DEFAULT_MAILAPP = 'mozilla-thunderbird -compose'
- DEFAULT_FILE_MANAGER = 'xffm'
-
- __options = {
- # name: [ type, default_value, help_string ]
- 'verbose': [ opt_bool, False, '', True ],
- 'autopopup': [ opt_bool, False ],
- 'notify_on_signin': [ opt_bool, True ],
- 'notify_on_signout': [ opt_bool, False ],
- 'notify_on_new_message': [ opt_bool, True ],
- 'autopopupaway': [ opt_bool, False ],
- 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')],
- 'use_notif_daemon': [ opt_bool, True , _('Use D-Bus and Notification-Daemon to show notifications') ],
- 'showoffline': [ opt_bool, False ],
- 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')],
- 'show_transports_group': [ opt_bool, True ],
- 'autoaway': [ opt_bool, True ],
- 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ],
- 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoaway time.') ],
- 'autoxa': [ opt_bool, True ],
- 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ],
- 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxa time.') ],
- 'ask_online_status': [ opt_bool, False ],
- 'ask_offline_status': [ opt_bool, False ],
- 'trayicon': [opt_str, 'always', _("When to show systray icon. Can be 'never', 'on_event', 'always'."), True],
- 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ],
- 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ],
- 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ],
- 'use_transports_iconsets': [ opt_bool, True, '', True ],
- 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ],
- 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ],
- 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ],
- 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ],
- 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ],
- 'markedmsgcolor': [ opt_color, '#ff8080', '', True ],
- 'urlmsgcolor': [ opt_color, '#204a87', '', True ],
- 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ],
- 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ],
- 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ],
- 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ],
- 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ],
- 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ],
- 'roster_theme': [ opt_str, _('default'), '', True ],
- 'mergeaccounts': [ opt_bool, False, '', True ],
- 'sort_by_show_in_roster': [ opt_bool, True, '', True ],
- 'sort_by_show_in_muc': [ opt_bool, False, '', True ],
- 'use_speller': [ opt_bool, False, ],
- 'ignore_incoming_xhtml': [ opt_bool, False, ],
- 'speller_language': [ opt_str, '', _('Language used by speller')],
- 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
- 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ],
- 'emoticons_theme': [opt_str, 'static', '', True ],
- 'ascii_formatting': [ opt_bool, True,
- _('Treat * / _ pairs as possible formatting characters.'), True],
- 'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not '
- 'remove */_ . So *abc* will be bold but with * * not removed.')],
- 'rst_formatting_outgoing_messages': [ opt_bool, False,
- _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')],
- 'sounds_on': [ opt_bool, True ],
- # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
- 'soundplayer': [ opt_str, '' ],
- 'openwith': [ opt_str, DEFAULT_OPENWITH ],
- 'custombrowser': [ opt_str, DEFAULT_BROWSER ],
- 'custommailapp': [ opt_str, DEFAULT_MAILAPP ],
- 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ],
- 'gc-hpaned-position': [opt_int, 430],
- 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')],
- 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')],
- 'msgwin-max-state': [opt_bool, False],
- 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'msgwin-width': [opt_int, 500],
- 'msgwin-height': [opt_int, 440],
- 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'chat-msgwin-width': [opt_int, 480],
- 'chat-msgwin-height': [opt_int, 440],
- 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'gc-msgwin-width': [opt_int, 600],
- 'gc-msgwin-height': [opt_int, 440],
- 'single-msg-x-position': [opt_int, 0],
- 'single-msg-y-position': [opt_int, 0],
- 'single-msg-width': [opt_int, 400],
- 'single-msg-height': [opt_int, 280],
- 'roster_x-position': [ opt_int, 0 ],
- 'roster_y-position': [ opt_int, 0 ],
- 'roster_width': [ opt_int, 200 ],
- 'roster_height': [ opt_int, 400 ],
- 'history_window_width': [ opt_int, 650 ],
- 'history_window_height': [ opt_int, 450 ],
- 'history_window_x-position': [ opt_int, 0 ],
- 'history_window_y-position': [ opt_int, 0 ],
- 'latest_disco_addresses': [ opt_str, '' ],
- 'recently_groupchat': [ opt_str, '' ],
- 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ],
- 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ],
- 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ],
- 'notify_on_new_gmail_email': [ opt_bool, True ],
- 'notify_on_new_gmail_email_extra': [ opt_bool, False ],
- 'use_gpg_agent': [ opt_bool, False ],
- 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')],
- 'restore_lines': [opt_int, 4, _('How many lines to remember from previous conversation when a chat tab/window is reopened.')],
- 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')],
- 'muc_restore_lines': [opt_int, 20, _('How many lines to request to server when entering a groupchat.')],
- 'muc_restore_timeout': [opt_int, 60, _('How many minutes back to request logs when a entering a groupchat.')],
- 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')],
- 'muc_autorejoin_on_kick': [opt_bool, False, 'Should autorejoin be activated when we are being kicked from a conference?'],
- 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')],
- 'show_roster_on_startup': [opt_bool, True],
- 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
- 'version': [ opt_str, defs.version ], # which version created the config
- 'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'],
- 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")],
- 'always_english_wikipedia': [opt_bool, False],
- 'always_english_wiktionary': [opt_bool, True],
- 'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
- 'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True],
- 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')],
- 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')],
- 'autodetect_browser_mailer': [opt_bool, False, '', True],
- 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
- 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
- 'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')],
- 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')],
- 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are control that can loose data (chat, private chat, groupchat that will not be minimized)')],
- 'notify_on_file_complete': [opt_bool, True],
- 'file_transfers_port': [opt_int, 28011],
- 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')],
- 'conversation_font': [opt_str, ''],
- 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')],
- 'notify_on_all_muc_messages': [opt_bool, False],
- 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')],
- 'last_save_dir': [opt_str, ''],
- 'last_send_dir': [opt_str, ''],
- 'last_emoticons_dir': [opt_str, ''],
- 'last_sounds_dir': [opt_str, ''],
- 'tabs_position': [opt_str, 'top'],
- 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')],
- 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
- 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
- 'esession_modp': [opt_str, '5,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')],
- 'chat_avatar_width': [opt_int, 52],
- 'chat_avatar_height': [opt_int, 52],
- 'roster_avatar_width': [opt_int, 32],
- 'roster_avatar_height': [opt_int, 32],
- 'tooltip_avatar_width': [opt_int, 125],
- 'tooltip_avatar_height': [opt_int, 125],
- 'vcard_avatar_width': [opt_int, 200],
- 'vcard_avatar_height': [opt_int, 200],
- 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')],
- 'notification_position_x': [opt_int, -1],
- 'notification_position_y': [opt_int, -1],
- 'notification_avatar_width': [opt_int, 48],
- 'notification_avatar_height': [opt_int, 48],
- 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')],
- 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')],
- 'check_if_gajim_is_default': [opt_bool, True, _('If True, Gajim will check if it\'s the default jabber client on each startup.')],
- 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
- 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True],
- 'show_avatars_in_roster': [opt_bool, True, '', True],
- 'show_mood_in_roster': [opt_bool, True, '', True],
- 'show_activity_in_roster': [opt_bool, True, '', True],
- 'show_tunes_in_roster': [opt_bool, True, '', True],
- 'show_location_in_roster': [opt_bool, True, '', True],
- 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True],
- 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
- 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
- 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')],
- 'log_contact_status_changes': [opt_bool, False],
- 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')],
- 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')],
- 'restored_messages_color': [opt_color, '#555753'],
- 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')],
- 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')],
- 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')],
- 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')],
- 'notification_timeout': [opt_int, 5],
- 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')],
- 'one_message_window': [opt_str, 'always',
+ DEFAULT_ICONSET = 'dcraven'
+ DEFAULT_MOOD_ICONSET = 'default'
+ DEFAULT_ACTIVITY_ICONSET = 'default'
+ DEFAULT_OPENWITH = 'gnome-open'
+ DEFAULT_BROWSER = 'firefox'
+ DEFAULT_MAILAPP = 'mozilla-thunderbird -compose'
+ DEFAULT_FILE_MANAGER = 'xffm'
+
+ __options = {
+ # name: [ type, default_value, help_string ]
+ 'verbose': [ opt_bool, False, '', True ],
+ 'autopopup': [ opt_bool, False ],
+ 'notify_on_signin': [ opt_bool, True ],
+ 'notify_on_signout': [ opt_bool, False ],
+ 'notify_on_new_message': [ opt_bool, True ],
+ 'autopopupaway': [ opt_bool, False ],
+ 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')],
+ 'use_notif_daemon': [ opt_bool, True , _('Use D-Bus and Notification-Daemon to show notifications') ],
+ 'showoffline': [ opt_bool, False ],
+ 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')],
+ 'show_transports_group': [ opt_bool, True ],
+ 'autoaway': [ opt_bool, True ],
+ 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ],
+ 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoaway time.') ],
+ 'autoxa': [ opt_bool, True ],
+ 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ],
+ 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxa time.') ],
+ 'ask_online_status': [ opt_bool, False ],
+ 'ask_offline_status': [ opt_bool, False ],
+ 'trayicon': [opt_str, 'always', _("When to show systray icon. Can be 'never', 'on_event', 'always'."), True],
+ 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ],
+ 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ],
+ 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ],
+ 'use_transports_iconsets': [ opt_bool, True, '', True ],
+ 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ],
+ 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ],
+ 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ],
+ 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ],
+ 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ],
+ 'markedmsgcolor': [ opt_color, '#ff8080', '', True ],
+ 'urlmsgcolor': [ opt_color, '#204a87', '', True ],
+ 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ],
+ 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ],
+ 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ],
+ 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ],
+ 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ],
+ 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ],
+ 'roster_theme': [ opt_str, _('default'), '', True ],
+ 'mergeaccounts': [ opt_bool, False, '', True ],
+ 'sort_by_show_in_roster': [ opt_bool, True, '', True ],
+ 'sort_by_show_in_muc': [ opt_bool, False, '', True ],
+ 'use_speller': [ opt_bool, False, ],
+ 'ignore_incoming_xhtml': [ opt_bool, False, ],
+ 'speller_language': [ opt_str, '', _('Language used by speller')],
+ 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
+ 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ],
+ 'emoticons_theme': [opt_str, 'static', '', True ],
+ 'ascii_formatting': [ opt_bool, True,
+ _('Treat * / _ pairs as possible formatting characters.'), True],
+ 'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not '
+ 'remove */_ . So *abc* will be bold but with * * not removed.')],
+ 'rst_formatting_outgoing_messages': [ opt_bool, False,
+ _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')],
+ 'sounds_on': [ opt_bool, True ],
+ # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
+ 'soundplayer': [ opt_str, '' ],
+ 'openwith': [ opt_str, DEFAULT_OPENWITH ],
+ 'custombrowser': [ opt_str, DEFAULT_BROWSER ],
+ 'custommailapp': [ opt_str, DEFAULT_MAILAPP ],
+ 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ],
+ 'gc-hpaned-position': [opt_int, 430],
+ 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')],
+ 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')],
+ 'msgwin-max-state': [opt_bool, False],
+ 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
+ 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
+ 'msgwin-width': [opt_int, 500],
+ 'msgwin-height': [opt_int, 440],
+ 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
+ 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
+ 'chat-msgwin-width': [opt_int, 480],
+ 'chat-msgwin-height': [opt_int, 440],
+ 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
+ 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
+ 'gc-msgwin-width': [opt_int, 600],
+ 'gc-msgwin-height': [opt_int, 440],
+ 'single-msg-x-position': [opt_int, 0],
+ 'single-msg-y-position': [opt_int, 0],
+ 'single-msg-width': [opt_int, 400],
+ 'single-msg-height': [opt_int, 280],
+ 'roster_x-position': [ opt_int, 0 ],
+ 'roster_y-position': [ opt_int, 0 ],
+ 'roster_width': [ opt_int, 200 ],
+ 'roster_height': [ opt_int, 400 ],
+ 'history_window_width': [ opt_int, 650 ],
+ 'history_window_height': [ opt_int, 450 ],
+ 'history_window_x-position': [ opt_int, 0 ],
+ 'history_window_y-position': [ opt_int, 0 ],
+ 'latest_disco_addresses': [ opt_str, '' ],
+ 'recently_groupchat': [ opt_str, '' ],
+ 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ],
+ 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ],
+ 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ],
+ 'notify_on_new_gmail_email': [ opt_bool, True ],
+ 'notify_on_new_gmail_email_extra': [ opt_bool, False ],
+ 'use_gpg_agent': [ opt_bool, False ],
+ 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')],
+ 'restore_lines': [opt_int, 4, _('How many lines to remember from previous conversation when a chat tab/window is reopened.')],
+ 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')],
+ 'muc_restore_lines': [opt_int, 20, _('How many lines to request to server when entering a groupchat.')],
+ 'muc_restore_timeout': [opt_int, 60, _('How many minutes back to request logs when a entering a groupchat.')],
+ 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')],
+ 'muc_autorejoin_on_kick': [opt_bool, False, 'Should autorejoin be activated when we are being kicked from a conference?'],
+ 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')],
+ 'show_roster_on_startup': [opt_bool, True],
+ 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
+ 'version': [ opt_str, defs.version ], # which version created the config
+ 'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'],
+ 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")],
+ 'always_english_wikipedia': [opt_bool, False],
+ 'always_english_wiktionary': [opt_bool, True],
+ 'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
+ 'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True],
+ 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')],
+ 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')],
+ 'autodetect_browser_mailer': [opt_bool, False, '', True],
+ 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
+ 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
+ 'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')],
+ 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')],
+ 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are control that can loose data (chat, private chat, groupchat that will not be minimized)')],
+ 'notify_on_file_complete': [opt_bool, True],
+ 'file_transfers_port': [opt_int, 28011],
+ 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')],
+ 'conversation_font': [opt_str, ''],
+ 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')],
+ 'notify_on_all_muc_messages': [opt_bool, False],
+ 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')],
+ 'last_save_dir': [opt_str, ''],
+ 'last_send_dir': [opt_str, ''],
+ 'last_emoticons_dir': [opt_str, ''],
+ 'last_sounds_dir': [opt_str, ''],
+ 'tabs_position': [opt_str, 'top'],
+ 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')],
+ 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
+ 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
+ 'esession_modp': [opt_str, '5,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')],
+ 'chat_avatar_width': [opt_int, 52],
+ 'chat_avatar_height': [opt_int, 52],
+ 'roster_avatar_width': [opt_int, 32],
+ 'roster_avatar_height': [opt_int, 32],
+ 'tooltip_avatar_width': [opt_int, 125],
+ 'tooltip_avatar_height': [opt_int, 125],
+ 'vcard_avatar_width': [opt_int, 200],
+ 'vcard_avatar_height': [opt_int, 200],
+ 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')],
+ 'notification_position_x': [opt_int, -1],
+ 'notification_position_y': [opt_int, -1],
+ 'notification_avatar_width': [opt_int, 48],
+ 'notification_avatar_height': [opt_int, 48],
+ 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')],
+ 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if trayicon is used.')],
+ 'check_if_gajim_is_default': [opt_bool, True, _('If True, Gajim will check if it\'s the default jabber client on each startup.')],
+ 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
+ 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True],
+ 'show_avatars_in_roster': [opt_bool, True, '', True],
+ 'show_mood_in_roster': [opt_bool, True, '', True],
+ 'show_activity_in_roster': [opt_bool, True, '', True],
+ 'show_tunes_in_roster': [opt_bool, True, '', True],
+ 'show_location_in_roster': [opt_bool, True, '', True],
+ 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True],
+ 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
+ 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
+ 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')],
+ 'log_contact_status_changes': [opt_bool, False],
+ 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')],
+ 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')],
+ 'restored_messages_color': [opt_color, '#555753'],
+ 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')],
+ 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')],
+ 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')],
+ 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')],
+ 'notification_timeout': [opt_int, 5],
+ 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')],
+ 'one_message_window': [opt_str, 'always',
#always, never, peracct, pertype should not be translated
- _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window.')],
- 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
- 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
- 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')],
- 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')],
- 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')],
- 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
- 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
- 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')],
- 'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')],
- 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ],
- 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
- 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
- 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')],
- 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')],
- 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')],
- 'use_gnomekeyring': [opt_bool, True, _('If True, Gajim will use Gnome Keyring (if available) to store account passwords.')],
- 'use_kwalletcli': [opt_bool, True, _('If True, Gajim will use KDE Wallet (if kwalletcli is available) to store account passwords.')],
- 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')],
- 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')],
- 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')],
- 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')],
- 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')],
- 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')],
- 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')],
- 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
- 'latex_png_dpi': [opt_str, '108',_('Change the value to change the size of latex formulas displayed. The higher is larger.') ],
- 'uri_schemes': [opt_str, 'aaa aaas acap cap cid crid data dav dict dns fax file ftp go gopher h323 http https icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mid modem msrp msrps mtqp mupdate news nfs nntp opaquelocktoken pop pres rtsp service shttp sip sips snmp soap.beep soap.beeps tag tel telnet tftp thismessage tip tv urn vemmi xmlrpc.beep xmlrpc.beeps z39.50r z39.50s about cvs daap ed2k feed fish git iax2 irc ircs ldaps magnet mms rsync ssh svn sftp smb webcal', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True],
- 'ask_offline_status_on_connection': [ opt_bool, False, _('Ask offline status message to all offline contacts when connection to an accoutn is established. WARNING: This causes a lot of requests to be sent!') ],
- 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')],
- 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True],
- 'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
- 'audio_output_device': [opt_str, 'autoaudiosink'],
- 'video_input_device': [opt_str, 'autovideosrc ! videoscale ! ffmpegcolorspace'],
- 'video_output_device': [opt_str, 'autovideosink'],
- 'audio_input_volume': [opt_int, 50],
- 'audio_output_volume': [opt_int, 50],
- 'use_stun_server': [opt_bool, True, _('If True, Gajim will try to use a STUN server when using jingle. The one in "stun_server" option, or the one given by the jabber server.')],
- 'stun_server': [opt_str, '', _('STUN server to use when using jingle')],
- 'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')],
- }
-
- __options_per_key = {
- 'accounts': ({
- 'name': [ opt_str, '', '', True ],
- 'hostname': [ opt_str, '', '', True ],
- 'anonymous_auth': [ opt_bool, False ],
- 'savepass': [ opt_bool, False ],
- 'password': [ opt_str, '' ],
- 'resource': [ opt_str, 'gajim', '', True ],
- 'priority': [ opt_int, 5, '', True ],
- 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ],
- 'autopriority_online': [ opt_int, 50],
- 'autopriority_chat': [ opt_int, 50],
- 'autopriority_away': [ opt_int, 40],
- 'autopriority_xa': [ opt_int, 30],
- 'autopriority_dnd': [ opt_int, 20],
- 'autopriority_invisible': [ opt_int, 10],
- 'autoconnect': [ opt_bool, False, '', True ],
- 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ],
- 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ],
- 'autoreconnect': [ opt_bool, True ],
- 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')],
- 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True],
- 'proxy': [ opt_str, '', '', True ],
- 'keyid': [ opt_str, '', '', True ],
- 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
- 'keyname': [ opt_str, '', '', True ],
- 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.')],
- 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session when possible?')],
- 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')],
- 'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ],
- 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ],
- 'ssl_fingerprint_sha1': [ opt_str, '', '', True ],
- 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ],
- 'use_srv': [ opt_bool, True, '', True ],
- 'use_custom_host': [ opt_bool, False, '', True ],
- 'custom_port': [ opt_int, 5222, '', True ],
- 'custom_host': [ opt_str, '', '', True ],
- 'sync_with_global_status': [ opt_bool, False, ],
- 'no_log_for': [ opt_str, '' ],
- 'minimized_gc': [ opt_str, '' ],
- 'attached_gpg_keys': [ opt_str, '' ],
- 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')],
- 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')],
- # send keepalive every N seconds of inactivity
- 'keep_alive_every_foo_secs': [ opt_int, 55 ],
- 'ping_alive_every_foo_secs': [ opt_int, 120 ],
- 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect.') ],
- # try for 1 minutes before giving up (aka. timeout after those seconds)
- 'try_connecting_for_foo_secs': [ opt_int, 60 ],
- 'http_auth': [opt_str, 'ask'], # yes, no, ask
- 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')],
- # proxy65 for FT
- 'file_transfer_proxies': [opt_str, 'proxy.eu.jabber.org, proxy.jabber.ru, proxy.jabbim.cz'],
- 'use_ft_proxies': [opt_bool, True, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True],
- 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide
- 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
- 'msgwin-width': [opt_int, 480],
- 'msgwin-height': [opt_int, 440],
- 'listen_to_network_manager' : [opt_bool, True],
- 'is_zeroconf': [opt_bool, False],
- 'last_status': [opt_str, 'online'],
- 'last_status_msg': [opt_str, ''],
- 'zeroconf_first_name': [ opt_str, '', '', True ],
- 'zeroconf_last_name': [ opt_str, '', '', True ],
- 'zeroconf_jabber_id': [ opt_str, '', '', True ],
- 'zeroconf_email': [ opt_str, '', '', True ],
- 'use_env_http_proxy' : [opt_bool, False],
- 'answer_receipts' : [opt_bool, True, _('Answer to receipt requests')],
- 'request_receipt' : [opt_bool, True, _('Sent receipt requests')],
- 'publish_tune': [opt_bool, False],
- 'publish_location': [opt_bool, False],
- 'subscribe_mood': [opt_bool, True],
- 'subscribe_activity': [opt_bool, True],
- 'subscribe_tune': [opt_bool, True],
- 'subscribe_nick': [opt_bool, True],
- 'subscribe_location': [opt_bool, True],
- 'ignore_unknown_contacts': [ opt_bool, False ],
- 'send_os_info': [ opt_bool, True ],
- 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')],
- 'send_idle_time': [ opt_bool, True ],
- 'roster_version': [opt_str, ''],
- 'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
- }, {}),
- 'statusmsg': ({
- 'message': [ opt_str, '' ],
- 'activity': [ opt_str, '' ],
- 'subactivity': [ opt_str, '' ],
- 'activity_text': [ opt_str, '' ],
- 'mood': [ opt_str, '' ],
- 'mood_text': [ opt_str, '' ],
- }, {}),
- 'defaultstatusmsg': ({
- 'enabled': [ opt_bool, False ],
- 'message': [ opt_str, '' ],
- }, {}),
- 'soundevents': ({
- 'enabled': [ opt_bool, True ],
- 'path': [ opt_str, '' ],
- }, {}),
- 'proxies': ({
- 'type': [ opt_str, 'http' ],
- 'host': [ opt_str, '' ],
- 'port': [ opt_int, 3128 ],
- 'useauth': [ opt_bool, False ],
- 'user': [ opt_str, '' ],
- 'pass': [ opt_str, '' ],
- 'bosh_uri': [ opt_str, '' ],
- 'bosh_useproxy': [ opt_bool, False ],
- 'bosh_wait': [ opt_int, 30 ],
- 'bosh_hold': [ opt_int, 2 ],
- 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ],
- 'bosh_http_pipelining': [ opt_bool, False ],
- 'bosh_wait_for_restart_response': [ opt_bool, False ],
- }, {}),
- 'themes': ({
- 'accounttextcolor': [ opt_color, 'black', '', True ],
- 'accountbgcolor': [ opt_color, 'white', '', True ],
- 'accountfont': [ opt_str, '', '', True ],
- 'accountfontattrs': [ opt_str, 'B', '', True ],
- 'grouptextcolor': [ opt_color, 'black', '', True ],
- 'groupbgcolor': [ opt_color, 'white', '', True ],
- 'groupfont': [ opt_str, '', '', True ],
- 'groupfontattrs': [ opt_str, 'I', '', True ],
- 'contacttextcolor': [ opt_color, 'black', '', True ],
- 'contactbgcolor': [ opt_color, 'white', '', True ],
- 'contactfont': [ opt_str, '', '', True ],
- 'contactfontattrs': [ opt_str, '', '', True ],
- 'bannertextcolor': [ opt_color, 'black', '', True ],
- 'bannerbgcolor': [ opt_color, '', '', True ],
- 'bannerfont': [ opt_str, '', '', True ],
- 'bannerfontattrs': [ opt_str, 'B', '', True ],
-
- # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html
- 'state_inactive_color': [ opt_color, 'grey62' ],
- 'state_composing_color': [ opt_color, 'green4' ],
- 'state_paused_color': [ opt_color, 'mediumblue' ],
- 'state_gone_color': [ opt_color, 'grey' ],
-
- # MUC chat states
- 'state_muc_msg_color': [ opt_color, 'mediumblue' ],
- 'state_muc_directed_msg_color': [ opt_color, 'red2' ],
- }, {}),
- 'contacts': ({
- 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')],
- 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session with this contact when possible?')],
- 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
- }, {}),
- 'rooms': ({
- 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
- }, {}),
- 'notifications': ({
- 'event': [opt_str, ''],
- 'recipient_type': [opt_str, 'all'],
- 'recipients': [opt_str, ''],
- 'status': [opt_str, 'all', _('all or space separated status')],
- 'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")],
- 'sound': [opt_str, '', _("'yes', 'no' or ''")],
- 'sound_file': [opt_str, ''],
- 'popup': [opt_str, '', _("'yes', 'no' or ''")],
- 'auto_open': [opt_str, '', _("'yes', 'no' or ''")],
- 'run_command': [opt_bool, False],
- 'command': [opt_str, ''],
- 'systray': [opt_str, '', _("'yes', 'no' or ''")],
- 'roster': [opt_str, '', _("'yes', 'no' or ''")],
- 'urgency_hint': [opt_bool, False],
- }, {}),
- }
-
- statusmsg_default = {
- _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ],
- _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ],
- _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ],
- _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ],
- _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ],
- _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ],
- _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ],
- '_last_online': ['', '', '', '', '', ''],
- '_last_chat': ['', '', '', '', '', ''],
- '_last_away': ['', '', '', '', '', ''],
- '_last_xa': ['', '', '', '', '', ''],
- '_last_dnd': ['', '', '', '', '', ''],
- '_last_invisible': ['', '', '', '', '', ''],
- '_last_offline': ['', '', '', '', '', ''],
- }
-
- defaultstatusmsg_default = {
- 'online': [ False, _("I'm available.") ],
- 'chat': [ False, _("I'm free for chat.") ],
- 'away': [ False, _('Be right back.') ],
- 'xa': [ False, _("I'm not available.") ],
- 'dnd': [ False, _('Do not disturb.') ],
- 'invisible': [ False, _('Bye!') ],
- 'offline': [ False, _('Bye!') ],
- }
-
- soundevents_default = {
- 'first_message_received': [ True, 'message1.wav' ],
- 'next_message_received_focused': [ True, 'message2.wav' ],
- 'next_message_received_unfocused': [ True, 'message2.wav' ],
- 'contact_connected': [ True, 'connected.wav' ],
- 'contact_disconnected': [ True, 'disconnected.wav' ],
- 'message_sent': [ True, 'sent.wav' ],
- 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')],
- 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ],
- 'gmail_received': [ False, 'message1.wav' ],
- }
-
- themes_default = {
- # sorted alphanum
- _('default'): [ '', '', '', 'B', '', '','', 'I', '', '', '', '', '','',
- '', 'B' ],
-
- _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7',
- '', 'I', '#000000', '', '', '', '',
- '#94aa8c', '', 'B' ],
-
- _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad',
- '', 'I', '#000000', '#efb26b', '', '', '',
- '#108abd', '', 'B' ],
-
- _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94',
- '', 'I', '#000000', '', '', '', '',
- '#996442', '', 'B' ],
-
- _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3',
- '', 'I', '#000000', '', '', '', '',
- '#918caa', '', 'B' ],
-
- }
-
- def foreach(self, cb, data = None):
- for opt in self.__options:
- cb(data, opt, None, self.__options[opt])
- for opt in self.__options_per_key:
- cb(data, opt, None, None)
- dict_ = self.__options_per_key[opt][1]
- for opt2 in dict_.keys():
- cb(data, opt2, [opt], None)
- for opt3 in dict_[opt2]:
- cb(data, opt3, [opt, opt2], dict_[opt2][opt3])
-
- def get_children(self, node=None):
- """
- Tree-like interface
- """
- if node is None:
- for child, option in self.__options.iteritems():
- yield (child, ), option
- for grandparent in self.__options_per_key:
- yield (grandparent, ), None
- elif len(node) == 1:
- grandparent, = node
- for parent in self.__options_per_key[grandparent][1]:
- yield (grandparent, parent), None
- elif len(node) == 2:
- grandparent, parent = node
- children = self.__options_per_key[grandparent][1][parent]
- for child, option in children.iteritems():
- yield (grandparent, parent, child), option
- else:
- raise ValueError('Invalid node')
-
- def is_valid_int(self, val):
- try:
- ival = int(val)
- except Exception:
- return None
- return ival
-
- def is_valid_bool(self, val):
- if val == 'True':
- return True
- elif val == 'False':
- return False
- else:
- ival = self.is_valid_int(val)
- if ival:
- return True
- elif ival is None:
- return None
- return False
- return None
-
- def is_valid_string(self, val):
- return val
-
- def is_valid(self, type_, val):
- if not type_:
- return None
- if type_[0] == 'boolean':
- return self.is_valid_bool(val)
- elif type_[0] == 'integer':
- return self.is_valid_int(val)
- elif type_[0] == 'string':
- return self.is_valid_string(val)
- else:
- if re.match(type_[1], val):
- return val
- else:
- return None
-
- def set(self, optname, value):
- if optname not in self.__options:
-# raise RuntimeError, 'option %s does not exist' % optname
- return
- opt = self.__options[optname]
- value = self.is_valid(opt[OPT_TYPE], value)
- if value is None:
-# raise RuntimeError, 'value of %s cannot be None' % optname
- return
-
- opt[OPT_VAL] = value
-
- def get(self, optname = None):
- if not optname:
- return self.__options.keys()
- if optname not in self.__options:
- return None
- return self.__options[optname][OPT_VAL]
-
- def get_desc(self, optname):
- if optname not in self.__options:
- return None
- if len(self.__options[optname]) > OPT_DESC:
- return self.__options[optname][OPT_DESC]
-
- def get_restart(self, optname):
- if optname not in self.__options:
- return None
- if len(self.__options[optname]) > OPT_RESTART:
- return self.__options[optname][OPT_RESTART]
-
- def add_per(self, typename, name): # per_group_of_option
- if typename not in self.__options_per_key:
-# raise RuntimeError, 'option %s does not exist' % typename
- return
-
- opt = self.__options_per_key[typename]
- if name in opt[1]:
- # we already have added group name before
- return 'you already have added %s before' % name
- opt[1][name] = copy.deepcopy(opt[0])
-
- def del_per(self, typename, name, subname = None): # per_group_of_option
- if typename not in self.__options_per_key:
-# raise RuntimeError, 'option %s does not exist' % typename
- return
-
- opt = self.__options_per_key[typename]
- if subname is None:
- del opt[1][name]
- # if subname is specified, delete the item in the group.
- elif subname in opt[1][name]:
- del opt[1][name][subname]
-
- def set_per(self, optname, key, subname, value): # per_group_of_option
- if optname not in self.__options_per_key:
-# raise RuntimeError, 'option %s does not exist' % optname
- return
- if not key:
- return
- dict_ = self.__options_per_key[optname][1]
- if key not in dict_:
-# raise RuntimeError, '%s is not a key of %s' % (key, dict_)
- self.add_per(optname, key)
- obj = dict_[key]
- if subname not in obj:
-# raise RuntimeError, '%s is not a key of %s' % (subname, obj)
- return
- subobj = obj[subname]
- value = self.is_valid(subobj[OPT_TYPE], value)
- if value is None:
-# raise RuntimeError, '%s of %s cannot be None' % optname
- return
- subobj[OPT_VAL] = value
-
- def get_per(self, optname, key = None, subname = None): # per_group_of_option
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][1]
- if not key:
- return dict_.keys()
- if key not in dict_:
- if optname in self.__options_per_key \
- and subname in self.__options_per_key[optname][0]:
- return self.__options_per_key \
- [optname][0][subname][1]
- return None
- obj = dict_[key]
- if not subname:
- return obj
- if subname not in obj:
- return None
- return obj[subname][OPT_VAL]
-
- def get_desc_per(self, optname, key = None, subname = None):
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][1]
- if not key:
- return None
- if key not in dict_:
- return None
- obj = dict_[key]
- if not subname:
- return None
- if subname not in obj:
- return None
- if len(obj[subname]) > OPT_DESC:
- return obj[subname][OPT_DESC]
- return None
-
- def get_restart_per(self, optname, key = None, subname = None):
- if optname not in self.__options_per_key:
- return False
- dict_ = self.__options_per_key[optname][1]
- if not key:
- return False
- if key not in dict_:
- return False
- obj = dict_[key]
- if not subname:
- return False
- if subname not in obj:
- return False
- if len(obj[subname]) > OPT_RESTART:
- return obj[subname][OPT_RESTART]
- return False
-
- def should_log(self, account, jid):
- """
- Should conversations between a local account and a remote jid be logged?
- """
- no_log_for = self.get_per('accounts', account, 'no_log_for')
-
- if not no_log_for:
- no_log_for = ''
-
- no_log_for = no_log_for.split()
-
- return (account not in no_log_for) and (jid not in no_log_for)
-
- def __init__(self):
- #init default values
- for event in self.soundevents_default:
- default = self.soundevents_default[event]
- self.add_per('soundevents', event)
- self.set_per('soundevents', event, 'enabled', default[0])
- self.set_per('soundevents', event, 'path', default[1])
-
- for status in self.defaultstatusmsg_default:
- default = self.defaultstatusmsg_default[status]
- self.add_per('defaultstatusmsg', status)
- self.set_per('defaultstatusmsg', status, 'enabled', default[0])
- self.set_per('defaultstatusmsg', status, 'message', default[1])
-
-# vim: se ts=3:
+ _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window.')],
+ 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
+ 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
+ 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')],
+ 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')],
+ 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')],
+ 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
+ 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
+ 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')],
+ 'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')],
+ 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ],
+ 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
+ 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
+ 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')],
+ 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')],
+ 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')],
+ 'use_gnomekeyring': [opt_bool, True, _('If True, Gajim will use Gnome Keyring (if available) to store account passwords.')],
+ 'use_kwalletcli': [opt_bool, True, _('If True, Gajim will use KDE Wallet (if kwalletcli is available) to store account passwords.')],
+ 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')],
+ 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')],
+ 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')],
+ 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')],
+ 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')],
+ 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')],
+ 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')],
+ 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
+ 'latex_png_dpi': [opt_str, '108',_('Change the value to change the size of latex formulas displayed. The higher is larger.') ],
+ 'uri_schemes': [opt_str, 'aaa aaas acap cap cid crid data dav dict dns fax file ftp go gopher h323 http https icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mid modem msrp msrps mtqp mupdate news nfs nntp opaquelocktoken pop pres rtsp service shttp sip sips snmp soap.beep soap.beeps tag tel telnet tftp thismessage tip tv urn vemmi xmlrpc.beep xmlrpc.beeps z39.50r z39.50s about cvs daap ed2k feed fish git iax2 irc ircs ldaps magnet mms rsync ssh svn sftp smb webcal', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True],
+ 'ask_offline_status_on_connection': [ opt_bool, False, _('Ask offline status message to all offline contacts when connection to an accoutn is established. WARNING: This causes a lot of requests to be sent!') ],
+ 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')],
+ 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True],
+ 'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
+ 'audio_output_device': [opt_str, 'autoaudiosink'],
+ 'video_input_device': [opt_str, 'autovideosrc ! videoscale ! ffmpegcolorspace'],
+ 'video_output_device': [opt_str, 'autovideosink'],
+ 'audio_input_volume': [opt_int, 50],
+ 'audio_output_volume': [opt_int, 50],
+ 'use_stun_server': [opt_bool, True, _('If True, Gajim will try to use a STUN server when using jingle. The one in "stun_server" option, or the one given by the jabber server.')],
+ 'stun_server': [opt_str, '', _('STUN server to use when using jingle')],
+ 'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')],
+ }
+
+ __options_per_key = {
+ 'accounts': ({
+ 'name': [ opt_str, '', '', True ],
+ 'hostname': [ opt_str, '', '', True ],
+ 'anonymous_auth': [ opt_bool, False ],
+ 'savepass': [ opt_bool, False ],
+ 'password': [ opt_str, '' ],
+ 'resource': [ opt_str, 'gajim', '', True ],
+ 'priority': [ opt_int, 5, '', True ],
+ 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ],
+ 'autopriority_online': [ opt_int, 50],
+ 'autopriority_chat': [ opt_int, 50],
+ 'autopriority_away': [ opt_int, 40],
+ 'autopriority_xa': [ opt_int, 30],
+ 'autopriority_dnd': [ opt_int, 20],
+ 'autopriority_invisible': [ opt_int, 10],
+ 'autoconnect': [ opt_bool, False, '', True ],
+ 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ],
+ 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ],
+ 'autoreconnect': [ opt_bool, True ],
+ 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')],
+ 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True],
+ 'proxy': [ opt_str, '', '', True ],
+ 'keyid': [ opt_str, '', '', True ],
+ 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
+ 'keyname': [ opt_str, '', '', True ],
+ 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.')],
+ 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session when possible?')],
+ 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')],
+ 'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ],
+ 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ],
+ 'ssl_fingerprint_sha1': [ opt_str, '', '', True ],
+ 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ],
+ 'use_srv': [ opt_bool, True, '', True ],
+ 'use_custom_host': [ opt_bool, False, '', True ],
+ 'custom_port': [ opt_int, 5222, '', True ],
+ 'custom_host': [ opt_str, '', '', True ],
+ 'sync_with_global_status': [ opt_bool, False, ],
+ 'no_log_for': [ opt_str, '' ],
+ 'minimized_gc': [ opt_str, '' ],
+ 'attached_gpg_keys': [ opt_str, '' ],
+ 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')],
+ 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')],
+ # send keepalive every N seconds of inactivity
+ 'keep_alive_every_foo_secs': [ opt_int, 55 ],
+ 'ping_alive_every_foo_secs': [ opt_int, 120 ],
+ 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect.') ],
+ # try for 1 minutes before giving up (aka. timeout after those seconds)
+ 'try_connecting_for_foo_secs': [ opt_int, 60 ],
+ 'http_auth': [opt_str, 'ask'], # yes, no, ask
+ 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')],
+ # proxy65 for FT
+ 'file_transfer_proxies': [opt_str, 'proxy.eu.jabber.org, proxy.jabber.ru, proxy.jabbim.cz'],
+ 'use_ft_proxies': [opt_bool, True, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True],
+ 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide
+ 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
+ 'msgwin-width': [opt_int, 480],
+ 'msgwin-height': [opt_int, 440],
+ 'listen_to_network_manager' : [opt_bool, True],
+ 'is_zeroconf': [opt_bool, False],
+ 'last_status': [opt_str, 'online'],
+ 'last_status_msg': [opt_str, ''],
+ 'zeroconf_first_name': [ opt_str, '', '', True ],
+ 'zeroconf_last_name': [ opt_str, '', '', True ],
+ 'zeroconf_jabber_id': [ opt_str, '', '', True ],
+ 'zeroconf_email': [ opt_str, '', '', True ],
+ 'use_env_http_proxy' : [opt_bool, False],
+ 'answer_receipts' : [opt_bool, True, _('Answer to receipt requests')],
+ 'request_receipt' : [opt_bool, True, _('Sent receipt requests')],
+ 'publish_tune': [opt_bool, False],
+ 'publish_location': [opt_bool, False],
+ 'subscribe_mood': [opt_bool, True],
+ 'subscribe_activity': [opt_bool, True],
+ 'subscribe_tune': [opt_bool, True],
+ 'subscribe_nick': [opt_bool, True],
+ 'subscribe_location': [opt_bool, True],
+ 'ignore_unknown_contacts': [ opt_bool, False ],
+ 'send_os_info': [ opt_bool, True ],
+ 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')],
+ 'send_idle_time': [ opt_bool, True ],
+ 'roster_version': [opt_str, ''],
+ 'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
+ }, {}),
+ 'statusmsg': ({
+ 'message': [ opt_str, '' ],
+ 'activity': [ opt_str, '' ],
+ 'subactivity': [ opt_str, '' ],
+ 'activity_text': [ opt_str, '' ],
+ 'mood': [ opt_str, '' ],
+ 'mood_text': [ opt_str, '' ],
+ }, {}),
+ 'defaultstatusmsg': ({
+ 'enabled': [ opt_bool, False ],
+ 'message': [ opt_str, '' ],
+ }, {}),
+ 'soundevents': ({
+ 'enabled': [ opt_bool, True ],
+ 'path': [ opt_str, '' ],
+ }, {}),
+ 'proxies': ({
+ 'type': [ opt_str, 'http' ],
+ 'host': [ opt_str, '' ],
+ 'port': [ opt_int, 3128 ],
+ 'useauth': [ opt_bool, False ],
+ 'user': [ opt_str, '' ],
+ 'pass': [ opt_str, '' ],
+ 'bosh_uri': [ opt_str, '' ],
+ 'bosh_useproxy': [ opt_bool, False ],
+ 'bosh_wait': [ opt_int, 30 ],
+ 'bosh_hold': [ opt_int, 2 ],
+ 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ],
+ 'bosh_http_pipelining': [ opt_bool, False ],
+ 'bosh_wait_for_restart_response': [ opt_bool, False ],
+ }, {}),
+ 'themes': ({
+ 'accounttextcolor': [ opt_color, 'black', '', True ],
+ 'accountbgcolor': [ opt_color, 'white', '', True ],
+ 'accountfont': [ opt_str, '', '', True ],
+ 'accountfontattrs': [ opt_str, 'B', '', True ],
+ 'grouptextcolor': [ opt_color, 'black', '', True ],
+ 'groupbgcolor': [ opt_color, 'white', '', True ],
+ 'groupfont': [ opt_str, '', '', True ],
+ 'groupfontattrs': [ opt_str, 'I', '', True ],
+ 'contacttextcolor': [ opt_color, 'black', '', True ],
+ 'contactbgcolor': [ opt_color, 'white', '', True ],
+ 'contactfont': [ opt_str, '', '', True ],
+ 'contactfontattrs': [ opt_str, '', '', True ],
+ 'bannertextcolor': [ opt_color, 'black', '', True ],
+ 'bannerbgcolor': [ opt_color, '', '', True ],
+ 'bannerfont': [ opt_str, '', '', True ],
+ 'bannerfontattrs': [ opt_str, 'B', '', True ],
+
+ # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html
+ 'state_inactive_color': [ opt_color, 'grey62' ],
+ 'state_composing_color': [ opt_color, 'green4' ],
+ 'state_paused_color': [ opt_color, 'mediumblue' ],
+ 'state_gone_color': [ opt_color, 'grey' ],
+
+ # MUC chat states
+ 'state_muc_msg_color': [ opt_color, 'mediumblue' ],
+ 'state_muc_directed_msg_color': [ opt_color, 'red2' ],
+ }, {}),
+ 'contacts': ({
+ 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')],
+ 'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session with this contact when possible?')],
+ 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
+ }, {}),
+ 'rooms': ({
+ 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
+ }, {}),
+ 'notifications': ({
+ 'event': [opt_str, ''],
+ 'recipient_type': [opt_str, 'all'],
+ 'recipients': [opt_str, ''],
+ 'status': [opt_str, 'all', _('all or space separated status')],
+ 'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")],
+ 'sound': [opt_str, '', _("'yes', 'no' or ''")],
+ 'sound_file': [opt_str, ''],
+ 'popup': [opt_str, '', _("'yes', 'no' or ''")],
+ 'auto_open': [opt_str, '', _("'yes', 'no' or ''")],
+ 'run_command': [opt_bool, False],
+ 'command': [opt_str, ''],
+ 'systray': [opt_str, '', _("'yes', 'no' or ''")],
+ 'roster': [opt_str, '', _("'yes', 'no' or ''")],
+ 'urgency_hint': [opt_bool, False],
+ }, {}),
+ }
+
+ statusmsg_default = {
+ _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ],
+ _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ],
+ _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ],
+ _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ],
+ _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ],
+ _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ],
+ _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ],
+ '_last_online': ['', '', '', '', '', ''],
+ '_last_chat': ['', '', '', '', '', ''],
+ '_last_away': ['', '', '', '', '', ''],
+ '_last_xa': ['', '', '', '', '', ''],
+ '_last_dnd': ['', '', '', '', '', ''],
+ '_last_invisible': ['', '', '', '', '', ''],
+ '_last_offline': ['', '', '', '', '', ''],
+ }
+
+ defaultstatusmsg_default = {
+ 'online': [ False, _("I'm available.") ],
+ 'chat': [ False, _("I'm free for chat.") ],
+ 'away': [ False, _('Be right back.') ],
+ 'xa': [ False, _("I'm not available.") ],
+ 'dnd': [ False, _('Do not disturb.') ],
+ 'invisible': [ False, _('Bye!') ],
+ 'offline': [ False, _('Bye!') ],
+ }
+
+ soundevents_default = {
+ 'first_message_received': [ True, 'message1.wav' ],
+ 'next_message_received_focused': [ True, 'message2.wav' ],
+ 'next_message_received_unfocused': [ True, 'message2.wav' ],
+ 'contact_connected': [ True, 'connected.wav' ],
+ 'contact_disconnected': [ True, 'disconnected.wav' ],
+ 'message_sent': [ True, 'sent.wav' ],
+ 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')],
+ 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ],
+ 'gmail_received': [ False, 'message1.wav' ],
+ }
+
+ themes_default = {
+ # sorted alphanum
+ _('default'): [ '', '', '', 'B', '', '','', 'I', '', '', '', '', '','',
+ '', 'B' ],
+
+ _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7',
+ '', 'I', '#000000', '', '', '', '',
+ '#94aa8c', '', 'B' ],
+
+ _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad',
+ '', 'I', '#000000', '#efb26b', '', '', '',
+ '#108abd', '', 'B' ],
+
+ _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94',
+ '', 'I', '#000000', '', '', '', '',
+ '#996442', '', 'B' ],
+
+ _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3',
+ '', 'I', '#000000', '', '', '', '',
+ '#918caa', '', 'B' ],
+
+ }
+
+ def foreach(self, cb, data = None):
+ for opt in self.__options:
+ cb(data, opt, None, self.__options[opt])
+ for opt in self.__options_per_key:
+ cb(data, opt, None, None)
+ dict_ = self.__options_per_key[opt][1]
+ for opt2 in dict_.keys():
+ cb(data, opt2, [opt], None)
+ for opt3 in dict_[opt2]:
+ cb(data, opt3, [opt, opt2], dict_[opt2][opt3])
+
+ def get_children(self, node=None):
+ """
+ Tree-like interface
+ """
+ if node is None:
+ for child, option in self.__options.iteritems():
+ yield (child, ), option
+ for grandparent in self.__options_per_key:
+ yield (grandparent, ), None
+ elif len(node) == 1:
+ grandparent, = node
+ for parent in self.__options_per_key[grandparent][1]:
+ yield (grandparent, parent), None
+ elif len(node) == 2:
+ grandparent, parent = node
+ children = self.__options_per_key[grandparent][1][parent]
+ for child, option in children.iteritems():
+ yield (grandparent, parent, child), option
+ else:
+ raise ValueError('Invalid node')
+
+ def is_valid_int(self, val):
+ try:
+ ival = int(val)
+ except Exception:
+ return None
+ return ival
+
+ def is_valid_bool(self, val):
+ if val == 'True':
+ return True
+ elif val == 'False':
+ return False
+ else:
+ ival = self.is_valid_int(val)
+ if ival:
+ return True
+ elif ival is None:
+ return None
+ return False
+ return None
+
+ def is_valid_string(self, val):
+ return val
+
+ def is_valid(self, type_, val):
+ if not type_:
+ return None
+ if type_[0] == 'boolean':
+ return self.is_valid_bool(val)
+ elif type_[0] == 'integer':
+ return self.is_valid_int(val)
+ elif type_[0] == 'string':
+ return self.is_valid_string(val)
+ else:
+ if re.match(type_[1], val):
+ return val
+ else:
+ return None
+
+ def set(self, optname, value):
+ if optname not in self.__options:
+# raise RuntimeError, 'option %s does not exist' % optname
+ return
+ opt = self.__options[optname]
+ value = self.is_valid(opt[OPT_TYPE], value)
+ if value is None:
+# raise RuntimeError, 'value of %s cannot be None' % optname
+ return
+
+ opt[OPT_VAL] = value
+
+ def get(self, optname = None):
+ if not optname:
+ return self.__options.keys()
+ if optname not in self.__options:
+ return None
+ return self.__options[optname][OPT_VAL]
+
+ def get_desc(self, optname):
+ if optname not in self.__options:
+ return None
+ if len(self.__options[optname]) > OPT_DESC:
+ return self.__options[optname][OPT_DESC]
+
+ def get_restart(self, optname):
+ if optname not in self.__options:
+ return None
+ if len(self.__options[optname]) > OPT_RESTART:
+ return self.__options[optname][OPT_RESTART]
+
+ def add_per(self, typename, name): # per_group_of_option
+ if typename not in self.__options_per_key:
+# raise RuntimeError, 'option %s does not exist' % typename
+ return
+
+ opt = self.__options_per_key[typename]
+ if name in opt[1]:
+ # we already have added group name before
+ return 'you already have added %s before' % name
+ opt[1][name] = copy.deepcopy(opt[0])
+
+ def del_per(self, typename, name, subname = None): # per_group_of_option
+ if typename not in self.__options_per_key:
+# raise RuntimeError, 'option %s does not exist' % typename
+ return
+
+ opt = self.__options_per_key[typename]
+ if subname is None:
+ del opt[1][name]
+ # if subname is specified, delete the item in the group.
+ elif subname in opt[1][name]:
+ del opt[1][name][subname]
+
+ def set_per(self, optname, key, subname, value): # per_group_of_option
+ if optname not in self.__options_per_key:
+# raise RuntimeError, 'option %s does not exist' % optname
+ return
+ if not key:
+ return
+ dict_ = self.__options_per_key[optname][1]
+ if key not in dict_:
+# raise RuntimeError, '%s is not a key of %s' % (key, dict_)
+ self.add_per(optname, key)
+ obj = dict_[key]
+ if subname not in obj:
+# raise RuntimeError, '%s is not a key of %s' % (subname, obj)
+ return
+ subobj = obj[subname]
+ value = self.is_valid(subobj[OPT_TYPE], value)
+ if value is None:
+# raise RuntimeError, '%s of %s cannot be None' % optname
+ return
+ subobj[OPT_VAL] = value
+
+ def get_per(self, optname, key = None, subname = None): # per_group_of_option
+ if optname not in self.__options_per_key:
+ return None
+ dict_ = self.__options_per_key[optname][1]
+ if not key:
+ return dict_.keys()
+ if key not in dict_:
+ if optname in self.__options_per_key \
+ and subname in self.__options_per_key[optname][0]:
+ return self.__options_per_key \
+ [optname][0][subname][1]
+ return None
+ obj = dict_[key]
+ if not subname:
+ return obj
+ if subname not in obj:
+ return None
+ return obj[subname][OPT_VAL]
+
+ def get_desc_per(self, optname, key = None, subname = None):
+ if optname not in self.__options_per_key:
+ return None
+ dict_ = self.__options_per_key[optname][1]
+ if not key:
+ return None
+ if key not in dict_:
+ return None
+ obj = dict_[key]
+ if not subname:
+ return None
+ if subname not in obj:
+ return None
+ if len(obj[subname]) > OPT_DESC:
+ return obj[subname][OPT_DESC]
+ return None
+
+ def get_restart_per(self, optname, key = None, subname = None):
+ if optname not in self.__options_per_key:
+ return False
+ dict_ = self.__options_per_key[optname][1]
+ if not key:
+ return False
+ if key not in dict_:
+ return False
+ obj = dict_[key]
+ if not subname:
+ return False
+ if subname not in obj:
+ return False
+ if len(obj[subname]) > OPT_RESTART:
+ return obj[subname][OPT_RESTART]
+ return False
+
+ def should_log(self, account, jid):
+ """
+ Should conversations between a local account and a remote jid be logged?
+ """
+ no_log_for = self.get_per('accounts', account, 'no_log_for')
+
+ if not no_log_for:
+ no_log_for = ''
+
+ no_log_for = no_log_for.split()
+
+ return (account not in no_log_for) and (jid not in no_log_for)
+
+ def __init__(self):
+ #init default values
+ for event in self.soundevents_default:
+ default = self.soundevents_default[event]
+ self.add_per('soundevents', event)
+ self.set_per('soundevents', event, 'enabled', default[0])
+ self.set_per('soundevents', event, 'path', default[1])
+
+ for status in self.defaultstatusmsg_default:
+ default = self.defaultstatusmsg_default[status]
+ self.add_per('defaultstatusmsg', status)
+ self.set_per('defaultstatusmsg', status, 'enabled', default[0])
+ self.set_per('defaultstatusmsg', status, 'message', default[1])
diff --git a/src/common/configpaths.py b/src/common/configpaths.py
index 00bdb427d..7e8570e33 100644
--- a/src/common/configpaths.py
+++ b/src/common/configpaths.py
@@ -28,9 +28,9 @@ import tempfile
import defs
HAVE_XDG = True
try:
- import xdg.BaseDirectory
+ import xdg.BaseDirectory
except:
- HAVE_XDG = False
+ HAVE_XDG = False
(
TYPE_CONFIG,
@@ -58,130 +58,128 @@ TYPE_DATA
# not displayed to the user, Unicode is not really necessary here.
def fse(s):
- """
- Convert from filesystem encoding if not already Unicode
- """
- return unicode(s, sys.getfilesystemencoding())
+ """
+ Convert from filesystem encoding if not already Unicode
+ """
+ return unicode(s, sys.getfilesystemencoding())
def windowsify(s):
- if os.name == 'nt':
- return s.capitalize()
- return s
+ if os.name == 'nt':
+ return s.capitalize()
+ return s
class ConfigPaths:
- def __init__(self):
- # {'name': (type, path), } type can be TYPE_CONFIG, TYPE_CACHE, TYPE_DATA
- # or None
- self.paths = {}
-
- if os.name == 'nt':
- try:
- # Documents and Settings\[User Name]\Application Data\Gajim
-
- # How are we supposed to know what encoding the environment
- # variable 'appdata' is in? Assuming it to be in filesystem
- # encoding.
- self.config_root = self.cache_root = self.data_root = \
- os.path.join(fse(os.environ[u'appdata']), u'Gajim')
- except KeyError:
- # win9x, in cwd
- self.config_root = self.cache_root = self.data_root = u'.'
- else: # Unices
- # Pass in an Unicode string, and hopefully get one back.
- if HAVE_XDG:
- self.config_root = xdg.BaseDirectory.load_first_config('gajim')
- if not self.config_root:
- # Folder doesn't exist yet.
- self.config_root = os.path.join(xdg.BaseDirectory.\
- xdg_config_dirs[0], u'gajim')
-
- self.cache_root = os.path.join(xdg.BaseDirectory.xdg_cache_home,
- u'gajim')
-
- self.data_root = xdg.BaseDirectory.save_data_path('gajim')
- if not self.data_root:
- self.data_root = os.path.join(xdg.BaseDirectory.\
- xdg_data_dirs[0], u'gajim')
- else:
- expand = os.path.expanduser
- base = os.getenv('XDG_CONFIG_HOME') or expand(u'~/.config')
- self.config_root = os.path.join(base, u'gajim')
- base = os.getenv('XDG_CACHE_HOME') or expand(u'~/.cache')
- self.cache_root = os.path.join(base, u'gajim')
- base = os.getenv('XDG_DATA_HOME') or expand(u'~/.local/share')
- self.data_root = os.path.join(base, u'gajim')
-
- def add(self, name, type_, path):
- self.paths[name] = (type_, path)
-
- def __getitem__(self, key):
- type_, path = self.paths[key]
- if type_ == TYPE_CONFIG:
- return os.path.join(self.config_root, path)
- elif type_ == TYPE_CACHE:
- return os.path.join(self.cache_root, path)
- elif type_ == TYPE_DATA:
- return os.path.join(self.data_root, path)
- return path
-
- def get(self, key, default=None):
- try:
- return self[key]
- except KeyError:
- return default
-
- def iteritems(self):
- for key in self.paths.iterkeys():
- yield (key, self[key])
-
- def init(self, root=None):
- if root is not None:
- self.config_root = self.cache_root = self.data_root = root
-
- d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
- 'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets',
- 'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities'}
- for name in d:
- self.add(name, TYPE_DATA, windowsify(d[name]))
-
- d = {'MY_CACHE': '', 'CACHE_DB': u'cache.db', 'VCARD': u'vcards',
- 'AVATAR': u'avatars'}
- for name in d:
- self.add(name, TYPE_CACHE, windowsify(d[name]))
-
- self.add('MY_CONFIG', TYPE_CONFIG, '')
-
- basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir))
- self.add('DATA', None, os.path.join(basedir, windowsify(u'data')))
- self.add('ICONS', None, os.path.join(basedir, windowsify(u'icons')))
- self.add('HOME', None, fse(os.path.expanduser('~')))
- try:
- self.add('TMP', None, fse(tempfile.gettempdir()))
- except IOError, e:
- print >> sys.stderr, 'Error opening tmp folder: %s\nUsing %s' % (
- str(e), os.path.expanduser('~'))
- self.add('TMP', None, fse(os.path.expanduser('~')))
-
- try:
- import svn_config
- svn_config.configure(self)
- except (ImportError, AttributeError):
- pass
-
- def init_profile(self, profile=''):
- conffile = windowsify(u'config')
- pidfile = windowsify(u'gajim')
- secretsfile = windowsify(u'secrets')
-
- if len(profile) > 0:
- conffile += u'.' + profile
- pidfile += u'.' + profile
- secretsfile += u'.' + profile
- pidfile += u'.pid'
- self.add('CONFIG_FILE', TYPE_CONFIG, conffile)
- self.add('PID_FILE', TYPE_CACHE, pidfile)
- self.add('SECRETS_FILE', TYPE_DATA, secretsfile)
+ def __init__(self):
+ # {'name': (type, path), } type can be TYPE_CONFIG, TYPE_CACHE, TYPE_DATA
+ # or None
+ self.paths = {}
+
+ if os.name == 'nt':
+ try:
+ # Documents and Settings\[User Name]\Application Data\Gajim
+
+ # How are we supposed to know what encoding the environment
+ # variable 'appdata' is in? Assuming it to be in filesystem
+ # encoding.
+ self.config_root = self.cache_root = self.data_root = \
+ os.path.join(fse(os.environ[u'appdata']), u'Gajim')
+ except KeyError:
+ # win9x, in cwd
+ self.config_root = self.cache_root = self.data_root = u'.'
+ else: # Unices
+ # Pass in an Unicode string, and hopefully get one back.
+ if HAVE_XDG:
+ self.config_root = xdg.BaseDirectory.load_first_config('gajim')
+ if not self.config_root:
+ # Folder doesn't exist yet.
+ self.config_root = os.path.join(xdg.BaseDirectory.\
+ xdg_config_dirs[0], u'gajim')
+
+ self.cache_root = os.path.join(xdg.BaseDirectory.xdg_cache_home,
+ u'gajim')
+
+ self.data_root = xdg.BaseDirectory.save_data_path('gajim')
+ if not self.data_root:
+ self.data_root = os.path.join(xdg.BaseDirectory.\
+ xdg_data_dirs[0], u'gajim')
+ else:
+ expand = os.path.expanduser
+ base = os.getenv('XDG_CONFIG_HOME') or expand(u'~/.config')
+ self.config_root = os.path.join(base, u'gajim')
+ base = os.getenv('XDG_CACHE_HOME') or expand(u'~/.cache')
+ self.cache_root = os.path.join(base, u'gajim')
+ base = os.getenv('XDG_DATA_HOME') or expand(u'~/.local/share')
+ self.data_root = os.path.join(base, u'gajim')
+
+ def add(self, name, type_, path):
+ self.paths[name] = (type_, path)
+
+ def __getitem__(self, key):
+ type_, path = self.paths[key]
+ if type_ == TYPE_CONFIG:
+ return os.path.join(self.config_root, path)
+ elif type_ == TYPE_CACHE:
+ return os.path.join(self.cache_root, path)
+ elif type_ == TYPE_DATA:
+ return os.path.join(self.data_root, path)
+ return path
+
+ def get(self, key, default=None):
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def iteritems(self):
+ for key in self.paths.iterkeys():
+ yield (key, self[key])
+
+ def init(self, root=None):
+ if root is not None:
+ self.config_root = self.cache_root = self.data_root = root
+
+ d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
+ 'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets',
+ 'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities'}
+ for name in d:
+ self.add(name, TYPE_DATA, windowsify(d[name]))
+
+ d = {'MY_CACHE': '', 'CACHE_DB': u'cache.db', 'VCARD': u'vcards',
+ 'AVATAR': u'avatars'}
+ for name in d:
+ self.add(name, TYPE_CACHE, windowsify(d[name]))
+
+ self.add('MY_CONFIG', TYPE_CONFIG, '')
+
+ basedir = fse(os.environ.get(u'GAJIM_BASEDIR', defs.basedir))
+ self.add('DATA', None, os.path.join(basedir, windowsify(u'data')))
+ self.add('ICONS', None, os.path.join(basedir, windowsify(u'icons')))
+ self.add('HOME', None, fse(os.path.expanduser('~')))
+ try:
+ self.add('TMP', None, fse(tempfile.gettempdir()))
+ except IOError, e:
+ print >> sys.stderr, 'Error opening tmp folder: %s\nUsing %s' % (
+ str(e), os.path.expanduser('~'))
+ self.add('TMP', None, fse(os.path.expanduser('~')))
+
+ try:
+ import svn_config
+ svn_config.configure(self)
+ except (ImportError, AttributeError):
+ pass
+
+ def init_profile(self, profile=''):
+ conffile = windowsify(u'config')
+ pidfile = windowsify(u'gajim')
+ secretsfile = windowsify(u'secrets')
+
+ if len(profile) > 0:
+ conffile += u'.' + profile
+ pidfile += u'.' + profile
+ secretsfile += u'.' + profile
+ pidfile += u'.pid'
+ self.add('CONFIG_FILE', TYPE_CONFIG, conffile)
+ self.add('PID_FILE', TYPE_CACHE, pidfile)
+ self.add('SECRETS_FILE', TYPE_DATA, secretsfile)
gajimpaths = ConfigPaths()
-
-# vim: se ts=3:
diff --git a/src/common/connection.py b/src/common/connection.py
index ca8dc19a2..550879d73 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -42,14 +42,14 @@ import locale
import hmac
try:
- randomsource = random.SystemRandom()
+ randomsource = random.SystemRandom()
except Exception:
- randomsource = random.Random()
- randomsource.seed()
+ randomsource = random.Random()
+ randomsource.seed()
import signal
if os.name != 'nt':
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
import common.xmpp
from common import helpers
@@ -100,2193 +100,2191 @@ ssl_error = {
}
class CommonConnection:
- """
- Common connection class, can be derivated for normal connection or zeroconf
- connection
- """
-
- def __init__(self, name):
- self.name = name
- # self.connected:
- # 0=>offline,
- # 1=>connection in progress,
- # 2=>online
- # 3=>free for chat
- # ...
- self.connected = 0
- self.connection = None # xmpppy ClientCommon instance
- self.on_purpose = False
- self.is_zeroconf = False
- self.password = ''
- self.server_resource = self._compute_resource()
- self.gpg = None
- self.USE_GPG = False
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
- self.status = ''
- self.old_show = ''
- self.priority = gajim.get_priority(name, 'offline')
- self.time_to_reconnect = None
- self.bookmarks = []
-
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_all = False
-
- self.pep_supported = False
- self.pep = {}
- # Do we continue connection when we get roster (send presence,get vcard..)
- self.continue_connect_info = None
-
- # Remember where we are in the register agent process
- self.agent_registrations = {}
- # To know the groupchat jid associated with a sranza ID. Useful to
- # request vcard or os info... to a real JID but act as if it comes from
- # the fake jid
- self.groupchat_jids = {} # {ID : groupchat_jid}
-
- self.privacy_rules_supported = False
- self.vcard_supported = False
- self.private_storage_supported = False
-
- self.muc_jid = {} # jid of muc server for each transport type
- self._stun_servers = [] # STUN servers of our jabber server
-
- self.get_config_values_or_default()
-
- def _compute_resource(self):
- resource = gajim.config.get_per('accounts', self.name, 'resource')
- # All valid resource substitution strings should be added to this hash.
- if resource:
- resource = Template(resource).safe_substitute({
- 'hostname': socket.gethostname()
- })
- return resource
-
- def dispatch(self, event, data):
- """
- Always passes account name as first param
- """
- gajim.ged.raise_event(event, self.name, data)
-
- def _reconnect(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def quit(self, kill_core):
- if kill_core and gajim.account_is_connected(self.name):
- self.disconnect(on_purpose=True)
-
- def test_gpg_passphrase(self, password):
- """
- Returns 'ok', 'bad_pass' or 'expired'
- """
- if not self.gpg:
- return False
- self.gpg.passphrase = password
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- signed = self.gpg.sign('test', keyID)
- self.gpg.password = None
- if signed == 'KEYEXPIRED':
- return 'expired'
- elif signed == 'BAD_PASSPHRASE':
- return 'bad_pass'
- return 'ok'
-
- def get_signed_msg(self, msg, callback = None):
- """
- Returns the signed message if possible or an empty string if gpg is not
- used or None if waiting for passphrase
-
- callback is the function to call when user give the passphrase
- """
- signed = ''
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- if keyID and self.USE_GPG:
- use_gpg_agent = gajim.config.get('use_gpg_agent')
- if self.gpg.passphrase is None and not use_gpg_agent:
- # We didn't set a passphrase
- return None
- if self.gpg.passphrase is not None or use_gpg_agent:
- signed = self.gpg.sign(msg, keyID)
- if signed == 'BAD_PASSPHRASE':
- self.USE_GPG = False
- signed = ''
- self.dispatch('BAD_PASSPHRASE', ())
- return signed
-
- def _on_disconnected(self):
- """
- Called when a disconnect request has completed successfully
- """
- self.disconnect(on_purpose=True)
- self.dispatch('STATUS', 'offline')
-
- def get_status(self):
- return gajim.SHOW_LIST[self.connected]
-
- def check_jid(self, jid):
- """
- This function must be implemented by derivated classes. It has to return
- the valid jid, or raise a helpers.InvalidFormat exception
- """
- raise NotImplementedError
-
- def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
- chatstate=None, msg_id=None, composing_xep=None, resource=None,
- user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
- original_message=None, delayed=None, callback=None):
- if not self.connection or self.connected < 2:
- return 1
- try:
- jid = self.check_jid(jid)
- except helpers.InvalidFormat:
- self.dispatch('ERROR', (_('Invalid Jabber ID'),
- _('It is not possible to send a message to %s, this JID is not '
- 'valid.') % jid))
- return
-
- if msg and not xhtml and gajim.config.get(
- 'rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- xhtml = create_xhtml(msg)
- if not msg and chatstate is None and form_node is None:
- return
- fjid = jid
- if resource:
- fjid += '/' + resource
- msgtxt = msg
- msgenc = ''
-
- if session:
- fjid = session.get_to()
-
- if keyID and self.USE_GPG:
- xhtml = None
- if keyID == 'UNKNOWN':
- error = _('Neither the remote presence is signed, nor a key was '
- 'assigned.')
- elif keyID.endswith('MISMATCH'):
- error = _('The contact\'s key (%s) does not match the key assigned '
- 'in Gajim.' % keyID[:8])
- else:
- def encrypt_thread(msg, keyID, always_trust=False):
- # encrypt message. This function returns (msgenc, error)
- return self.gpg.encrypt(msg, [keyID], always_trust)
- def _on_encrypted(output):
- msgenc, error = output
- if error == 'NOT_TRUSTED':
- def _on_always_trust(answer):
- if answer:
- gajim.thread_interface(encrypt_thread, [msg, keyID,
- True], _on_encrypted, [])
- else:
- self._message_encrypted_cb(output, type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml,
- subject, chatstate, composing_xep, forward_from,
- delayed, session, form_node, user_nick, keyID,
- callback)
- self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust)
- else:
- self._message_encrypted_cb(output, type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml, subject,
- chatstate, composing_xep, forward_from, delayed, session,
- form_node, user_nick, keyID, callback)
- gajim.thread_interface(encrypt_thread, [msg, keyID, False],
- _on_encrypted, [])
- return
-
- self._message_encrypted_cb(('', error), type_, msg, msgtxt,
- original_message, fjid, resource, jid, xhtml, subject, chatstate,
- composing_xep, forward_from, delayed, session, form_node, user_nick,
- keyID, callback)
-
- self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
- resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
- forward_from, delayed, session, form_node, user_nick, callback)
-
- def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message,
- fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from,
- delayed, session, form_node, user_nick, keyID, callback):
- msgenc, error = output
-
- if msgenc and not error:
- msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
- lang = os.getenv('LANG')
- if lang is not None and lang != 'en': # we're not english
- # one in locale and one en
- msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
- ' (' + msgtxt + ')'
- self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
- resource, jid, xhtml, subject, msgenc, keyID, chatstate,
- composing_xep, forward_from, delayed, session, form_node, user_nick,
- callback)
- return
- # Encryption failed, do not send message
- tim = localtime()
- self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
-
- def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
- resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
- forward_from, delayed, session, form_node, user_nick, callback):
- if type_ == 'chat':
- msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
- xhtml=xhtml)
- else:
- if subject:
- msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
- subject=subject, xhtml=xhtml)
- else:
- msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
- xhtml=xhtml)
- if msgenc:
- msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
-
- if form_node:
- msg_iq.addChild(node=form_node)
-
- # XEP-0172: user_nickname
- if user_nick:
- msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
- user_nick)
-
- # TODO: We might want to write a function so we don't need to
- # reproduce that ugly if somewhere else.
- if resource:
- contact = gajim.contacts.get_contact(self.name, jid, resource)
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(self.name,
- jid)
-
- # chatstates - if peer supports xep85 or xep22, send chatstates
- # please note that the only valid tag inside a message containing a <body>
- # tag is the active event
- if chatstate is not None and contact:
- if ((composing_xep == 'XEP-0085' or not composing_xep) \
- and composing_xep != 'asked_once') or \
- contact.supports(common.xmpp.NS_CHATSTATES):
- # XEP-0085
- msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
- if composing_xep in ('XEP-0022', 'asked_once') or \
- not composing_xep:
- # XEP-0022
- chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
- if chatstate is 'composing' or msgtxt:
- chatstate_node.addChild(name='composing')
-
- if forward_from:
- addresses = msg_iq.addChild('addresses',
- namespace=common.xmpp.NS_ADDRESS)
- addresses.addChild('address', attrs = {'type': 'ofrom',
- 'jid': forward_from})
-
- # XEP-0203
- if delayed:
- our_jid = gajim.get_jid_from_account(self.name) + '/' + \
- self.server_resource
- timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed))
- msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2,
- attrs={'from': our_jid, 'stamp': timestamp})
-
- # XEP-0184
- if msgtxt and gajim.config.get_per('accounts', self.name,
- 'request_receipt') and contact and contact.supports(
- common.xmpp.NS_RECEIPTS):
- msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
-
- if session:
- # XEP-0201
- session.last_send = time.time()
- msg_iq.setThread(session.thread_id)
-
- # XEP-0200
- if session.enable_encryption:
- msg_iq = session.encrypt_stanza(msg_iq)
-
- if callback:
- callback(jid, msg, keyID, forward_from, session, original_message,
- subject, type_, msg_iq)
-
- def log_message(self, jid, msg, forward_from, session, original_message,
- subject, type_):
- if not forward_from and session and session.is_loggable():
- ji = gajim.get_jid_without_resource(jid)
- if gajim.config.should_log(self.name, ji):
- log_msg = msg
- if original_message is not None:
- log_msg = original_message
- if subject:
- log_msg = _('Subject: %(subject)s\n%(message)s') % \
- {'subject': subject, 'message': log_msg}
- if log_msg:
- if type_ == 'chat':
- kind = 'chat_msg_sent'
- else:
- kind = 'single_msg_sent'
- try:
- gajim.logger.write(kind, jid, log_msg)
- except exceptions.PysqliteOperationalError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
- except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to '
- 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
- ' or remove it (all history will be lost).') % \
- common.logger.LOG_DB_PATH
-
- def ack_subscribed(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def ack_unsubscribed(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def request_subscription(self, jid, msg='', name='', groups=[],
- auto_auth=False):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def send_authorization(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def refuse_authorization(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def unsubscribe(self, jid, remove_auth = True):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def unsubscribe_agent(self, agent):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def update_contact(self, jid, name, groups):
- if self.connection:
- self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
-
- def update_contacts(self, contacts):
- """
- Update multiple roster items
- """
- if self.connection:
- self.connection.getRoster().setItemMulti(contacts)
-
- def new_account(self, name, config, sync=False):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def _on_new_account(self, con=None, con_type=None):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def account_changed(self, new_name):
- self.name = new_name
-
- def request_last_status_time(self, jid, resource):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def request_os_info(self, jid, resource):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_settings(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_bookmarks(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def store_bookmarks(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_metacontacts(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def send_agent_status(self, agent, ptype):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def gpg_passphrase(self, passphrase):
- if self.gpg:
- use_gpg_agent = gajim.config.get('use_gpg_agent')
- if use_gpg_agent:
- self.gpg.passphrase = None
- else:
- self.gpg.passphrase = passphrase
-
- def ask_gpg_keys(self):
- if self.gpg:
- keys = self.gpg.get_keys()
- return keys
- return None
-
- def ask_gpg_secrete_keys(self):
- if self.gpg:
- keys = self.gpg.get_secret_keys()
- return keys
- return None
-
- def load_roster_from_db(self):
- # Do nothing by default
- return
-
- def _event_dispatcher(self, realm, event, data):
- if realm == '':
- if event == common.xmpp.transports_nb.DATA_RECEIVED:
- self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore'))
- elif event == common.xmpp.transports_nb.DATA_SENT:
- self.dispatch('STANZA_SENT', unicode(data))
-
- def change_status(self, show, msg, auto=False):
- if not msg:
- msg = ''
- sign_msg = False
- if not auto and not show == 'offline':
- sign_msg = True
- if show != 'invisible':
- # We save it only when privacy list is accepted
- self.status = msg
- if show != 'offline' and self.connected < 1:
- # set old_show to requested 'show' in case we need to
- # recconect before we auth to server
- self.old_show = show
- self.on_purpose = False
- self.server_resource = self._compute_resource()
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
- self.connect_and_init(show, msg, sign_msg)
- return
-
- if show == 'offline':
- self.connected = 0
- if self.connection:
- p = common.xmpp.Presence(typ = 'unavailable')
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
-
- self.connection.RegisterDisconnectHandler(self._on_disconnected)
- self.connection.send(p, now=True)
- self.connection.start_disconnect()
- else:
- self._on_disconnected()
- return
-
- if show != 'offline' and self.connected > 0:
- # dont'try to connect, when we are in state 'connecting'
- if self.connected == 1:
- return
- if show == 'invisible':
- self._change_to_invisible(msg)
- return
- if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
- return -1
- was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
- self.connected = gajim.SHOW_LIST.index(show)
- if was_invisible:
- self._change_from_invisible()
- self._update_status(show, msg)
+ """
+ Common connection class, can be derivated for normal connection or zeroconf
+ connection
+ """
+
+ def __init__(self, name):
+ self.name = name
+ # self.connected:
+ # 0=>offline,
+ # 1=>connection in progress,
+ # 2=>online
+ # 3=>free for chat
+ # ...
+ self.connected = 0
+ self.connection = None # xmpppy ClientCommon instance
+ self.on_purpose = False
+ self.is_zeroconf = False
+ self.password = ''
+ self.server_resource = self._compute_resource()
+ self.gpg = None
+ self.USE_GPG = False
+ if gajim.HAVE_GPG:
+ self.USE_GPG = True
+ self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
+ self.status = ''
+ self.old_show = ''
+ self.priority = gajim.get_priority(name, 'offline')
+ self.time_to_reconnect = None
+ self.bookmarks = []
+
+ self.blocked_list = []
+ self.blocked_contacts = []
+ self.blocked_groups = []
+ self.blocked_all = False
+
+ self.pep_supported = False
+ self.pep = {}
+ # Do we continue connection when we get roster (send presence,get vcard..)
+ self.continue_connect_info = None
+
+ # Remember where we are in the register agent process
+ self.agent_registrations = {}
+ # To know the groupchat jid associated with a sranza ID. Useful to
+ # request vcard or os info... to a real JID but act as if it comes from
+ # the fake jid
+ self.groupchat_jids = {} # {ID : groupchat_jid}
+
+ self.privacy_rules_supported = False
+ self.vcard_supported = False
+ self.private_storage_supported = False
+
+ self.muc_jid = {} # jid of muc server for each transport type
+ self._stun_servers = [] # STUN servers of our jabber server
+
+ self.get_config_values_or_default()
+
+ def _compute_resource(self):
+ resource = gajim.config.get_per('accounts', self.name, 'resource')
+ # All valid resource substitution strings should be added to this hash.
+ if resource:
+ resource = Template(resource).safe_substitute({
+ 'hostname': socket.gethostname()
+ })
+ return resource
+
+ def dispatch(self, event, data):
+ """
+ Always passes account name as first param
+ """
+ gajim.ged.raise_event(event, self.name, data)
+
+ def _reconnect(self):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def quit(self, kill_core):
+ if kill_core and gajim.account_is_connected(self.name):
+ self.disconnect(on_purpose=True)
+
+ def test_gpg_passphrase(self, password):
+ """
+ Returns 'ok', 'bad_pass' or 'expired'
+ """
+ if not self.gpg:
+ return False
+ self.gpg.passphrase = password
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ signed = self.gpg.sign('test', keyID)
+ self.gpg.password = None
+ if signed == 'KEYEXPIRED':
+ return 'expired'
+ elif signed == 'BAD_PASSPHRASE':
+ return 'bad_pass'
+ return 'ok'
+
+ def get_signed_msg(self, msg, callback = None):
+ """
+ Returns the signed message if possible or an empty string if gpg is not
+ used or None if waiting for passphrase
+
+ callback is the function to call when user give the passphrase
+ """
+ signed = ''
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ if keyID and self.USE_GPG:
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+ if self.gpg.passphrase is None and not use_gpg_agent:
+ # We didn't set a passphrase
+ return None
+ if self.gpg.passphrase is not None or use_gpg_agent:
+ signed = self.gpg.sign(msg, keyID)
+ if signed == 'BAD_PASSPHRASE':
+ self.USE_GPG = False
+ signed = ''
+ self.dispatch('BAD_PASSPHRASE', ())
+ return signed
+
+ def _on_disconnected(self):
+ """
+ Called when a disconnect request has completed successfully
+ """
+ self.disconnect(on_purpose=True)
+ self.dispatch('STATUS', 'offline')
+
+ def get_status(self):
+ return gajim.SHOW_LIST[self.connected]
+
+ def check_jid(self, jid):
+ """
+ This function must be implemented by derivated classes. It has to return
+ the valid jid, or raise a helpers.InvalidFormat exception
+ """
+ raise NotImplementedError
+
+ def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
+ chatstate=None, msg_id=None, composing_xep=None, resource=None,
+ user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
+ original_message=None, delayed=None, callback=None):
+ if not self.connection or self.connected < 2:
+ return 1
+ try:
+ jid = self.check_jid(jid)
+ except helpers.InvalidFormat:
+ self.dispatch('ERROR', (_('Invalid Jabber ID'),
+ _('It is not possible to send a message to %s, this JID is not '
+ 'valid.') % jid))
+ return
+
+ if msg and not xhtml and gajim.config.get(
+ 'rst_formatting_outgoing_messages'):
+ from common.rst_xhtml_generator import create_xhtml
+ xhtml = create_xhtml(msg)
+ if not msg and chatstate is None and form_node is None:
+ return
+ fjid = jid
+ if resource:
+ fjid += '/' + resource
+ msgtxt = msg
+ msgenc = ''
+
+ if session:
+ fjid = session.get_to()
+
+ if keyID and self.USE_GPG:
+ xhtml = None
+ if keyID == 'UNKNOWN':
+ error = _('Neither the remote presence is signed, nor a key was '
+ 'assigned.')
+ elif keyID.endswith('MISMATCH'):
+ error = _('The contact\'s key (%s) does not match the key assigned '
+ 'in Gajim.' % keyID[:8])
+ else:
+ def encrypt_thread(msg, keyID, always_trust=False):
+ # encrypt message. This function returns (msgenc, error)
+ return self.gpg.encrypt(msg, [keyID], always_trust)
+ def _on_encrypted(output):
+ msgenc, error = output
+ if error == 'NOT_TRUSTED':
+ def _on_always_trust(answer):
+ if answer:
+ gajim.thread_interface(encrypt_thread, [msg, keyID,
+ True], _on_encrypted, [])
+ else:
+ self._message_encrypted_cb(output, type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml,
+ subject, chatstate, composing_xep, forward_from,
+ delayed, session, form_node, user_nick, keyID,
+ callback)
+ self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust)
+ else:
+ self._message_encrypted_cb(output, type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml, subject,
+ chatstate, composing_xep, forward_from, delayed, session,
+ form_node, user_nick, keyID, callback)
+ gajim.thread_interface(encrypt_thread, [msg, keyID, False],
+ _on_encrypted, [])
+ return
+
+ self._message_encrypted_cb(('', error), type_, msg, msgtxt,
+ original_message, fjid, resource, jid, xhtml, subject, chatstate,
+ composing_xep, forward_from, delayed, session, form_node, user_nick,
+ keyID, callback)
+
+ self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
+ forward_from, delayed, session, form_node, user_nick, callback)
+
+ def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message,
+ fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from,
+ delayed, session, form_node, user_nick, keyID, callback):
+ msgenc, error = output
+
+ if msgenc and not error:
+ msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
+ lang = os.getenv('LANG')
+ if lang is not None and lang != 'en': # we're not english
+ # one in locale and one en
+ msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
+ ' (' + msgtxt + ')'
+ self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate,
+ composing_xep, forward_from, delayed, session, form_node, user_nick,
+ callback)
+ return
+ # Encryption failed, do not send message
+ tim = localtime()
+ self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
+
+ def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
+ resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep,
+ forward_from, delayed, session, form_node, user_nick, callback):
+ if type_ == 'chat':
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
+ xhtml=xhtml)
+ else:
+ if subject:
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
+ subject=subject, xhtml=xhtml)
+ else:
+ msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
+ xhtml=xhtml)
+ if msgenc:
+ msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
+
+ if form_node:
+ msg_iq.addChild(node=form_node)
+
+ # XEP-0172: user_nickname
+ if user_nick:
+ msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
+ user_nick)
+
+ # TODO: We might want to write a function so we don't need to
+ # reproduce that ugly if somewhere else.
+ if resource:
+ contact = gajim.contacts.get_contact(self.name, jid, resource)
+ else:
+ contact = gajim.contacts.get_contact_with_highest_priority(self.name,
+ jid)
+
+ # chatstates - if peer supports xep85 or xep22, send chatstates
+ # please note that the only valid tag inside a message containing a <body>
+ # tag is the active event
+ if chatstate is not None and contact:
+ if ((composing_xep == 'XEP-0085' or not composing_xep) \
+ and composing_xep != 'asked_once') or \
+ contact.supports(common.xmpp.NS_CHATSTATES):
+ # XEP-0085
+ msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
+ if composing_xep in ('XEP-0022', 'asked_once') or \
+ not composing_xep:
+ # XEP-0022
+ chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
+ if chatstate is 'composing' or msgtxt:
+ chatstate_node.addChild(name='composing')
+
+ if forward_from:
+ addresses = msg_iq.addChild('addresses',
+ namespace=common.xmpp.NS_ADDRESS)
+ addresses.addChild('address', attrs = {'type': 'ofrom',
+ 'jid': forward_from})
+
+ # XEP-0203
+ if delayed:
+ our_jid = gajim.get_jid_from_account(self.name) + '/' + \
+ self.server_resource
+ timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(delayed))
+ msg_iq.addChild('delay', namespace=common.xmpp.NS_DELAY2,
+ attrs={'from': our_jid, 'stamp': timestamp})
+
+ # XEP-0184
+ if msgtxt and gajim.config.get_per('accounts', self.name,
+ 'request_receipt') and contact and contact.supports(
+ common.xmpp.NS_RECEIPTS):
+ msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
+
+ if session:
+ # XEP-0201
+ session.last_send = time.time()
+ msg_iq.setThread(session.thread_id)
+
+ # XEP-0200
+ if session.enable_encryption:
+ msg_iq = session.encrypt_stanza(msg_iq)
+
+ if callback:
+ callback(jid, msg, keyID, forward_from, session, original_message,
+ subject, type_, msg_iq)
+
+ def log_message(self, jid, msg, forward_from, session, original_message,
+ subject, type_):
+ if not forward_from and session and session.is_loggable():
+ ji = gajim.get_jid_without_resource(jid)
+ if gajim.config.should_log(self.name, ji):
+ log_msg = msg
+ if original_message is not None:
+ log_msg = original_message
+ if subject:
+ log_msg = _('Subject: %(subject)s\n%(message)s') % \
+ {'subject': subject, 'message': log_msg}
+ if log_msg:
+ if type_ == 'chat':
+ kind = 'chat_msg_sent'
+ else:
+ kind = 'single_msg_sent'
+ try:
+ gajim.logger.write(kind, jid, log_msg)
+ except exceptions.PysqliteOperationalError, e:
+ self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
+ except exceptions.DatabaseMalformed:
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to '
+ 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
+ ' or remove it (all history will be lost).') % \
+ common.logger.LOG_DB_PATH
+
+ def ack_subscribed(self, jid):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def ack_unsubscribed(self, jid):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def request_subscription(self, jid, msg='', name='', groups=[],
+ auto_auth=False):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def send_authorization(self, jid):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def refuse_authorization(self, jid):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def unsubscribe(self, jid, remove_auth = True):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def unsubscribe_agent(self, agent):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def update_contact(self, jid, name, groups):
+ if self.connection:
+ self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
+
+ def update_contacts(self, contacts):
+ """
+ Update multiple roster items
+ """
+ if self.connection:
+ self.connection.getRoster().setItemMulti(contacts)
+
+ def new_account(self, name, config, sync=False):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def _on_new_account(self, con=None, con_type=None):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def account_changed(self, new_name):
+ self.name = new_name
+
+ def request_last_status_time(self, jid, resource):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def request_os_info(self, jid, resource):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def get_settings(self):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def get_bookmarks(self):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def store_bookmarks(self):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def get_metacontacts(self):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def send_agent_status(self, agent, ptype):
+ """
+ To be implemented by derivated classes
+ """
+ raise NotImplementedError
+
+ def gpg_passphrase(self, passphrase):
+ if self.gpg:
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+ if use_gpg_agent:
+ self.gpg.passphrase = None
+ else:
+ self.gpg.passphrase = passphrase
+
+ def ask_gpg_keys(self):
+ if self.gpg:
+ keys = self.gpg.get_keys()
+ return keys
+ return None
+
+ def ask_gpg_secrete_keys(self):
+ if self.gpg:
+ keys = self.gpg.get_secret_keys()
+ return keys
+ return None
+
+ def load_roster_from_db(self):
+ # Do nothing by default
+ return
+
+ def _event_dispatcher(self, realm, event, data):
+ if realm == '':
+ if event == common.xmpp.transports_nb.DATA_RECEIVED:
+ self.dispatch('STANZA_ARRIVED', unicode(data, errors='ignore'))
+ elif event == common.xmpp.transports_nb.DATA_SENT:
+ self.dispatch('STANZA_SENT', unicode(data))
+
+ def change_status(self, show, msg, auto=False):
+ if not msg:
+ msg = ''
+ sign_msg = False
+ if not auto and not show == 'offline':
+ sign_msg = True
+ if show != 'invisible':
+ # We save it only when privacy list is accepted
+ self.status = msg
+ if show != 'offline' and self.connected < 1:
+ # set old_show to requested 'show' in case we need to
+ # recconect before we auth to server
+ self.old_show = show
+ self.on_purpose = False
+ self.server_resource = self._compute_resource()
+ if gajim.HAVE_GPG:
+ self.USE_GPG = True
+ self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
+ self.connect_and_init(show, msg, sign_msg)
+ return
+
+ if show == 'offline':
+ self.connected = 0
+ if self.connection:
+ p = common.xmpp.Presence(typ = 'unavailable')
+ p = self.add_sha(p, False)
+ if msg:
+ p.setStatus(msg)
+
+ self.connection.RegisterDisconnectHandler(self._on_disconnected)
+ self.connection.send(p, now=True)
+ self.connection.start_disconnect()
+ else:
+ self._on_disconnected()
+ return
+
+ if show != 'offline' and self.connected > 0:
+ # dont'try to connect, when we are in state 'connecting'
+ if self.connected == 1:
+ return
+ if show == 'invisible':
+ self._change_to_invisible(msg)
+ return
+ if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
+ return -1
+ was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
+ self.connected = gajim.SHOW_LIST.index(show)
+ if was_invisible:
+ self._change_from_invisible()
+ self._update_status(show, msg)
class Connection(CommonConnection, ConnectionHandlers):
- def __init__(self, name):
- CommonConnection.__init__(self, name)
- ConnectionHandlers.__init__(self)
- # this property is used to prevent double connections
- self.last_connection = None # last ClientCommon instance
- # If we succeed to connect, remember it so next time we try (after a
- # disconnection) we try only this type.
- self.last_connection_type = None
- self.lang = None
- if locale.getdefaultlocale()[0]:
- self.lang = locale.getdefaultlocale()[0].split('_')[0]
- # increase/decrease default timeout for server responses
- self.try_connecting_for_foo_secs = 45
- # holds the actual hostname to which we are connected
- self.connected_hostname = None
- self.last_time_to_reconnect = None
- self.new_account_info = None
- self.new_account_form = None
- self.annotations = {}
- self.last_io = gajim.idlequeue.current_time()
- self.last_sent = []
- self.last_history_time = {}
- self.password = passwords.get_password(name)
-
- self.music_track_info = 0
- self.location_info = {}
- self.pubsub_supported = False
- self.pubsub_publish_options_supported = False
- # Do we auto accept insecure connection
- self.connection_auto_accepted = False
- self.pasword_callback = None
-
- self.on_connect_success = None
- self.on_connect_failure = None
- self.retrycount = 0
- self.jids_for_auto_auth = [] # list of jid to auto-authorize
- self.available_transports = {} # list of available transports on this
- # server {'icq': ['icq.server.com', 'icq2.server.com'], }
- self.private_storage_supported = True
- self.streamError = ''
- self.secret_hmac = str(random.random())[2:]
- # END __init__
-
- def get_config_values_or_default(self):
- if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
- self.keepalives = gajim.config.get_per('accounts', self.name,
- 'keep_alive_every_foo_secs')
- else:
- self.keepalives = 0
- if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'):
- self.pingalives = gajim.config.get_per('accounts', self.name,
- 'ping_alive_every_foo_secs')
- else:
- self.pingalives = 0
-
- def check_jid(self, jid):
- return helpers.parse_jid(jid)
-
- def _reconnect(self):
- # Do not try to reco while we are already trying
- self.time_to_reconnect = None
- if self.connected < 2: # connection failed
- log.debug('reconnect')
- self.connected = 1
- self.dispatch('STATUS', 'connecting')
- self.retrycount += 1
- self.on_connect_auth = self._discover_server_at_connection
- self.connect_and_init(self.old_show, self.status, self.USE_GPG)
- else:
- # reconnect succeeded
- self.time_to_reconnect = None
- self.retrycount = 0
-
- # We are doing disconnect at so many places, better use one function in all
- def disconnect(self, on_purpose=False):
- gajim.interface.music_track_changed(None, None, self.name)
- self.reset_awaiting_pep()
- self.on_purpose = on_purpose
- self.connected = 0
- self.time_to_reconnect = None
- self.privacy_rules_supported = False
- if self.connection:
- # make sure previous connection is completely closed
- gajim.proxy65_manager.disconnect(self.connection)
- self.terminate_sessions()
- self.remove_all_transfers()
- self.connection.disconnect()
- self.last_connection = None
- self.connection = None
-
- def _disconnectedReconnCB(self):
- """
- Called when we are disconnected
- """
- log.info('disconnectedReconnCB called')
- if gajim.account_is_connected(self.name):
- # we cannot change our status to offline or connecting
- # after we auth to server
- self.old_show = gajim.SHOW_LIST[self.connected]
- self.connected = 0
- if not self.on_purpose:
- self.dispatch('STATUS', 'offline')
- self.disconnect()
- if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
- self.connected = -1
- self.dispatch('STATUS', 'error')
- if gajim.status_before_autoaway[self.name]:
- # We were auto away. So go back online
- self.status = gajim.status_before_autoaway[self.name]
- gajim.status_before_autoaway[self.name] = ''
- self.old_show = 'online'
- # this check has moved from _reconnect method
- # do exponential backoff until 15 minutes,
- # then small linear increase
- if self.retrycount < 2 or self.last_time_to_reconnect is None:
- self.last_time_to_reconnect = 5
- if self.last_time_to_reconnect < 800:
- self.last_time_to_reconnect *= 1.5
- self.last_time_to_reconnect += randomsource.randint(0, 5)
- self.time_to_reconnect = int(self.last_time_to_reconnect)
- log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
- gajim.idlequeue.set_alarm(self._reconnect_alarm,
- self.time_to_reconnect)
- elif self.on_connect_failure:
- self.on_connect_failure()
- self.on_connect_failure = None
- else:
- # show error dialog
- self._connection_lost()
- else:
- self.disconnect()
- self.on_purpose = False
- # END disconnectedReconnCB
-
- def _connection_lost(self):
- log.debug('_connection_lost')
- self.disconnect(on_purpose = False)
- self.dispatch('STATUS', 'offline')
- self.dispatch('CONNECTION_LOST',
- (_('Connection with account "%s" has been lost') % self.name,
- _('Reconnect manually.')))
-
- def _event_dispatcher(self, realm, event, data):
- CommonConnection._event_dispatcher(self, realm, event, data)
- if realm == common.xmpp.NS_REGISTER:
- if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
- # data is (agent, DataFrom, is_form, error_msg)
- if self.new_account_info and \
- self.new_account_info['hostname'] == data[0]:
- # it's a new account
- if not data[1]: # wrong answer
- self.dispatch('ACC_NOT_OK', (
- _('Server %(name)s answered wrongly to register request: '
- '%(error)s') % {'name': data[0], 'error': data[3]}))
- return
- is_form = data[2]
- conf = data[1]
- if self.new_account_form:
- def _on_register_result(result):
- if not common.xmpp.isResultNode(result):
- self.dispatch('ACC_NOT_OK', (result.getError()))
- return
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = GnuPG.GnuPG(gajim.config.get(
- 'use_gpg_agent'))
- self.dispatch('ACC_OK', (self.new_account_info))
- self.new_account_info = None
- self.new_account_form = None
- if self.connection:
- self.connection.UnregisterDisconnectHandler(
- self._on_new_account)
- self.disconnect(on_purpose=True)
- # it's the second time we get the form, we have info user
- # typed, so send them
- if is_form:
- #TODO: Check if form has changed
- iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname)
- iq.setTag('query').addChild(node=self.new_account_form)
- self.connection.SendAndCallForResponse(iq,
- _on_register_result)
- else:
- if self.new_account_form.keys().sort() != \
- conf.keys().sort():
- # requested config has changed since first connection
- self.dispatch('ACC_NOT_OK', (_(
- 'Server %s provided a different registration form')\
- % data[0]))
- return
- common.xmpp.features_nb.register(self.connection,
- self._hostname, self.new_account_form,
- _on_register_result)
- return
- try:
- errnum = self.connection.Connection.ssl_errnum
- except AttributeError:
- errnum = -1 # we don't have an errnum
- ssl_msg = ''
- if errnum > 0:
- ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum)
- ssl_cert = ''
- if hasattr(self.connection.Connection, 'ssl_cert_pem'):
- ssl_cert = self.connection.Connection.ssl_cert_pem
- ssl_fingerprint = ''
- if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'):
- ssl_fingerprint = \
- self.connection.Connection.ssl_fingerprint_sha1
- self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg,
- errnum, ssl_cert, ssl_fingerprint))
- self.connection.UnregisterDisconnectHandler(
- self._on_new_account)
- self.disconnect(on_purpose=True)
- return
- if not data[1]: # wrong answer
- self.dispatch('ERROR', (_('Invalid answer'),
- _('Transport %(name)s answered wrongly to register request: '
- '%(error)s') % {'name': data[0], 'error': data[3]}))
- return
- is_form = data[2]
- conf = data[1]
- self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form))
- elif realm == common.xmpp.NS_PRIVACY:
- if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED:
- # data is (list)
- self.dispatch('PRIVACY_LISTS_RECEIVED', (data))
- elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED:
- # data is (resp)
- if not data:
- return
- rules = []
- name = data.getTag('query').getTag('list').getAttr('name')
- for child in data.getTag('query').getTag('list').getChildren():
- dict_item = child.getAttrs()
- childs = []
- if 'type' in dict_item:
- for scnd_child in child.getChildren():
- childs += [scnd_child.getName()]
- rules.append({'action':dict_item['action'],
- 'type':dict_item['type'], 'order':dict_item['order'],
- 'value':dict_item['value'], 'child':childs})
- else:
- for scnd_child in child.getChildren():
- childs.append(scnd_child.getName())
- rules.append({'action':dict_item['action'],
- 'order':dict_item['order'], 'child':childs})
- self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules))
- elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
- # data is (dict)
- self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
-
- def _select_next_host(self, hosts):
- """
- Selects the next host according to RFC2782 p.3 based on it's priority.
- Chooses between hosts with the same priority randomly, where the
- probability of being selected is proportional to the weight of the host
- """
- hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))
-
- try:
- lowest_prio = hosts_by_prio[0]['prio']
- except IndexError:
- raise ValueError("No hosts to choose from!")
-
- hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio]
-
- if len(hosts_lowest_prio) == 1:
- return hosts_lowest_prio[0]
- else:
- rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio))
- weightsum = 0
- for host in sorted(hosts_lowest_prio, key=operator.itemgetter(
- 'weight')):
- weightsum += host['weight']
- if weightsum >= rndint:
- return host
-
- def connect(self, data = None):
- """
- Start a connection to the Jabber server
-
- Returns connection, and connection type ('tls', 'ssl', 'plain', '') data
- MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if
- use_custom_host), custom_port (if use_custom_host)
- """
- if self.connection:
- return self.connection, ''
-
- if data:
- hostname = data['hostname']
- self.try_connecting_for_foo_secs = 45
- p = data['proxy']
- use_srv = True
- use_custom = data['use_custom_host']
- if use_custom:
- custom_h = data['custom_host']
- custom_p = data['custom_port']
- else:
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- usessl = gajim.config.get_per('accounts', self.name, 'usessl')
- self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
- self.name, 'try_connecting_for_foo_secs')
- p = gajim.config.get_per('accounts', self.name, 'proxy')
- use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
- use_custom = gajim.config.get_per('accounts', self.name,
- 'use_custom_host')
- custom_h = gajim.config.get_per('accounts', self.name, 'custom_host')
- custom_p = gajim.config.get_per('accounts', self.name, 'custom_port')
-
- # create connection if it doesn't already exist
- self.connected = 1
- if p and p in gajim.config.get_per('proxies'):
- proxy = {}
- proxyptr = gajim.config.get_per('proxies', p)
- for key in proxyptr.keys():
- proxy[key] = proxyptr[key][1]
-
- elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
- try:
- try:
- env_http_proxy = os.environ['HTTP_PROXY']
- except Exception:
- env_http_proxy = os.environ['http_proxy']
- env_http_proxy = env_http_proxy.strip('"')
- # Dispose of the http:// prefix
- env_http_proxy = env_http_proxy.split('://')
- env_http_proxy = env_http_proxy[len(env_http_proxy)-1]
- env_http_proxy = env_http_proxy.split('@')
-
- if len(env_http_proxy) == 2:
- login = env_http_proxy[0].split(':')
- addr = env_http_proxy[1].split(':')
- else:
- login = ['', '']
- addr = env_http_proxy[0].split(':')
-
- proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]}
-
- if len(addr) == 2:
- proxy['port'] = addr[1]
- else:
- proxy['port'] = 3128
-
- if len(login) == 2:
- proxy['password'] = login[1]
- else:
- proxy['password'] = u''
-
- except Exception:
- proxy = None
- else:
- proxy = None
- h = hostname
- p = 5222
- ssl_p = 5223
-# use_srv = False # wants ssl? disable srv lookup
- if use_custom:
- h = custom_h
- p = custom_p
- ssl_p = custom_p
- use_srv = False
-
- # SRV resolver
- self._proxy = proxy
- self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
- 'weight': 10} ]
- self._hostname = hostname
- if use_srv:
- # add request for srv query to the resolve, on result '_on_resolve'
- # will be called
- gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h),
- self._on_resolve)
- else:
- self._on_resolve('', [])
-
- def _on_resolve(self, host, result_array):
- # SRV query returned at least one valid result, we put it in hosts dict
- if len(result_array) != 0:
- self._hosts = [i for i in result_array]
- # Add ssl port
- ssl_p = 5223
- if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
- ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port')
- for i in self._hosts:
- i['ssl_port'] = ssl_p
- self._connect_to_next_host()
-
-
- def _connect_to_next_host(self, retry=False):
- log.debug('Connection to next host')
- if len(self._hosts):
- # No config option exist when creating a new account
- if self.last_connection_type:
- self._connection_types = [self.last_connection_type]
- elif self.name in gajim.config.get_per('accounts'):
- self._connection_types = gajim.config.get_per('accounts', self.name,
- 'connection_types').split()
- else:
- self._connection_types = ['tls', 'ssl', 'plain']
-
- if self._proxy and self._proxy['type']=='bosh':
- # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
- # connection and TLS with handshake right after TCP connecting ("ssl")
- scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
- if scheme=='https':
- self._connection_types = ['ssl']
- else:
- self._connection_types = ['plain']
-
- host = self._select_next_host(self._hosts)
- self._current_host = host
- self._hosts.remove(host)
- self.connect_to_next_type()
-
- else:
- if not retry and self.retrycount == 0:
- log.debug("Out of hosts, giving up connecting to %s", self.name)
- self.time_to_reconnect = None
- if self.on_connect_failure:
- self.on_connect_failure()
- self.on_connect_failure = None
- else:
- # shown error dialog
- self._connection_lost()
- else:
- # try reconnect if connection has failed before auth to server
- self._disconnectedReconnCB()
-
- def connect_to_next_type(self, retry=False):
- if len(self._connection_types):
- self._current_type = self._connection_types.pop(0)
- if self.last_connection:
- self.last_connection.socket.disconnect()
- self.last_connection = None
- self.connection = None
-
- if self._current_type == 'ssl':
- # SSL (force TLS on different port than plain)
- # If we do TLS over BOSH, port of XMPP server should be the standard one
- # and TLS should be negotiated because TLS on 5223 is deprecated
- if self._proxy and self._proxy['type']=='bosh':
- port = self._current_host['port']
- else:
- port = self._current_host['ssl_port']
- elif self._current_type == 'tls':
- # TLS - negotiate tls after XMPP stream is estabilished
- port = self._current_host['port']
- elif self._current_type == 'plain':
- # plain connection on defined port
- port = self._current_host['port']
-
- cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
- mycerts = common.gajim.MY_CACERTS
- secure_tuple = (self._current_type, cacerts, mycerts)
-
- con = common.xmpp.NonBlockingClient(
- domain=self._hostname,
- caller=self,
- idlequeue=gajim.idlequeue)
-
- self.last_connection = con
- # increase default timeout for server responses
- common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
- # FIXME: this is a hack; need a better way
- if self.on_connect_success == self._on_new_account:
- con.RegisterDisconnectHandler(self._on_new_account)
-
- self.log_hosttype_info(port)
- con.connect(
- hostname=self._current_host['host'],
- port=port,
- on_connect=self.on_connect_success,
- on_proxy_failure=self.on_proxy_failure,
- on_connect_failure=self.connect_to_next_type,
- proxy=self._proxy,
- secure_tuple = secure_tuple)
- else:
- self._connect_to_next_host(retry)
-
- def log_hosttype_info(self, port):
- msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
- self._current_host['host'], port, self._current_type)
- log.info(msg)
- if self._proxy:
- msg = '>>>>>> '
- if self._proxy['type']=='bosh':
- msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri'])
- if self._proxy['type'] in ['http','socks5'] or self._proxy['bosh_useproxy']:
- msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
- log.info(msg)
-
- def _connect_failure(self, con_type=None):
- if not con_type:
- # we are not retrying, and not conecting
- if not self.retrycount and self.connected != 0:
- self.disconnect(on_purpose = True)
- self.dispatch('STATUS', 'offline')
- pritxt = _('Could not connect to "%s"') % self._hostname
- sectxt = _('Check your connection or try again later.')
- if self.streamError:
- # show error dialog
- key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError
- if key in common.xmpp.ERRORS:
- sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2]
- self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt)))
- return
- # show popup
- self.dispatch('CONNECTION_LOST', (pritxt, sectxt))
-
- def on_proxy_failure(self, reason):
- log.error('Connection to proxy failed: %s' % reason)
- self.time_to_reconnect = None
- self.on_connect_failure = None
- self.disconnect(on_purpose = True)
- self.dispatch('STATUS', 'offline')
- self.dispatch('CONNECTION_LOST',
- (_('Connection to proxy failed'), reason))
-
- def _connect_success(self, con, con_type):
- if not self.connected: # We went offline during connecting process
- # FIXME - not possible, maybe it was when we used threads
- return
- _con_type = con_type
- if _con_type != self._current_type:
- log.info('Connecting to next type beacuse desired is %s and returned is %s'
- % (self._current_type, _con_type))
- self.connect_to_next_type()
- return
- con.RegisterDisconnectHandler(self._on_disconnected)
- if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
- 'warn_when_plaintext_connection'):
- self.dispatch('PLAIN_CONNECTION', (con,))
- return True
- if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \
- and gajim.config.get_per('accounts', self.name,
- 'warn_when_insecure_ssl_connection') and \
- not self.connection_auto_accepted:
- # Pyopenssl is not used
- self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type))
- return True
- return self.connection_accepted(con, con_type)
-
- def connection_accepted(self, con, con_type):
- if not con or not con.Connection:
- self.disconnect(on_purpose=True)
- self.dispatch('STATUS', 'offline')
- self.dispatch('CONNECTION_LOST',
- (_('Could not connect to account %s') % self.name,
- _('Connection with account %s has been lost. Retry connecting.') % \
- self.name))
- return
- self.hosts = []
- self.connection_auto_accepted = False
- self.connected_hostname = self._current_host['host']
- self.on_connect_failure = None
- con.UnregisterDisconnectHandler(self._on_disconnected)
- con.RegisterDisconnectHandler(self._disconnectedReconnCB)
- log.debug('Connected to server %s:%s with %s' % (
- self._current_host['host'], self._current_host['port'], con_type))
-
- self.last_connection_type = con_type
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- name = None
- else:
- name = gajim.config.get_per('accounts', self.name, 'name')
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- self.connection = con
- try:
- errnum = con.Connection.ssl_errnum
- except AttributeError:
- errnum = -1 # we don't have an errnum
- if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts',
- self.name, 'ignore_ssl_errors'):
- text = _('The authenticity of the %s certificate could be invalid.') %\
- hostname
- if errnum in ssl_error:
- text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
- else:
- text += _('\nUnknown SSL error: %d') % errnum
- self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem,
- con.Connection.ssl_fingerprint_sha1))
- return True
- if hasattr(con.Connection, 'ssl_fingerprint_sha1'):
- saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1')
- if saved_fingerprint:
- # Check sha1 fingerprint
- if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint:
- self.dispatch('FINGERPRINT_ERROR',
- (con.Connection.ssl_fingerprint_sha1,))
- return True
- else:
- gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1',
- con.Connection.ssl_fingerprint_sha1)
- self._register_handlers(con, con_type)
- con.auth(
- user=name,
- password=self.password,
- resource=self.server_resource,
- sasl=1,
- on_auth=self.__on_auth)
-
- def ssl_certificate_accepted(self):
- if not self.connection:
- self.disconnect(on_purpose=True)
- self.dispatch('STATUS', 'offline')
- self.dispatch('CONNECTION_LOST',
- (_('Could not connect to account %s') % self.name,
- _('Connection with account %s has been lost. Retry connecting.') % \
- self.name))
- return
- name = gajim.config.get_per('accounts', self.name, 'name')
- self._register_handlers(self.connection, 'ssl')
- self.connection.auth(name, self.password, self.server_resource, 1,
- self.__on_auth)
-
- def _register_handlers(self, con, con_type):
- self.peerhost = con.get_peerhost()
- # notify the gui about con_type
- self.dispatch('CON_TYPE', con_type)
- ConnectionHandlers._register_handlers(self, con, con_type)
-
- def __on_auth(self, con, auth):
- if not con:
- self.disconnect(on_purpose=True)
- self.dispatch('STATUS', 'offline')
- self.dispatch('CONNECTION_LOST',
- (_('Could not connect to "%s"') % self._hostname,
- _('Check your connection or try again later')))
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- return
- if not self.connected: # We went offline during connecting process
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- return
- if hasattr(con, 'Resource'):
- self.server_resource = con.Resource
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- # Get jid given by server
- old_jid = gajim.get_jid_from_account(self.name)
- gajim.config.set_per('accounts', self.name, 'name', con.User)
- new_jid = gajim.get_jid_from_account(self.name)
- self.dispatch('NEW_JID', (old_jid, new_jid))
- if auth:
- self.last_io = gajim.idlequeue.current_time()
- self.connected = 2
- self.retrycount = 0
- if self.on_connect_auth:
- self.on_connect_auth(con)
- self.on_connect_auth = None
- else:
- # Forget password, it's wrong
- self.password = None
- gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
- self.disconnect(on_purpose = True)
- self.dispatch('STATUS', 'offline')
- self.dispatch('ERROR', (_('Authentication failed with "%s"') % \
- self._hostname,
- _('Please check your login and password for correctness.')))
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- # END connect
-
- def add_lang(self, stanza):
- if self.lang:
- stanza.setAttr('xml:lang', self.lang)
-
- def get_privacy_lists(self):
- if not self.connection:
- return
- common.xmpp.features_nb.getPrivacyLists(self.connection)
-
- def send_keepalive(self):
- # nothing received for the last foo seconds
- if self.connection:
- self.connection.send(' ')
-
- def _on_xmpp_ping_answer(self, iq_obj):
- id_ = unicode(iq_obj.getAttr('id'))
- assert id_ == self.awaiting_xmpp_ping_id
- self.awaiting_xmpp_ping_id = None
-
- def sendPing(self, pingTo=None):
- """
- Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to
- server to detect connection failure at application level
- """
- if not self.connection:
- return
- id_ = self.connection.getAnID()
- if pingTo:
- to = pingTo.get_full_jid()
- self.dispatch('PING_SENT', (pingTo))
- else:
- to = gajim.config.get_per('accounts', self.name, 'hostname')
- self.awaiting_xmpp_ping_id = id_
- iq = common.xmpp.Iq('get', to=to)
- iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING)
- iq.setID(id_)
- def _on_response(resp):
- timePong = time_time()
- if not common.xmpp.isResultNode(resp):
- self.dispatch('PING_ERROR', (pingTo))
- return
- timeDiff = round(timePong - timePing,2)
- self.dispatch('PING_REPLY', (pingTo, timeDiff))
- if pingTo:
- timePing = time_time()
- self.connection.SendAndCallForResponse(iq, _on_response)
- else:
- self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer)
- gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per(
- 'accounts', self.name, 'time_for_ping_alive_answer'))
-
- def get_active_default_lists(self):
- if not self.connection:
- return
- common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
-
- def del_privacy_list(self, privacy_list):
- if not self.connection:
- return
- def _on_del_privacy_list_result(result):
- if result:
- self.dispatch('PRIVACY_LIST_REMOVED', privacy_list)
- else:
- self.dispatch('ERROR', (_('Error while removing privacy list'),
- _('Privacy list %s has not been removed. It is maybe active in '
- 'one of your connected resources. Deactivate it and try '
- 'again.') % privacy_list))
- common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list,
- _on_del_privacy_list_result)
-
- def get_privacy_list(self, title):
- if not self.connection:
- return
- common.xmpp.features_nb.getPrivacyList(self.connection, title)
-
- def set_privacy_list(self, listname, tags):
- if not self.connection:
- return
- common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
-
- def set_active_list(self, listname):
- if not self.connection:
- return
- common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
-
- def set_default_list(self, listname):
- if not self.connection:
- return
- common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
-
- def build_privacy_rule(self, name, action, order=1):
- """
- Build a Privacy rule stanza for invisibility
- """
- iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
- l = iq.getTag('query').setTag('list', {'name': name})
- i = l.setTag('item', {'action': action, 'order': str(order)})
- i.setTag('presence-out')
- return iq
-
- def build_invisible_rule(self):
- iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
- l = iq.getTag('query').setTag('list', {'name': 'invisible'})
- if self.name in gajim.interface.status_sent_to_groups and \
- len(gajim.interface.status_sent_to_groups[self.name]) > 0:
- for group in gajim.interface.status_sent_to_groups[self.name]:
- i = l.setTag('item', {'type': 'group', 'value': group,
- 'action': 'allow', 'order': '1'})
- i.setTag('presence-out')
- if self.name in gajim.interface.status_sent_to_users and \
- len(gajim.interface.status_sent_to_users[self.name]) > 0:
- for jid in gajim.interface.status_sent_to_users[self.name]:
- i = l.setTag('item', {'type': 'jid', 'value': jid,
- 'action': 'allow', 'order': '2'})
- i.setTag('presence-out')
- i = l.setTag('item', {'action': 'deny', 'order': '3'})
- i.setTag('presence-out')
- return iq
-
- def set_invisible_rule(self):
- if not gajim.account_is_connected(self.name):
- return
- iq = self.build_invisible_rule()
- self.connection.send(iq)
-
- def activate_privacy_rule(self, name):
- """
- Activate a privacy rule
- """
- if not self.connection:
- return
- iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
- iq.getTag('query').setTag('active', {'name': name})
- self.connection.send(iq)
-
- def send_invisible_presence(self, msg, signed, initial = False):
- if not self.connection:
- return
- if not self.privacy_rules_supported:
- self.dispatch('STATUS', gajim.SHOW_LIST[self.connected])
- self.dispatch('ERROR', (_('Invisibility not supported'),
- _('Account %s doesn\'t support invisibility.') % self.name))
- return
- # If we are already connected, and privacy rules are supported, send
- # offline presence first as it's required by XEP-0126
- if self.connected > 1 and self.privacy_rules_supported:
- self.on_purpose = True
- p = common.xmpp.Presence(typ = 'unavailable')
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
- self.remove_all_transfers()
- self.connection.send(p)
-
- # try to set the privacy rule
- iq = self.build_invisible_rule()
- self.connection.SendAndCallForResponse(iq, self._continue_invisible,
- {'msg': msg, 'signed': signed, 'initial': initial})
-
- def _continue_invisible(self, con, iq_obj, msg, signed, initial):
- if iq_obj.getType() == 'error': # server doesn't support privacy lists
- return
- # active the privacy rule
- self.privacy_rules_supported = True
- self.activate_privacy_rule('invisible')
- self.connected = gajim.SHOW_LIST.index('invisible')
- self.status = msg
- priority = unicode(gajim.get_priority(self.name, 'invisible'))
- p = common.xmpp.Presence(priority = priority)
- p = self.add_sha(p, True)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
- self.connection.send(p)
- self.priority = priority
- self.dispatch('STATUS', 'invisible')
- if initial:
- # ask our VCard
- self.request_vcard(None)
-
- # Get bookmarks from private namespace
- self.get_bookmarks()
-
- # Get annotations
- self.get_annotations()
-
- # Inform GUI we just signed in
- self.dispatch('SIGNED_IN', ())
-
- def get_signed_presence(self, msg, callback = None):
- if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'):
- return self.get_signed_msg(msg, callback)
- return ''
-
- def connect_and_auth(self):
- self.on_connect_success = self._connect_success
- self.on_connect_failure = self._connect_failure
- self.connect()
-
- def connect_and_init(self, show, msg, sign_msg):
- self.continue_connect_info = [show, msg, sign_msg]
- self.on_connect_auth = self._discover_server_at_connection
- self.connect_and_auth()
-
- def _discover_server_at_connection(self, con):
- self.connection = con
- if not self.connection:
- return
- self.connection.set_send_timeout(self.keepalives, self.send_keepalive)
- self.connection.set_send_timeout2(self.pingalives, self.sendPing)
- self.connection.onreceive(None)
- self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'),
- id_prefix='Gajim_')
- self.privacy_rules_requested = False
- # Discover Stun server(s)
- gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(
- self.connected_hostname), self._on_stun_resolved)
-
- def _on_stun_resolved(self, host, result_array):
- if len(result_array) != 0:
- self._stun_servers = self._hosts = [i for i in result_array]
-
- def _request_privacy(self):
- iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (PRIVACY_ARRIVED, )
- self.connection.send(iq)
-
- def send_custom_status(self, show, msg, jid):
- if not show in gajim.SHOW_LIST:
- return -1
- if not self.connection:
- return
- sshow = helpers.get_xmpp_show(show)
- if not msg:
- msg = ''
- if show == 'offline':
- p = common.xmpp.Presence(typ = 'unavailable', to = jid)
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
- else:
- signed = self.get_signed_presence(msg)
- priority = unicode(gajim.get_priority(self.name, sshow))
- p = common.xmpp.Presence(typ = None, priority = priority, show = sshow,
- to = jid)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
- self.connection.send(p)
-
- def _change_to_invisible(self, msg):
- signed = self.get_signed_presence(msg)
- self.send_invisible_presence(msg, signed)
-
- def _change_from_invisible(self):
- if self.privacy_rules_supported:
- iq = self.build_privacy_rule('visible', 'allow')
- self.connection.send(iq)
- self.activate_privacy_rule('visible')
-
- def _update_status(self, show, msg):
- xmpp_show = helpers.get_xmpp_show(show)
- priority = unicode(gajim.get_priority(self.name, xmpp_show))
- p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- signed = self.get_signed_presence(msg)
- if signed:
- p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
- if self.connection:
- self.connection.send(p)
- self.priority = priority
- self.dispatch('STATUS', show)
-
- def send_motd(self, jid, subject = '', msg = '', xhtml = None):
- if not self.connection:
- return
- msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject,
- xhtml = xhtml)
-
- self.connection.send(msg_iq)
-
- def send_message(self, jid, msg, keyID, type_='chat', subject='',
- chatstate=None, msg_id=None, composing_xep=None, resource=None,
- user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
- original_message=None, delayed=None, callback=None, callback_args=[]):
-
- def cb(jid, msg, keyID, forward_from, session, original_message, subject,
- type_, msg_iq):
- msg_id = self.connection.send(msg_iq)
- jid = helpers.parse_jid(jid)
- self.dispatch('MSGSENT', (jid, msg, keyID))
- if callback:
- callback(msg_id, *callback_args)
-
- self.log_message(jid, msg, forward_from, session, original_message,
- subject, type_)
-
- self._prepare_message(jid, msg, keyID, type_=type_, subject=subject,
- chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
- resource=resource, user_nick=user_nick, xhtml=xhtml, session=session,
- forward_from=forward_from, form_node=form_node,
- original_message=original_message, delayed=delayed, callback=cb)
-
- def send_contacts(self, contacts, jid):
- """
- Send contacts with RosterX (Xep-0144)
- """
- if not self.connection:
- return
- if len(contacts) == 1:
- msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(),
- contacts[0].get_shown_name())
- else:
- msg = _('Sent contacts:')
- for contact in contacts:
- msg += '\n "%s" (%s)' % (contact.get_full_jid(),
- contact.get_shown_name())
- msg_iq = common.xmpp.Message(to=jid, body=msg)
- x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX)
- for contact in contacts:
- x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid,
- 'name': contact.get_shown_name()})
- self.connection.send(msg_iq)
-
- def send_stanza(self, stanza):
- """
- Send a stanza untouched
- """
- if not self.connection:
- return
- self.connection.send(stanza)
-
- def ack_subscribed(self, jid):
- if not self.connection:
- return
- log.debug('ack\'ing subscription complete for %s' % jid)
- p = common.xmpp.Presence(jid, 'subscribe')
- self.connection.send(p)
-
- def ack_unsubscribed(self, jid):
- if not self.connection:
- return
- log.debug('ack\'ing unsubscription complete for %s' % jid)
- p = common.xmpp.Presence(jid, 'unsubscribe')
- self.connection.send(p)
-
- def request_subscription(self, jid, msg='', name='', groups=[],
- auto_auth=False, user_nick=''):
- if not self.connection:
- return
- log.debug('subscription request for %s' % jid)
- if auto_auth:
- self.jids_for_auto_auth.append(jid)
- # RFC 3921 section 8.2
- infos = {'jid': jid}
- if name:
- infos['name'] = name
- iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER)
- q = iq.getTag('query')
- item = q.addChild('item', attrs=infos)
- for g in groups:
- item.addChild('group').setData(g)
- self.connection.send(iq)
-
- p = common.xmpp.Presence(jid, 'subscribe')
- if user_nick:
- p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- self.connection.send(p)
-
- def send_authorization(self, jid):
- if not self.connection:
- return
- p = common.xmpp.Presence(jid, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
-
- def refuse_authorization(self, jid):
- if not self.connection:
- return
- p = common.xmpp.Presence(jid, 'unsubscribed')
- p = self.add_sha(p)
- self.connection.send(p)
-
- def unsubscribe(self, jid, remove_auth = True):
- if not self.connection:
- return
- if remove_auth:
- self.connection.getRoster().delItem(jid)
- jid_list = gajim.config.get_per('contacts')
- for j in jid_list:
- if j.startswith(jid):
- gajim.config.del_per('contacts', j)
- else:
- self.connection.getRoster().Unsubscribe(jid)
- self.update_contact(jid, '', [])
-
- def unsubscribe_agent(self, agent):
- if not self.connection:
- return
- iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
- iq.getTag('query').setTag('remove')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
- self.connection.send(iq)
- self.connection.getRoster().delItem(agent)
-
- def send_new_account_infos(self, form, is_form):
- if is_form:
- # Get username and password and put them in new_account_info
- for field in form.iter_fields():
- if field.var == 'username':
- self.new_account_info['name'] = field.value
- if field.var == 'password':
- self.new_account_info['password'] = field.value
- else:
- # Get username and password and put them in new_account_info
- if 'username' in form:
- self.new_account_info['name'] = form['username']
- if 'password' in form:
- self.new_account_info['password'] = form['password']
- self.new_account_form = form
- self.new_account(self.name, self.new_account_info)
-
- def new_account(self, name, config, sync=False):
- # If a connection already exist we cannot create a new account
- if self.connection:
- return
- self._hostname = config['hostname']
- self.new_account_info = config
- self.name = name
- self.on_connect_success = self._on_new_account
- self.on_connect_failure = self._on_new_account
- self.connect(config)
-
- def _on_new_account(self, con=None, con_type=None):
- if not con_type:
- if len(self._connection_types) or len(self._hosts):
- # There are still other way to try to connect
- return
- self.dispatch('NEW_ACC_NOT_CONNECTED',
- (_('Could not connect to "%s"') % self._hostname))
- return
- self.on_connect_failure = None
- self.connection = con
- common.xmpp.features_nb.getRegInfo(con, self._hostname)
-
- def request_last_status_time(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not self.connection:
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\
- common.xmpp.NS_LAST)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.last_ids.append(id_)
- self.connection.send(iq)
-
- def request_os_info(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not self.connection:
- return
- # If we are invisible, do not request
- if self.connected == gajim.SHOW_LIST.index('invisible'):
- self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\
- common.xmpp.NS_VERSION)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.version_ids.append(id_)
- self.connection.send(iq)
-
- def request_entity_time(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not self.connection:
- return
- # If we are invisible, do not request
- if self.connected == gajim.SHOW_LIST.index('invisible'):
- self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status')))
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = common.xmpp.Iq(to=to_whom_jid, typ='get')
- iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.entity_time_ids.append(id_)
- self.connection.send(iq)
-
- def get_settings(self):
- """
- Get Gajim settings as described in XEP 0049
- """
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
- iq2.addChild(name='gajim', namespace='gajim:prefs')
- self.connection.send(iq)
-
- def _request_bookmarks_xml(self):
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:bookmarks')
- self.connection.send(iq)
-
- def _check_bookmarks_received(self):
- if not self.bookmarks:
- self._request_bookmarks_xml()
-
- def get_bookmarks(self, storage_type=None):
- """
- Get Bookmarks from storage or PubSub if supported as described in XEP
- 0048
-
- storage_type can be set to xml to force request to xml storage
- """
- if not self.connection:
- return
- if self.pubsub_supported and storage_type != 'xml':
- self.send_pb_retrieve('', 'storage:bookmarks')
- # some server (ejabberd) are so slow to answer that we request via XML
- # if we don't get answer in the next 30 seconds
- gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30)
- else:
- self._request_bookmarks_xml()
-
- def store_bookmarks(self, storage_type=None):
- """
- Send bookmarks to the storage namespace or PubSub if supported
-
- storage_type can be set to 'pubsub' or 'xml' so store in only one method
- else it will be stored on both
- """
- if not self.connection:
- return
- iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'})
- for bm in self.bookmarks:
- iq2 = iq.addChild(name = "conference")
- iq2.setAttr('jid', bm['jid'])
- iq2.setAttr('autojoin', bm['autojoin'])
- iq2.setAttr('minimize', bm['minimize'])
- iq2.setAttr('name', bm['name'])
- # Only add optional elements if not empty
- # Note: need to handle both None and '' as empty
- # thus shouldn't use "is not None"
- if bm.get('nick', None):
- iq2.setTagData('nick', bm['nick'])
- if bm.get('password', None):
- iq2.setTagData('password', bm['password'])
- if bm.get('print_status', None):
- iq2.setTagData('print_status', bm['print_status'])
-
- if self.pubsub_supported and storage_type != 'xml':
- if self.pubsub_publish_options_supported:
- options = common.xmpp.Node(common.xmpp.NS_DATA + ' x',
- attrs={'type': 'submit'})
- f = options.addChild('field', attrs={'var': 'FORM_TYPE',
- 'type': 'hidden'})
- f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS)
- f = options.addChild('field', attrs={'var': 'pubsub#persist_items'})
- f.setTagData('value', 'true')
- f = options.addChild('field', attrs={'var': 'pubsub#access_model'})
- f.setTagData('value', 'whitelist')
- else:
- options = None
- self.send_pb_publish('', 'storage:bookmarks', iq, 'current',
- options=options)
- if storage_type != 'pubsub':
- iqA = common.xmpp.Iq(typ='set')
- iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
- iqB.addChild(node=iq)
- self.connection.send(iqA)
-
- def get_annotations(self):
- """
- Get Annonations from storage as described in XEP 0048, and XEP 0145
- """
- self.annotations = {}
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:rosternotes')
- self.connection.send(iq)
-
- def store_annotations(self):
- """
- Set Annonations in private storage as described in XEP 0048, and XEP 0145
- """
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes')
- for jid in self.annotations.keys():
- if self.annotations[jid]:
- iq4 = iq3.addChild(name = "note")
- iq4.setAttr('jid', jid)
- iq4.setData(self.annotations[jid])
- self.connection.send(iq)
-
-
- def get_metacontacts(self):
- """
- Get metacontacts list from storage as described in XEP 0049
- """
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:metacontacts')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, )
- self.connection.send(iq)
-
- def store_metacontacts(self, tags_list):
- """
- Send meta contacts to the storage namespace
- """
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
- for tag in tags_list:
- for data in tags_list[tag]:
- jid = data['jid']
- dict_ = {'jid': jid, 'tag': tag}
- if 'order' in data:
- dict_['order'] = data['order']
- iq3.addChild(name = 'meta', attrs = dict_)
- self.connection.send(iq)
-
- def send_agent_status(self, agent, ptype):
- if not self.connection:
- return
- show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- p = common.xmpp.Presence(to = agent, typ = ptype, show = show)
- p = self.add_sha(p, ptype != 'unavailable')
- self.connection.send(p)
-
- def check_unique_room_id_support(self, server, instance):
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'get', to = server)
- iq.setAttr('id', 'unique1')
- iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE)
- def _on_response(resp):
- if not common.xmpp.isResultNode(resp):
- self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance))
- return
- self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance,
- resp.getTag('unique').getData()))
- self.connection.SendAndCallForResponse(iq, _on_response)
-
- def join_gc(self, nick, room_jid, password, change_nick=False):
- # FIXME: This room JID needs to be normalized; see #1364
- if not self.connection:
- return
- show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- if show == 'invisible':
- # Never join a room when invisible
- return
-
- # last date/time in history to avoid duplicate
- if room_jid not in self.last_history_time:
- # Not in memory, get it from DB
- last_log = None
- # Do not check if we are not logging for this room
- if gajim.config.should_log(self.name, room_jid):
- # Check time first in the FAST table
- last_log = gajim.logger.get_room_last_message_time(room_jid)
- if last_log is None:
- # Not in special table, get it from messages DB
- last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
- is_room=True)
- # Create self.last_history_time[room_jid] even if not logging,
- # could be used in connection_handlers
- if last_log is None:
- last_log = 0
- self.last_history_time[room_jid] = last_log
-
- p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick),
- show=show, status=self.status)
- h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6]
- id_ = self.connection.getAnID()
- id_ = 'gajim_muc_' + id_ + '_' + h
- p.setID(id_)
- if gajim.config.get('send_sha_in_gc_presence'):
- p = self.add_sha(p)
- self.add_lang(p)
- if not change_nick:
- t = p.setTag(common.xmpp.NS_MUC + ' x')
- last_date = self.last_history_time[room_jid]
- if last_date == 0:
- last_date = time.time() - gajim.config.get(
- 'muc_restore_timeout') * 60
- else:
- last_date = min(last_date, time.time() - gajim.config.get(
- 'muc_restore_timeout') * 60)
- last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date))
- t.setTag('history', {'maxstanzas': gajim.config.get(
- 'muc_restore_lines'), 'since': last_date})
- if password:
- t.setTagData('password', password)
- self.connection.send(p)
-
- def send_gc_message(self, jid, msg, xhtml = None):
- if not self.connection:
- return
- if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- xhtml = create_xhtml(msg)
- msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml)
- self.connection.send(msg_iq)
- self.dispatch('MSGSENT', (jid, msg))
-
- def send_gc_subject(self, jid, subject):
- if not self.connection:
- return
- msg_iq = common.xmpp.Message(jid,typ = 'groupchat', subject = subject)
- self.connection.send(msg_iq)
-
- def request_gc_config(self, room_jid):
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER,
- to = room_jid)
- self.add_lang(iq)
- self.connection.send(iq)
-
- def destroy_gc_room(self, room_jid, reason = '', jid = ''):
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER,
- to = room_jid)
- destroy = iq.getTag('query').setTag('destroy')
- if reason:
- destroy.setTagData('reason', reason)
- if jid:
- destroy.setAttr('jid', jid)
- self.connection.send(iq)
-
- def send_gc_status(self, nick, jid, show, status):
- if not gajim.account_is_connected(self.name):
- return
- if show == 'invisible':
- show = 'offline'
- ptype = None
- if show == 'offline':
- ptype = 'unavailable'
- xmpp_show = helpers.get_xmpp_show(show)
- p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype,
- show = xmpp_show, status = status)
- h = hmac.new(self.secret_hmac, jid).hexdigest()[:6]
- id_ = self.connection.getAnID()
- id_ = 'gajim_muc_' + id_ + '_' + h
- p.setID(id_)
- if gajim.config.get('send_sha_in_gc_presence') and show != 'offline':
- p = self.add_sha(p, ptype != 'unavailable')
- self.add_lang(p)
- # send instantly so when we go offline, status is sent to gc before we
- # disconnect from jabber server
- self.connection.send(p)
-
- def gc_got_disconnected(self, room_jid):
- """
- A groupchat got disconnected. This can be or purpose or not
-
- Save the time we had last message to avoid duplicate logs AND be faster
- than get that date from DB. Save time that we have in mem in a small
- table (with fast access)
- """
- gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid])
-
- def gc_set_role(self, room_jid, nick, role, reason = ''):
- """
- Role is for all the life of the room so it's based on nick
- """
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
- common.xmpp.NS_MUC_ADMIN)
- item = iq.getTag('query').setTag('item')
- item.setAttr('nick', nick)
- item.setAttr('role', role)
- if reason:
- item.addChild(name = 'reason', payload = reason)
- self.connection.send(iq)
-
- def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
- """
- Affiliation is for all the life of the room so it's based on jid
- """
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
- common.xmpp.NS_MUC_ADMIN)
- item = iq.getTag('query').setTag('item')
- item.setAttr('jid', jid)
- item.setAttr('affiliation', affiliation)
- if reason:
- item.addChild(name = 'reason', payload = reason)
- self.connection.send(iq)
-
- def send_gc_affiliation_list(self, room_jid, users_dict):
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \
- common.xmpp.NS_MUC_ADMIN)
- item = iq.getTag('query')
- for jid in users_dict:
- item_tag = item.addChild('item', {'jid': jid,
- 'affiliation': users_dict[jid]['affiliation']})
- if 'reason' in users_dict[jid] and users_dict[jid]['reason']:
- item_tag.setTagData('reason', users_dict[jid]['reason'])
- self.connection.send(iq)
-
- def get_affiliation_list(self, room_jid, affiliation):
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \
- common.xmpp.NS_MUC_ADMIN)
- item = iq.getTag('query').setTag('item')
- item.setAttr('affiliation', affiliation)
- self.connection.send(iq)
-
- def send_gc_config(self, room_jid, form):
- if not self.connection:
- return
- iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
- common.xmpp.NS_MUC_OWNER)
- query = iq.getTag('query')
- form.setAttr('type', 'submit')
- query.addChild(node = form)
- self.connection.send(iq)
-
- def change_password(self, password):
- if not self.connection:
- return
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- username = gajim.config.get_per('accounts', self.name, 'name')
- iq = common.xmpp.Iq(typ = 'set', to = hostname)
- q = iq.setTag(common.xmpp.NS_REGISTER + ' query')
- q.setTagData('username',username)
- q.setTagData('password',password)
- self.connection.send(iq)
-
- def get_password(self, callback):
- if self.password:
- callback(self.password)
- return
- self.pasword_callback = callback
- self.dispatch('PASSWORD_REQUIRED', None)
-
- def set_password(self, password):
- self.password = password
- if self.pasword_callback:
- self.pasword_callback(password)
- self.pasword_callback = None
-
- def unregister_account(self, on_remove_success):
- # no need to write this as a class method and keep the value of
- # on_remove_success as a class property as pass it as an argument
- def _on_unregister_account_connect(con):
- self.on_connect_auth = None
- if gajim.account_is_connected(self.name):
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- iq = common.xmpp.Iq(typ = 'set', to = hostname)
- iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove')
- def _on_answer(result):
- if result.getType() == 'result':
- on_remove_success(True)
- return
- self.dispatch('ERROR', (_('Unregister failed'),
- _('Unregistration with server %(server)s failed: %(error)s') \
- % {'server': hostname, 'error': result.getErrorMsg()}))
- on_remove_success(False)
- con.SendAndCallForResponse(iq, _on_answer)
- return
- on_remove_success(False)
- if self.connected == 0:
- self.on_connect_auth = _on_unregister_account_connect
- self.connect_and_auth()
- else:
- _on_unregister_account_connect(self.connection)
-
- def send_invite(self, room, to, reason='', continue_tag=False):
- """
- Send invitation
- """
- message=common.xmpp.Message(to = room)
- c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER)
- c = c.addChild(name = 'invite', attrs={'to' : to})
- if continue_tag:
- c.addChild(name = 'continue')
- if reason != '':
- c.setTagData('reason', reason)
- self.connection.send(message)
-
- def check_pingalive(self):
- if self.awaiting_xmpp_ping_id:
- # We haven't got the pong in time, disco and reconnect
- log.warn("No reply received for keepalive ping. Reconnecting.")
- self._disconnectedReconnCB()
-
- def _reconnect_alarm(self):
- if self.time_to_reconnect:
- if self.connected < 2:
- self._reconnect()
- else:
- self.time_to_reconnect = None
-
- def request_search_fields(self, jid):
- iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \
- common.xmpp.NS_SEARCH)
- self.connection.send(iq)
-
- def send_search_form(self, jid, form, is_form):
- iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \
- common.xmpp.NS_SEARCH)
- item = iq.getTag('query')
- if is_form:
- item.addChild(node = form)
- else:
- for i in form.keys():
- item.setTagData(i,form[i])
- def _on_response(resp):
- jid = jid = helpers.get_jid_from_iq(resp)
- tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH)
- if not tag:
- self.dispatch('SEARCH_RESULT', (jid, None, False))
- return
- df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
- if df:
- self.dispatch('SEARCH_RESULT', (jid, df, True))
- return
- df = []
- for item in tag.getTags('item'):
- # We also show attributes. jid is there
- f = item.attrs
- for i in item.getPayload():
- f[i.getName()] = i.getData()
- df.append(f)
- self.dispatch('SEARCH_RESULT', (jid, df, False))
-
- self.connection.SendAndCallForResponse(iq, _on_response)
-
- def load_roster_from_db(self):
- roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name))
- self.dispatch('ROSTER', roster)
+ def __init__(self, name):
+ CommonConnection.__init__(self, name)
+ ConnectionHandlers.__init__(self)
+ # this property is used to prevent double connections
+ self.last_connection = None # last ClientCommon instance
+ # If we succeed to connect, remember it so next time we try (after a
+ # disconnection) we try only this type.
+ self.last_connection_type = None
+ self.lang = None
+ if locale.getdefaultlocale()[0]:
+ self.lang = locale.getdefaultlocale()[0].split('_')[0]
+ # increase/decrease default timeout for server responses
+ self.try_connecting_for_foo_secs = 45
+ # holds the actual hostname to which we are connected
+ self.connected_hostname = None
+ self.last_time_to_reconnect = None
+ self.new_account_info = None
+ self.new_account_form = None
+ self.annotations = {}
+ self.last_io = gajim.idlequeue.current_time()
+ self.last_sent = []
+ self.last_history_time = {}
+ self.password = passwords.get_password(name)
+
+ self.music_track_info = 0
+ self.location_info = {}
+ self.pubsub_supported = False
+ self.pubsub_publish_options_supported = False
+ # Do we auto accept insecure connection
+ self.connection_auto_accepted = False
+ self.pasword_callback = None
+
+ self.on_connect_success = None
+ self.on_connect_failure = None
+ self.retrycount = 0
+ self.jids_for_auto_auth = [] # list of jid to auto-authorize
+ self.available_transports = {} # list of available transports on this
+ # server {'icq': ['icq.server.com', 'icq2.server.com'], }
+ self.private_storage_supported = True
+ self.streamError = ''
+ self.secret_hmac = str(random.random())[2:]
+ # END __init__
+
+ def get_config_values_or_default(self):
+ if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
+ self.keepalives = gajim.config.get_per('accounts', self.name,
+ 'keep_alive_every_foo_secs')
+ else:
+ self.keepalives = 0
+ if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'):
+ self.pingalives = gajim.config.get_per('accounts', self.name,
+ 'ping_alive_every_foo_secs')
+ else:
+ self.pingalives = 0
+
+ def check_jid(self, jid):
+ return helpers.parse_jid(jid)
+
+ def _reconnect(self):
+ # Do not try to reco while we are already trying
+ self.time_to_reconnect = None
+ if self.connected < 2: # connection failed
+ log.debug('reconnect')
+ self.connected = 1
+ self.dispatch('STATUS', 'connecting')
+ self.retrycount += 1
+ self.on_connect_auth = self._discover_server_at_connection
+ self.connect_and_init(self.old_show, self.status, self.USE_GPG)
+ else:
+ # reconnect succeeded
+ self.time_to_reconnect = None
+ self.retrycount = 0
+
+ # We are doing disconnect at so many places, better use one function in all
+ def disconnect(self, on_purpose=False):
+ gajim.interface.music_track_changed(None, None, self.name)
+ self.reset_awaiting_pep()
+ self.on_purpose = on_purpose
+ self.connected = 0
+ self.time_to_reconnect = None
+ self.privacy_rules_supported = False
+ if self.connection:
+ # make sure previous connection is completely closed
+ gajim.proxy65_manager.disconnect(self.connection)
+ self.terminate_sessions()
+ self.remove_all_transfers()
+ self.connection.disconnect()
+ self.last_connection = None
+ self.connection = None
+
+ def _disconnectedReconnCB(self):
+ """
+ Called when we are disconnected
+ """
+ log.info('disconnectedReconnCB called')
+ if gajim.account_is_connected(self.name):
+ # we cannot change our status to offline or connecting
+ # after we auth to server
+ self.old_show = gajim.SHOW_LIST[self.connected]
+ self.connected = 0
+ if not self.on_purpose:
+ self.dispatch('STATUS', 'offline')
+ self.disconnect()
+ if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
+ self.connected = -1
+ self.dispatch('STATUS', 'error')
+ if gajim.status_before_autoaway[self.name]:
+ # We were auto away. So go back online
+ self.status = gajim.status_before_autoaway[self.name]
+ gajim.status_before_autoaway[self.name] = ''
+ self.old_show = 'online'
+ # this check has moved from _reconnect method
+ # do exponential backoff until 15 minutes,
+ # then small linear increase
+ if self.retrycount < 2 or self.last_time_to_reconnect is None:
+ self.last_time_to_reconnect = 5
+ if self.last_time_to_reconnect < 800:
+ self.last_time_to_reconnect *= 1.5
+ self.last_time_to_reconnect += randomsource.randint(0, 5)
+ self.time_to_reconnect = int(self.last_time_to_reconnect)
+ log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
+ gajim.idlequeue.set_alarm(self._reconnect_alarm,
+ self.time_to_reconnect)
+ elif self.on_connect_failure:
+ self.on_connect_failure()
+ self.on_connect_failure = None
+ else:
+ # show error dialog
+ self._connection_lost()
+ else:
+ self.disconnect()
+ self.on_purpose = False
+ # END disconnectedReconnCB
+
+ def _connection_lost(self):
+ log.debug('_connection_lost')
+ self.disconnect(on_purpose = False)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('CONNECTION_LOST',
+ (_('Connection with account "%s" has been lost') % self.name,
+ _('Reconnect manually.')))
+
+ def _event_dispatcher(self, realm, event, data):
+ CommonConnection._event_dispatcher(self, realm, event, data)
+ if realm == common.xmpp.NS_REGISTER:
+ if event == common.xmpp.features_nb.REGISTER_DATA_RECEIVED:
+ # data is (agent, DataFrom, is_form, error_msg)
+ if self.new_account_info and \
+ self.new_account_info['hostname'] == data[0]:
+ # it's a new account
+ if not data[1]: # wrong answer
+ self.dispatch('ACC_NOT_OK', (
+ _('Server %(name)s answered wrongly to register request: '
+ '%(error)s') % {'name': data[0], 'error': data[3]}))
+ return
+ is_form = data[2]
+ conf = data[1]
+ if self.new_account_form:
+ def _on_register_result(result):
+ if not common.xmpp.isResultNode(result):
+ self.dispatch('ACC_NOT_OK', (result.getError()))
+ return
+ if gajim.HAVE_GPG:
+ self.USE_GPG = True
+ self.gpg = GnuPG.GnuPG(gajim.config.get(
+ 'use_gpg_agent'))
+ self.dispatch('ACC_OK', (self.new_account_info))
+ self.new_account_info = None
+ self.new_account_form = None
+ if self.connection:
+ self.connection.UnregisterDisconnectHandler(
+ self._on_new_account)
+ self.disconnect(on_purpose=True)
+ # it's the second time we get the form, we have info user
+ # typed, so send them
+ if is_form:
+ #TODO: Check if form has changed
+ iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname)
+ iq.setTag('query').addChild(node=self.new_account_form)
+ self.connection.SendAndCallForResponse(iq,
+ _on_register_result)
+ else:
+ if self.new_account_form.keys().sort() != \
+ conf.keys().sort():
+ # requested config has changed since first connection
+ self.dispatch('ACC_NOT_OK', (_(
+ 'Server %s provided a different registration form')\
+ % data[0]))
+ return
+ common.xmpp.features_nb.register(self.connection,
+ self._hostname, self.new_account_form,
+ _on_register_result)
+ return
+ try:
+ errnum = self.connection.Connection.ssl_errnum
+ except AttributeError:
+ errnum = -1 # we don't have an errnum
+ ssl_msg = ''
+ if errnum > 0:
+ ssl_msg = ssl_error.get(errnum, _('Unknown SSL error: %d') % errnum)
+ ssl_cert = ''
+ if hasattr(self.connection.Connection, 'ssl_cert_pem'):
+ ssl_cert = self.connection.Connection.ssl_cert_pem
+ ssl_fingerprint = ''
+ if hasattr(self.connection.Connection, 'ssl_fingerprint_sha1'):
+ ssl_fingerprint = \
+ self.connection.Connection.ssl_fingerprint_sha1
+ self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg,
+ errnum, ssl_cert, ssl_fingerprint))
+ self.connection.UnregisterDisconnectHandler(
+ self._on_new_account)
+ self.disconnect(on_purpose=True)
+ return
+ if not data[1]: # wrong answer
+ self.dispatch('ERROR', (_('Invalid answer'),
+ _('Transport %(name)s answered wrongly to register request: '
+ '%(error)s') % {'name': data[0], 'error': data[3]}))
+ return
+ is_form = data[2]
+ conf = data[1]
+ self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form))
+ elif realm == common.xmpp.NS_PRIVACY:
+ if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED:
+ # data is (list)
+ self.dispatch('PRIVACY_LISTS_RECEIVED', (data))
+ elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED:
+ # data is (resp)
+ if not data:
+ return
+ rules = []
+ name = data.getTag('query').getTag('list').getAttr('name')
+ for child in data.getTag('query').getTag('list').getChildren():
+ dict_item = child.getAttrs()
+ childs = []
+ if 'type' in dict_item:
+ for scnd_child in child.getChildren():
+ childs += [scnd_child.getName()]
+ rules.append({'action':dict_item['action'],
+ 'type':dict_item['type'], 'order':dict_item['order'],
+ 'value':dict_item['value'], 'child':childs})
+ else:
+ for scnd_child in child.getChildren():
+ childs.append(scnd_child.getName())
+ rules.append({'action':dict_item['action'],
+ 'order':dict_item['order'], 'child':childs})
+ self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules))
+ elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
+ # data is (dict)
+ self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
+
+ def _select_next_host(self, hosts):
+ """
+ Selects the next host according to RFC2782 p.3 based on it's priority.
+ Chooses between hosts with the same priority randomly, where the
+ probability of being selected is proportional to the weight of the host
+ """
+ hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))
+
+ try:
+ lowest_prio = hosts_by_prio[0]['prio']
+ except IndexError:
+ raise ValueError("No hosts to choose from!")
+
+ hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio]
+
+ if len(hosts_lowest_prio) == 1:
+ return hosts_lowest_prio[0]
+ else:
+ rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio))
+ weightsum = 0
+ for host in sorted(hosts_lowest_prio, key=operator.itemgetter(
+ 'weight')):
+ weightsum += host['weight']
+ if weightsum >= rndint:
+ return host
+
+ def connect(self, data = None):
+ """
+ Start a connection to the Jabber server
+
+ Returns connection, and connection type ('tls', 'ssl', 'plain', '') data
+ MUST contain hostname, usessl, proxy, use_custom_host, custom_host (if
+ use_custom_host), custom_port (if use_custom_host)
+ """
+ if self.connection:
+ return self.connection, ''
+
+ if data:
+ hostname = data['hostname']
+ self.try_connecting_for_foo_secs = 45
+ p = data['proxy']
+ use_srv = True
+ use_custom = data['use_custom_host']
+ if use_custom:
+ custom_h = data['custom_host']
+ custom_p = data['custom_port']
+ else:
+ hostname = gajim.config.get_per('accounts', self.name, 'hostname')
+ usessl = gajim.config.get_per('accounts', self.name, 'usessl')
+ self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
+ self.name, 'try_connecting_for_foo_secs')
+ p = gajim.config.get_per('accounts', self.name, 'proxy')
+ use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
+ use_custom = gajim.config.get_per('accounts', self.name,
+ 'use_custom_host')
+ custom_h = gajim.config.get_per('accounts', self.name, 'custom_host')
+ custom_p = gajim.config.get_per('accounts', self.name, 'custom_port')
+
+ # create connection if it doesn't already exist
+ self.connected = 1
+ if p and p in gajim.config.get_per('proxies'):
+ proxy = {}
+ proxyptr = gajim.config.get_per('proxies', p)
+ for key in proxyptr.keys():
+ proxy[key] = proxyptr[key][1]
+
+ elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
+ try:
+ try:
+ env_http_proxy = os.environ['HTTP_PROXY']
+ except Exception:
+ env_http_proxy = os.environ['http_proxy']
+ env_http_proxy = env_http_proxy.strip('"')
+ # Dispose of the http:// prefix
+ env_http_proxy = env_http_proxy.split('://')
+ env_http_proxy = env_http_proxy[len(env_http_proxy)-1]
+ env_http_proxy = env_http_proxy.split('@')
+
+ if len(env_http_proxy) == 2:
+ login = env_http_proxy[0].split(':')
+ addr = env_http_proxy[1].split(':')
+ else:
+ login = ['', '']
+ addr = env_http_proxy[0].split(':')
+
+ proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]}
+
+ if len(addr) == 2:
+ proxy['port'] = addr[1]
+ else:
+ proxy['port'] = 3128
+
+ if len(login) == 2:
+ proxy['password'] = login[1]
+ else:
+ proxy['password'] = u''
+
+ except Exception:
+ proxy = None
+ else:
+ proxy = None
+ h = hostname
+ p = 5222
+ ssl_p = 5223
+# use_srv = False # wants ssl? disable srv lookup
+ if use_custom:
+ h = custom_h
+ p = custom_p
+ ssl_p = custom_p
+ use_srv = False
+
+ # SRV resolver
+ self._proxy = proxy
+ self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
+ 'weight': 10} ]
+ self._hostname = hostname
+ if use_srv:
+ # add request for srv query to the resolve, on result '_on_resolve'
+ # will be called
+ gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h),
+ self._on_resolve)
+ else:
+ self._on_resolve('', [])
+
+ def _on_resolve(self, host, result_array):
+ # SRV query returned at least one valid result, we put it in hosts dict
+ if len(result_array) != 0:
+ self._hosts = [i for i in result_array]
+ # Add ssl port
+ ssl_p = 5223
+ if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
+ ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port')
+ for i in self._hosts:
+ i['ssl_port'] = ssl_p
+ self._connect_to_next_host()
+
+
+ def _connect_to_next_host(self, retry=False):
+ log.debug('Connection to next host')
+ if len(self._hosts):
+ # No config option exist when creating a new account
+ if self.last_connection_type:
+ self._connection_types = [self.last_connection_type]
+ elif self.name in gajim.config.get_per('accounts'):
+ self._connection_types = gajim.config.get_per('accounts', self.name,
+ 'connection_types').split()
+ else:
+ self._connection_types = ['tls', 'ssl', 'plain']
+
+ if self._proxy and self._proxy['type']=='bosh':
+ # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
+ # connection and TLS with handshake right after TCP connecting ("ssl")
+ scheme = common.xmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
+ if scheme=='https':
+ self._connection_types = ['ssl']
+ else:
+ self._connection_types = ['plain']
+
+ host = self._select_next_host(self._hosts)
+ self._current_host = host
+ self._hosts.remove(host)
+ self.connect_to_next_type()
+
+ else:
+ if not retry and self.retrycount == 0:
+ log.debug("Out of hosts, giving up connecting to %s", self.name)
+ self.time_to_reconnect = None
+ if self.on_connect_failure:
+ self.on_connect_failure()
+ self.on_connect_failure = None
+ else:
+ # shown error dialog
+ self._connection_lost()
+ else:
+ # try reconnect if connection has failed before auth to server
+ self._disconnectedReconnCB()
+
+ def connect_to_next_type(self, retry=False):
+ if len(self._connection_types):
+ self._current_type = self._connection_types.pop(0)
+ if self.last_connection:
+ self.last_connection.socket.disconnect()
+ self.last_connection = None
+ self.connection = None
+
+ if self._current_type == 'ssl':
+ # SSL (force TLS on different port than plain)
+ # If we do TLS over BOSH, port of XMPP server should be the standard one
+ # and TLS should be negotiated because TLS on 5223 is deprecated
+ if self._proxy and self._proxy['type']=='bosh':
+ port = self._current_host['port']
+ else:
+ port = self._current_host['ssl_port']
+ elif self._current_type == 'tls':
+ # TLS - negotiate tls after XMPP stream is estabilished
+ port = self._current_host['port']
+ elif self._current_type == 'plain':
+ # plain connection on defined port
+ port = self._current_host['port']
+
+ cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
+ mycerts = common.gajim.MY_CACERTS
+ secure_tuple = (self._current_type, cacerts, mycerts)
+
+ con = common.xmpp.NonBlockingClient(
+ domain=self._hostname,
+ caller=self,
+ idlequeue=gajim.idlequeue)
+
+ self.last_connection = con
+ # increase default timeout for server responses
+ common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
+ # FIXME: this is a hack; need a better way
+ if self.on_connect_success == self._on_new_account:
+ con.RegisterDisconnectHandler(self._on_new_account)
+
+ self.log_hosttype_info(port)
+ con.connect(
+ hostname=self._current_host['host'],
+ port=port,
+ on_connect=self.on_connect_success,
+ on_proxy_failure=self.on_proxy_failure,
+ on_connect_failure=self.connect_to_next_type,
+ proxy=self._proxy,
+ secure_tuple = secure_tuple)
+ else:
+ self._connect_to_next_host(retry)
+
+ def log_hosttype_info(self, port):
+ msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
+ self._current_host['host'], port, self._current_type)
+ log.info(msg)
+ if self._proxy:
+ msg = '>>>>>> '
+ if self._proxy['type']=='bosh':
+ msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri'])
+ if self._proxy['type'] in ['http','socks5'] or self._proxy['bosh_useproxy']:
+ msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
+ log.info(msg)
+
+ def _connect_failure(self, con_type=None):
+ if not con_type:
+ # we are not retrying, and not conecting
+ if not self.retrycount and self.connected != 0:
+ self.disconnect(on_purpose = True)
+ self.dispatch('STATUS', 'offline')
+ pritxt = _('Could not connect to "%s"') % self._hostname
+ sectxt = _('Check your connection or try again later.')
+ if self.streamError:
+ # show error dialog
+ key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError
+ if key in common.xmpp.ERRORS:
+ sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2]
+ self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt)))
+ return
+ # show popup
+ self.dispatch('CONNECTION_LOST', (pritxt, sectxt))
+
+ def on_proxy_failure(self, reason):
+ log.error('Connection to proxy failed: %s' % reason)
+ self.time_to_reconnect = None
+ self.on_connect_failure = None
+ self.disconnect(on_purpose = True)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('CONNECTION_LOST',
+ (_('Connection to proxy failed'), reason))
+
+ def _connect_success(self, con, con_type):
+ if not self.connected: # We went offline during connecting process
+ # FIXME - not possible, maybe it was when we used threads
+ return
+ _con_type = con_type
+ if _con_type != self._current_type:
+ log.info('Connecting to next type beacuse desired is %s and returned is %s'
+ % (self._current_type, _con_type))
+ self.connect_to_next_type()
+ return
+ con.RegisterDisconnectHandler(self._on_disconnected)
+ if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
+ 'warn_when_plaintext_connection'):
+ self.dispatch('PLAIN_CONNECTION', (con,))
+ return True
+ if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \
+ and gajim.config.get_per('accounts', self.name,
+ 'warn_when_insecure_ssl_connection') and \
+ not self.connection_auto_accepted:
+ # Pyopenssl is not used
+ self.dispatch('INSECURE_SSL_CONNECTION', (con, _con_type))
+ return True
+ return self.connection_accepted(con, con_type)
+
+ def connection_accepted(self, con, con_type):
+ if not con or not con.Connection:
+ self.disconnect(on_purpose=True)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not connect to account %s') % self.name,
+ _('Connection with account %s has been lost. Retry connecting.') % \
+ self.name))
+ return
+ self.hosts = []
+ self.connection_auto_accepted = False
+ self.connected_hostname = self._current_host['host']
+ self.on_connect_failure = None
+ con.UnregisterDisconnectHandler(self._on_disconnected)
+ con.RegisterDisconnectHandler(self._disconnectedReconnCB)
+ log.debug('Connected to server %s:%s with %s' % (
+ self._current_host['host'], self._current_host['port'], con_type))
+
+ self.last_connection_type = con_type
+ if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
+ name = None
+ else:
+ name = gajim.config.get_per('accounts', self.name, 'name')
+ hostname = gajim.config.get_per('accounts', self.name, 'hostname')
+ self.connection = con
+ try:
+ errnum = con.Connection.ssl_errnum
+ except AttributeError:
+ errnum = -1 # we don't have an errnum
+ if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts',
+ self.name, 'ignore_ssl_errors'):
+ text = _('The authenticity of the %s certificate could be invalid.') %\
+ hostname
+ if errnum in ssl_error:
+ text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
+ else:
+ text += _('\nUnknown SSL error: %d') % errnum
+ self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem,
+ con.Connection.ssl_fingerprint_sha1))
+ return True
+ if hasattr(con.Connection, 'ssl_fingerprint_sha1'):
+ saved_fingerprint = gajim.config.get_per('accounts', self.name, 'ssl_fingerprint_sha1')
+ if saved_fingerprint:
+ # Check sha1 fingerprint
+ if con.Connection.ssl_fingerprint_sha1 != saved_fingerprint:
+ self.dispatch('FINGERPRINT_ERROR',
+ (con.Connection.ssl_fingerprint_sha1,))
+ return True
+ else:
+ gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1',
+ con.Connection.ssl_fingerprint_sha1)
+ self._register_handlers(con, con_type)
+ con.auth(
+ user=name,
+ password=self.password,
+ resource=self.server_resource,
+ sasl=1,
+ on_auth=self.__on_auth)
+
+ def ssl_certificate_accepted(self):
+ if not self.connection:
+ self.disconnect(on_purpose=True)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not connect to account %s') % self.name,
+ _('Connection with account %s has been lost. Retry connecting.') % \
+ self.name))
+ return
+ name = gajim.config.get_per('accounts', self.name, 'name')
+ self._register_handlers(self.connection, 'ssl')
+ self.connection.auth(name, self.password, self.server_resource, 1,
+ self.__on_auth)
+
+ def _register_handlers(self, con, con_type):
+ self.peerhost = con.get_peerhost()
+ # notify the gui about con_type
+ self.dispatch('CON_TYPE', con_type)
+ ConnectionHandlers._register_handlers(self, con, con_type)
+
+ def __on_auth(self, con, auth):
+ if not con:
+ self.disconnect(on_purpose=True)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not connect to "%s"') % self._hostname,
+ _('Check your connection or try again later')))
+ if self.on_connect_auth:
+ self.on_connect_auth(None)
+ self.on_connect_auth = None
+ return
+ if not self.connected: # We went offline during connecting process
+ if self.on_connect_auth:
+ self.on_connect_auth(None)
+ self.on_connect_auth = None
+ return
+ if hasattr(con, 'Resource'):
+ self.server_resource = con.Resource
+ if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
+ # Get jid given by server
+ old_jid = gajim.get_jid_from_account(self.name)
+ gajim.config.set_per('accounts', self.name, 'name', con.User)
+ new_jid = gajim.get_jid_from_account(self.name)
+ self.dispatch('NEW_JID', (old_jid, new_jid))
+ if auth:
+ self.last_io = gajim.idlequeue.current_time()
+ self.connected = 2
+ self.retrycount = 0
+ if self.on_connect_auth:
+ self.on_connect_auth(con)
+ self.on_connect_auth = None
+ else:
+ # Forget password, it's wrong
+ self.password = None
+ gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
+ self.disconnect(on_purpose = True)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('ERROR', (_('Authentication failed with "%s"') % \
+ self._hostname,
+ _('Please check your login and password for correctness.')))
+ if self.on_connect_auth:
+ self.on_connect_auth(None)
+ self.on_connect_auth = None
+ # END connect
+
+ def add_lang(self, stanza):
+ if self.lang:
+ stanza.setAttr('xml:lang', self.lang)
+
+ def get_privacy_lists(self):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.getPrivacyLists(self.connection)
+
+ def send_keepalive(self):
+ # nothing received for the last foo seconds
+ if self.connection:
+ self.connection.send(' ')
+
+ def _on_xmpp_ping_answer(self, iq_obj):
+ id_ = unicode(iq_obj.getAttr('id'))
+ assert id_ == self.awaiting_xmpp_ping_id
+ self.awaiting_xmpp_ping_id = None
+
+ def sendPing(self, pingTo=None):
+ """
+ Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to
+ server to detect connection failure at application level
+ """
+ if not self.connection:
+ return
+ id_ = self.connection.getAnID()
+ if pingTo:
+ to = pingTo.get_full_jid()
+ self.dispatch('PING_SENT', (pingTo))
+ else:
+ to = gajim.config.get_per('accounts', self.name, 'hostname')
+ self.awaiting_xmpp_ping_id = id_
+ iq = common.xmpp.Iq('get', to=to)
+ iq.addChild(name = 'ping', namespace = common.xmpp.NS_PING)
+ iq.setID(id_)
+ def _on_response(resp):
+ timePong = time_time()
+ if not common.xmpp.isResultNode(resp):
+ self.dispatch('PING_ERROR', (pingTo))
+ return
+ timeDiff = round(timePong - timePing,2)
+ self.dispatch('PING_REPLY', (pingTo, timeDiff))
+ if pingTo:
+ timePing = time_time()
+ self.connection.SendAndCallForResponse(iq, _on_response)
+ else:
+ self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer)
+ gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per(
+ 'accounts', self.name, 'time_for_ping_alive_answer'))
+
+ def get_active_default_lists(self):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
+
+ def del_privacy_list(self, privacy_list):
+ if not self.connection:
+ return
+ def _on_del_privacy_list_result(result):
+ if result:
+ self.dispatch('PRIVACY_LIST_REMOVED', privacy_list)
+ else:
+ self.dispatch('ERROR', (_('Error while removing privacy list'),
+ _('Privacy list %s has not been removed. It is maybe active in '
+ 'one of your connected resources. Deactivate it and try '
+ 'again.') % privacy_list))
+ common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list,
+ _on_del_privacy_list_result)
+
+ def get_privacy_list(self, title):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.getPrivacyList(self.connection, title)
+
+ def set_privacy_list(self, listname, tags):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
+
+ def set_active_list(self, listname):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
+
+ def set_default_list(self, listname):
+ if not self.connection:
+ return
+ common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
+
+ def build_privacy_rule(self, name, action, order=1):
+ """
+ Build a Privacy rule stanza for invisibility
+ """
+ iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
+ l = iq.getTag('query').setTag('list', {'name': name})
+ i = l.setTag('item', {'action': action, 'order': str(order)})
+ i.setTag('presence-out')
+ return iq
+
+ def build_invisible_rule(self):
+ iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
+ l = iq.getTag('query').setTag('list', {'name': 'invisible'})
+ if self.name in gajim.interface.status_sent_to_groups and \
+ len(gajim.interface.status_sent_to_groups[self.name]) > 0:
+ for group in gajim.interface.status_sent_to_groups[self.name]:
+ i = l.setTag('item', {'type': 'group', 'value': group,
+ 'action': 'allow', 'order': '1'})
+ i.setTag('presence-out')
+ if self.name in gajim.interface.status_sent_to_users and \
+ len(gajim.interface.status_sent_to_users[self.name]) > 0:
+ for jid in gajim.interface.status_sent_to_users[self.name]:
+ i = l.setTag('item', {'type': 'jid', 'value': jid,
+ 'action': 'allow', 'order': '2'})
+ i.setTag('presence-out')
+ i = l.setTag('item', {'action': 'deny', 'order': '3'})
+ i.setTag('presence-out')
+ return iq
+
+ def set_invisible_rule(self):
+ if not gajim.account_is_connected(self.name):
+ return
+ iq = self.build_invisible_rule()
+ self.connection.send(iq)
+
+ def activate_privacy_rule(self, name):
+ """
+ Activate a privacy rule
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
+ iq.getTag('query').setTag('active', {'name': name})
+ self.connection.send(iq)
+
+ def send_invisible_presence(self, msg, signed, initial = False):
+ if not self.connection:
+ return
+ if not self.privacy_rules_supported:
+ self.dispatch('STATUS', gajim.SHOW_LIST[self.connected])
+ self.dispatch('ERROR', (_('Invisibility not supported'),
+ _('Account %s doesn\'t support invisibility.') % self.name))
+ return
+ # If we are already connected, and privacy rules are supported, send
+ # offline presence first as it's required by XEP-0126
+ if self.connected > 1 and self.privacy_rules_supported:
+ self.on_purpose = True
+ p = common.xmpp.Presence(typ = 'unavailable')
+ p = self.add_sha(p, False)
+ if msg:
+ p.setStatus(msg)
+ self.remove_all_transfers()
+ self.connection.send(p)
+
+ # try to set the privacy rule
+ iq = self.build_invisible_rule()
+ self.connection.SendAndCallForResponse(iq, self._continue_invisible,
+ {'msg': msg, 'signed': signed, 'initial': initial})
+
+ def _continue_invisible(self, con, iq_obj, msg, signed, initial):
+ if iq_obj.getType() == 'error': # server doesn't support privacy lists
+ return
+ # active the privacy rule
+ self.privacy_rules_supported = True
+ self.activate_privacy_rule('invisible')
+ self.connected = gajim.SHOW_LIST.index('invisible')
+ self.status = msg
+ priority = unicode(gajim.get_priority(self.name, 'invisible'))
+ p = common.xmpp.Presence(priority = priority)
+ p = self.add_sha(p, True)
+ if msg:
+ p.setStatus(msg)
+ if signed:
+ p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
+ self.connection.send(p)
+ self.priority = priority
+ self.dispatch('STATUS', 'invisible')
+ if initial:
+ # ask our VCard
+ self.request_vcard(None)
+
+ # Get bookmarks from private namespace
+ self.get_bookmarks()
+
+ # Get annotations
+ self.get_annotations()
+
+ # Inform GUI we just signed in
+ self.dispatch('SIGNED_IN', ())
+
+ def get_signed_presence(self, msg, callback = None):
+ if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'):
+ return self.get_signed_msg(msg, callback)
+ return ''
+
+ def connect_and_auth(self):
+ self.on_connect_success = self._connect_success
+ self.on_connect_failure = self._connect_failure
+ self.connect()
+
+ def connect_and_init(self, show, msg, sign_msg):
+ self.continue_connect_info = [show, msg, sign_msg]
+ self.on_connect_auth = self._discover_server_at_connection
+ self.connect_and_auth()
+
+ def _discover_server_at_connection(self, con):
+ self.connection = con
+ if not self.connection:
+ return
+ self.connection.set_send_timeout(self.keepalives, self.send_keepalive)
+ self.connection.set_send_timeout2(self.pingalives, self.sendPing)
+ self.connection.onreceive(None)
+ self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'),
+ id_prefix='Gajim_')
+ self.privacy_rules_requested = False
+ # Discover Stun server(s)
+ gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(
+ self.connected_hostname), self._on_stun_resolved)
+
+ def _on_stun_resolved(self, host, result_array):
+ if len(result_array) != 0:
+ self._stun_servers = self._hosts = [i for i in result_array]
+
+ def _request_privacy(self):
+ iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '')
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ self.awaiting_answers[id_] = (PRIVACY_ARRIVED, )
+ self.connection.send(iq)
+
+ def send_custom_status(self, show, msg, jid):
+ if not show in gajim.SHOW_LIST:
+ return -1
+ if not self.connection:
+ return
+ sshow = helpers.get_xmpp_show(show)
+ if not msg:
+ msg = ''
+ if show == 'offline':
+ p = common.xmpp.Presence(typ = 'unavailable', to = jid)
+ p = self.add_sha(p, False)
+ if msg:
+ p.setStatus(msg)
+ else:
+ signed = self.get_signed_presence(msg)
+ priority = unicode(gajim.get_priority(self.name, sshow))
+ p = common.xmpp.Presence(typ = None, priority = priority, show = sshow,
+ to = jid)
+ p = self.add_sha(p)
+ if msg:
+ p.setStatus(msg)
+ if signed:
+ p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
+ self.connection.send(p)
+
+ def _change_to_invisible(self, msg):
+ signed = self.get_signed_presence(msg)
+ self.send_invisible_presence(msg, signed)
+
+ def _change_from_invisible(self):
+ if self.privacy_rules_supported:
+ iq = self.build_privacy_rule('visible', 'allow')
+ self.connection.send(iq)
+ self.activate_privacy_rule('visible')
+
+ def _update_status(self, show, msg):
+ xmpp_show = helpers.get_xmpp_show(show)
+ priority = unicode(gajim.get_priority(self.name, xmpp_show))
+ p = common.xmpp.Presence(typ=None, priority=priority, show=xmpp_show)
+ p = self.add_sha(p)
+ if msg:
+ p.setStatus(msg)
+ signed = self.get_signed_presence(msg)
+ if signed:
+ p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
+ if self.connection:
+ self.connection.send(p)
+ self.priority = priority
+ self.dispatch('STATUS', show)
+
+ def send_motd(self, jid, subject = '', msg = '', xhtml = None):
+ if not self.connection:
+ return
+ msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject,
+ xhtml = xhtml)
+
+ self.connection.send(msg_iq)
+
+ def send_message(self, jid, msg, keyID, type_='chat', subject='',
+ chatstate=None, msg_id=None, composing_xep=None, resource=None,
+ user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
+ original_message=None, delayed=None, callback=None, callback_args=[]):
+
+ def cb(jid, msg, keyID, forward_from, session, original_message, subject,
+ type_, msg_iq):
+ msg_id = self.connection.send(msg_iq)
+ jid = helpers.parse_jid(jid)
+ self.dispatch('MSGSENT', (jid, msg, keyID))
+ if callback:
+ callback(msg_id, *callback_args)
+
+ self.log_message(jid, msg, forward_from, session, original_message,
+ subject, type_)
+
+ self._prepare_message(jid, msg, keyID, type_=type_, subject=subject,
+ chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
+ resource=resource, user_nick=user_nick, xhtml=xhtml, session=session,
+ forward_from=forward_from, form_node=form_node,
+ original_message=original_message, delayed=delayed, callback=cb)
+
+ def send_contacts(self, contacts, jid):
+ """
+ Send contacts with RosterX (Xep-0144)
+ """
+ if not self.connection:
+ return
+ if len(contacts) == 1:
+ msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(),
+ contacts[0].get_shown_name())
+ else:
+ msg = _('Sent contacts:')
+ for contact in contacts:
+ msg += '\n "%s" (%s)' % (contact.get_full_jid(),
+ contact.get_shown_name())
+ msg_iq = common.xmpp.Message(to=jid, body=msg)
+ x = msg_iq.addChild(name='x', namespace=common.xmpp.NS_ROSTERX)
+ for contact in contacts:
+ x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid,
+ 'name': contact.get_shown_name()})
+ self.connection.send(msg_iq)
+
+ def send_stanza(self, stanza):
+ """
+ Send a stanza untouched
+ """
+ if not self.connection:
+ return
+ self.connection.send(stanza)
+
+ def ack_subscribed(self, jid):
+ if not self.connection:
+ return
+ log.debug('ack\'ing subscription complete for %s' % jid)
+ p = common.xmpp.Presence(jid, 'subscribe')
+ self.connection.send(p)
+
+ def ack_unsubscribed(self, jid):
+ if not self.connection:
+ return
+ log.debug('ack\'ing unsubscription complete for %s' % jid)
+ p = common.xmpp.Presence(jid, 'unsubscribe')
+ self.connection.send(p)
+
+ def request_subscription(self, jid, msg='', name='', groups=[],
+ auto_auth=False, user_nick=''):
+ if not self.connection:
+ return
+ log.debug('subscription request for %s' % jid)
+ if auto_auth:
+ self.jids_for_auto_auth.append(jid)
+ # RFC 3921 section 8.2
+ infos = {'jid': jid}
+ if name:
+ infos['name'] = name
+ iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER)
+ q = iq.getTag('query')
+ item = q.addChild('item', attrs=infos)
+ for g in groups:
+ item.addChild('group').setData(g)
+ self.connection.send(iq)
+
+ p = common.xmpp.Presence(jid, 'subscribe')
+ if user_nick:
+ p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick)
+ p = self.add_sha(p)
+ if msg:
+ p.setStatus(msg)
+ self.connection.send(p)
+
+ def send_authorization(self, jid):
+ if not self.connection:
+ return
+ p = common.xmpp.Presence(jid, 'subscribed')
+ p = self.add_sha(p)
+ self.connection.send(p)
+
+ def refuse_authorization(self, jid):
+ if not self.connection:
+ return
+ p = common.xmpp.Presence(jid, 'unsubscribed')
+ p = self.add_sha(p)
+ self.connection.send(p)
+
+ def unsubscribe(self, jid, remove_auth = True):
+ if not self.connection:
+ return
+ if remove_auth:
+ self.connection.getRoster().delItem(jid)
+ jid_list = gajim.config.get_per('contacts')
+ for j in jid_list:
+ if j.startswith(jid):
+ gajim.config.del_per('contacts', j)
+ else:
+ self.connection.getRoster().Unsubscribe(jid)
+ self.update_contact(jid, '', [])
+
+ def unsubscribe_agent(self, agent):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
+ iq.getTag('query').setTag('remove')
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
+ self.connection.send(iq)
+ self.connection.getRoster().delItem(agent)
+
+ def send_new_account_infos(self, form, is_form):
+ if is_form:
+ # Get username and password and put them in new_account_info
+ for field in form.iter_fields():
+ if field.var == 'username':
+ self.new_account_info['name'] = field.value
+ if field.var == 'password':
+ self.new_account_info['password'] = field.value
+ else:
+ # Get username and password and put them in new_account_info
+ if 'username' in form:
+ self.new_account_info['name'] = form['username']
+ if 'password' in form:
+ self.new_account_info['password'] = form['password']
+ self.new_account_form = form
+ self.new_account(self.name, self.new_account_info)
+
+ def new_account(self, name, config, sync=False):
+ # If a connection already exist we cannot create a new account
+ if self.connection:
+ return
+ self._hostname = config['hostname']
+ self.new_account_info = config
+ self.name = name
+ self.on_connect_success = self._on_new_account
+ self.on_connect_failure = self._on_new_account
+ self.connect(config)
+
+ def _on_new_account(self, con=None, con_type=None):
+ if not con_type:
+ if len(self._connection_types) or len(self._hosts):
+ # There are still other way to try to connect
+ return
+ self.dispatch('NEW_ACC_NOT_CONNECTED',
+ (_('Could not connect to "%s"') % self._hostname))
+ return
+ self.on_connect_failure = None
+ self.connection = con
+ common.xmpp.features_nb.getRegInfo(con, self._hostname)
+
+ def request_last_status_time(self, jid, resource, groupchat_jid=None):
+ """
+ groupchat_jid is used when we want to send a request to a real jid and
+ act as if the answer comes from the groupchat_jid
+ """
+ if not self.connection:
+ return
+ to_whom_jid = jid
+ if resource:
+ to_whom_jid += '/' + resource
+ iq = common.xmpp.Iq(to = to_whom_jid, typ = 'get', queryNS =\
+ common.xmpp.NS_LAST)
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ if groupchat_jid:
+ self.groupchat_jids[id_] = groupchat_jid
+ self.last_ids.append(id_)
+ self.connection.send(iq)
+
+ def request_os_info(self, jid, resource, groupchat_jid=None):
+ """
+ groupchat_jid is used when we want to send a request to a real jid and
+ act as if the answer comes from the groupchat_jid
+ """
+ if not self.connection:
+ return
+ # If we are invisible, do not request
+ if self.connected == gajim.SHOW_LIST.index('invisible'):
+ self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
+ return
+ to_whom_jid = jid
+ if resource:
+ to_whom_jid += '/' + resource
+ iq = common.xmpp.Iq(to=to_whom_jid, typ='get', queryNS=\
+ common.xmpp.NS_VERSION)
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ if groupchat_jid:
+ self.groupchat_jids[id_] = groupchat_jid
+ self.version_ids.append(id_)
+ self.connection.send(iq)
+
+ def request_entity_time(self, jid, resource, groupchat_jid=None):
+ """
+ groupchat_jid is used when we want to send a request to a real jid and
+ act as if the answer comes from the groupchat_jid
+ """
+ if not self.connection:
+ return
+ # If we are invisible, do not request
+ if self.connected == gajim.SHOW_LIST.index('invisible'):
+ self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status')))
+ return
+ to_whom_jid = jid
+ if resource:
+ to_whom_jid += '/' + resource
+ iq = common.xmpp.Iq(to=to_whom_jid, typ='get')
+ iq.addChild('time', namespace=common.xmpp.NS_TIME_REVISED)
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ if groupchat_jid:
+ self.groupchat_jids[id_] = groupchat_jid
+ self.entity_time_ids.append(id_)
+ self.connection.send(iq)
+
+ def get_settings(self):
+ """
+ Get Gajim settings as described in XEP 0049
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ='get')
+ iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
+ iq2.addChild(name='gajim', namespace='gajim:prefs')
+ self.connection.send(iq)
+
+ def _request_bookmarks_xml(self):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ='get')
+ iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
+ iq2.addChild(name='storage', namespace='storage:bookmarks')
+ self.connection.send(iq)
+
+ def _check_bookmarks_received(self):
+ if not self.bookmarks:
+ self._request_bookmarks_xml()
+
+ def get_bookmarks(self, storage_type=None):
+ """
+ Get Bookmarks from storage or PubSub if supported as described in XEP
+ 0048
+
+ storage_type can be set to xml to force request to xml storage
+ """
+ if not self.connection:
+ return
+ if self.pubsub_supported and storage_type != 'xml':
+ self.send_pb_retrieve('', 'storage:bookmarks')
+ # some server (ejabberd) are so slow to answer that we request via XML
+ # if we don't get answer in the next 30 seconds
+ gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30)
+ else:
+ self._request_bookmarks_xml()
+
+ def store_bookmarks(self, storage_type=None):
+ """
+ Send bookmarks to the storage namespace or PubSub if supported
+
+ storage_type can be set to 'pubsub' or 'xml' so store in only one method
+ else it will be stored on both
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'})
+ for bm in self.bookmarks:
+ iq2 = iq.addChild(name = "conference")
+ iq2.setAttr('jid', bm['jid'])
+ iq2.setAttr('autojoin', bm['autojoin'])
+ iq2.setAttr('minimize', bm['minimize'])
+ iq2.setAttr('name', bm['name'])
+ # Only add optional elements if not empty
+ # Note: need to handle both None and '' as empty
+ # thus shouldn't use "is not None"
+ if bm.get('nick', None):
+ iq2.setTagData('nick', bm['nick'])
+ if bm.get('password', None):
+ iq2.setTagData('password', bm['password'])
+ if bm.get('print_status', None):
+ iq2.setTagData('print_status', bm['print_status'])
+
+ if self.pubsub_supported and storage_type != 'xml':
+ if self.pubsub_publish_options_supported:
+ options = common.xmpp.Node(common.xmpp.NS_DATA + ' x',
+ attrs={'type': 'submit'})
+ f = options.addChild('field', attrs={'var': 'FORM_TYPE',
+ 'type': 'hidden'})
+ f.setTagData('value', common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS)
+ f = options.addChild('field', attrs={'var': 'pubsub#persist_items'})
+ f.setTagData('value', 'true')
+ f = options.addChild('field', attrs={'var': 'pubsub#access_model'})
+ f.setTagData('value', 'whitelist')
+ else:
+ options = None
+ self.send_pb_publish('', 'storage:bookmarks', iq, 'current',
+ options=options)
+ if storage_type != 'pubsub':
+ iqA = common.xmpp.Iq(typ='set')
+ iqB = iqA.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
+ iqB.addChild(node=iq)
+ self.connection.send(iqA)
+
+ def get_annotations(self):
+ """
+ Get Annonations from storage as described in XEP 0048, and XEP 0145
+ """
+ self.annotations = {}
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ='get')
+ iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
+ iq2.addChild(name='storage', namespace='storage:rosternotes')
+ self.connection.send(iq)
+
+ def store_annotations(self):
+ """
+ Set Annonations in private storage as described in XEP 0048, and XEP 0145
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ='set')
+ iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
+ iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes')
+ for jid in self.annotations.keys():
+ if self.annotations[jid]:
+ iq4 = iq3.addChild(name = "note")
+ iq4.setAttr('jid', jid)
+ iq4.setData(self.annotations[jid])
+ self.connection.send(iq)
+
+
+ def get_metacontacts(self):
+ """
+ Get metacontacts list from storage as described in XEP 0049
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ='get')
+ iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
+ iq2.addChild(name='storage', namespace='storage:metacontacts')
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, )
+ self.connection.send(iq)
+
+ def store_metacontacts(self, tags_list):
+ """
+ Send meta contacts to the storage namespace
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ='set')
+ iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
+ iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
+ for tag in tags_list:
+ for data in tags_list[tag]:
+ jid = data['jid']
+ dict_ = {'jid': jid, 'tag': tag}
+ if 'order' in data:
+ dict_['order'] = data['order']
+ iq3.addChild(name = 'meta', attrs = dict_)
+ self.connection.send(iq)
+
+ def send_agent_status(self, agent, ptype):
+ if not self.connection:
+ return
+ show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
+ p = common.xmpp.Presence(to = agent, typ = ptype, show = show)
+ p = self.add_sha(p, ptype != 'unavailable')
+ self.connection.send(p)
+
+ def check_unique_room_id_support(self, server, instance):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'get', to = server)
+ iq.setAttr('id', 'unique1')
+ iq.addChild('unique', namespace=common.xmpp.NS_MUC_UNIQUE)
+ def _on_response(resp):
+ if not common.xmpp.isResultNode(resp):
+ self.dispatch('UNIQUE_ROOM_ID_UNSUPPORTED', (server, instance))
+ return
+ self.dispatch('UNIQUE_ROOM_ID_SUPPORTED', (server, instance,
+ resp.getTag('unique').getData()))
+ self.connection.SendAndCallForResponse(iq, _on_response)
+
+ def join_gc(self, nick, room_jid, password, change_nick=False):
+ # FIXME: This room JID needs to be normalized; see #1364
+ if not self.connection:
+ return
+ show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
+ if show == 'invisible':
+ # Never join a room when invisible
+ return
+
+ # last date/time in history to avoid duplicate
+ if room_jid not in self.last_history_time:
+ # Not in memory, get it from DB
+ last_log = None
+ # Do not check if we are not logging for this room
+ if gajim.config.should_log(self.name, room_jid):
+ # Check time first in the FAST table
+ last_log = gajim.logger.get_room_last_message_time(room_jid)
+ if last_log is None:
+ # Not in special table, get it from messages DB
+ last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
+ is_room=True)
+ # Create self.last_history_time[room_jid] even if not logging,
+ # could be used in connection_handlers
+ if last_log is None:
+ last_log = 0
+ self.last_history_time[room_jid] = last_log
+
+ p = common.xmpp.Presence(to='%s/%s' % (room_jid, nick),
+ show=show, status=self.status)
+ h = hmac.new(self.secret_hmac, room_jid).hexdigest()[:6]
+ id_ = self.connection.getAnID()
+ id_ = 'gajim_muc_' + id_ + '_' + h
+ p.setID(id_)
+ if gajim.config.get('send_sha_in_gc_presence'):
+ p = self.add_sha(p)
+ self.add_lang(p)
+ if not change_nick:
+ t = p.setTag(common.xmpp.NS_MUC + ' x')
+ last_date = self.last_history_time[room_jid]
+ if last_date == 0:
+ last_date = time.time() - gajim.config.get(
+ 'muc_restore_timeout') * 60
+ else:
+ last_date = min(last_date, time.time() - gajim.config.get(
+ 'muc_restore_timeout') * 60)
+ last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(last_date))
+ t.setTag('history', {'maxstanzas': gajim.config.get(
+ 'muc_restore_lines'), 'since': last_date})
+ if password:
+ t.setTagData('password', password)
+ self.connection.send(p)
+
+ def send_gc_message(self, jid, msg, xhtml = None):
+ if not self.connection:
+ return
+ if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
+ from common.rst_xhtml_generator import create_xhtml
+ xhtml = create_xhtml(msg)
+ msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml)
+ self.connection.send(msg_iq)
+ self.dispatch('MSGSENT', (jid, msg))
+
+ def send_gc_subject(self, jid, subject):
+ if not self.connection:
+ return
+ msg_iq = common.xmpp.Message(jid,typ = 'groupchat', subject = subject)
+ self.connection.send(msg_iq)
+
+ def request_gc_config(self, room_jid):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER,
+ to = room_jid)
+ self.add_lang(iq)
+ self.connection.send(iq)
+
+ def destroy_gc_room(self, room_jid, reason = '', jid = ''):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER,
+ to = room_jid)
+ destroy = iq.getTag('query').setTag('destroy')
+ if reason:
+ destroy.setTagData('reason', reason)
+ if jid:
+ destroy.setAttr('jid', jid)
+ self.connection.send(iq)
+
+ def send_gc_status(self, nick, jid, show, status):
+ if not gajim.account_is_connected(self.name):
+ return
+ if show == 'invisible':
+ show = 'offline'
+ ptype = None
+ if show == 'offline':
+ ptype = 'unavailable'
+ xmpp_show = helpers.get_xmpp_show(show)
+ p = common.xmpp.Presence(to = '%s/%s' % (jid, nick), typ = ptype,
+ show = xmpp_show, status = status)
+ h = hmac.new(self.secret_hmac, jid).hexdigest()[:6]
+ id_ = self.connection.getAnID()
+ id_ = 'gajim_muc_' + id_ + '_' + h
+ p.setID(id_)
+ if gajim.config.get('send_sha_in_gc_presence') and show != 'offline':
+ p = self.add_sha(p, ptype != 'unavailable')
+ self.add_lang(p)
+ # send instantly so when we go offline, status is sent to gc before we
+ # disconnect from jabber server
+ self.connection.send(p)
+
+ def gc_got_disconnected(self, room_jid):
+ """
+ A groupchat got disconnected. This can be or purpose or not
+
+ Save the time we had last message to avoid duplicate logs AND be faster
+ than get that date from DB. Save time that we have in mem in a small
+ table (with fast access)
+ """
+ gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid])
+
+ def gc_set_role(self, room_jid, nick, role, reason = ''):
+ """
+ Role is for all the life of the room so it's based on nick
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
+ common.xmpp.NS_MUC_ADMIN)
+ item = iq.getTag('query').setTag('item')
+ item.setAttr('nick', nick)
+ item.setAttr('role', role)
+ if reason:
+ item.addChild(name = 'reason', payload = reason)
+ self.connection.send(iq)
+
+ def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
+ """
+ Affiliation is for all the life of the room so it's based on jid
+ """
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
+ common.xmpp.NS_MUC_ADMIN)
+ item = iq.getTag('query').setTag('item')
+ item.setAttr('jid', jid)
+ item.setAttr('affiliation', affiliation)
+ if reason:
+ item.addChild(name = 'reason', payload = reason)
+ self.connection.send(iq)
+
+ def send_gc_affiliation_list(self, room_jid, users_dict):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \
+ common.xmpp.NS_MUC_ADMIN)
+ item = iq.getTag('query')
+ for jid in users_dict:
+ item_tag = item.addChild('item', {'jid': jid,
+ 'affiliation': users_dict[jid]['affiliation']})
+ if 'reason' in users_dict[jid] and users_dict[jid]['reason']:
+ item_tag.setTagData('reason', users_dict[jid]['reason'])
+ self.connection.send(iq)
+
+ def get_affiliation_list(self, room_jid, affiliation):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \
+ common.xmpp.NS_MUC_ADMIN)
+ item = iq.getTag('query').setTag('item')
+ item.setAttr('affiliation', affiliation)
+ self.connection.send(iq)
+
+ def send_gc_config(self, room_jid, form):
+ if not self.connection:
+ return
+ iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
+ common.xmpp.NS_MUC_OWNER)
+ query = iq.getTag('query')
+ form.setAttr('type', 'submit')
+ query.addChild(node = form)
+ self.connection.send(iq)
+
+ def change_password(self, password):
+ if not self.connection:
+ return
+ hostname = gajim.config.get_per('accounts', self.name, 'hostname')
+ username = gajim.config.get_per('accounts', self.name, 'name')
+ iq = common.xmpp.Iq(typ = 'set', to = hostname)
+ q = iq.setTag(common.xmpp.NS_REGISTER + ' query')
+ q.setTagData('username',username)
+ q.setTagData('password',password)
+ self.connection.send(iq)
+
+ def get_password(self, callback):
+ if self.password:
+ callback(self.password)
+ return
+ self.pasword_callback = callback
+ self.dispatch('PASSWORD_REQUIRED', None)
+
+ def set_password(self, password):
+ self.password = password
+ if self.pasword_callback:
+ self.pasword_callback(password)
+ self.pasword_callback = None
+
+ def unregister_account(self, on_remove_success):
+ # no need to write this as a class method and keep the value of
+ # on_remove_success as a class property as pass it as an argument
+ def _on_unregister_account_connect(con):
+ self.on_connect_auth = None
+ if gajim.account_is_connected(self.name):
+ hostname = gajim.config.get_per('accounts', self.name, 'hostname')
+ iq = common.xmpp.Iq(typ = 'set', to = hostname)
+ iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove')
+ def _on_answer(result):
+ if result.getType() == 'result':
+ on_remove_success(True)
+ return
+ self.dispatch('ERROR', (_('Unregister failed'),
+ _('Unregistration with server %(server)s failed: %(error)s') \
+ % {'server': hostname, 'error': result.getErrorMsg()}))
+ on_remove_success(False)
+ con.SendAndCallForResponse(iq, _on_answer)
+ return
+ on_remove_success(False)
+ if self.connected == 0:
+ self.on_connect_auth = _on_unregister_account_connect
+ self.connect_and_auth()
+ else:
+ _on_unregister_account_connect(self.connection)
+
+ def send_invite(self, room, to, reason='', continue_tag=False):
+ """
+ Send invitation
+ """
+ message=common.xmpp.Message(to = room)
+ c = message.addChild(name = 'x', namespace = common.xmpp.NS_MUC_USER)
+ c = c.addChild(name = 'invite', attrs={'to' : to})
+ if continue_tag:
+ c.addChild(name = 'continue')
+ if reason != '':
+ c.setTagData('reason', reason)
+ self.connection.send(message)
+
+ def check_pingalive(self):
+ if self.awaiting_xmpp_ping_id:
+ # We haven't got the pong in time, disco and reconnect
+ log.warn("No reply received for keepalive ping. Reconnecting.")
+ self._disconnectedReconnCB()
+
+ def _reconnect_alarm(self):
+ if self.time_to_reconnect:
+ if self.connected < 2:
+ self._reconnect()
+ else:
+ self.time_to_reconnect = None
+
+ def request_search_fields(self, jid):
+ iq = common.xmpp.Iq(typ = 'get', to = jid, queryNS = \
+ common.xmpp.NS_SEARCH)
+ self.connection.send(iq)
+
+ def send_search_form(self, jid, form, is_form):
+ iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \
+ common.xmpp.NS_SEARCH)
+ item = iq.getTag('query')
+ if is_form:
+ item.addChild(node = form)
+ else:
+ for i in form.keys():
+ item.setTagData(i,form[i])
+ def _on_response(resp):
+ jid = jid = helpers.get_jid_from_iq(resp)
+ tag = resp.getTag('query', namespace = common.xmpp.NS_SEARCH)
+ if not tag:
+ self.dispatch('SEARCH_RESULT', (jid, None, False))
+ return
+ df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
+ if df:
+ self.dispatch('SEARCH_RESULT', (jid, df, True))
+ return
+ df = []
+ for item in tag.getTags('item'):
+ # We also show attributes. jid is there
+ f = item.attrs
+ for i in item.getPayload():
+ f[i.getName()] = i.getData()
+ df.append(f)
+ self.dispatch('SEARCH_RESULT', (jid, df, False))
+
+ self.connection.SendAndCallForResponse(iq, _on_response)
+
+ def load_roster_from_db(self):
+ roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name))
+ self.dispatch('ROSTER', roster)
# END Connection
-
-# vim: se ts=3:
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 66cbbd82d..818699620 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -36,7 +36,7 @@ import hashlib
import hmac
from time import (altzone, daylight, gmtime, localtime, mktime, strftime,
- time as time_time, timezone, tzname)
+ time as time_time, timezone, tzname)
from calendar import timegm
import datetime
@@ -52,18 +52,18 @@ from common.protocol.caps import ConnectionCaps
from common.protocol.bytestream import ConnectionBytestream
import common.caps_cache as capscache
if gajim.HAVE_FARSIGHT:
- from common.jingle import ConnectionJingle
+ from common.jingle import ConnectionJingle
else:
- class ConnectionJingle():
- def __init__(self):
- pass
- def _JingleCB(self, con, stanza):
- pass
+ class ConnectionJingle():
+ def __init__(self):
+ pass
+ def _JingleCB(self, con, stanza):
+ pass
from common import dbus_support
if dbus_support.supported:
- import dbus
- from music_track_listener import MusicTrackListener
+ import dbus
+ from music_track_listener import MusicTrackListener
import logging
log = logging.getLogger('gajim.c.connection_handlers')
@@ -78,2225 +78,2223 @@ PRIVACY_ARRIVED = 'privacy_arrived'
PEP_CONFIG = 'pep_config'
HAS_IDLE = True
try:
-# import idle
- import common.sleepy
+# import idle
+ import common.sleepy
except Exception:
- log.debug(_('Unable to load idle module'))
- HAS_IDLE = False
+ log.debug(_('Unable to load idle module'))
+ HAS_IDLE = False
class ConnectionDisco:
- """
- Holds xmpppy handlers and public methods for discover services
- """
-
- def discoverItems(self, jid, node = None, id_prefix = None):
- """
- According to XEP-0030:
- jid is mandatory;
- name, node, action is optional.
- """
- self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix)
-
- def discoverInfo(self, jid, node = None, id_prefix = None):
- """
- According to XEP-0030:
- For identity: category, type is mandatory, name is optional.
- For feature: var is mandatory.
- """
- self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix)
-
- def request_register_agent_info(self, agent):
- if not self.connection or self.connected < 2:
- return None
- iq = common.xmpp.Iq('get', common.xmpp.NS_REGISTER, to=agent)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- # Wait the answer during 30 secondes
- self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_,
- _('Registration information for transport %s has not arrived in time')\
- % agent)
- self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo,
- {'agent': agent})
-
- def _agent_registered_cb(self, con, resp, agent):
- if resp.getType() == 'result':
- self.dispatch('INFORMATION', (_('Registration succeeded'),
- _('Registration with agent %s succeeded') % agent))
- self.request_subscription(agent, auto_auth=True)
- self.agent_registrations[agent]['roster_push'] = True
- if self.agent_registrations[agent]['sub_received']:
- p = common.xmpp.Presence(agent, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
- if resp.getType() == 'error':
- self.dispatch('ERROR', (_('Registration failed'), _('Registration with'
- ' agent %(agent)s failed with error %(error)s: %(error_msg)s') % {
- 'agent': agent, 'error': resp.getError(),
- 'error_msg': resp.getErrorMsg()}))
-
- def register_agent(self, agent, info, is_form = False):
- if not self.connection or self.connected < 2:
- return
- if is_form:
- iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=agent)
- query = iq.getTag('query')
- info.setAttr('type', 'submit')
- query.addChild(node=info)
- self.connection.SendAndCallForResponse(iq, self._agent_registered_cb,
- {'agent': agent})
- else:
- # fixed: blocking
- common.xmpp.features_nb.register(self.connection, agent, info,
- self._agent_registered_cb, {'agent': agent})
- self.agent_registrations[agent] = {'roster_push': False,
- 'sub_received': False}
-
- def _discover(self, ns, jid, node=None, id_prefix=None):
- if not self.connection or self.connected < 2:
- return
- iq = common.xmpp.Iq(typ='get', to=jid, queryNS=ns)
- if id_prefix:
- id_ = self.connection.getAnID()
- iq.setID('%s%s' % (id_prefix, id_))
- if node:
- iq.setQuerynode(node)
- self.connection.send(iq)
-
- def _ReceivedRegInfo(self, con, resp, agent):
- common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent)
- self._IqCB(con, resp)
-
- def _discoGetCB(self, con, iq_obj):
- """
- Get disco info
- """
- if not self.connection or self.connected < 2:
- return
- frm = helpers.get_full_jid_from_iq(iq_obj)
- to = unicode(iq_obj.getAttr('to'))
- id_ = unicode(iq_obj.getAttr('id'))
- iq = common.xmpp.Iq(to=frm, typ='result', queryNS=common.xmpp.NS_DISCO,
- frm=to)
- iq.setAttr('id', id_)
- query = iq.setTag('query')
- query.setAttr('node','http://gajim.org#' + gajim.version.split('-', 1)[0])
- for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI,
- common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS):
- feature = common.xmpp.Node('feature')
- feature.setAttr('var', f)
- query.addChild(node=feature)
-
- self.connection.send(iq)
- raise common.xmpp.NodeProcessed
-
- def _DiscoverItemsErrorCB(self, con, iq_obj):
- log.debug('DiscoverItemsErrorCB')
- jid = helpers.get_full_jid_from_iq(iq_obj)
- self.dispatch('AGENT_ERROR_ITEMS', (jid))
-
- def _DiscoverItemsCB(self, con, iq_obj):
- log.debug('DiscoverItemsCB')
- q = iq_obj.getTag('query')
- node = q.getAttr('node')
- if not node:
- node = ''
- qp = iq_obj.getQueryPayload()
- items = []
- if not qp:
- qp = []
- for i in qp:
- # CDATA payload is not processed, only nodes
- if not isinstance(i, common.xmpp.simplexml.Node):
- continue
- attr = {}
- for key in i.getAttrs():
- attr[key] = i.getAttrs()[key]
- if 'jid' not in attr:
- continue
- try:
- attr['jid'] = helpers.parse_jid(attr['jid'])
- except common.helpers.InvalidFormat:
- # jid is not conform
- continue
- items.append(attr)
- jid = helpers.get_full_jid_from_iq(iq_obj)
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- id_ = iq_obj.getID()
- if jid == hostname and id_[:6] == 'Gajim_':
- for item in items:
- self.discoverInfo(item['jid'], id_prefix='Gajim_')
- else:
- self.dispatch('AGENT_INFO_ITEMS', (jid, node, items))
-
- def _DiscoverItemsGetCB(self, con, iq_obj):
- log.debug('DiscoverItemsGetCB')
-
- if not self.connection or self.connected < 2:
- return
-
- if self.commandItemsQuery(con, iq_obj):
- raise common.xmpp.NodeProcessed
- node = iq_obj.getTagAttr('query', 'node')
- if node is None:
- result = iq_obj.buildReply('result')
- self.connection.send(result)
- raise common.xmpp.NodeProcessed
- if node==common.xmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
- raise common.xmpp.NodeProcessed
-
- def _DiscoverInfoGetCB(self, con, iq_obj):
- log.debug('DiscoverInfoGetCB')
- if not self.connection or self.connected < 2:
- return
- q = iq_obj.getTag('query')
- node = q.getAttr('node')
-
- if self.commandInfoQuery(con, iq_obj):
- raise common.xmpp.NodeProcessed
-
- id_ = unicode(iq_obj.getAttr('id'))
- if id_[:6] == 'Gajim_':
- # We get this request from echo.server
- raise common.xmpp.NodeProcessed
-
- iq = iq_obj.buildReply('result')
- q = iq.getTag('query')
- if node:
- q.setAttr('node', node)
- q.addChild('identity', attrs = gajim.gajim_identity)
- client_version = 'http://gajim.org#' + gajim.caps_hash[self.name]
-
- if node in (None, client_version):
- for f in gajim.gajim_common_features:
- q.addChild('feature', attrs = {'var': f})
- for f in gajim.gajim_optional_features[self.name]:
- q.addChild('feature', attrs = {'var': f})
-
- if q.getChildren():
- self.connection.send(iq)
- raise common.xmpp.NodeProcessed
-
- def _DiscoverInfoErrorCB(self, con, iq_obj):
- log.debug('DiscoverInfoErrorCB')
- jid = helpers.get_full_jid_from_iq(iq_obj)
- id_ = iq_obj.getID()
- if id_[:6] == 'Gajim_':
- if not self.privacy_rules_requested:
- self.privacy_rules_requested = True
- self._request_privacy()
- self.dispatch('AGENT_ERROR_INFO', (jid))
-
- def _DiscoverInfoCB(self, con, iq_obj):
- log.debug('DiscoverInfoCB')
- if not self.connection or self.connected < 2:
- return
- # According to XEP-0030:
- # For identity: category, type is mandatory, name is optional.
- # For feature: var is mandatory
- identities, features, data = [], [], []
- q = iq_obj.getTag('query')
- node = q.getAttr('node')
- if not node:
- node = ''
- qc = iq_obj.getQueryChildren()
- if not qc:
- qc = []
- is_muc = False
- transport_type = ''
- for i in qc:
- if i.getName() == 'identity':
- attr = {}
- for key in i.getAttrs().keys():
- attr[key] = i.getAttr(key)
- if 'category' in attr and \
- attr['category'] in ('gateway', 'headline') and \
- 'type' in attr:
- transport_type = attr['type']
- if 'category' in attr and \
- attr['category'] == 'conference' and \
- 'type' in attr and attr['type'] == 'text':
- is_muc = True
- identities.append(attr)
- elif i.getName() == 'feature':
- var = i.getAttr('var')
- if var:
- features.append(var)
- elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA:
- data.append(common.xmpp.DataForm(node=i))
- jid = helpers.get_full_jid_from_iq(iq_obj)
- if transport_type and jid not in gajim.transport_type:
- gajim.transport_type[jid] = transport_type
- gajim.logger.save_transport_type(jid, transport_type)
- id_ = iq_obj.getID()
- if id_ is None:
- log.warn('Invalid IQ received without an ID. Ignoring it: %s' % iq_obj)
- return
- if not identities: # ejabberd doesn't send identities when we browse online users
- #FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
- identities = [{'category': 'server', 'type': 'im', 'name': node}]
- if id_[:6] == 'Gajim_':
- if jid == gajim.config.get_per('accounts', self.name, 'hostname'):
- if features.__contains__(common.xmpp.NS_GMAILNOTIFY):
- gajim.gmail_domains.append(jid)
- self.request_gmail_notifications()
- for identity in identities:
- if identity['category'] == 'pubsub' and identity.get('type') == \
- 'pep':
- self.pep_supported = True
- break
- if features.__contains__(common.xmpp.NS_VCARD):
- self.vcard_supported = True
- if features.__contains__(common.xmpp.NS_PUBSUB):
- self.pubsub_supported = True
- if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS):
- self.pubsub_publish_options_supported = True
- if features.__contains__(common.xmpp.NS_BYTESTREAM):
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\
- '/' + self.server_resource)
- gajim.proxy65_manager.resolve(jid, self.connection, our_jid,
- self.name)
- if features.__contains__(common.xmpp.NS_MUC) and is_muc:
- type_ = transport_type or 'jabber'
- self.muc_jid[type_] = jid
- if transport_type:
- if transport_type in self.available_transports:
- self.available_transports[transport_type].append(jid)
- else:
- self.available_transports[transport_type] = [jid]
- if not self.privacy_rules_requested:
- self.privacy_rules_requested = True
- self._request_privacy()
-
- self.dispatch('AGENT_INFO_INFO', (jid, node, identities,
- features, data))
- self._capsDiscoCB(jid, node, identities, features, data)
+ """
+ Holds xmpppy handlers and public methods for discover services
+ """
+
+ def discoverItems(self, jid, node = None, id_prefix = None):
+ """
+ According to XEP-0030:
+ jid is mandatory;
+ name, node, action is optional.
+ """
+ self._discover(common.xmpp.NS_DISCO_ITEMS, jid, node, id_prefix)
+
+ def discoverInfo(self, jid, node = None, id_prefix = None):
+ """
+ According to XEP-0030:
+ For identity: category, type is mandatory, name is optional.
+ For feature: var is mandatory.
+ """
+ self._discover(common.xmpp.NS_DISCO_INFO, jid, node, id_prefix)
+
+ def request_register_agent_info(self, agent):
+ if not self.connection or self.connected < 2:
+ return None
+ iq = common.xmpp.Iq('get', common.xmpp.NS_REGISTER, to=agent)
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ # Wait the answer during 30 secondes
+ self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_,
+ _('Registration information for transport %s has not arrived in time')\
+ % agent)
+ self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo,
+ {'agent': agent})
+
+ def _agent_registered_cb(self, con, resp, agent):
+ if resp.getType() == 'result':
+ self.dispatch('INFORMATION', (_('Registration succeeded'),
+ _('Registration with agent %s succeeded') % agent))
+ self.request_subscription(agent, auto_auth=True)
+ self.agent_registrations[agent]['roster_push'] = True
+ if self.agent_registrations[agent]['sub_received']:
+ p = common.xmpp.Presence(agent, 'subscribed')
+ p = self.add_sha(p)
+ self.connection.send(p)
+ if resp.getType() == 'error':
+ self.dispatch('ERROR', (_('Registration failed'), _('Registration with'
+ ' agent %(agent)s failed with error %(error)s: %(error_msg)s') % {
+ 'agent': agent, 'error': resp.getError(),
+ 'error_msg': resp.getErrorMsg()}))
+
+ def register_agent(self, agent, info, is_form = False):
+ if not self.connection or self.connected < 2:
+ return
+ if is_form:
+ iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=agent)
+ query = iq.getTag('query')
+ info.setAttr('type', 'submit')
+ query.addChild(node=info)
+ self.connection.SendAndCallForResponse(iq, self._agent_registered_cb,
+ {'agent': agent})
+ else:
+ # fixed: blocking
+ common.xmpp.features_nb.register(self.connection, agent, info,
+ self._agent_registered_cb, {'agent': agent})
+ self.agent_registrations[agent] = {'roster_push': False,
+ 'sub_received': False}
+
+ def _discover(self, ns, jid, node=None, id_prefix=None):
+ if not self.connection or self.connected < 2:
+ return
+ iq = common.xmpp.Iq(typ='get', to=jid, queryNS=ns)
+ if id_prefix:
+ id_ = self.connection.getAnID()
+ iq.setID('%s%s' % (id_prefix, id_))
+ if node:
+ iq.setQuerynode(node)
+ self.connection.send(iq)
+
+ def _ReceivedRegInfo(self, con, resp, agent):
+ common.xmpp.features_nb._ReceivedRegInfo(con, resp, agent)
+ self._IqCB(con, resp)
+
+ def _discoGetCB(self, con, iq_obj):
+ """
+ Get disco info
+ """
+ if not self.connection or self.connected < 2:
+ return
+ frm = helpers.get_full_jid_from_iq(iq_obj)
+ to = unicode(iq_obj.getAttr('to'))
+ id_ = unicode(iq_obj.getAttr('id'))
+ iq = common.xmpp.Iq(to=frm, typ='result', queryNS=common.xmpp.NS_DISCO,
+ frm=to)
+ iq.setAttr('id', id_)
+ query = iq.setTag('query')
+ query.setAttr('node','http://gajim.org#' + gajim.version.split('-', 1)[0])
+ for f in (common.xmpp.NS_BYTESTREAM, common.xmpp.NS_SI,
+ common.xmpp.NS_FILE, common.xmpp.NS_COMMANDS):
+ feature = common.xmpp.Node('feature')
+ feature.setAttr('var', f)
+ query.addChild(node=feature)
+
+ self.connection.send(iq)
+ raise common.xmpp.NodeProcessed
+
+ def _DiscoverItemsErrorCB(self, con, iq_obj):
+ log.debug('DiscoverItemsErrorCB')
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ self.dispatch('AGENT_ERROR_ITEMS', (jid))
+
+ def _DiscoverItemsCB(self, con, iq_obj):
+ log.debug('DiscoverItemsCB')
+ q = iq_obj.getTag('query')
+ node = q.getAttr('node')
+ if not node:
+ node = ''
+ qp = iq_obj.getQueryPayload()
+ items = []
+ if not qp:
+ qp = []
+ for i in qp:
+ # CDATA payload is not processed, only nodes
+ if not isinstance(i, common.xmpp.simplexml.Node):
+ continue
+ attr = {}
+ for key in i.getAttrs():
+ attr[key] = i.getAttrs()[key]
+ if 'jid' not in attr:
+ continue
+ try:
+ attr['jid'] = helpers.parse_jid(attr['jid'])
+ except common.helpers.InvalidFormat:
+ # jid is not conform
+ continue
+ items.append(attr)
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ hostname = gajim.config.get_per('accounts', self.name, 'hostname')
+ id_ = iq_obj.getID()
+ if jid == hostname and id_[:6] == 'Gajim_':
+ for item in items:
+ self.discoverInfo(item['jid'], id_prefix='Gajim_')
+ else:
+ self.dispatch('AGENT_INFO_ITEMS', (jid, node, items))
+
+ def _DiscoverItemsGetCB(self, con, iq_obj):
+ log.debug('DiscoverItemsGetCB')
+
+ if not self.connection or self.connected < 2:
+ return
+
+ if self.commandItemsQuery(con, iq_obj):
+ raise common.xmpp.NodeProcessed
+ node = iq_obj.getTagAttr('query', 'node')
+ if node is None:
+ result = iq_obj.buildReply('result')
+ self.connection.send(result)
+ raise common.xmpp.NodeProcessed
+ if node==common.xmpp.NS_COMMANDS:
+ self.commandListQuery(con, iq_obj)
+ raise common.xmpp.NodeProcessed
+
+ def _DiscoverInfoGetCB(self, con, iq_obj):
+ log.debug('DiscoverInfoGetCB')
+ if not self.connection or self.connected < 2:
+ return
+ q = iq_obj.getTag('query')
+ node = q.getAttr('node')
+
+ if self.commandInfoQuery(con, iq_obj):
+ raise common.xmpp.NodeProcessed
+
+ id_ = unicode(iq_obj.getAttr('id'))
+ if id_[:6] == 'Gajim_':
+ # We get this request from echo.server
+ raise common.xmpp.NodeProcessed
+
+ iq = iq_obj.buildReply('result')
+ q = iq.getTag('query')
+ if node:
+ q.setAttr('node', node)
+ q.addChild('identity', attrs = gajim.gajim_identity)
+ client_version = 'http://gajim.org#' + gajim.caps_hash[self.name]
+
+ if node in (None, client_version):
+ for f in gajim.gajim_common_features:
+ q.addChild('feature', attrs = {'var': f})
+ for f in gajim.gajim_optional_features[self.name]:
+ q.addChild('feature', attrs = {'var': f})
+
+ if q.getChildren():
+ self.connection.send(iq)
+ raise common.xmpp.NodeProcessed
+
+ def _DiscoverInfoErrorCB(self, con, iq_obj):
+ log.debug('DiscoverInfoErrorCB')
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ id_ = iq_obj.getID()
+ if id_[:6] == 'Gajim_':
+ if not self.privacy_rules_requested:
+ self.privacy_rules_requested = True
+ self._request_privacy()
+ self.dispatch('AGENT_ERROR_INFO', (jid))
+
+ def _DiscoverInfoCB(self, con, iq_obj):
+ log.debug('DiscoverInfoCB')
+ if not self.connection or self.connected < 2:
+ return
+ # According to XEP-0030:
+ # For identity: category, type is mandatory, name is optional.
+ # For feature: var is mandatory
+ identities, features, data = [], [], []
+ q = iq_obj.getTag('query')
+ node = q.getAttr('node')
+ if not node:
+ node = ''
+ qc = iq_obj.getQueryChildren()
+ if not qc:
+ qc = []
+ is_muc = False
+ transport_type = ''
+ for i in qc:
+ if i.getName() == 'identity':
+ attr = {}
+ for key in i.getAttrs().keys():
+ attr[key] = i.getAttr(key)
+ if 'category' in attr and \
+ attr['category'] in ('gateway', 'headline') and \
+ 'type' in attr:
+ transport_type = attr['type']
+ if 'category' in attr and \
+ attr['category'] == 'conference' and \
+ 'type' in attr and attr['type'] == 'text':
+ is_muc = True
+ identities.append(attr)
+ elif i.getName() == 'feature':
+ var = i.getAttr('var')
+ if var:
+ features.append(var)
+ elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA:
+ data.append(common.xmpp.DataForm(node=i))
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ if transport_type and jid not in gajim.transport_type:
+ gajim.transport_type[jid] = transport_type
+ gajim.logger.save_transport_type(jid, transport_type)
+ id_ = iq_obj.getID()
+ if id_ is None:
+ log.warn('Invalid IQ received without an ID. Ignoring it: %s' % iq_obj)
+ return
+ if not identities: # ejabberd doesn't send identities when we browse online users
+ #FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
+ identities = [{'category': 'server', 'type': 'im', 'name': node}]
+ if id_[:6] == 'Gajim_':
+ if jid == gajim.config.get_per('accounts', self.name, 'hostname'):
+ if features.__contains__(common.xmpp.NS_GMAILNOTIFY):
+ gajim.gmail_domains.append(jid)
+ self.request_gmail_notifications()
+ for identity in identities:
+ if identity['category'] == 'pubsub' and identity.get('type') == \
+ 'pep':
+ self.pep_supported = True
+ break
+ if features.__contains__(common.xmpp.NS_VCARD):
+ self.vcard_supported = True
+ if features.__contains__(common.xmpp.NS_PUBSUB):
+ self.pubsub_supported = True
+ if features.__contains__(common.xmpp.NS_PUBSUB_PUBLISH_OPTIONS):
+ self.pubsub_publish_options_supported = True
+ if features.__contains__(common.xmpp.NS_BYTESTREAM):
+ our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\
+ '/' + self.server_resource)
+ gajim.proxy65_manager.resolve(jid, self.connection, our_jid,
+ self.name)
+ if features.__contains__(common.xmpp.NS_MUC) and is_muc:
+ type_ = transport_type or 'jabber'
+ self.muc_jid[type_] = jid
+ if transport_type:
+ if transport_type in self.available_transports:
+ self.available_transports[transport_type].append(jid)
+ else:
+ self.available_transports[transport_type] = [jid]
+ if not self.privacy_rules_requested:
+ self.privacy_rules_requested = True
+ self._request_privacy()
+
+ self.dispatch('AGENT_INFO_INFO', (jid, node, identities,
+ features, data))
+ self._capsDiscoCB(jid, node, identities, features, data)
class ConnectionVcard:
- def __init__(self):
- self.vcard_sha = None
- self.vcard_shas = {} # sha of contacts
- self.room_jids = [] # list of gc jids so that vcard are saved in a folder
-
- def add_sha(self, p, send_caps = True):
- c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE)
- if self.vcard_sha is not None:
- c.setTagData('photo', self.vcard_sha)
- if send_caps:
- return self._add_caps(p)
- return p
-
- def _add_caps(self, p):
- ''' advertise our capabilities in presence stanza (xep-0115)'''
- c = p.setTag('c', namespace = common.xmpp.NS_CAPS)
- c.setAttr('hash', 'sha-1')
- c.setAttr('node', 'http://gajim.org')
- c.setAttr('ver', gajim.caps_hash[self.name])
- return p
-
- def _node_to_dict(self, node):
- dict_ = {}
- for info in node.getChildren():
- name = info.getName()
- if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
- dict_.setdefault(name, [])
- entry = {}
- for c in info.getChildren():
- entry[c.getName()] = c.getData()
- dict_[name].append(entry)
- elif info.getChildren() == []:
- dict_[name] = info.getData()
- else:
- dict_[name] = {}
- for c in info.getChildren():
- dict_[name][c.getName()] = c.getData()
- return dict_
-
- def _save_vcard_to_hd(self, full_jid, card):
- jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
- puny_jid = helpers.sanitize_filename(jid)
- path = os.path.join(gajim.VCARD_PATH, puny_jid)
- if jid in self.room_jids or os.path.isdir(path):
- if not nick:
- return
- # remove room_jid file if needed
- if os.path.isfile(path):
- os.remove(path)
- # create folder if needed
- if not os.path.isdir(path):
- os.mkdir(path, 0700)
- puny_nick = helpers.sanitize_filename(nick)
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- else:
- path_to_file = path
- try:
- fil = open(path_to_file, 'w')
- fil.write(str(card))
- fil.close()
- except IOError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
-
- def get_cached_vcard(self, fjid, is_fake_jid=False):
- """
- Return the vcard as a dict.
- Return {} if vcard was too old.
- Return None if we don't have cached vcard.
- """
- jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
- puny_jid = helpers.sanitize_filename(jid)
- if is_fake_jid:
- puny_nick = helpers.sanitize_filename(nick)
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- else:
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid)
- if not os.path.isfile(path_to_file):
- return None
- # We have the vcard cached
- f = open(path_to_file)
- c = f.read()
- f.close()
- try:
- card = common.xmpp.Node(node=c)
- except Exception:
- # We are unable to parse it. Remove it
- os.remove(path_to_file)
- return None
- vcard = self._node_to_dict(card)
- if 'PHOTO' in vcard:
- if not isinstance(vcard['PHOTO'], dict):
- del vcard['PHOTO']
- elif 'SHA' in vcard['PHOTO']:
- cached_sha = vcard['PHOTO']['SHA']
- if jid in self.vcard_shas and self.vcard_shas[jid] != \
- cached_sha:
- # user change his vcard so don't use the cached one
- return {}
- vcard['jid'] = jid
- vcard['resource'] = gajim.get_resource_from_jid(fjid)
- return vcard
-
- def request_vcard(self, jid=None, groupchat_jid=None):
- """
- Request the VCARD
-
- If groupchat_jid is not null, it means we request a vcard to a fake jid,
- like in private messages in groupchat. jid can be the real jid of the
- contact, but we want to consider it comes from a fake jid
- """
- if not self.connection or self.connected < 2:
- return
- iq = common.xmpp.Iq(typ = 'get')
- if jid:
- iq.setTo(jid)
- iq.setTag(common.xmpp.NS_VCARD + ' vCard')
-
- id_ = self.connection.getAnID()
- iq.setID(id_)
- j = jid
- if not j:
- j = gajim.get_jid_from_account(self.name)
- self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid)
- if groupchat_jid:
- room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0]
- if not room_jid in self.room_jids:
- self.room_jids.append(room_jid)
- self.groupchat_jids[id_] = groupchat_jid
- self.connection.send(iq)
-
- def send_vcard(self, vcard):
- if not self.connection or self.connected < 2:
- return
- iq = common.xmpp.Iq(typ = 'set')
- iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard')
- for i in vcard:
- if i == 'jid':
- continue
- if isinstance(vcard[i], dict):
- iq3 = iq2.addChild(i)
- for j in vcard[i]:
- iq3.addChild(j).setData(vcard[i][j])
- elif isinstance(vcard[i], list):
- for j in vcard[i]:
- iq3 = iq2.addChild(i)
- for k in j:
- iq3.addChild(k).setData(j[k])
- else:
- iq2.addChild(i).setData(vcard[i])
-
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.connection.send(iq)
-
- our_jid = gajim.get_jid_from_account(self.name)
- # Add the sha of the avatar
- if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
- 'BINVAL' in vcard['PHOTO']:
- photo = vcard['PHOTO']['BINVAL']
- photo_decoded = base64.decodestring(photo)
- gajim.interface.save_avatar_files(our_jid, photo_decoded)
- avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
- iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
- else:
- gajim.interface.remove_avatar_files(our_jid)
-
- self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2)
-
- def _IqCB(self, con, iq_obj):
- id_ = iq_obj.getID()
-
- # Check if we were waiting a timeout for this id
- found_tim = None
- for tim in self.awaiting_timeouts:
- if id_ == self.awaiting_timeouts[tim][0]:
- found_tim = tim
- break
- if found_tim:
- del self.awaiting_timeouts[found_tim]
-
- if id_ not in self.awaiting_answers:
- return
- if self.awaiting_answers[id_][0] == VCARD_PUBLISHED:
- if iq_obj.getType() == 'result':
- vcard_iq = self.awaiting_answers[id_][1]
- # Save vcard to HD
- if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'):
- new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA')
- else:
- new_sha = ''
-
- # Save it to file
- our_jid = gajim.get_jid_from_account(self.name)
- self._save_vcard_to_hd(our_jid, vcard_iq)
-
- # Send new presence if sha changed and we are not invisible
- if self.vcard_sha != new_sha and gajim.SHOW_LIST[self.connected] !=\
- 'invisible':
- if not self.connection or self.connected < 2:
- return
- self.vcard_sha = new_sha
- sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- p = common.xmpp.Presence(typ = None, priority = self.priority,
- show = sshow, status = self.status)
- p = self.add_sha(p)
- self.connection.send(p)
- self.dispatch('VCARD_PUBLISHED', ())
- elif iq_obj.getType() == 'error':
- self.dispatch('VCARD_NOT_PUBLISHED', ())
- elif self.awaiting_answers[id_][0] == VCARD_ARRIVED:
- # If vcard is empty, we send to the interface an empty vcard so that
- # it knows it arrived
- jid = self.awaiting_answers[id_][1]
- groupchat_jid = self.awaiting_answers[id_][2]
- frm = jid
- if groupchat_jid:
- # We do as if it comes from the fake_jid
- frm = groupchat_jid
- our_jid = gajim.get_jid_from_account(self.name)
- if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error':
- if frm and frm != our_jid:
- # Write an empty file
- self._save_vcard_to_hd(frm, '')
- jid, resource = gajim.get_room_and_nick_from_fjid(frm)
- self.dispatch('VCARD', {'jid': jid, 'resource': resource})
- elif frm == our_jid:
- self.dispatch('MYVCARD', {'jid': frm})
- elif self.awaiting_answers[id_][0] == AGENT_REMOVED:
- jid = self.awaiting_answers[id_][1]
- self.dispatch('AGENT_REMOVED', jid)
- elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED:
- if not self.connection:
- return
- if iq_obj.getType() == 'result':
- # Metacontact tags
- # http://www.xmpp.org/extensions/xep-0209.html
- meta_list = {}
- query = iq_obj.getTag('query')
- storage = query.getTag('storage')
- metas = storage.getTags('meta')
- for meta in metas:
- try:
- jid = helpers.parse_jid(meta.getAttr('jid'))
- except common.helpers.InvalidFormat:
- continue
- tag = meta.getAttr('tag')
- data = {'jid': jid}
- order = meta.getAttr('order')
- try:
- order = int(order)
- except Exception:
- order = 0
- if order is not None:
- data['order'] = order
- if tag in meta_list:
- meta_list[tag].append(data)
- else:
- meta_list[tag] = [data]
- self.dispatch('METACONTACTS', meta_list)
- else:
- if iq_obj.getErrorCode() not in ('403', '406', '404'):
- self.private_storage_supported = False
- # We can now continue connection by requesting the roster
- version = gajim.config.get_per('accounts', self.name,
- 'roster_version')
- iq_id = self.connection.initRoster(version=version)
- self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
- elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
- if iq_obj.getType() == 'result':
- if not iq_obj.getTag('query'):
- account_jid = gajim.get_jid_from_account(self.name)
- roster_data = gajim.logger.get_roster(account_jid)
- roster = self.connection.getRoster(force=True)
- roster.setRaw(roster_data)
- self._getRoster()
- elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
- if iq_obj.getType() != 'error':
- self.privacy_rules_supported = True
- self.get_privacy_list('block')
- elif self.continue_connect_info:
- if self.continue_connect_info[0] == 'invisible':
- # Trying to login as invisible but privacy list not supported
- self.disconnect(on_purpose=True)
- self.dispatch('STATUS', 'offline')
- self.dispatch('ERROR', (_('Invisibility not supported'),
- _('Account %s doesn\'t support invisibility.') % self.name))
- return
- # Ask metacontacts before roster
- self.get_metacontacts()
- elif self.awaiting_answers[id_][0] == PEP_CONFIG:
- conf = iq_obj.getTag('pubsub').getTag('configure')
- node = conf.getAttr('node')
- form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA)
- if form_tag:
- form = common.dataforms.ExtendForm(node=form_tag)
- self.dispatch('PEP_CONFIG', (node, form))
-
- del self.awaiting_answers[id_]
-
- def _vCardCB(self, con, vc):
- """
- Called when we receive a vCard Parse the vCard and send it to plugins
- """
- if not vc.getTag('vCard'):
- return
- if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD:
- return
- id_ = vc.getID()
- frm_iq = vc.getFrom()
- our_jid = gajim.get_jid_from_account(self.name)
- resource = ''
- if id_ in self.groupchat_jids:
- who = self.groupchat_jids[id_]
- frm, resource = gajim.get_room_and_nick_from_fjid(who)
- del self.groupchat_jids[id_]
- elif frm_iq:
- who = helpers.get_full_jid_from_iq(vc)
- frm, resource = gajim.get_room_and_nick_from_fjid(who)
- else:
- who = frm = our_jid
- card = vc.getChildren()[0]
- vcard = self._node_to_dict(card)
- photo_decoded = None
- if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
- 'BINVAL' in vcard['PHOTO']:
- photo = vcard['PHOTO']['BINVAL']
- try:
- photo_decoded = base64.decodestring(photo)
- avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
- except Exception:
- avatar_sha = ''
- else:
- avatar_sha = ''
-
- if avatar_sha:
- card.getTag('PHOTO').setTagData('SHA', avatar_sha)
-
- # Save it to file
- self._save_vcard_to_hd(who, card)
- # Save the decoded avatar to a separate file too, and generate files for dbus notifications
- puny_jid = helpers.sanitize_filename(frm)
- puny_nick = None
- begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid)
- frm_jid = frm
- if frm in self.room_jids:
- puny_nick = helpers.sanitize_filename(resource)
- # create folder if needed
- if not os.path.isdir(begin_path):
- os.mkdir(begin_path, 0700)
- begin_path = os.path.join(begin_path, puny_nick)
- frm_jid += '/' + resource
- if photo_decoded:
- avatar_file = begin_path + '_notif_size_colored.png'
- if frm_jid == our_jid and avatar_sha != self.vcard_sha:
- gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
- elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \
- frm_jid not in self.vcard_shas or \
- avatar_sha != self.vcard_shas[frm_jid]):
- gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
- if avatar_sha:
- self.vcard_shas[frm_jid] = avatar_sha
- elif frm in self.vcard_shas:
- del self.vcard_shas[frm]
- else:
- for ext in ('.jpeg', '.png', '_notif_size_bw.png',
- '_notif_size_colored.png'):
- path = begin_path + ext
- if os.path.isfile(path):
- os.remove(path)
-
- vcard['jid'] = frm
- vcard['resource'] = resource
- if frm_jid == our_jid:
- self.dispatch('MYVCARD', vcard)
- # we re-send our presence with sha if has changed and if we are
- # not invisible
- if self.vcard_sha == avatar_sha:
- return
- self.vcard_sha = avatar_sha
- if gajim.SHOW_LIST[self.connected] == 'invisible':
- return
- if not self.connection:
- return
- sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- p = common.xmpp.Presence(typ = None, priority = self.priority,
- show = sshow, status = self.status)
- p = self.add_sha(p)
- self.connection.send(p)
- else:
- #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
- self.dispatch('VCARD', vcard)
+ def __init__(self):
+ self.vcard_sha = None
+ self.vcard_shas = {} # sha of contacts
+ self.room_jids = [] # list of gc jids so that vcard are saved in a folder
+
+ def add_sha(self, p, send_caps = True):
+ c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE)
+ if self.vcard_sha is not None:
+ c.setTagData('photo', self.vcard_sha)
+ if send_caps:
+ return self._add_caps(p)
+ return p
+
+ def _add_caps(self, p):
+ ''' advertise our capabilities in presence stanza (xep-0115)'''
+ c = p.setTag('c', namespace = common.xmpp.NS_CAPS)
+ c.setAttr('hash', 'sha-1')
+ c.setAttr('node', 'http://gajim.org')
+ c.setAttr('ver', gajim.caps_hash[self.name])
+ return p
+
+ def _node_to_dict(self, node):
+ dict_ = {}
+ for info in node.getChildren():
+ name = info.getName()
+ if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
+ dict_.setdefault(name, [])
+ entry = {}
+ for c in info.getChildren():
+ entry[c.getName()] = c.getData()
+ dict_[name].append(entry)
+ elif info.getChildren() == []:
+ dict_[name] = info.getData()
+ else:
+ dict_[name] = {}
+ for c in info.getChildren():
+ dict_[name][c.getName()] = c.getData()
+ return dict_
+
+ def _save_vcard_to_hd(self, full_jid, card):
+ jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
+ puny_jid = helpers.sanitize_filename(jid)
+ path = os.path.join(gajim.VCARD_PATH, puny_jid)
+ if jid in self.room_jids or os.path.isdir(path):
+ if not nick:
+ return
+ # remove room_jid file if needed
+ if os.path.isfile(path):
+ os.remove(path)
+ # create folder if needed
+ if not os.path.isdir(path):
+ os.mkdir(path, 0700)
+ puny_nick = helpers.sanitize_filename(nick)
+ path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+ else:
+ path_to_file = path
+ try:
+ fil = open(path_to_file, 'w')
+ fil.write(str(card))
+ fil.close()
+ except IOError, e:
+ self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
+
+ def get_cached_vcard(self, fjid, is_fake_jid=False):
+ """
+ Return the vcard as a dict.
+ Return {} if vcard was too old.
+ Return None if we don't have cached vcard.
+ """
+ jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
+ puny_jid = helpers.sanitize_filename(jid)
+ if is_fake_jid:
+ puny_nick = helpers.sanitize_filename(nick)
+ path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+ else:
+ path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid)
+ if not os.path.isfile(path_to_file):
+ return None
+ # We have the vcard cached
+ f = open(path_to_file)
+ c = f.read()
+ f.close()
+ try:
+ card = common.xmpp.Node(node=c)
+ except Exception:
+ # We are unable to parse it. Remove it
+ os.remove(path_to_file)
+ return None
+ vcard = self._node_to_dict(card)
+ if 'PHOTO' in vcard:
+ if not isinstance(vcard['PHOTO'], dict):
+ del vcard['PHOTO']
+ elif 'SHA' in vcard['PHOTO']:
+ cached_sha = vcard['PHOTO']['SHA']
+ if jid in self.vcard_shas and self.vcard_shas[jid] != \
+ cached_sha:
+ # user change his vcard so don't use the cached one
+ return {}
+ vcard['jid'] = jid
+ vcard['resource'] = gajim.get_resource_from_jid(fjid)
+ return vcard
+
+ def request_vcard(self, jid=None, groupchat_jid=None):
+ """
+ Request the VCARD
+
+ If groupchat_jid is not null, it means we request a vcard to a fake jid,
+ like in private messages in groupchat. jid can be the real jid of the
+ contact, but we want to consider it comes from a fake jid
+ """
+ if not self.connection or self.connected < 2:
+ return
+ iq = common.xmpp.Iq(typ = 'get')
+ if jid:
+ iq.setTo(jid)
+ iq.setTag(common.xmpp.NS_VCARD + ' vCard')
+
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ j = jid
+ if not j:
+ j = gajim.get_jid_from_account(self.name)
+ self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid)
+ if groupchat_jid:
+ room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0]
+ if not room_jid in self.room_jids:
+ self.room_jids.append(room_jid)
+ self.groupchat_jids[id_] = groupchat_jid
+ self.connection.send(iq)
+
+ def send_vcard(self, vcard):
+ if not self.connection or self.connected < 2:
+ return
+ iq = common.xmpp.Iq(typ = 'set')
+ iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard')
+ for i in vcard:
+ if i == 'jid':
+ continue
+ if isinstance(vcard[i], dict):
+ iq3 = iq2.addChild(i)
+ for j in vcard[i]:
+ iq3.addChild(j).setData(vcard[i][j])
+ elif isinstance(vcard[i], list):
+ for j in vcard[i]:
+ iq3 = iq2.addChild(i)
+ for k in j:
+ iq3.addChild(k).setData(j[k])
+ else:
+ iq2.addChild(i).setData(vcard[i])
+
+ id_ = self.connection.getAnID()
+ iq.setID(id_)
+ self.connection.send(iq)
+
+ our_jid = gajim.get_jid_from_account(self.name)
+ # Add the sha of the avatar
+ if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
+ 'BINVAL' in vcard['PHOTO']:
+ photo = vcard['PHOTO']['BINVAL']
+ photo_decoded = base64.decodestring(photo)
+ gajim.interface.save_avatar_files(our_jid, photo_decoded)
+ avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
+ iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
+ else:
+ gajim.interface.remove_avatar_files(our_jid)
+
+ self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2)
+
+ def _IqCB(self, con, iq_obj):
+ id_ = iq_obj.getID()
+
+ # Check if we were waiting a timeout for this id
+ found_tim = None
+ for tim in self.awaiting_timeouts:
+ if id_ == self.awaiting_timeouts[tim][0]:
+ found_tim = tim
+ break
+ if found_tim:
+ del self.awaiting_timeouts[found_tim]
+
+ if id_ not in self.awaiting_answers:
+ return
+ if self.awaiting_answers[id_][0] == VCARD_PUBLISHED:
+ if iq_obj.getType() == 'result':
+ vcard_iq = self.awaiting_answers[id_][1]
+ # Save vcard to HD
+ if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag('SHA'):
+ new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA')
+ else:
+ new_sha = ''
+
+ # Save it to file
+ our_jid = gajim.get_jid_from_account(self.name)
+ self._save_vcard_to_hd(our_jid, vcard_iq)
+
+ # Send new presence if sha changed and we are not invisible
+ if self.vcard_sha != new_sha and gajim.SHOW_LIST[self.connected] !=\
+ 'invisible':
+ if not self.connection or self.connected < 2:
+ return
+ self.vcard_sha = new_sha
+ sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
+ p = common.xmpp.Presence(typ = None, priority = self.priority,
+ show = sshow, status = self.status)
+ p = self.add_sha(p)
+ self.connection.send(p)
+ self.dispatch('VCARD_PUBLISHED', ())
+ elif iq_obj.getType() == 'error':
+ self.dispatch('VCARD_NOT_PUBLISHED', ())
+ elif self.awaiting_answers[id_][0] == VCARD_ARRIVED:
+ # If vcard is empty, we send to the interface an empty vcard so that
+ # it knows it arrived
+ jid = self.awaiting_answers[id_][1]
+ groupchat_jid = self.awaiting_answers[id_][2]
+ frm = jid
+ if groupchat_jid:
+ # We do as if it comes from the fake_jid
+ frm = groupchat_jid
+ our_jid = gajim.get_jid_from_account(self.name)
+ if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error':
+ if frm and frm != our_jid:
+ # Write an empty file
+ self._save_vcard_to_hd(frm, '')
+ jid, resource = gajim.get_room_and_nick_from_fjid(frm)
+ self.dispatch('VCARD', {'jid': jid, 'resource': resource})
+ elif frm == our_jid:
+ self.dispatch('MYVCARD', {'jid': frm})
+ elif self.awaiting_answers[id_][0] == AGENT_REMOVED:
+ jid = self.awaiting_answers[id_][1]
+ self.dispatch('AGENT_REMOVED', jid)
+ elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED:
+ if not self.connection:
+ return
+ if iq_obj.getType() == 'result':
+ # Metacontact tags
+ # http://www.xmpp.org/extensions/xep-0209.html
+ meta_list = {}
+ query = iq_obj.getTag('query')
+ storage = query.getTag('storage')
+ metas = storage.getTags('meta')
+ for meta in metas:
+ try:
+ jid = helpers.parse_jid(meta.getAttr('jid'))
+ except common.helpers.InvalidFormat:
+ continue
+ tag = meta.getAttr('tag')
+ data = {'jid': jid}
+ order = meta.getAttr('order')
+ try:
+ order = int(order)
+ except Exception:
+ order = 0
+ if order is not None:
+ data['order'] = order
+ if tag in meta_list:
+ meta_list[tag].append(data)
+ else:
+ meta_list[tag] = [data]
+ self.dispatch('METACONTACTS', meta_list)
+ else:
+ if iq_obj.getErrorCode() not in ('403', '406', '404'):
+ self.private_storage_supported = False
+ # We can now continue connection by requesting the roster
+ version = gajim.config.get_per('accounts', self.name,
+ 'roster_version')
+ iq_id = self.connection.initRoster(version=version)
+ self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
+ elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
+ if iq_obj.getType() == 'result':
+ if not iq_obj.getTag('query'):
+ account_jid = gajim.get_jid_from_account(self.name)
+ roster_data = gajim.logger.get_roster(account_jid)
+ roster = self.connection.getRoster(force=True)
+ roster.setRaw(roster_data)
+ self._getRoster()
+ elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
+ if iq_obj.getType() != 'error':
+ self.privacy_rules_supported = True
+ self.get_privacy_list('block')
+ elif self.continue_connect_info:
+ if self.continue_connect_info[0] == 'invisible':
+ # Trying to login as invisible but privacy list not supported
+ self.disconnect(on_purpose=True)
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('ERROR', (_('Invisibility not supported'),
+ _('Account %s doesn\'t support invisibility.') % self.name))
+ return
+ # Ask metacontacts before roster
+ self.get_metacontacts()
+ elif self.awaiting_answers[id_][0] == PEP_CONFIG:
+ conf = iq_obj.getTag('pubsub').getTag('configure')
+ node = conf.getAttr('node')
+ form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA)
+ if form_tag:
+ form = common.dataforms.ExtendForm(node=form_tag)
+ self.dispatch('PEP_CONFIG', (node, form))
+
+ del self.awaiting_answers[id_]
+
+ def _vCardCB(self, con, vc):
+ """
+ Called when we receive a vCard Parse the vCard and send it to plugins
+ """
+ if not vc.getTag('vCard'):
+ return
+ if not vc.getTag('vCard').getNamespace() == common.xmpp.NS_VCARD:
+ return
+ id_ = vc.getID()
+ frm_iq = vc.getFrom()
+ our_jid = gajim.get_jid_from_account(self.name)
+ resource = ''
+ if id_ in self.groupchat_jids:
+ who = self.groupchat_jids[id_]
+ frm, resource = gajim.get_room_and_nick_from_fjid(who)
+ del self.groupchat_jids[id_]
+ elif frm_iq:
+ who = helpers.get_full_jid_from_iq(vc)
+ frm, resource = gajim.get_room_and_nick_from_fjid(who)
+ else:
+ who = frm = our_jid
+ card = vc.getChildren()[0]
+ vcard = self._node_to_dict(card)
+ photo_decoded = None
+ if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
+ 'BINVAL' in vcard['PHOTO']:
+ photo = vcard['PHOTO']['BINVAL']
+ try:
+ photo_decoded = base64.decodestring(photo)
+ avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
+ except Exception:
+ avatar_sha = ''
+ else:
+ avatar_sha = ''
+
+ if avatar_sha:
+ card.getTag('PHOTO').setTagData('SHA', avatar_sha)
+
+ # Save it to file
+ self._save_vcard_to_hd(who, card)
+ # Save the decoded avatar to a separate file too, and generate files for dbus notifications
+ puny_jid = helpers.sanitize_filename(frm)
+ puny_nick = None
+ begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid)
+ frm_jid = frm
+ if frm in self.room_jids:
+ puny_nick = helpers.sanitize_filename(resource)
+ # create folder if needed
+ if not os.path.isdir(begin_path):
+ os.mkdir(begin_path, 0700)
+ begin_path = os.path.join(begin_path, puny_nick)
+ frm_jid += '/' + resource
+ if photo_decoded:
+ avatar_file = begin_path + '_notif_size_colored.png'
+ if frm_jid == our_jid and avatar_sha != self.vcard_sha:
+ gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
+ elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \
+ frm_jid not in self.vcard_shas or \
+ avatar_sha != self.vcard_shas[frm_jid]):
+ gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
+ if avatar_sha:
+ self.vcard_shas[frm_jid] = avatar_sha
+ elif frm in self.vcard_shas:
+ del self.vcard_shas[frm]
+ else:
+ for ext in ('.jpeg', '.png', '_notif_size_bw.png',
+ '_notif_size_colored.png'):
+ path = begin_path + ext
+ if os.path.isfile(path):
+ os.remove(path)
+
+ vcard['jid'] = frm
+ vcard['resource'] = resource
+ if frm_jid == our_jid:
+ self.dispatch('MYVCARD', vcard)
+ # we re-send our presence with sha if has changed and if we are
+ # not invisible
+ if self.vcard_sha == avatar_sha:
+ return
+ self.vcard_sha = avatar_sha
+ if gajim.SHOW_LIST[self.connected] == 'invisible':
+ return
+ if not self.connection:
+ return
+ sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
+ p = common.xmpp.Presence(typ = None, priority = self.priority,
+ show = sshow, status = self.status)
+ p = self.add_sha(p)
+ self.connection.send(p)
+ else:
+ #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
+ self.dispatch('VCARD', vcard)
# basic connection handlers used here and in zeroconf
class ConnectionHandlersBase:
- def __init__(self):
- # List of IDs we are waiting answers for {id: (type_of_request, data), }
- self.awaiting_answers = {}
- # List of IDs that will produce a timeout is answer doesn't arrive
- # {time_of_the_timeout: (id, message to send to gui), }
- self.awaiting_timeouts = {}
- # keep the jids we auto added (transports contacts) to not send the
- # SUBSCRIBED event to gui
- self.automatically_added = []
-
- # keep track of sessions this connection has with other JIDs
- self.sessions = {}
-
- def get_sessions(self, jid):
- """
- Get all sessions for the given full jid
- """
- if not gajim.interface.is_pm_contact(jid, self.name):
- jid = gajim.get_jid_without_resource(jid)
-
- try:
- return self.sessions[jid].values()
- except KeyError:
- return []
-
- def get_or_create_session(self, fjid, thread_id):
- """
- Return an existing session between this connection and 'jid', returns a
- new one if none exist
- """
- pm = True
- jid = fjid
-
- if not gajim.interface.is_pm_contact(fjid, self.name):
- pm = False
- jid = gajim.get_jid_without_resource(fjid)
-
- session = self.find_session(jid, thread_id)
-
- if session:
- return session
-
- if pm:
- return self.make_new_session(fjid, thread_id, type_='pm')
- else:
- return self.make_new_session(fjid, thread_id)
-
- def find_session(self, jid, thread_id):
- try:
- if not thread_id:
- return self.find_null_session(jid)
- else:
- return self.sessions[jid][thread_id]
- except KeyError:
- return None
-
- def terminate_sessions(self, send_termination=False):
- """
- Send termination messages and delete all active sessions
- """
- for jid in self.sessions:
- for thread_id in self.sessions[jid]:
- self.sessions[jid][thread_id].terminate(send_termination)
-
- self.sessions = {}
-
- def delete_session(self, jid, thread_id):
- if not jid in self.sessions:
- jid = gajim.get_jid_without_resource(jid)
- if not jid in self.sessions:
- return
-
- del self.sessions[jid][thread_id]
-
- if not self.sessions[jid]:
- del self.sessions[jid]
-
- def find_null_session(self, jid):
- """
- Find all of the sessions between us and a remote jid in which we haven't
- received a thread_id yet and returns the session that we last sent a
- message to
- """
- sessions = self.sessions[jid].values()
-
- # sessions that we haven't received a thread ID in
- idless = [s for s in sessions if not s.received_thread_id]
-
- # filter out everything except the default session type
- chat_sessions = [s for s in idless if isinstance(s,
- gajim.default_session_type)]
-
- if chat_sessions:
- # return the session that we last sent a message in
- return sorted(chat_sessions, key=operator.attrgetter("last_send"))[-1]
- else:
- return None
-
- def find_controlless_session(self, jid, resource=None):
- """
- Find an active session that doesn't have a control attached
- """
- try:
- sessions = self.sessions[jid].values()
-
- # filter out everything except the default session type
- chat_sessions = [s for s in sessions if isinstance(s,
- gajim.default_session_type)]
-
- orphaned = [s for s in chat_sessions if not s.control]
-
- if resource:
- orphaned = [s for s in orphaned if s.resource == resource]
-
- return orphaned[0]
- except (KeyError, IndexError):
- return None
-
- def make_new_session(self, jid, thread_id=None, type_='chat', cls=None):
- """
- Create and register a new session
-
- thread_id=None to generate one.
- type_ should be 'chat' or 'pm'.
- """
- if not cls:
- cls = gajim.default_session_type
-
- sess = cls(self, common.xmpp.JID(jid), thread_id, type_)
-
- # determine if this session is a pm session
- # if not, discard the resource so that all sessions are stored bare
- if not type_ == 'pm':
- jid = gajim.get_jid_without_resource(jid)
-
- if not jid in self.sessions:
- self.sessions[jid] = {}
-
- self.sessions[jid][sess.thread_id] = sess
-
- return sess
+ def __init__(self):
+ # List of IDs we are waiting answers for {id: (type_of_request, data), }
+ self.awaiting_answers = {}
+ # List of IDs that will produce a timeout is answer doesn't arrive
+ # {time_of_the_timeout: (id, message to send to gui), }
+ self.awaiting_timeouts = {}
+ # keep the jids we auto added (transports contacts) to not send the
+ # SUBSCRIBED event to gui
+ self.automatically_added = []
+
+ # keep track of sessions this connection has with other JIDs
+ self.sessions = {}
+
+ def get_sessions(self, jid):
+ """
+ Get all sessions for the given full jid
+ """
+ if not gajim.interface.is_pm_contact(jid, self.name):
+ jid = gajim.get_jid_without_resource(jid)
+
+ try:
+ return self.sessions[jid].values()
+ except KeyError:
+ return []
+
+ def get_or_create_session(self, fjid, thread_id):
+ """
+ Return an existing session between this connection and 'jid', returns a
+ new one if none exist
+ """
+ pm = True
+ jid = fjid
+
+ if not gajim.interface.is_pm_contact(fjid, self.name):
+ pm = False
+ jid = gajim.get_jid_without_resource(fjid)
+
+ session = self.find_session(jid, thread_id)
+
+ if session:
+ return session
+
+ if pm:
+ return self.make_new_session(fjid, thread_id, type_='pm')
+ else:
+ return self.make_new_session(fjid, thread_id)
+
+ def find_session(self, jid, thread_id):
+ try:
+ if not thread_id:
+ return self.find_null_session(jid)
+ else:
+ return self.sessions[jid][thread_id]
+ except KeyError:
+ return None
+
+ def terminate_sessions(self, send_termination=False):
+ """
+ Send termination messages and delete all active sessions
+ """
+ for jid in self.sessions:
+ for thread_id in self.sessions[jid]:
+ self.sessions[jid][thread_id].terminate(send_termination)
+
+ self.sessions = {}
+
+ def delete_session(self, jid, thread_id):
+ if not jid in self.sessions:
+ jid = gajim.get_jid_without_resource(jid)
+ if not jid in self.sessions:
+ return
+
+ del self.sessions[jid][thread_id]
+
+ if not self.sessions[jid]:
+ del self.sessions[jid]
+
+ def find_null_session(self, jid):
+ """
+ Find all of the sessions between us and a remote jid in which we haven't
+ received a thread_id yet and returns the session that we last sent a
+ message to
+ """
+ sessions = self.sessions[jid].values()
+
+ # sessions that we haven't received a thread ID in
+ idless = [s for s in sessions if not s.received_thread_id]
+
+ # filter out everything except the default session type
+ chat_sessions = [s for s in idless if isinstance(s,
+ gajim.default_session_type)]
+
+ if chat_sessions:
+ # return the session that we last sent a message in
+ return sorted(chat_sessions, key=operator.attrgetter("last_send"))[-1]
+ else:
+ return None
+
+ def find_controlless_session(self, jid, resource=None):
+ """
+ Find an active session that doesn't have a control attached
+ """
+ try:
+ sessions = self.sessions[jid].values()
+
+ # filter out everything except the default session type
+ chat_sessions = [s for s in sessions if isinstance(s,
+ gajim.default_session_type)]
+
+ orphaned = [s for s in chat_sessions if not s.control]
+
+ if resource:
+ orphaned = [s for s in orphaned if s.resource == resource]
+
+ return orphaned[0]
+ except (KeyError, IndexError):
+ return None
+
+ def make_new_session(self, jid, thread_id=None, type_='chat', cls=None):
+ """
+ Create and register a new session
+
+ thread_id=None to generate one.
+ type_ should be 'chat' or 'pm'.
+ """
+ if not cls:
+ cls = gajim.default_session_type
+
+ sess = cls(self, common.xmpp.JID(jid), thread_id, type_)
+
+ # determine if this session is a pm session
+ # if not, discard the resource so that all sessions are stored bare
+ if not type_ == 'pm':
+ jid = gajim.get_jid_without_resource(jid)
+
+ if not jid in self.sessions:
+ self.sessions[jid] = {}
+
+ self.sessions[jid][sess.thread_id] = sess
+
+ return sess
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream,
- ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
- ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
- def __init__(self):
- global HAS_IDLE
- ConnectionVcard.__init__(self)
- ConnectionBytestream.__init__(self)
- ConnectionCommands.__init__(self)
- ConnectionPubSub.__init__(self)
- ConnectionPEP.__init__(self, account=self.name, dispatcher=self,
- pubsub_connection=self)
- ConnectionCaps.__init__(self, account=self.name,
- dispatch_event=self.dispatch, capscache=capscache.capscache,
- client_caps_factory=capscache.create_suitable_client_caps)
- ConnectionJingle.__init__(self)
- ConnectionHandlersBase.__init__(self)
- self.gmail_url = None
-
- # keep the latest subscribed event for each jid to prevent loop when we
- # acknowledge presences
- self.subscribed_events = {}
- # IDs of jabber:iq:last requests
- self.last_ids = []
- # IDs of jabber:iq:version requests
- self.version_ids = []
- # IDs of urn:xmpp:time requests
- self.entity_time_ids = []
- # ID of urn:xmpp:ping requests
- self.awaiting_xmpp_ping_id = None
- self.continue_connect_info = None
-
- try:
- self.sleeper = common.sleepy.Sleepy()
-# idle.init()
- HAS_IDLE = True
- except Exception:
- HAS_IDLE = False
-
- self.gmail_last_tid = None
- self.gmail_last_time = None
-
- def build_http_auth_answer(self, iq_obj, answer):
- if not self.connection or self.connected < 2:
- return
- if answer == 'yes':
- self.connection.send(iq_obj.buildReply('result'))
- elif answer == 'no':
- err = common.xmpp.Error(iq_obj,
- common.xmpp.protocol.ERR_NOT_AUTHORIZED)
- self.connection.send(err)
-
- def _HttpAuthCB(self, con, iq_obj):
- log.debug('HttpAuthCB')
- opt = gajim.config.get_per('accounts', self.name, 'http_auth')
- if opt in ('yes', 'no'):
- self.build_http_auth_answer(iq_obj, opt)
- else:
- id_ = iq_obj.getTagAttr('confirm', 'id')
- method = iq_obj.getTagAttr('confirm', 'method')
- url = iq_obj.getTagAttr('confirm', 'url')
- msg = iq_obj.getTagData('body') # In case it's a message with a body
- self.dispatch('HTTP_AUTH', (method, url, id_, iq_obj, msg))
- raise common.xmpp.NodeProcessed
-
- def _ErrorCB(self, con, iq_obj):
- log.debug('ErrorCB')
- jid_from = helpers.get_full_jid_from_iq(iq_obj)
- jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from)
- id_ = unicode(iq_obj.getID())
- if id_ in self.version_ids:
- self.dispatch('OS_INFO', (jid_stripped, resource, '', ''))
- self.version_ids.remove(id_)
- return
- if id_ in self.last_ids:
- self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, ''))
- self.last_ids.remove(id_)
- return
- if id_ in self.entity_time_ids:
- self.dispatch('ENTITY_TIME', (jid_stripped, resource, ''))
- self.entity_time_ids.remove(id_)
- return
- errmsg = iq_obj.getErrorMsg()
- errcode = iq_obj.getErrorCode()
- self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode))
-
- def _PrivateCB(self, con, iq_obj):
- """
- Private Data (XEP 048 and 049)
- """
- log.debug('PrivateCB')
- query = iq_obj.getTag('query')
- storage = query.getTag('storage')
- if storage:
- ns = storage.getNamespace()
- if ns == 'storage:bookmarks':
- self._parse_bookmarks(storage, 'xml')
- elif ns == 'gajim:prefs':
- # Preferences data
- # http://www.xmpp.org/extensions/xep-0049.html
- #TODO: implement this
- pass
- elif ns == 'storage:rosternotes':
- # Annotations
- # http://www.xmpp.org/extensions/xep-0145.html
- notes = storage.getTags('note')
- for note in notes:
- try:
- jid = helpers.parse_jid(note.getAttr('jid'))
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % note.getAttr('jid'))
- continue
- annotation = note.getData()
- self.annotations[jid] = annotation
-
- def _parse_bookmarks(self, storage, storage_type):
- """
- storage_type can be 'pubsub' or 'xml' to tell from where we got bookmarks
- """
- # Bookmarked URLs and Conferences
- # http://www.xmpp.org/extensions/xep-0048.html
- resend_to_pubsub = False
- confs = storage.getTags('conference')
- for conf in confs:
- autojoin_val = conf.getAttr('autojoin')
- if autojoin_val is None: # not there (it's optional)
- autojoin_val = False
- minimize_val = conf.getAttr('minimize')
- if minimize_val is None: # not there (it's optional)
- minimize_val = False
- print_status = conf.getTagData('print_status')
- if not print_status:
- print_status = conf.getTagData('show_status')
- try:
- bm = {'name': conf.getAttr('name'),
- 'jid': helpers.parse_jid(conf.getAttr('jid')),
- 'autojoin': autojoin_val,
- 'minimize': minimize_val,
- 'password': conf.getTagData('password'),
- 'nick': conf.getTagData('nick'),
- 'print_status': print_status}
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % conf.getAttr('jid'))
- continue
-
- if bm not in self.bookmarks:
- self.bookmarks.append(bm)
- if storage_type == 'xml':
- # We got a bookmark that was not in pubsub
- resend_to_pubsub = True
- self.dispatch('BOOKMARKS', self.bookmarks)
- if storage_type == 'pubsub':
- # We gor bookmarks from pubsub, now get those from xml to merge them
- self.get_bookmarks(storage_type='xml')
- if self.pubsub_supported and resend_to_pubsub:
- self.store_bookmarks('pubsub')
-
- def _rosterSetCB(self, con, iq_obj):
- log.debug('rosterSetCB')
- version = iq_obj.getTagAttr('query', 'ver')
- for item in iq_obj.getTag('query').getChildren():
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
- continue
- name = item.getAttr('name')
- sub = item.getAttr('subscription')
- ask = item.getAttr('ask')
- groups = []
- for group in item.getTags('group'):
- groups.append(group.getData())
- self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups))
- account_jid = gajim.get_jid_from_account(self.name)
- gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask,
- groups)
- if version:
- gajim.config.set_per('accounts', self.name, 'roster_version',
- version)
- if not self.connection or self.connected < 2:
- raise common.xmpp.NodeProcessed
- reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()},
- to=iq_obj.getFrom(), frm=iq_obj.getTo(), xmlns=None)
- self.connection.send(reply)
- raise common.xmpp.NodeProcessed
-
- def _VersionCB(self, con, iq_obj):
- log.debug('VersionCB')
- if not self.connection or self.connected < 2:
- return
- iq_obj = iq_obj.buildReply('result')
- qp = iq_obj.getTag('query')
- qp.setTagData('name', 'Gajim')
- qp.setTagData('version', gajim.version)
- send_os = gajim.config.get_per('accounts', self.name, 'send_os_info')
- if send_os:
- qp.setTagData('os', helpers.get_os_info())
- self.connection.send(iq_obj)
- raise common.xmpp.NodeProcessed
-
- def _LastCB(self, con, iq_obj):
- global HAS_IDLE
- log.debug('LastCB')
- if not self.connection or self.connected < 2:
- return
- if HAS_IDLE and gajim.config.get_per('accounts', self.name,
- 'send_idle_time'):
- iq_obj = iq_obj.buildReply('result')
- qp = iq_obj.getTag('query')
- qp.attrs['seconds'] = int(self.sleeper.getIdleSec())
- else:
- iq_obj = iq_obj.buildReply('error')
- err = common.xmpp.ErrorNode(name=common.xmpp.NS_STANZAS+' service-unavailable')
- iq_obj.addChild(node=err)
-
- self.connection.send(iq_obj)
- raise common.xmpp.NodeProcessed
-
- def _LastResultCB(self, con, iq_obj):
- log.debug('LastResultCB')
- qp = iq_obj.getTag('query')
- seconds = qp.getAttr('seconds')
- status = qp.getData()
- try:
- seconds = int(seconds)
- except Exception:
- return
- id_ = iq_obj.getID()
- if id_ in self.groupchat_jids:
- who = self.groupchat_jids[id_]
- del self.groupchat_jids[id_]
- else:
- who = helpers.get_full_jid_from_iq(iq_obj)
- if id_ in self.last_ids:
- self.last_ids.remove(id_)
- jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
- self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status))
-
- def _VersionResultCB(self, con, iq_obj):
- log.debug('VersionResultCB')
- client_info = ''
- os_info = ''
- qp = iq_obj.getTag('query')
- if qp.getTag('name'):
- client_info += qp.getTag('name').getData()
- if qp.getTag('version'):
- client_info += ' ' + qp.getTag('version').getData()
- if qp.getTag('os'):
- os_info += qp.getTag('os').getData()
- id_ = iq_obj.getID()
- if id_ in self.groupchat_jids:
- who = self.groupchat_jids[id_]
- del self.groupchat_jids[id_]
- else:
- who = helpers.get_full_jid_from_iq(iq_obj)
- jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
- if id_ in self.version_ids:
- self.version_ids.remove(id_)
- self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info))
-
- def _TimeCB(self, con, iq_obj):
- log.debug('TimeCB')
- if not self.connection or self.connected < 2:
- return
- iq_obj = iq_obj.buildReply('result')
- qp = iq_obj.getTag('query')
- qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime()))
- qp.setTagData('tz', helpers.decode_string(tzname[daylight]))
- qp.setTagData('display', helpers.decode_string(strftime('%c',
- localtime())))
- self.connection.send(iq_obj)
- raise common.xmpp.NodeProcessed
-
- def _TimeRevisedCB(self, con, iq_obj):
- log.debug('TimeRevisedCB')
- if not self.connection or self.connected < 2:
- return
- iq_obj = iq_obj.buildReply('result')
- qp = iq_obj.setTag('time',
- namespace=common.xmpp.NS_TIME_REVISED)
- qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime()))
- isdst = localtime().tm_isdst
- zone = -(timezone, altzone)[isdst] / 60
- tzo = (zone / 60, abs(zone % 60))
- qp.setTagData('tzo', '%+03d:%02d' % (tzo))
- self.connection.send(iq_obj)
- raise common.xmpp.NodeProcessed
-
- def _TimeRevisedResultCB(self, con, iq_obj):
- log.debug('TimeRevisedResultCB')
- time_info = ''
- qp = iq_obj.getTag('time')
- if not qp:
- # wrong answer
- return
- tzo = qp.getTag('tzo').getData()
- if tzo.lower() == 'z':
- tzo = '0:0'
- tzoh, tzom = tzo.split(':')
- utc_time = qp.getTag('utc').getData()
- ZERO = datetime.timedelta(0)
- class UTC(datetime.tzinfo):
- def utcoffset(self, dt):
- return ZERO
- def tzname(self, dt):
- return "UTC"
- def dst(self, dt):
- return ZERO
-
- class contact_tz(datetime.tzinfo):
- def utcoffset(self, dt):
- return datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
- def tzname(self, dt):
- return "remote timezone"
- def dst(self, dt):
- return ZERO
-
- try:
- t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ')
- t = t.replace(tzinfo=UTC())
- time_info = t.astimezone(contact_tz()).strftime('%c')
- except ValueError, e:
- log.info('Wrong time format: %s' % str(e))
-
- id_ = iq_obj.getID()
- if id_ in self.groupchat_jids:
- who = self.groupchat_jids[id_]
- del self.groupchat_jids[id_]
- else:
- who = helpers.get_full_jid_from_iq(iq_obj)
- jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
- if id_ in self.entity_time_ids:
- self.entity_time_ids.remove(id_)
- self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info))
-
- def _gMailNewMailCB(self, con, gm):
- """
- Called when we get notified of new mail messages in gmail account
- """
- if not self.connection or self.connected < 2:
- return
- if not gm.getTag('new-mail'):
- return
- if gm.getTag('new-mail').getNamespace() == common.xmpp.NS_GMAILNOTIFY:
- # we'll now ask the server for the exact number of new messages
- jid = gajim.get_jid_from_account(self.name)
- log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid)
- iq = common.xmpp.Iq(typ = 'get')
- iq.setID(self.connection.getAnID())
- query = iq.setTag('query')
- query.setNamespace(common.xmpp.NS_GMAILNOTIFY)
- # we want only be notified about newer mails
- if self.gmail_last_tid:
- query.setAttr('newer-than-tid', self.gmail_last_tid)
- if self.gmail_last_time:
- query.setAttr('newer-than-time', self.gmail_last_time)
- self.connection.send(iq)
- raise common.xmpp.NodeProcessed
-
- def _gMailQueryCB(self, con, gm):
- """
- Called when we receive results from Querying the server for mail messages
- in gmail account
- """
- if not gm.getTag('mailbox'):
- return
- self.gmail_url = gm.getTag('mailbox').getAttr('url')
- if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY:
- newmsgs = gm.getTag('mailbox').getAttr('total-matched')
- if newmsgs != '0':
- # there are new messages
- gmail_messages_list = []
- if gm.getTag('mailbox').getTag('mail-thread-info'):
- gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
- for gmessage in gmail_messages:
- unread_senders = []
- for sender in gmessage.getTag('senders').getTags('sender'):
- if sender.getAttr('unread') != '1':
- continue
- if sender.getAttr('name'):
- unread_senders.append(sender.getAttr('name') + '< ' + \
- sender.getAttr('address') + '>')
- else:
- unread_senders.append(sender.getAttr('address'))
-
- if not unread_senders:
- continue
- gmail_subject = gmessage.getTag('subject').getData()
- gmail_snippet = gmessage.getTag('snippet').getData()
- tid = int(gmessage.getAttr('tid'))
- if not self.gmail_last_tid or tid > self.gmail_last_tid:
- self.gmail_last_tid = tid
- gmail_messages_list.append({ \
- 'From': unread_senders, \
- 'Subject': gmail_subject, \
- 'Snippet': gmail_snippet, \
- 'url': gmessage.getAttr('url'), \
- 'participation': gmessage.getAttr('participation'), \
- 'messages': gmessage.getAttr('messages'), \
- 'date': gmessage.getAttr('date')})
- self.gmail_last_time = int(gm.getTag('mailbox').getAttr(
- 'result-time'))
-
- jid = gajim.get_jid_from_account(self.name)
- log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid))
- self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list))
- raise common.xmpp.NodeProcessed
-
- def _rosterItemExchangeCB(self, con, msg):
- """
- XEP-0144 Roster Item Echange
- """
- exchange_items_list = {}
- jid_from = helpers.get_full_jid_from_iq(msg)
- items_list = msg.getTag('x').getChildren()
- if not items_list:
- return
- action = items_list[0].getAttr('action')
- if action == None:
- action = 'add'
- for item in msg.getTag('x',
- namespace=common.xmpp.NS_ROSTERX).getChildren():
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
- continue
- name = item.getAttr('name')
- contact = gajim.contacts.get_contact(self.name, jid)
- groups = []
- same_groups = True
- for group in item.getTags('group'):
- groups.append(group.getData())
- # check that all suggested groups are in the groups we have for this
- # contact
- if not contact or group not in contact.groups:
- same_groups = False
- if contact:
- # check that all groups we have for this contact are in the
- # suggested groups
- for group in contact.groups:
- if group not in groups:
- same_groups = False
- if contact.sub in ('both', 'to') and same_groups:
- continue
- exchange_items_list[jid] = []
- exchange_items_list[jid].append(name)
- exchange_items_list[jid].append(groups)
- if exchange_items_list:
- self.dispatch('ROSTERX', (action, exchange_items_list, jid_from))
- raise common.xmpp.NodeProcessed
-
- def _messageCB(self, con, msg):
- """
- Called when we receive a message
- """
- log.debug('MessageCB')
- mtype = msg.getType()
-
- # check if the message is a roster item exchange (XEP-0144)
- if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX):
- self._rosterItemExchangeCB(con, msg)
- return
-
- # check if the message is a XEP-0070 confirmation request
- if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH):
- self._HttpAuthCB(con, msg)
- return
-
- try:
- frm = helpers.get_full_jid_from_iq(msg)
- jid = helpers.get_jid_from_iq(msg)
- except helpers.InvalidFormat:
- self.dispatch('ERROR', (_('Invalid Jabber ID'),
- _('A message from a non-valid JID arrived, it has been ignored.')))
- return
-
- addressTag = msg.getTag('addresses', namespace = common.xmpp.NS_ADDRESS)
-
- # Be sure it comes from one of our resource, else ignore address element
- if addressTag and jid == gajim.get_jid_from_account(self.name):
- address = addressTag.getTag('address', attrs={'type': 'ofrom'})
- if address:
- try:
- frm = helpers.parse_jid(address.getAttr('jid'))
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % address.getAttr('jid'))
- return
- jid = gajim.get_jid_without_resource(frm)
-
- # invitations
- invite = None
- encTag = msg.getTag('x', namespace=common.xmpp.NS_ENCRYPTED)
-
- if not encTag:
- invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
- if invite and not invite.getTag('invite'):
- invite = None
-
- # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED
- # invitation
- # stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED
- xtags = msg.getTags('x')
- for xtag in xtags:
- if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite:
- try:
- room_jid = helpers.parse_jid(xtag.getAttr('jid'))
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % xtag.getAttr('jid'))
- continue
- is_continued = False
- if xtag.getTag('continue'):
- is_continued = True
- self.dispatch('GC_INVITATION', (room_jid, frm, '', None,
- is_continued))
- return
-
- thread_id = msg.getThread()
-
- if not mtype:
- mtype = 'normal'
-
- msgtxt = msg.getBody()
-
- encrypted = False
- xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO)
-
- session = None
- if mtype != 'groupchat':
- session = self.get_or_create_session(frm, thread_id)
-
- if thread_id and not session.received_thread_id:
- session.received_thread_id = True
-
- session.last_receive = time_time()
-
- # check if the message is a XEP-0020 feature negotiation request
- if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE):
- if gajim.HAVE_PYCRYPTO:
- feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE)
- form = common.xmpp.DataForm(node=feature.getTag('x'))
-
- if form['FORM_TYPE'] == 'urn:xmpp:ssn':
- session.handle_negotiation(form)
- else:
- reply = msg.buildReply()
- reply.setType('error')
-
- reply.addChild(feature)
- err = common.xmpp.ErrorNode('service-unavailable', typ='cancel')
- reply.addChild(node=err)
-
- con.send(reply)
-
- raise common.xmpp.NodeProcessed
-
- return
-
- if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT):
- init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT)
- form = common.xmpp.DataForm(node=init.getTag('x'))
-
- session.handle_negotiation(form)
-
- raise common.xmpp.NodeProcessed
-
- tim = msg.getTimestamp()
- tim = helpers.datetime_tuple(tim)
- tim = localtime(timegm(tim))
-
- if xep_200_encrypted:
- encrypted = 'xep200'
-
- try:
- msg = session.decrypt_stanza(msg)
- msgtxt = msg.getBody()
- except Exception:
- self.dispatch('FAILED_DECRYPT', (frm, tim, session))
-
- # Receipt requested
- # TODO: We shouldn't answer if we're invisible!
- contact = gajim.contacts.get_contact(self.name, jid)
- nick = gajim.get_room_and_nick_from_fjid(frm)[1]
- gc_contact = gajim.contacts.get_gc_contact(self.name, jid, nick)
- if msg.getTag('request', namespace=common.xmpp.NS_RECEIPTS) \
- and gajim.config.get_per('accounts', self.name,
- 'answer_receipts') and ((contact and contact.sub \
- not in (u'to', u'none')) or gc_contact) and mtype != 'error':
- receipt = common.xmpp.Message(to=frm, typ='chat')
- receipt.setID(msg.getID())
- receipt.setTag('received',
- namespace='urn:xmpp:receipts')
-
- if thread_id:
- receipt.setThread(thread_id)
- con.send(receipt)
-
- # We got our message's receipt
- if msg.getTag('received', namespace=common.xmpp.NS_RECEIPTS) and \
- session.control and gajim.config.get_per('accounts', self.name,
- 'request_receipt'):
- session.control.conv_textview.hide_xep0184_warning(msg.getID())
-
- if encTag and self.USE_GPG:
- encmsg = encTag.getData()
-
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- if keyID:
- def decrypt_thread(encmsg, keyID):
- decmsg = self.gpg.decrypt(encmsg, keyID)
- # \x00 chars are not allowed in C (so in GTK)
- msgtxt = helpers.decode_string(decmsg.replace('\x00', ''))
- encrypted = 'xep27'
- return (msgtxt, encrypted)
- gajim.thread_interface(decrypt_thread, [encmsg, keyID],
- self._on_message_decrypted, [mtype, msg, session, frm, jid,
- invite, tim])
- return
- self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm,
- jid, invite, tim)
-
- def _on_message_decrypted(self, output, mtype, msg, session, frm, jid,
- invite, tim):
- msgtxt, encrypted = output
- if mtype == 'error':
- self.dispatch_error_message(msg, msgtxt, session, frm, tim)
- elif mtype == 'groupchat':
- self.dispatch_gc_message(msg, frm, msgtxt, jid, tim)
- elif invite is not None:
- self.dispatch_invite_message(invite, frm)
- else:
- if isinstance(session, gajim.default_session_type):
- session.received(frm, msgtxt, tim, encrypted, msg)
- else:
- session.received(msg)
- # END messageCB
-
- # process and dispatch an error message
- def dispatch_error_message(self, msg, msgtxt, session, frm, tim):
- error_msg = msg.getErrorMsg()
-
- if not error_msg:
- error_msg = msgtxt
- msgtxt = None
-
- subject = msg.getSubject()
-
- if session.is_loggable():
- try:
- gajim.logger.write('error', frm, error_msg, tim=tim,
- subject=subject)
- except exceptions.PysqliteOperationalError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
- except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to repair '
- 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
- 'it (all history will be lost).') % common.logger.LOG_DB_PATH
- self.dispatch('ERROR', (pritext, sectext))
- self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
- tim, session))
-
- # process and dispatch a groupchat message
- def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim):
- has_timestamp = bool(msg.timestamp)
-
- subject = msg.getSubject()
-
- if subject is not None:
- self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp))
- return
-
- statusCode = msg.getStatusCode()
-
- if not msg.getTag('body'): # no <body>
- # It could be a config change. See
- # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
- if msg.getTag('x'):
- if statusCode != []:
- self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode))
- return
-
- # Ignore message from room in which we are not
- if jid not in self.last_history_time:
- return
-
- self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(),
- statusCode))
-
- tim_int = int(float(mktime(tim)))
- if gajim.config.should_log(self.name, jid) and not \
- tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0:
- # if frm.find('/') < 0, it means message comes from room itself
- # usually it hold description and can be send at each connection
- # so don't store it in logs
- try:
- gajim.logger.write('gc_msg', frm, msgtxt, tim=tim)
- # store in memory time of last message logged.
- # this will also be saved in rooms_last_message_time table
- # when we quit this muc
- self.last_history_time[jid] = mktime(tim)
-
- except exceptions.PysqliteOperationalError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
- except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to repair '
- 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
- 'it (all history will be lost).') % common.logger.LOG_DB_PATH
- self.dispatch('ERROR', (pritext, sectext))
-
- def dispatch_invite_message(self, invite, frm):
- item = invite.getTag('invite')
- try:
- jid_from = helpers.parse_jid(item.getAttr('from'))
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % item.getAttr('from'))
- return
- reason = item.getTagData('reason')
- item = invite.getTag('password')
- password = invite.getTagData('password')
-
- is_continued = False
- if invite.getTag('invite').getTag('continue'):
- is_continued = True
- self.dispatch('GC_INVITATION',(frm, jid_from, reason, password,
- is_continued))
-
- def _presenceCB(self, con, prs):
- """
- Called when we receive a presence
- """
- ptype = prs.getType()
- if ptype == 'available':
- ptype = None
- rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed',
- 'unsubscribe', 'unsubscribed')
- if ptype and not ptype in rfc_types:
- ptype = None
- log.debug('PresenceCB: %s' % ptype)
- if not self.connection or self.connected < 2:
- log.debug('account is no more connected')
- return
- try:
- who = helpers.get_full_jid_from_iq(prs)
- except Exception:
- if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'):
- # wrong jid, we probably tried to change our nick in a room to a non
- # valid one
- who = str(prs.getFrom())
- jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
- self.dispatch('GC_MSG', (jid_stripped,
- _('Nickname not allowed: %s') % resource, None, False, None, []))
- return
- jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
- timestamp = None
- id_ = prs.getID()
- is_gc = False # is it a GC presence ?
- sigTag = None
- ns_muc_user_x = None
- avatar_sha = None
- # XEP-0172 User Nickname
- user_nick = prs.getTagData('nick')
- if not user_nick:
- user_nick = ''
- contact_nickname = None
- transport_auto_auth = False
- # XEP-0203
- delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2)
- if delay_tag:
- tim = prs.getTimestamp2()
- tim = helpers.datetime_tuple(tim)
- timestamp = localtime(timegm(tim))
- xtags = prs.getTags('x')
- for x in xtags:
- namespace = x.getNamespace()
- if namespace.startswith(common.xmpp.NS_MUC):
- is_gc = True
- if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'):
- ns_muc_user_x = x
- elif namespace == common.xmpp.NS_SIGNED:
- sigTag = x
- elif namespace == common.xmpp.NS_VCARD_UPDATE:
- avatar_sha = x.getTagData('photo')
- contact_nickname = x.getTagData('nickname')
- elif namespace == common.xmpp.NS_DELAY and not timestamp:
- # XEP-0091
- tim = prs.getTimestamp()
- tim = helpers.datetime_tuple(tim)
- timestamp = localtime(timegm(tim))
- elif namespace == 'http://delx.cjb.net/protocol/roster-subsync':
- # see http://trac.gajim.org/ticket/326
- agent = gajim.get_server_from_jid(jid_stripped)
- if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact
- transport_auto_auth = True
-
- if not is_gc and id_ and id_.startswith('gajim_muc_') and \
- ptype == 'error':
- # Error presences may not include sent stanza, so we don't detect it's
- # a muc preence. So detect it by ID
- h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6]
- if id_.split('_')[-1] == h:
- is_gc = True
- status = prs.getStatus() or ''
- show = prs.getShow()
- if show not in ('chat', 'away', 'xa', 'dnd'):
- show = '' # We ignore unknown show
- if not ptype and not show:
- show = 'online'
- elif ptype == 'unavailable':
- show = 'offline'
-
- prio = prs.getPriority()
- try:
- prio = int(prio)
- except Exception:
- prio = 0
- keyID = ''
- if sigTag and self.USE_GPG and ptype != 'error':
- # error presences contain our own signature
- # verify
- sigmsg = sigTag.getData()
- keyID = self.gpg.verify(status, sigmsg)
-
- if is_gc:
- if ptype == 'error':
- errcon = prs.getError()
- errmsg = prs.getErrorMsg()
- errcode = prs.getErrorCode()
- room_jid, nick = gajim.get_room_and_nick_from_fjid(who)
-
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- self.name)
-
- # If gc_control is missing - it may be minimized. Try to get it from
- # there. If it's not there - then it's missing anyway and will
- # remain set to None.
- if gc_control is None:
- minimized = gajim.interface.minimized_controls[self.name]
- gc_control = minimized.get(room_jid)
-
- if errcode == '502':
- # Internal Timeout:
- self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
- prio, keyID, timestamp, None))
- elif (errcode == '503'):
- # maximum user number reached
- self.dispatch('ERROR', (_('Unable to join group chat'),
- _('Maximum number of users for %s has been reached') % \
- room_jid))
- elif (errcode == '401') or (errcon == 'not-authorized'):
- # password required to join
- self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick))
- elif (errcode == '403') or (errcon == 'forbidden'):
- # we are banned
- self.dispatch('ERROR', (_('Unable to join group chat'),
- _('You are banned from group chat %s.') % room_jid))
- elif (errcode == '404') or (errcon in ('item-not-found',
- 'remote-server-not-found')):
- if gc_control is None or gc_control.autorejoin is None:
- # group chat does not exist
- self.dispatch('ERROR', (_('Unable to join group chat'),
- _('Group chat %s does not exist.') % room_jid))
- elif (errcode == '405') or (errcon == 'not-allowed'):
- self.dispatch('ERROR', (_('Unable to join group chat'),
- _('Group chat creation is restricted.')))
- elif (errcode == '406') or (errcon == 'not-acceptable'):
- self.dispatch('ERROR', (_('Unable to join group chat'),
- _('Your registered nickname must be used in group chat %s.') \
- % room_jid))
- elif (errcode == '407') or (errcon == 'registration-required'):
- self.dispatch('ERROR', (_('Unable to join group chat'),
- _('You are not in the members list in groupchat %s.') % \
- room_jid))
- elif (errcode == '409') or (errcon == 'conflict'):
- # nick conflict
- room_jid = gajim.get_room_from_fjid(who)
- self.dispatch('ASK_NEW_NICK', (room_jid,))
- else: # print in the window the error
- self.dispatch('ERROR_ANSWER', ('', jid_stripped,
- errmsg, errcode))
- if not ptype or ptype == 'unavailable':
- if gajim.config.get('log_contact_status_changes') and \
- gajim.config.should_log(self.name, jid_stripped):
- gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped,
- resource)
- st = status or ''
- if gc_c:
- jid = gc_c.jid
- else:
- jid = prs.getJid()
- if jid:
- # we know real jid, save it in db
- st += ' (%s)' % jid
- try:
- gajim.logger.write('gcstatus', who, st, show)
- except exceptions.PysqliteOperationalError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
- except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to '
- 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
- ' or remove it (all history will be lost).') % \
- common.logger.LOG_DB_PATH
- self.dispatch('ERROR', (pritext, sectext))
- if avatar_sha or avatar_sha == '':
- if avatar_sha == '':
- # contact has no avatar
- puny_nick = helpers.sanitize_filename(resource)
- gajim.interface.remove_avatar_files(jid_stripped, puny_nick)
- # if it's a gc presence, don't ask vcard here. We may ask it to
- # real jid in gui part.
- if ns_muc_user_x:
- # Room has been destroyed. see
- # http://www.xmpp.org/extensions/xep-0045.html#destroyroom
- reason = _('Room has been destroyed')
- destroy = ns_muc_user_x.getTag('destroy')
- r = destroy.getTagData('reason')
- if r:
- reason += ' (%s)' % r
- if destroy.getAttr('jid'):
- try:
- jid = helpers.parse_jid(destroy.getAttr('jid'))
- reason += '\n' + _('You can join this room instead: %s') \
- % jid
- except common.helpers.InvalidFormat:
- pass
- statusCode = ['destroyed']
- else:
- reason = prs.getReason()
- statusCode = prs.getStatusCode()
- self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource,
- prs.getRole(), prs.getAffiliation(), prs.getJid(),
- reason, prs.getActor(), statusCode, prs.getNewNick(),
- avatar_sha))
- return
-
- if ptype == 'subscribe':
- log.debug('subscribe request from %s' % who)
- if who.find('@') <= 0 and who in self.agent_registrations:
- self.agent_registrations[who]['sub_received'] = True
- if not self.agent_registrations[who]['roster_push']:
- # We'll reply after roster push result
- return
- if gajim.config.get_per('accounts', self.name, 'autoauth') or \
- who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \
- transport_auto_auth:
- if self.connection:
- p = common.xmpp.Presence(who, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
- if who.find('@') <= 0 or transport_auto_auth:
- self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline',
- resource, prio, keyID, timestamp, None))
- if transport_auto_auth:
- self.automatically_added.append(jid_stripped)
- self.request_subscription(jid_stripped, name = user_nick)
- else:
- if not status:
- status = _('I would like to add you to my roster.')
- self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick))
- elif ptype == 'subscribed':
- if jid_stripped in self.automatically_added:
- self.automatically_added.remove(jid_stripped)
- else:
- # detect a subscription loop
- if jid_stripped not in self.subscribed_events:
- self.subscribed_events[jid_stripped] = []
- self.subscribed_events[jid_stripped].append(time_time())
- block = False
- if len(self.subscribed_events[jid_stripped]) > 5:
- if time_time() - self.subscribed_events[jid_stripped][0] < 5:
- block = True
- self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
- if block:
- gajim.config.set_per('account', self.name,
- 'dont_ack_subscription', True)
- else:
- self.dispatch('SUBSCRIBED', (jid_stripped, resource))
- # BE CAREFUL: no con.updateRosterItem() in a callback
- log.debug(_('we are now subscribed to %s') % who)
- elif ptype == 'unsubscribe':
- log.debug(_('unsubscribe request from %s') % who)
- elif ptype == 'unsubscribed':
- log.debug(_('we are now unsubscribed from %s') % who)
- # detect a unsubscription loop
- if jid_stripped not in self.subscribed_events:
- self.subscribed_events[jid_stripped] = []
- self.subscribed_events[jid_stripped].append(time_time())
- block = False
- if len(self.subscribed_events[jid_stripped]) > 5:
- if time_time() - self.subscribed_events[jid_stripped][0] < 5:
- block = True
- self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
- if block:
- gajim.config.set_per('account', self.name, 'dont_ack_subscription',
- True)
- else:
- self.dispatch('UNSUBSCRIBED', jid_stripped)
- elif ptype == 'error':
- errmsg = prs.getError()
- errcode = prs.getErrorCode()
- if errcode != '502': # Internal Timeout:
- # print in the window the error
- self.dispatch('ERROR_ANSWER', ('', jid_stripped,
- errmsg, errcode))
- if errcode != '409': # conflict # See #5120
- self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
- prio, keyID, timestamp, None))
-
- if ptype == 'unavailable':
- for jid in [jid_stripped, who]:
- if jid not in self.sessions:
- continue
- # automatically terminate sessions that they haven't sent a thread
- # ID in, only if other part support thread ID
- for sess in self.sessions[jid].values():
- if not sess.received_thread_id:
- contact = gajim.contacts.get_contact(self.name, jid)
- # FIXME: I don't know if this is the correct behavior here.
- # Anyway, it is the old behavior when we assumed that
- # not-existing contacts don't support anything
- contact_exists = bool(contact)
- session_supported = contact_exists and (
- contact.supports(common.xmpp.NS_SSN) or
- contact.supports(common.xmpp.NS_ESESSION))
- if session_supported:
- sess.terminate()
- del self.sessions[jid][sess.thread_id]
-
- if avatar_sha is not None and ptype != 'error':
- if jid_stripped not in self.vcard_shas:
- cached_vcard = self.get_cached_vcard(jid_stripped)
- if cached_vcard and 'PHOTO' in cached_vcard and \
- 'SHA' in cached_vcard['PHOTO']:
- self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA']
- else:
- self.vcard_shas[jid_stripped] = ''
- if avatar_sha != self.vcard_shas[jid_stripped]:
- # avatar has been updated
- self.request_vcard(jid_stripped)
- if not ptype or ptype == 'unavailable':
- if gajim.config.get('log_contact_status_changes') and \
- gajim.config.should_log(self.name, jid_stripped):
- try:
- gajim.logger.write('status', jid_stripped, status, show)
- except exceptions.PysqliteOperationalError, e:
- self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
- except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to '
- 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
- 'or remove it (all history will be lost).') % \
- common.logger.LOG_DB_PATH
- self.dispatch('ERROR', (pritext, sectext))
- our_jid = gajim.get_jid_from_account(self.name)
- if jid_stripped == our_jid and resource == self.server_resource:
- # We got our own presence
- self.dispatch('STATUS', show)
- else:
- self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio,
- keyID, timestamp, contact_nickname))
- # END presenceCB
-
- def _StanzaArrivedCB(self, con, obj):
- self.last_io = gajim.idlequeue.current_time()
-
- def _MucOwnerCB(self, con, iq_obj):
- log.debug('MucOwnerCB')
- qp = iq_obj.getQueryPayload()
- node = None
- for q in qp:
- if q.getNamespace() == common.xmpp.NS_DATA:
- node = q
- if not node:
- return
- self.dispatch('GC_CONFIG', (helpers.get_full_jid_from_iq(iq_obj), node))
-
- def _MucAdminCB(self, con, iq_obj):
- log.debug('MucAdminCB')
- items = iq_obj.getTag('query', namespace=common.xmpp.NS_MUC_ADMIN).\
- getTags('item')
- users_dict = {}
- for item in items:
- if item.has_attr('jid') and item.has_attr('affiliation'):
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except common.helpers.InvalidFormat:
- log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
- continue
- affiliation = item.getAttr('affiliation')
- users_dict[jid] = {'affiliation': affiliation}
- if item.has_attr('nick'):
- users_dict[jid]['nick'] = item.getAttr('nick')
- if item.has_attr('role'):
- users_dict[jid]['role'] = item.getAttr('role')
- reason = item.getTagData('reason')
- if reason:
- users_dict[jid]['reason'] = reason
-
- self.dispatch('GC_AFFILIATION', (helpers.get_full_jid_from_iq(iq_obj),
- users_dict))
-
- def _MucErrorCB(self, con, iq_obj):
- log.debug('MucErrorCB')
- jid = helpers.get_full_jid_from_iq(iq_obj)
- errmsg = iq_obj.getError()
- errcode = iq_obj.getErrorCode()
- self.dispatch('MSGERROR', (jid, errcode, errmsg))
-
- def _IqPingCB(self, con, iq_obj):
- log.debug('IqPingCB')
- if not self.connection or self.connected < 2:
- return
- iq_obj = iq_obj.buildReply('result')
- self.connection.send(iq_obj)
- raise common.xmpp.NodeProcessed
-
- def _PrivacySetCB(self, con, iq_obj):
- """
- Privacy lists (XEP 016)
-
- A list has been set.
- """
- log.debug('PrivacySetCB')
- if not self.connection or self.connected < 2:
- return
- result = iq_obj.buildReply('result')
- q = result.getTag('query')
- if q:
- result.delChild(q)
- self.connection.send(result)
- raise common.xmpp.NodeProcessed
-
- def _getRoster(self):
- log.debug('getRosterCB')
- if not self.connection:
- return
- self.connection.getRoster(self._on_roster_set)
- self.discoverItems(gajim.config.get_per('accounts', self.name,
- 'hostname'), id_prefix='Gajim_')
- if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
- self.discover_ft_proxies()
-
- def discover_ft_proxies(self):
- cfg_proxies = gajim.config.get_per('accounts', self.name,
- 'file_transfer_proxies')
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + '/' +\
- self.server_resource)
- if cfg_proxies:
- proxies = [e.strip() for e in cfg_proxies.split(',')]
- for proxy in proxies:
- gajim.proxy65_manager.resolve(proxy, self.connection, our_jid)
-
- def _on_roster_set(self, roster):
- roster_version = roster.version
- received_from_server = roster.received_from_server
- raw_roster = roster.getRaw()
- roster = {}
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
- if self.connected > 1 and self.continue_connect_info:
- msg = self.continue_connect_info[1]
- sign_msg = self.continue_connect_info[2]
- signed = ''
- send_first_presence = True
- if sign_msg:
- signed = self.get_signed_presence(msg, self._send_first_presence)
- if signed is None:
- self.dispatch('GPG_PASSWORD_REQUIRED',
- (self._send_first_presence,))
- # _send_first_presence will be called when user enter passphrase
- send_first_presence = False
- if send_first_presence:
- self._send_first_presence(signed)
-
- for jid in raw_roster:
- try:
- j = helpers.parse_jid(jid)
- except Exception:
- print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid
- else:
- infos = raw_roster[jid]
- if jid != our_jid and (not infos['subscription'] or \
- infos['subscription'] == 'none') and (not infos['ask'] or \
- infos['ask'] == 'none') and not infos['name'] and \
- not infos['groups']:
- # remove this useless item, it won't be shown in roster anyway
- self.connection.getRoster().delItem(jid)
- elif jid != our_jid: # don't add our jid
- roster[j] = raw_roster[jid]
- if gajim.jid_is_transport(jid) and \
- not gajim.get_transport_name_from_jid(jid):
- # we can't determine which iconset to use
- self.discoverInfo(jid)
-
- gajim.logger.replace_roster(self.name, roster_version, roster)
- if received_from_server:
- for contact in gajim.contacts.iter_contacts(self.name):
- if not contact.is_groupchat() and contact.jid not in roster and \
- contact.jid != gajim.get_jid_from_account(self.name):
- self.dispatch('ROSTER_INFO', (contact.jid, None, None, None,
- ()))
- for jid in roster:
- self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'],
- roster[jid]['subscription'], roster[jid]['ask'],
- roster[jid]['groups']))
-
- def _send_first_presence(self, signed = ''):
- show = self.continue_connect_info[0]
- msg = self.continue_connect_info[1]
- sign_msg = self.continue_connect_info[2]
- if sign_msg and not signed:
- signed = self.get_signed_presence(msg)
- if signed is None:
- self.dispatch('BAD_PASSPHRASE', ())
- self.USE_GPG = False
- signed = ''
- self.connected = gajim.SHOW_LIST.index(show)
- sshow = helpers.get_xmpp_show(show)
- # send our presence
- if show == 'invisible':
- self.send_invisible_presence(msg, signed, True)
- return
- if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
- return
- priority = gajim.get_priority(self.name, sshow)
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
- vcard = self.get_cached_vcard(our_jid)
- if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']:
- self.vcard_sha = vcard['PHOTO']['SHA']
- p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
-
- if self.connection:
- self.connection.send(p)
- self.priority = priority
- self.dispatch('STATUS', show)
- if self.vcard_supported:
- # ask our VCard
- self.request_vcard(None)
-
- # Get bookmarks from private namespace
- self.get_bookmarks()
-
- # Get annotations from private namespace
- self.get_annotations()
-
- # Inform GUI we just signed in
- self.dispatch('SIGNED_IN', ())
- self.send_awaiting_pep()
- self.continue_connect_info = None
-
- def request_gmail_notifications(self):
- if not self.connection or self.connected < 2:
- return
- # It's a gmail account,
- # inform the server that we want e-mail notifications
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
- log.debug(('%s is a gmail account. Setting option '
- 'to get e-mail notifications on the server.') % (our_jid))
- iq = common.xmpp.Iq(typ = 'set', to = our_jid)
- iq.setAttr('id', 'MailNotify')
- query = iq.setTag('usersetting')
- query.setNamespace(common.xmpp.NS_GTALKSETTING)
- query = query.setTag('mailnotifications')
- query.setAttr('value', 'true')
- self.connection.send(iq)
- # Ask how many messages there are now
- iq = common.xmpp.Iq(typ = 'get')
- iq.setID(self.connection.getAnID())
- query = iq.setTag('query')
- query.setNamespace(common.xmpp.NS_GMAILNOTIFY)
- self.connection.send(iq)
-
-
- def _search_fields_received(self, con, iq_obj):
- jid = jid = helpers.get_jid_from_iq(iq_obj)
- tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH)
- if not tag:
- self.dispatch('SEARCH_FORM', (jid, None, False))
- return
- df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
- if df:
- self.dispatch('SEARCH_FORM', (jid, df, True))
- return
- df = {}
- for i in iq_obj.getQueryPayload():
- df[i.getName()] = i.getData()
- self.dispatch('SEARCH_FORM', (jid, df, False))
-
- def _StreamCB(self, con, obj):
- if obj.getTag('conflict'):
- # disconnected because of a resource conflict
- self.dispatch('RESOURCE_CONFLICT', ())
-
- def _register_handlers(self, con, con_type):
- # try to find another way to register handlers in each class
- # that defines handlers
- con.RegisterHandler('message', self._messageCB)
- con.RegisterHandler('presence', self._presenceCB)
- con.RegisterHandler('presence', self._capsPresenceCB)
- # We use makefirst so that this handler is called before _messageCB, and
- # can prevent calling it when it's not needed.
- # We also don't check for namespace, else it cannot stop _messageCB to be
- # called
- con.RegisterHandler('message', self._pubsubEventCB, makefirst=True)
- con.RegisterHandler('iq', self._vCardCB, 'result',
- common.xmpp.NS_VCARD)
- con.RegisterHandler('iq', self._rosterSetCB, 'set',
- common.xmpp.NS_ROSTER)
- con.RegisterHandler('iq', self._siSetCB, 'set',
- common.xmpp.NS_SI)
- con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set',
- common.xmpp.NS_ROSTERX)
- con.RegisterHandler('iq', self._siErrorCB, 'error',
- common.xmpp.NS_SI)
- con.RegisterHandler('iq', self._siResultCB, 'result',
- common.xmpp.NS_SI)
- con.RegisterHandler('iq', self._discoGetCB, 'get',
- common.xmpp.NS_DISCO)
- con.RegisterHandler('iq', self._bytestreamSetCB, 'set',
- common.xmpp.NS_BYTESTREAM)
- con.RegisterHandler('iq', self._bytestreamResultCB, 'result',
- common.xmpp.NS_BYTESTREAM)
- con.RegisterHandler('iq', self._bytestreamErrorCB, 'error',
- common.xmpp.NS_BYTESTREAM)
- con.RegisterHandler('iq', self._DiscoverItemsCB, 'result',
- common.xmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error',
- common.xmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._DiscoverInfoCB, 'result',
- common.xmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error',
- common.xmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._VersionCB, 'get',
- common.xmpp.NS_VERSION)
- con.RegisterHandler('iq', self._TimeCB, 'get',
- common.xmpp.NS_TIME)
- con.RegisterHandler('iq', self._TimeRevisedCB, 'get',
- common.xmpp.NS_TIME_REVISED)
- con.RegisterHandler('iq', self._LastCB, 'get',
- common.xmpp.NS_LAST)
- con.RegisterHandler('iq', self._LastResultCB, 'result',
- common.xmpp.NS_LAST)
- con.RegisterHandler('iq', self._VersionResultCB, 'result',
- common.xmpp.NS_VERSION)
- con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result',
- common.xmpp.NS_TIME_REVISED)
- con.RegisterHandler('iq', self._MucOwnerCB, 'result',
- common.xmpp.NS_MUC_OWNER)
- con.RegisterHandler('iq', self._MucAdminCB, 'result',
- common.xmpp.NS_MUC_ADMIN)
- con.RegisterHandler('iq', self._PrivateCB, 'result',
- common.xmpp.NS_PRIVATE)
- con.RegisterHandler('iq', self._HttpAuthCB, 'get',
- common.xmpp.NS_HTTP_AUTH)
- con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
- common.xmpp.NS_COMMANDS)
- con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
- common.xmpp.NS_GMAILNOTIFY)
- con.RegisterHandler('iq', self._gMailQueryCB, 'result',
- common.xmpp.NS_GMAILNOTIFY)
- con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
- common.xmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
- common.xmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._IqPingCB, 'get',
- common.xmpp.NS_PING)
- con.RegisterHandler('iq', self._search_fields_received, 'result',
- common.xmpp.NS_SEARCH)
- con.RegisterHandler('iq', self._PrivacySetCB, 'set',
- common.xmpp.NS_PRIVACY)
- con.RegisterHandler('iq', self._PubSubCB, 'result')
- con.RegisterHandler('iq', self._PubSubErrorCB, 'error')
- con.RegisterHandler('iq', self._JingleCB, 'result')
- con.RegisterHandler('iq', self._JingleCB, 'error')
- con.RegisterHandler('iq', self._JingleCB, 'set',
- common.xmpp.NS_JINGLE)
- con.RegisterHandler('iq', self._ErrorCB, 'error')
- con.RegisterHandler('iq', self._IqCB)
- con.RegisterHandler('iq', self._StanzaArrivedCB)
- con.RegisterHandler('iq', self._ResultCB, 'result')
- con.RegisterHandler('presence', self._StanzaArrivedCB)
- con.RegisterHandler('message', self._StanzaArrivedCB)
- con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams')
-
-# vim: se ts=3:
+ ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
+ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
+ def __init__(self):
+ global HAS_IDLE
+ ConnectionVcard.__init__(self)
+ ConnectionBytestream.__init__(self)
+ ConnectionCommands.__init__(self)
+ ConnectionPubSub.__init__(self)
+ ConnectionPEP.__init__(self, account=self.name, dispatcher=self,
+ pubsub_connection=self)
+ ConnectionCaps.__init__(self, account=self.name,
+ dispatch_event=self.dispatch, capscache=capscache.capscache,
+ client_caps_factory=capscache.create_suitable_client_caps)
+ ConnectionJingle.__init__(self)
+ ConnectionHandlersBase.__init__(self)
+ self.gmail_url = None
+
+ # keep the latest subscribed event for each jid to prevent loop when we
+ # acknowledge presences
+ self.subscribed_events = {}
+ # IDs of jabber:iq:last requests
+ self.last_ids = []
+ # IDs of jabber:iq:version requests
+ self.version_ids = []
+ # IDs of urn:xmpp:time requests
+ self.entity_time_ids = []
+ # ID of urn:xmpp:ping requests
+ self.awaiting_xmpp_ping_id = None
+ self.continue_connect_info = None
+
+ try:
+ self.sleeper = common.sleepy.Sleepy()
+# idle.init()
+ HAS_IDLE = True
+ except Exception:
+ HAS_IDLE = False
+
+ self.gmail_last_tid = None
+ self.gmail_last_time = None
+
+ def build_http_auth_answer(self, iq_obj, answer):
+ if not self.connection or self.connected < 2:
+ return
+ if answer == 'yes':
+ self.connection.send(iq_obj.buildReply('result'))
+ elif answer == 'no':
+ err = common.xmpp.Error(iq_obj,
+ common.xmpp.protocol.ERR_NOT_AUTHORIZED)
+ self.connection.send(err)
+
+ def _HttpAuthCB(self, con, iq_obj):
+ log.debug('HttpAuthCB')
+ opt = gajim.config.get_per('accounts', self.name, 'http_auth')
+ if opt in ('yes', 'no'):
+ self.build_http_auth_answer(iq_obj, opt)
+ else:
+ id_ = iq_obj.getTagAttr('confirm', 'id')
+ method = iq_obj.getTagAttr('confirm', 'method')
+ url = iq_obj.getTagAttr('confirm', 'url')
+ msg = iq_obj.getTagData('body') # In case it's a message with a body
+ self.dispatch('HTTP_AUTH', (method, url, id_, iq_obj, msg))
+ raise common.xmpp.NodeProcessed
+
+ def _ErrorCB(self, con, iq_obj):
+ log.debug('ErrorCB')
+ jid_from = helpers.get_full_jid_from_iq(iq_obj)
+ jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from)
+ id_ = unicode(iq_obj.getID())
+ if id_ in self.version_ids:
+ self.dispatch('OS_INFO', (jid_stripped, resource, '', ''))
+ self.version_ids.remove(id_)
+ return
+ if id_ in self.last_ids:
+ self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, ''))
+ self.last_ids.remove(id_)
+ return
+ if id_ in self.entity_time_ids:
+ self.dispatch('ENTITY_TIME', (jid_stripped, resource, ''))
+ self.entity_time_ids.remove(id_)
+ return
+ errmsg = iq_obj.getErrorMsg()
+ errcode = iq_obj.getErrorCode()
+ self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode))
+
+ def _PrivateCB(self, con, iq_obj):
+ """
+ Private Data (XEP 048 and 049)
+ """
+ log.debug('PrivateCB')
+ query = iq_obj.getTag('query')
+ storage = query.getTag('storage')
+ if storage:
+ ns = storage.getNamespace()
+ if ns == 'storage:bookmarks':
+ self._parse_bookmarks(storage, 'xml')
+ elif ns == 'gajim:prefs':
+ # Preferences data
+ # http://www.xmpp.org/extensions/xep-0049.html
+ #TODO: implement this
+ pass
+ elif ns == 'storage:rosternotes':
+ # Annotations
+ # http://www.xmpp.org/extensions/xep-0145.html
+ notes = storage.getTags('note')
+ for note in notes:
+ try:
+ jid = helpers.parse_jid(note.getAttr('jid'))
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % note.getAttr('jid'))
+ continue
+ annotation = note.getData()
+ self.annotations[jid] = annotation
+
+ def _parse_bookmarks(self, storage, storage_type):
+ """
+ storage_type can be 'pubsub' or 'xml' to tell from where we got bookmarks
+ """
+ # Bookmarked URLs and Conferences
+ # http://www.xmpp.org/extensions/xep-0048.html
+ resend_to_pubsub = False
+ confs = storage.getTags('conference')
+ for conf in confs:
+ autojoin_val = conf.getAttr('autojoin')
+ if autojoin_val is None: # not there (it's optional)
+ autojoin_val = False
+ minimize_val = conf.getAttr('minimize')
+ if minimize_val is None: # not there (it's optional)
+ minimize_val = False
+ print_status = conf.getTagData('print_status')
+ if not print_status:
+ print_status = conf.getTagData('show_status')
+ try:
+ bm = {'name': conf.getAttr('name'),
+ 'jid': helpers.parse_jid(conf.getAttr('jid')),
+ 'autojoin': autojoin_val,
+ 'minimize': minimize_val,
+ 'password': conf.getTagData('password'),
+ 'nick': conf.getTagData('nick'),
+ 'print_status': print_status}
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % conf.getAttr('jid'))
+ continue
+
+ if bm not in self.bookmarks:
+ self.bookmarks.append(bm)
+ if storage_type == 'xml':
+ # We got a bookmark that was not in pubsub
+ resend_to_pubsub = True
+ self.dispatch('BOOKMARKS', self.bookmarks)
+ if storage_type == 'pubsub':
+ # We gor bookmarks from pubsub, now get those from xml to merge them
+ self.get_bookmarks(storage_type='xml')
+ if self.pubsub_supported and resend_to_pubsub:
+ self.store_bookmarks('pubsub')
+
+ def _rosterSetCB(self, con, iq_obj):
+ log.debug('rosterSetCB')
+ version = iq_obj.getTagAttr('query', 'ver')
+ for item in iq_obj.getTag('query').getChildren():
+ try:
+ jid = helpers.parse_jid(item.getAttr('jid'))
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
+ continue
+ name = item.getAttr('name')
+ sub = item.getAttr('subscription')
+ ask = item.getAttr('ask')
+ groups = []
+ for group in item.getTags('group'):
+ groups.append(group.getData())
+ self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups))
+ account_jid = gajim.get_jid_from_account(self.name)
+ gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask,
+ groups)
+ if version:
+ gajim.config.set_per('accounts', self.name, 'roster_version',
+ version)
+ if not self.connection or self.connected < 2:
+ raise common.xmpp.NodeProcessed
+ reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()},
+ to=iq_obj.getFrom(), frm=iq_obj.getTo(), xmlns=None)
+ self.connection.send(reply)
+ raise common.xmpp.NodeProcessed
+
+ def _VersionCB(self, con, iq_obj):
+ log.debug('VersionCB')
+ if not self.connection or self.connected < 2:
+ return
+ iq_obj = iq_obj.buildReply('result')
+ qp = iq_obj.getTag('query')
+ qp.setTagData('name', 'Gajim')
+ qp.setTagData('version', gajim.version)
+ send_os = gajim.config.get_per('accounts', self.name, 'send_os_info')
+ if send_os:
+ qp.setTagData('os', helpers.get_os_info())
+ self.connection.send(iq_obj)
+ raise common.xmpp.NodeProcessed
+
+ def _LastCB(self, con, iq_obj):
+ global HAS_IDLE
+ log.debug('LastCB')
+ if not self.connection or self.connected < 2:
+ return
+ if HAS_IDLE and gajim.config.get_per('accounts', self.name,
+ 'send_idle_time'):
+ iq_obj = iq_obj.buildReply('result')
+ qp = iq_obj.getTag('query')
+ qp.attrs['seconds'] = int(self.sleeper.getIdleSec())
+ else:
+ iq_obj = iq_obj.buildReply('error')
+ err = common.xmpp.ErrorNode(name=common.xmpp.NS_STANZAS+' service-unavailable')
+ iq_obj.addChild(node=err)
+
+ self.connection.send(iq_obj)
+ raise common.xmpp.NodeProcessed
+
+ def _LastResultCB(self, con, iq_obj):
+ log.debug('LastResultCB')
+ qp = iq_obj.getTag('query')
+ seconds = qp.getAttr('seconds')
+ status = qp.getData()
+ try:
+ seconds = int(seconds)
+ except Exception:
+ return
+ id_ = iq_obj.getID()
+ if id_ in self.groupchat_jids:
+ who = self.groupchat_jids[id_]
+ del self.groupchat_jids[id_]
+ else:
+ who = helpers.get_full_jid_from_iq(iq_obj)
+ if id_ in self.last_ids:
+ self.last_ids.remove(id_)
+ jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
+ self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status))
+
+ def _VersionResultCB(self, con, iq_obj):
+ log.debug('VersionResultCB')
+ client_info = ''
+ os_info = ''
+ qp = iq_obj.getTag('query')
+ if qp.getTag('name'):
+ client_info += qp.getTag('name').getData()
+ if qp.getTag('version'):
+ client_info += ' ' + qp.getTag('version').getData()
+ if qp.getTag('os'):
+ os_info += qp.getTag('os').getData()
+ id_ = iq_obj.getID()
+ if id_ in self.groupchat_jids:
+ who = self.groupchat_jids[id_]
+ del self.groupchat_jids[id_]
+ else:
+ who = helpers.get_full_jid_from_iq(iq_obj)
+ jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
+ if id_ in self.version_ids:
+ self.version_ids.remove(id_)
+ self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info))
+
+ def _TimeCB(self, con, iq_obj):
+ log.debug('TimeCB')
+ if not self.connection or self.connected < 2:
+ return
+ iq_obj = iq_obj.buildReply('result')
+ qp = iq_obj.getTag('query')
+ qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime()))
+ qp.setTagData('tz', helpers.decode_string(tzname[daylight]))
+ qp.setTagData('display', helpers.decode_string(strftime('%c',
+ localtime())))
+ self.connection.send(iq_obj)
+ raise common.xmpp.NodeProcessed
+
+ def _TimeRevisedCB(self, con, iq_obj):
+ log.debug('TimeRevisedCB')
+ if not self.connection or self.connected < 2:
+ return
+ iq_obj = iq_obj.buildReply('result')
+ qp = iq_obj.setTag('time',
+ namespace=common.xmpp.NS_TIME_REVISED)
+ qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime()))
+ isdst = localtime().tm_isdst
+ zone = -(timezone, altzone)[isdst] / 60
+ tzo = (zone / 60, abs(zone % 60))
+ qp.setTagData('tzo', '%+03d:%02d' % (tzo))
+ self.connection.send(iq_obj)
+ raise common.xmpp.NodeProcessed
+
+ def _TimeRevisedResultCB(self, con, iq_obj):
+ log.debug('TimeRevisedResultCB')
+ time_info = ''
+ qp = iq_obj.getTag('time')
+ if not qp:
+ # wrong answer
+ return
+ tzo = qp.getTag('tzo').getData()
+ if tzo.lower() == 'z':
+ tzo = '0:0'
+ tzoh, tzom = tzo.split(':')
+ utc_time = qp.getTag('utc').getData()
+ ZERO = datetime.timedelta(0)
+ class UTC(datetime.tzinfo):
+ def utcoffset(self, dt):
+ return ZERO
+ def tzname(self, dt):
+ return "UTC"
+ def dst(self, dt):
+ return ZERO
+
+ class contact_tz(datetime.tzinfo):
+ def utcoffset(self, dt):
+ return datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
+ def tzname(self, dt):
+ return "remote timezone"
+ def dst(self, dt):
+ return ZERO
+
+ try:
+ t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ')
+ t = t.replace(tzinfo=UTC())
+ time_info = t.astimezone(contact_tz()).strftime('%c')
+ except ValueError, e:
+ log.info('Wrong time format: %s' % str(e))
+
+ id_ = iq_obj.getID()
+ if id_ in self.groupchat_jids:
+ who = self.groupchat_jids[id_]
+ del self.groupchat_jids[id_]
+ else:
+ who = helpers.get_full_jid_from_iq(iq_obj)
+ jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
+ if id_ in self.entity_time_ids:
+ self.entity_time_ids.remove(id_)
+ self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info))
+
+ def _gMailNewMailCB(self, con, gm):
+ """
+ Called when we get notified of new mail messages in gmail account
+ """
+ if not self.connection or self.connected < 2:
+ return
+ if not gm.getTag('new-mail'):
+ return
+ if gm.getTag('new-mail').getNamespace() == common.xmpp.NS_GMAILNOTIFY:
+ # we'll now ask the server for the exact number of new messages
+ jid = gajim.get_jid_from_account(self.name)
+ log.debug('Got notification of new gmail e-mail on %s. Asking the server for more info.' % jid)
+ iq = common.xmpp.Iq(typ = 'get')
+ iq.setID(self.connection.getAnID())
+ query = iq.setTag('query')
+ query.setNamespace(common.xmpp.NS_GMAILNOTIFY)
+ # we want only be notified about newer mails
+ if self.gmail_last_tid:
+ query.setAttr('newer-than-tid', self.gmail_last_tid)
+ if self.gmail_last_time:
+ query.setAttr('newer-than-time', self.gmail_last_time)
+ self.connection.send(iq)
+ raise common.xmpp.NodeProcessed
+
+ def _gMailQueryCB(self, con, gm):
+ """
+ Called when we receive results from Querying the server for mail messages
+ in gmail account
+ """
+ if not gm.getTag('mailbox'):
+ return
+ self.gmail_url = gm.getTag('mailbox').getAttr('url')
+ if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY:
+ newmsgs = gm.getTag('mailbox').getAttr('total-matched')
+ if newmsgs != '0':
+ # there are new messages
+ gmail_messages_list = []
+ if gm.getTag('mailbox').getTag('mail-thread-info'):
+ gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
+ for gmessage in gmail_messages:
+ unread_senders = []
+ for sender in gmessage.getTag('senders').getTags('sender'):
+ if sender.getAttr('unread') != '1':
+ continue
+ if sender.getAttr('name'):
+ unread_senders.append(sender.getAttr('name') + '< ' + \
+ sender.getAttr('address') + '>')
+ else:
+ unread_senders.append(sender.getAttr('address'))
+
+ if not unread_senders:
+ continue
+ gmail_subject = gmessage.getTag('subject').getData()
+ gmail_snippet = gmessage.getTag('snippet').getData()
+ tid = int(gmessage.getAttr('tid'))
+ if not self.gmail_last_tid or tid > self.gmail_last_tid:
+ self.gmail_last_tid = tid
+ gmail_messages_list.append({ \
+ 'From': unread_senders, \
+ 'Subject': gmail_subject, \
+ 'Snippet': gmail_snippet, \
+ 'url': gmessage.getAttr('url'), \
+ 'participation': gmessage.getAttr('participation'), \
+ 'messages': gmessage.getAttr('messages'), \
+ 'date': gmessage.getAttr('date')})
+ self.gmail_last_time = int(gm.getTag('mailbox').getAttr(
+ 'result-time'))
+
+ jid = gajim.get_jid_from_account(self.name)
+ log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid))
+ self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list))
+ raise common.xmpp.NodeProcessed
+
+ def _rosterItemExchangeCB(self, con, msg):
+ """
+ XEP-0144 Roster Item Echange
+ """
+ exchange_items_list = {}
+ jid_from = helpers.get_full_jid_from_iq(msg)
+ items_list = msg.getTag('x').getChildren()
+ if not items_list:
+ return
+ action = items_list[0].getAttr('action')
+ if action == None:
+ action = 'add'
+ for item in msg.getTag('x',
+ namespace=common.xmpp.NS_ROSTERX).getChildren():
+ try:
+ jid = helpers.parse_jid(item.getAttr('jid'))
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
+ continue
+ name = item.getAttr('name')
+ contact = gajim.contacts.get_contact(self.name, jid)
+ groups = []
+ same_groups = True
+ for group in item.getTags('group'):
+ groups.append(group.getData())
+ # check that all suggested groups are in the groups we have for this
+ # contact
+ if not contact or group not in contact.groups:
+ same_groups = False
+ if contact:
+ # check that all groups we have for this contact are in the
+ # suggested groups
+ for group in contact.groups:
+ if group not in groups:
+ same_groups = False
+ if contact.sub in ('both', 'to') and same_groups:
+ continue
+ exchange_items_list[jid] = []
+ exchange_items_list[jid].append(name)
+ exchange_items_list[jid].append(groups)
+ if exchange_items_list:
+ self.dispatch('ROSTERX', (action, exchange_items_list, jid_from))
+ raise common.xmpp.NodeProcessed
+
+ def _messageCB(self, con, msg):
+ """
+ Called when we receive a message
+ """
+ log.debug('MessageCB')
+ mtype = msg.getType()
+
+ # check if the message is a roster item exchange (XEP-0144)
+ if msg.getTag('x', namespace=common.xmpp.NS_ROSTERX):
+ self._rosterItemExchangeCB(con, msg)
+ return
+
+ # check if the message is a XEP-0070 confirmation request
+ if msg.getTag('confirm', namespace=common.xmpp.NS_HTTP_AUTH):
+ self._HttpAuthCB(con, msg)
+ return
+
+ try:
+ frm = helpers.get_full_jid_from_iq(msg)
+ jid = helpers.get_jid_from_iq(msg)
+ except helpers.InvalidFormat:
+ self.dispatch('ERROR', (_('Invalid Jabber ID'),
+ _('A message from a non-valid JID arrived, it has been ignored.')))
+ return
+
+ addressTag = msg.getTag('addresses', namespace = common.xmpp.NS_ADDRESS)
+
+ # Be sure it comes from one of our resource, else ignore address element
+ if addressTag and jid == gajim.get_jid_from_account(self.name):
+ address = addressTag.getTag('address', attrs={'type': 'ofrom'})
+ if address:
+ try:
+ frm = helpers.parse_jid(address.getAttr('jid'))
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % address.getAttr('jid'))
+ return
+ jid = gajim.get_jid_without_resource(frm)
+
+ # invitations
+ invite = None
+ encTag = msg.getTag('x', namespace=common.xmpp.NS_ENCRYPTED)
+
+ if not encTag:
+ invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
+ if invite and not invite.getTag('invite'):
+ invite = None
+
+ # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED
+ # invitation
+ # stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED
+ xtags = msg.getTags('x')
+ for xtag in xtags:
+ if xtag.getNamespace() == common.xmpp.NS_CONFERENCE and not invite:
+ try:
+ room_jid = helpers.parse_jid(xtag.getAttr('jid'))
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % xtag.getAttr('jid'))
+ continue
+ is_continued = False
+ if xtag.getTag('continue'):
+ is_continued = True
+ self.dispatch('GC_INVITATION', (room_jid, frm, '', None,
+ is_continued))
+ return
+
+ thread_id = msg.getThread()
+
+ if not mtype:
+ mtype = 'normal'
+
+ msgtxt = msg.getBody()
+
+ encrypted = False
+ xep_200_encrypted = msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO)
+
+ session = None
+ if mtype != 'groupchat':
+ session = self.get_or_create_session(frm, thread_id)
+
+ if thread_id and not session.received_thread_id:
+ session.received_thread_id = True
+
+ session.last_receive = time_time()
+
+ # check if the message is a XEP-0020 feature negotiation request
+ if msg.getTag('feature', namespace=common.xmpp.NS_FEATURE):
+ if gajim.HAVE_PYCRYPTO:
+ feature = msg.getTag(name='feature', namespace=common.xmpp.NS_FEATURE)
+ form = common.xmpp.DataForm(node=feature.getTag('x'))
+
+ if form['FORM_TYPE'] == 'urn:xmpp:ssn':
+ session.handle_negotiation(form)
+ else:
+ reply = msg.buildReply()
+ reply.setType('error')
+
+ reply.addChild(feature)
+ err = common.xmpp.ErrorNode('service-unavailable', typ='cancel')
+ reply.addChild(node=err)
+
+ con.send(reply)
+
+ raise common.xmpp.NodeProcessed
+
+ return
+
+ if msg.getTag('init', namespace=common.xmpp.NS_ESESSION_INIT):
+ init = msg.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT)
+ form = common.xmpp.DataForm(node=init.getTag('x'))
+
+ session.handle_negotiation(form)
+
+ raise common.xmpp.NodeProcessed
+
+ tim = msg.getTimestamp()
+ tim = helpers.datetime_tuple(tim)
+ tim = localtime(timegm(tim))
+
+ if xep_200_encrypted:
+ encrypted = 'xep200'
+
+ try:
+ msg = session.decrypt_stanza(msg)
+ msgtxt = msg.getBody()
+ except Exception:
+ self.dispatch('FAILED_DECRYPT', (frm, tim, session))
+
+ # Receipt requested
+ # TODO: We shouldn't answer if we're invisible!
+ contact = gajim.contacts.get_contact(self.name, jid)
+ nick = gajim.get_room_and_nick_from_fjid(frm)[1]
+ gc_contact = gajim.contacts.get_gc_contact(self.name, jid, nick)
+ if msg.getTag('request', namespace=common.xmpp.NS_RECEIPTS) \
+ and gajim.config.get_per('accounts', self.name,
+ 'answer_receipts') and ((contact and contact.sub \
+ not in (u'to', u'none')) or gc_contact) and mtype != 'error':
+ receipt = common.xmpp.Message(to=frm, typ='chat')
+ receipt.setID(msg.getID())
+ receipt.setTag('received',
+ namespace='urn:xmpp:receipts')
+
+ if thread_id:
+ receipt.setThread(thread_id)
+ con.send(receipt)
+
+ # We got our message's receipt
+ if msg.getTag('received', namespace=common.xmpp.NS_RECEIPTS) and \
+ session.control and gajim.config.get_per('accounts', self.name,
+ 'request_receipt'):
+ session.control.conv_textview.hide_xep0184_warning(msg.getID())
+
+ if encTag and self.USE_GPG:
+ encmsg = encTag.getData()
+
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ if keyID:
+ def decrypt_thread(encmsg, keyID):
+ decmsg = self.gpg.decrypt(encmsg, keyID)
+ # \x00 chars are not allowed in C (so in GTK)
+ msgtxt = helpers.decode_string(decmsg.replace('\x00', ''))
+ encrypted = 'xep27'
+ return (msgtxt, encrypted)
+ gajim.thread_interface(decrypt_thread, [encmsg, keyID],
+ self._on_message_decrypted, [mtype, msg, session, frm, jid,
+ invite, tim])
+ return
+ self._on_message_decrypted((msgtxt, encrypted), mtype, msg, session, frm,
+ jid, invite, tim)
+
+ def _on_message_decrypted(self, output, mtype, msg, session, frm, jid,
+ invite, tim):
+ msgtxt, encrypted = output
+ if mtype == 'error':
+ self.dispatch_error_message(msg, msgtxt, session, frm, tim)
+ elif mtype == 'groupchat':
+ self.dispatch_gc_message(msg, frm, msgtxt, jid, tim)
+ elif invite is not None:
+ self.dispatch_invite_message(invite, frm)
+ else:
+ if isinstance(session, gajim.default_session_type):
+ session.received(frm, msgtxt, tim, encrypted, msg)
+ else:
+ session.received(msg)
+ # END messageCB
+
+ # process and dispatch an error message
+ def dispatch_error_message(self, msg, msgtxt, session, frm, tim):
+ error_msg = msg.getErrorMsg()
+
+ if not error_msg:
+ error_msg = msgtxt
+ msgtxt = None
+
+ subject = msg.getSubject()
+
+ if session.is_loggable():
+ try:
+ gajim.logger.write('error', frm, error_msg, tim=tim,
+ subject=subject)
+ except exceptions.PysqliteOperationalError, e:
+ self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
+ except exceptions.DatabaseMalformed:
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to repair '
+ 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
+ 'it (all history will be lost).') % common.logger.LOG_DB_PATH
+ self.dispatch('ERROR', (pritext, sectext))
+ self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
+ tim, session))
+
+ # process and dispatch a groupchat message
+ def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim):
+ has_timestamp = bool(msg.timestamp)
+
+ subject = msg.getSubject()
+
+ if subject is not None:
+ self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp))
+ return
+
+ statusCode = msg.getStatusCode()
+
+ if not msg.getTag('body'): # no <body>
+ # It could be a config change. See
+ # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
+ if msg.getTag('x'):
+ if statusCode != []:
+ self.dispatch('GC_CONFIG_CHANGE', (jid, statusCode))
+ return
+
+ # Ignore message from room in which we are not
+ if jid not in self.last_history_time:
+ return
+
+ self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(),
+ statusCode))
+
+ tim_int = int(float(mktime(tim)))
+ if gajim.config.should_log(self.name, jid) and not \
+ tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0:
+ # if frm.find('/') < 0, it means message comes from room itself
+ # usually it hold description and can be send at each connection
+ # so don't store it in logs
+ try:
+ gajim.logger.write('gc_msg', frm, msgtxt, tim=tim)
+ # store in memory time of last message logged.
+ # this will also be saved in rooms_last_message_time table
+ # when we quit this muc
+ self.last_history_time[jid] = mktime(tim)
+
+ except exceptions.PysqliteOperationalError, e:
+ self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
+ except exceptions.DatabaseMalformed:
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to repair '
+ 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
+ 'it (all history will be lost).') % common.logger.LOG_DB_PATH
+ self.dispatch('ERROR', (pritext, sectext))
+
+ def dispatch_invite_message(self, invite, frm):
+ item = invite.getTag('invite')
+ try:
+ jid_from = helpers.parse_jid(item.getAttr('from'))
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % item.getAttr('from'))
+ return
+ reason = item.getTagData('reason')
+ item = invite.getTag('password')
+ password = invite.getTagData('password')
+
+ is_continued = False
+ if invite.getTag('invite').getTag('continue'):
+ is_continued = True
+ self.dispatch('GC_INVITATION',(frm, jid_from, reason, password,
+ is_continued))
+
+ def _presenceCB(self, con, prs):
+ """
+ Called when we receive a presence
+ """
+ ptype = prs.getType()
+ if ptype == 'available':
+ ptype = None
+ rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed',
+ 'unsubscribe', 'unsubscribed')
+ if ptype and not ptype in rfc_types:
+ ptype = None
+ log.debug('PresenceCB: %s' % ptype)
+ if not self.connection or self.connected < 2:
+ log.debug('account is no more connected')
+ return
+ try:
+ who = helpers.get_full_jid_from_iq(prs)
+ except Exception:
+ if prs.getTag('error') and prs.getTag('error').getTag('jid-malformed'):
+ # wrong jid, we probably tried to change our nick in a room to a non
+ # valid one
+ who = str(prs.getFrom())
+ jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
+ self.dispatch('GC_MSG', (jid_stripped,
+ _('Nickname not allowed: %s') % resource, None, False, None, []))
+ return
+ jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
+ timestamp = None
+ id_ = prs.getID()
+ is_gc = False # is it a GC presence ?
+ sigTag = None
+ ns_muc_user_x = None
+ avatar_sha = None
+ # XEP-0172 User Nickname
+ user_nick = prs.getTagData('nick')
+ if not user_nick:
+ user_nick = ''
+ contact_nickname = None
+ transport_auto_auth = False
+ # XEP-0203
+ delay_tag = prs.getTag('delay', namespace=common.xmpp.NS_DELAY2)
+ if delay_tag:
+ tim = prs.getTimestamp2()
+ tim = helpers.datetime_tuple(tim)
+ timestamp = localtime(timegm(tim))
+ xtags = prs.getTags('x')
+ for x in xtags:
+ namespace = x.getNamespace()
+ if namespace.startswith(common.xmpp.NS_MUC):
+ is_gc = True
+ if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'):
+ ns_muc_user_x = x
+ elif namespace == common.xmpp.NS_SIGNED:
+ sigTag = x
+ elif namespace == common.xmpp.NS_VCARD_UPDATE:
+ avatar_sha = x.getTagData('photo')
+ contact_nickname = x.getTagData('nickname')
+ elif namespace == common.xmpp.NS_DELAY and not timestamp:
+ # XEP-0091
+ tim = prs.getTimestamp()
+ tim = helpers.datetime_tuple(tim)
+ timestamp = localtime(timegm(tim))
+ elif namespace == 'http://delx.cjb.net/protocol/roster-subsync':
+ # see http://trac.gajim.org/ticket/326
+ agent = gajim.get_server_from_jid(jid_stripped)
+ if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact
+ transport_auto_auth = True
+
+ if not is_gc and id_ and id_.startswith('gajim_muc_') and \
+ ptype == 'error':
+ # Error presences may not include sent stanza, so we don't detect it's
+ # a muc preence. So detect it by ID
+ h = hmac.new(self.secret_hmac, jid_stripped).hexdigest()[:6]
+ if id_.split('_')[-1] == h:
+ is_gc = True
+ status = prs.getStatus() or ''
+ show = prs.getShow()
+ if show not in ('chat', 'away', 'xa', 'dnd'):
+ show = '' # We ignore unknown show
+ if not ptype and not show:
+ show = 'online'
+ elif ptype == 'unavailable':
+ show = 'offline'
+
+ prio = prs.getPriority()
+ try:
+ prio = int(prio)
+ except Exception:
+ prio = 0
+ keyID = ''
+ if sigTag and self.USE_GPG and ptype != 'error':
+ # error presences contain our own signature
+ # verify
+ sigmsg = sigTag.getData()
+ keyID = self.gpg.verify(status, sigmsg)
+
+ if is_gc:
+ if ptype == 'error':
+ errcon = prs.getError()
+ errmsg = prs.getErrorMsg()
+ errcode = prs.getErrorCode()
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(who)
+
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
+ self.name)
+
+ # If gc_control is missing - it may be minimized. Try to get it from
+ # there. If it's not there - then it's missing anyway and will
+ # remain set to None.
+ if gc_control is None:
+ minimized = gajim.interface.minimized_controls[self.name]
+ gc_control = minimized.get(room_jid)
+
+ if errcode == '502':
+ # Internal Timeout:
+ self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
+ prio, keyID, timestamp, None))
+ elif (errcode == '503'):
+ # maximum user number reached
+ self.dispatch('ERROR', (_('Unable to join group chat'),
+ _('Maximum number of users for %s has been reached') % \
+ room_jid))
+ elif (errcode == '401') or (errcon == 'not-authorized'):
+ # password required to join
+ self.dispatch('GC_PASSWORD_REQUIRED', (room_jid, nick))
+ elif (errcode == '403') or (errcon == 'forbidden'):
+ # we are banned
+ self.dispatch('ERROR', (_('Unable to join group chat'),
+ _('You are banned from group chat %s.') % room_jid))
+ elif (errcode == '404') or (errcon in ('item-not-found',
+ 'remote-server-not-found')):
+ if gc_control is None or gc_control.autorejoin is None:
+ # group chat does not exist
+ self.dispatch('ERROR', (_('Unable to join group chat'),
+ _('Group chat %s does not exist.') % room_jid))
+ elif (errcode == '405') or (errcon == 'not-allowed'):
+ self.dispatch('ERROR', (_('Unable to join group chat'),
+ _('Group chat creation is restricted.')))
+ elif (errcode == '406') or (errcon == 'not-acceptable'):
+ self.dispatch('ERROR', (_('Unable to join group chat'),
+ _('Your registered nickname must be used in group chat %s.') \
+ % room_jid))
+ elif (errcode == '407') or (errcon == 'registration-required'):
+ self.dispatch('ERROR', (_('Unable to join group chat'),
+ _('You are not in the members list in groupchat %s.') % \
+ room_jid))
+ elif (errcode == '409') or (errcon == 'conflict'):
+ # nick conflict
+ room_jid = gajim.get_room_from_fjid(who)
+ self.dispatch('ASK_NEW_NICK', (room_jid,))
+ else: # print in the window the error
+ self.dispatch('ERROR_ANSWER', ('', jid_stripped,
+ errmsg, errcode))
+ if not ptype or ptype == 'unavailable':
+ if gajim.config.get('log_contact_status_changes') and \
+ gajim.config.should_log(self.name, jid_stripped):
+ gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped,
+ resource)
+ st = status or ''
+ if gc_c:
+ jid = gc_c.jid
+ else:
+ jid = prs.getJid()
+ if jid:
+ # we know real jid, save it in db
+ st += ' (%s)' % jid
+ try:
+ gajim.logger.write('gcstatus', who, st, show)
+ except exceptions.PysqliteOperationalError, e:
+ self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
+ except exceptions.DatabaseMalformed:
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to '
+ 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup)'
+ ' or remove it (all history will be lost).') % \
+ common.logger.LOG_DB_PATH
+ self.dispatch('ERROR', (pritext, sectext))
+ if avatar_sha or avatar_sha == '':
+ if avatar_sha == '':
+ # contact has no avatar
+ puny_nick = helpers.sanitize_filename(resource)
+ gajim.interface.remove_avatar_files(jid_stripped, puny_nick)
+ # if it's a gc presence, don't ask vcard here. We may ask it to
+ # real jid in gui part.
+ if ns_muc_user_x:
+ # Room has been destroyed. see
+ # http://www.xmpp.org/extensions/xep-0045.html#destroyroom
+ reason = _('Room has been destroyed')
+ destroy = ns_muc_user_x.getTag('destroy')
+ r = destroy.getTagData('reason')
+ if r:
+ reason += ' (%s)' % r
+ if destroy.getAttr('jid'):
+ try:
+ jid = helpers.parse_jid(destroy.getAttr('jid'))
+ reason += '\n' + _('You can join this room instead: %s') \
+ % jid
+ except common.helpers.InvalidFormat:
+ pass
+ statusCode = ['destroyed']
+ else:
+ reason = prs.getReason()
+ statusCode = prs.getStatusCode()
+ self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource,
+ prs.getRole(), prs.getAffiliation(), prs.getJid(),
+ reason, prs.getActor(), statusCode, prs.getNewNick(),
+ avatar_sha))
+ return
+
+ if ptype == 'subscribe':
+ log.debug('subscribe request from %s' % who)
+ if who.find('@') <= 0 and who in self.agent_registrations:
+ self.agent_registrations[who]['sub_received'] = True
+ if not self.agent_registrations[who]['roster_push']:
+ # We'll reply after roster push result
+ return
+ if gajim.config.get_per('accounts', self.name, 'autoauth') or \
+ who.find('@') <= 0 or jid_stripped in self.jids_for_auto_auth or \
+ transport_auto_auth:
+ if self.connection:
+ p = common.xmpp.Presence(who, 'subscribed')
+ p = self.add_sha(p)
+ self.connection.send(p)
+ if who.find('@') <= 0 or transport_auto_auth:
+ self.dispatch('NOTIFY', (jid_stripped, 'offline', 'offline',
+ resource, prio, keyID, timestamp, None))
+ if transport_auto_auth:
+ self.automatically_added.append(jid_stripped)
+ self.request_subscription(jid_stripped, name = user_nick)
+ else:
+ if not status:
+ status = _('I would like to add you to my roster.')
+ self.dispatch('SUBSCRIBE', (jid_stripped, status, user_nick))
+ elif ptype == 'subscribed':
+ if jid_stripped in self.automatically_added:
+ self.automatically_added.remove(jid_stripped)
+ else:
+ # detect a subscription loop
+ if jid_stripped not in self.subscribed_events:
+ self.subscribed_events[jid_stripped] = []
+ self.subscribed_events[jid_stripped].append(time_time())
+ block = False
+ if len(self.subscribed_events[jid_stripped]) > 5:
+ if time_time() - self.subscribed_events[jid_stripped][0] < 5:
+ block = True
+ self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
+ if block:
+ gajim.config.set_per('account', self.name,
+ 'dont_ack_subscription', True)
+ else:
+ self.dispatch('SUBSCRIBED', (jid_stripped, resource))
+ # BE CAREFUL: no con.updateRosterItem() in a callback
+ log.debug(_('we are now subscribed to %s') % who)
+ elif ptype == 'unsubscribe':
+ log.debug(_('unsubscribe request from %s') % who)
+ elif ptype == 'unsubscribed':
+ log.debug(_('we are now unsubscribed from %s') % who)
+ # detect a unsubscription loop
+ if jid_stripped not in self.subscribed_events:
+ self.subscribed_events[jid_stripped] = []
+ self.subscribed_events[jid_stripped].append(time_time())
+ block = False
+ if len(self.subscribed_events[jid_stripped]) > 5:
+ if time_time() - self.subscribed_events[jid_stripped][0] < 5:
+ block = True
+ self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
+ if block:
+ gajim.config.set_per('account', self.name, 'dont_ack_subscription',
+ True)
+ else:
+ self.dispatch('UNSUBSCRIBED', jid_stripped)
+ elif ptype == 'error':
+ errmsg = prs.getError()
+ errcode = prs.getErrorCode()
+ if errcode != '502': # Internal Timeout:
+ # print in the window the error
+ self.dispatch('ERROR_ANSWER', ('', jid_stripped,
+ errmsg, errcode))
+ if errcode != '409': # conflict # See #5120
+ self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
+ prio, keyID, timestamp, None))
+
+ if ptype == 'unavailable':
+ for jid in [jid_stripped, who]:
+ if jid not in self.sessions:
+ continue
+ # automatically terminate sessions that they haven't sent a thread
+ # ID in, only if other part support thread ID
+ for sess in self.sessions[jid].values():
+ if not sess.received_thread_id:
+ contact = gajim.contacts.get_contact(self.name, jid)
+ # FIXME: I don't know if this is the correct behavior here.
+ # Anyway, it is the old behavior when we assumed that
+ # not-existing contacts don't support anything
+ contact_exists = bool(contact)
+ session_supported = contact_exists and (
+ contact.supports(common.xmpp.NS_SSN) or
+ contact.supports(common.xmpp.NS_ESESSION))
+ if session_supported:
+ sess.terminate()
+ del self.sessions[jid][sess.thread_id]
+
+ if avatar_sha is not None and ptype != 'error':
+ if jid_stripped not in self.vcard_shas:
+ cached_vcard = self.get_cached_vcard(jid_stripped)
+ if cached_vcard and 'PHOTO' in cached_vcard and \
+ 'SHA' in cached_vcard['PHOTO']:
+ self.vcard_shas[jid_stripped] = cached_vcard['PHOTO']['SHA']
+ else:
+ self.vcard_shas[jid_stripped] = ''
+ if avatar_sha != self.vcard_shas[jid_stripped]:
+ # avatar has been updated
+ self.request_vcard(jid_stripped)
+ if not ptype or ptype == 'unavailable':
+ if gajim.config.get('log_contact_status_changes') and \
+ gajim.config.should_log(self.name, jid_stripped):
+ try:
+ gajim.logger.write('status', jid_stripped, status, show)
+ except exceptions.PysqliteOperationalError, e:
+ self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
+ except exceptions.DatabaseMalformed:
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to '
+ 'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
+ 'or remove it (all history will be lost).') % \
+ common.logger.LOG_DB_PATH
+ self.dispatch('ERROR', (pritext, sectext))
+ our_jid = gajim.get_jid_from_account(self.name)
+ if jid_stripped == our_jid and resource == self.server_resource:
+ # We got our own presence
+ self.dispatch('STATUS', show)
+ else:
+ self.dispatch('NOTIFY', (jid_stripped, show, status, resource, prio,
+ keyID, timestamp, contact_nickname))
+ # END presenceCB
+
+ def _StanzaArrivedCB(self, con, obj):
+ self.last_io = gajim.idlequeue.current_time()
+
+ def _MucOwnerCB(self, con, iq_obj):
+ log.debug('MucOwnerCB')
+ qp = iq_obj.getQueryPayload()
+ node = None
+ for q in qp:
+ if q.getNamespace() == common.xmpp.NS_DATA:
+ node = q
+ if not node:
+ return
+ self.dispatch('GC_CONFIG', (helpers.get_full_jid_from_iq(iq_obj), node))
+
+ def _MucAdminCB(self, con, iq_obj):
+ log.debug('MucAdminCB')
+ items = iq_obj.getTag('query', namespace=common.xmpp.NS_MUC_ADMIN).\
+ getTags('item')
+ users_dict = {}
+ for item in items:
+ if item.has_attr('jid') and item.has_attr('affiliation'):
+ try:
+ jid = helpers.parse_jid(item.getAttr('jid'))
+ except common.helpers.InvalidFormat:
+ log.warn('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
+ continue
+ affiliation = item.getAttr('affiliation')
+ users_dict[jid] = {'affiliation': affiliation}
+ if item.has_attr('nick'):
+ users_dict[jid]['nick'] = item.getAttr('nick')
+ if item.has_attr('role'):
+ users_dict[jid]['role'] = item.getAttr('role')
+ reason = item.getTagData('reason')
+ if reason:
+ users_dict[jid]['reason'] = reason
+
+ self.dispatch('GC_AFFILIATION', (helpers.get_full_jid_from_iq(iq_obj),
+ users_dict))
+
+ def _MucErrorCB(self, con, iq_obj):
+ log.debug('MucErrorCB')
+ jid = helpers.get_full_jid_from_iq(iq_obj)
+ errmsg = iq_obj.getError()
+ errcode = iq_obj.getErrorCode()
+ self.dispatch('MSGERROR', (jid, errcode, errmsg))
+
+ def _IqPingCB(self, con, iq_obj):
+ log.debug('IqPingCB')
+ if not self.connection or self.connected < 2:
+ return
+ iq_obj = iq_obj.buildReply('result')
+ self.connection.send(iq_obj)
+ raise common.xmpp.NodeProcessed
+
+ def _PrivacySetCB(self, con, iq_obj):
+ """
+ Privacy lists (XEP 016)
+
+ A list has been set.
+ """
+ log.debug('PrivacySetCB')
+ if not self.connection or self.connected < 2:
+ return
+ result = iq_obj.buildReply('result')
+ q = result.getTag('query')
+ if q:
+ result.delChild(q)
+ self.connection.send(result)
+ raise common.xmpp.NodeProcessed
+
+ def _getRoster(self):
+ log.debug('getRosterCB')
+ if not self.connection:
+ return
+ self.connection.getRoster(self._on_roster_set)
+ self.discoverItems(gajim.config.get_per('accounts', self.name,
+ 'hostname'), id_prefix='Gajim_')
+ if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
+ self.discover_ft_proxies()
+
+ def discover_ft_proxies(self):
+ cfg_proxies = gajim.config.get_per('accounts', self.name,
+ 'file_transfer_proxies')
+ our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + '/' +\
+ self.server_resource)
+ if cfg_proxies:
+ proxies = [e.strip() for e in cfg_proxies.split(',')]
+ for proxy in proxies:
+ gajim.proxy65_manager.resolve(proxy, self.connection, our_jid)
+
+ def _on_roster_set(self, roster):
+ roster_version = roster.version
+ received_from_server = roster.received_from_server
+ raw_roster = roster.getRaw()
+ roster = {}
+ our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
+ if self.connected > 1 and self.continue_connect_info:
+ msg = self.continue_connect_info[1]
+ sign_msg = self.continue_connect_info[2]
+ signed = ''
+ send_first_presence = True
+ if sign_msg:
+ signed = self.get_signed_presence(msg, self._send_first_presence)
+ if signed is None:
+ self.dispatch('GPG_PASSWORD_REQUIRED',
+ (self._send_first_presence,))
+ # _send_first_presence will be called when user enter passphrase
+ send_first_presence = False
+ if send_first_presence:
+ self._send_first_presence(signed)
+
+ for jid in raw_roster:
+ try:
+ j = helpers.parse_jid(jid)
+ except Exception:
+ print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid
+ else:
+ infos = raw_roster[jid]
+ if jid != our_jid and (not infos['subscription'] or \
+ infos['subscription'] == 'none') and (not infos['ask'] or \
+ infos['ask'] == 'none') and not infos['name'] and \
+ not infos['groups']:
+ # remove this useless item, it won't be shown in roster anyway
+ self.connection.getRoster().delItem(jid)
+ elif jid != our_jid: # don't add our jid
+ roster[j] = raw_roster[jid]
+ if gajim.jid_is_transport(jid) and \
+ not gajim.get_transport_name_from_jid(jid):
+ # we can't determine which iconset to use
+ self.discoverInfo(jid)
+
+ gajim.logger.replace_roster(self.name, roster_version, roster)
+ if received_from_server:
+ for contact in gajim.contacts.iter_contacts(self.name):
+ if not contact.is_groupchat() and contact.jid not in roster and \
+ contact.jid != gajim.get_jid_from_account(self.name):
+ self.dispatch('ROSTER_INFO', (contact.jid, None, None, None,
+ ()))
+ for jid in roster:
+ self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'],
+ roster[jid]['subscription'], roster[jid]['ask'],
+ roster[jid]['groups']))
+
+ def _send_first_presence(self, signed = ''):
+ show = self.continue_connect_info[0]
+ msg = self.continue_connect_info[1]
+ sign_msg = self.continue_connect_info[2]
+ if sign_msg and not signed:
+ signed = self.get_signed_presence(msg)
+ if signed is None:
+ self.dispatch('BAD_PASSPHRASE', ())
+ self.USE_GPG = False
+ signed = ''
+ self.connected = gajim.SHOW_LIST.index(show)
+ sshow = helpers.get_xmpp_show(show)
+ # send our presence
+ if show == 'invisible':
+ self.send_invisible_presence(msg, signed, True)
+ return
+ if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
+ return
+ priority = gajim.get_priority(self.name, sshow)
+ our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
+ vcard = self.get_cached_vcard(our_jid)
+ if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']:
+ self.vcard_sha = vcard['PHOTO']['SHA']
+ p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
+ p = self.add_sha(p)
+ if msg:
+ p.setStatus(msg)
+ if signed:
+ p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
+
+ if self.connection:
+ self.connection.send(p)
+ self.priority = priority
+ self.dispatch('STATUS', show)
+ if self.vcard_supported:
+ # ask our VCard
+ self.request_vcard(None)
+
+ # Get bookmarks from private namespace
+ self.get_bookmarks()
+
+ # Get annotations from private namespace
+ self.get_annotations()
+
+ # Inform GUI we just signed in
+ self.dispatch('SIGNED_IN', ())
+ self.send_awaiting_pep()
+ self.continue_connect_info = None
+
+ def request_gmail_notifications(self):
+ if not self.connection or self.connected < 2:
+ return
+ # It's a gmail account,
+ # inform the server that we want e-mail notifications
+ our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
+ log.debug(('%s is a gmail account. Setting option '
+ 'to get e-mail notifications on the server.') % (our_jid))
+ iq = common.xmpp.Iq(typ = 'set', to = our_jid)
+ iq.setAttr('id', 'MailNotify')
+ query = iq.setTag('usersetting')
+ query.setNamespace(common.xmpp.NS_GTALKSETTING)
+ query = query.setTag('mailnotifications')
+ query.setAttr('value', 'true')
+ self.connection.send(iq)
+ # Ask how many messages there are now
+ iq = common.xmpp.Iq(typ = 'get')
+ iq.setID(self.connection.getAnID())
+ query = iq.setTag('query')
+ query.setNamespace(common.xmpp.NS_GMAILNOTIFY)
+ self.connection.send(iq)
+
+
+ def _search_fields_received(self, con, iq_obj):
+ jid = jid = helpers.get_jid_from_iq(iq_obj)
+ tag = iq_obj.getTag('query', namespace = common.xmpp.NS_SEARCH)
+ if not tag:
+ self.dispatch('SEARCH_FORM', (jid, None, False))
+ return
+ df = tag.getTag('x', namespace = common.xmpp.NS_DATA)
+ if df:
+ self.dispatch('SEARCH_FORM', (jid, df, True))
+ return
+ df = {}
+ for i in iq_obj.getQueryPayload():
+ df[i.getName()] = i.getData()
+ self.dispatch('SEARCH_FORM', (jid, df, False))
+
+ def _StreamCB(self, con, obj):
+ if obj.getTag('conflict'):
+ # disconnected because of a resource conflict
+ self.dispatch('RESOURCE_CONFLICT', ())
+
+ def _register_handlers(self, con, con_type):
+ # try to find another way to register handlers in each class
+ # that defines handlers
+ con.RegisterHandler('message', self._messageCB)
+ con.RegisterHandler('presence', self._presenceCB)
+ con.RegisterHandler('presence', self._capsPresenceCB)
+ # We use makefirst so that this handler is called before _messageCB, and
+ # can prevent calling it when it's not needed.
+ # We also don't check for namespace, else it cannot stop _messageCB to be
+ # called
+ con.RegisterHandler('message', self._pubsubEventCB, makefirst=True)
+ con.RegisterHandler('iq', self._vCardCB, 'result',
+ common.xmpp.NS_VCARD)
+ con.RegisterHandler('iq', self._rosterSetCB, 'set',
+ common.xmpp.NS_ROSTER)
+ con.RegisterHandler('iq', self._siSetCB, 'set',
+ common.xmpp.NS_SI)
+ con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set',
+ common.xmpp.NS_ROSTERX)
+ con.RegisterHandler('iq', self._siErrorCB, 'error',
+ common.xmpp.NS_SI)
+ con.RegisterHandler('iq', self._siResultCB, 'result',
+ common.xmpp.NS_SI)
+ con.RegisterHandler('iq', self._discoGetCB, 'get',
+ common.xmpp.NS_DISCO)
+ con.RegisterHandler('iq', self._bytestreamSetCB, 'set',
+ common.xmpp.NS_BYTESTREAM)
+ con.RegisterHandler('iq', self._bytestreamResultCB, 'result',
+ common.xmpp.NS_BYTESTREAM)
+ con.RegisterHandler('iq', self._bytestreamErrorCB, 'error',
+ common.xmpp.NS_BYTESTREAM)
+ con.RegisterHandler('iq', self._DiscoverItemsCB, 'result',
+ common.xmpp.NS_DISCO_ITEMS)
+ con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error',
+ common.xmpp.NS_DISCO_ITEMS)
+ con.RegisterHandler('iq', self._DiscoverInfoCB, 'result',
+ common.xmpp.NS_DISCO_INFO)
+ con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error',
+ common.xmpp.NS_DISCO_INFO)
+ con.RegisterHandler('iq', self._VersionCB, 'get',
+ common.xmpp.NS_VERSION)
+ con.RegisterHandler('iq', self._TimeCB, 'get',
+ common.xmpp.NS_TIME)
+ con.RegisterHandler('iq', self._TimeRevisedCB, 'get',
+ common.xmpp.NS_TIME_REVISED)
+ con.RegisterHandler('iq', self._LastCB, 'get',
+ common.xmpp.NS_LAST)
+ con.RegisterHandler('iq', self._LastResultCB, 'result',
+ common.xmpp.NS_LAST)
+ con.RegisterHandler('iq', self._VersionResultCB, 'result',
+ common.xmpp.NS_VERSION)
+ con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result',
+ common.xmpp.NS_TIME_REVISED)
+ con.RegisterHandler('iq', self._MucOwnerCB, 'result',
+ common.xmpp.NS_MUC_OWNER)
+ con.RegisterHandler('iq', self._MucAdminCB, 'result',
+ common.xmpp.NS_MUC_ADMIN)
+ con.RegisterHandler('iq', self._PrivateCB, 'result',
+ common.xmpp.NS_PRIVATE)
+ con.RegisterHandler('iq', self._HttpAuthCB, 'get',
+ common.xmpp.NS_HTTP_AUTH)
+ con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
+ common.xmpp.NS_COMMANDS)
+ con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
+ common.xmpp.NS_GMAILNOTIFY)
+ con.RegisterHandler('iq', self._gMailQueryCB, 'result',
+ common.xmpp.NS_GMAILNOTIFY)
+ con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
+ common.xmpp.NS_DISCO_INFO)
+ con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
+ common.xmpp.NS_DISCO_ITEMS)
+ con.RegisterHandler('iq', self._IqPingCB, 'get',
+ common.xmpp.NS_PING)
+ con.RegisterHandler('iq', self._search_fields_received, 'result',
+ common.xmpp.NS_SEARCH)
+ con.RegisterHandler('iq', self._PrivacySetCB, 'set',
+ common.xmpp.NS_PRIVACY)
+ con.RegisterHandler('iq', self._PubSubCB, 'result')
+ con.RegisterHandler('iq', self._PubSubErrorCB, 'error')
+ con.RegisterHandler('iq', self._JingleCB, 'result')
+ con.RegisterHandler('iq', self._JingleCB, 'error')
+ con.RegisterHandler('iq', self._JingleCB, 'set',
+ common.xmpp.NS_JINGLE)
+ con.RegisterHandler('iq', self._ErrorCB, 'error')
+ con.RegisterHandler('iq', self._IqCB)
+ con.RegisterHandler('iq', self._StanzaArrivedCB)
+ con.RegisterHandler('iq', self._ResultCB, 'result')
+ con.RegisterHandler('presence', self._StanzaArrivedCB)
+ con.RegisterHandler('message', self._StanzaArrivedCB)
+ con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams')
diff --git a/src/common/contacts.py b/src/common/contacts.py
index 7ce83dba4..edd9c22e3 100644
--- a/src/common/contacts.py
+++ b/src/common/contacts.py
@@ -34,809 +34,807 @@ from common.account import Account
import common.gajim
class XMPPEntity(object):
- """
- Base representation of entities in XMPP
- """
+ """
+ Base representation of entities in XMPP
+ """
- def __init__(self, jid, account, resource):
- self.jid = jid
- self.resource = resource
- self.account = account
+ def __init__(self, jid, account, resource):
+ self.jid = jid
+ self.resource = resource
+ self.account = account
class CommonContact(XMPPEntity):
- def __init__(self, jid, account, resource, show, status, name,
- our_chatstate, composing_xep, chatstate, client_caps=None):
-
- XMPPEntity.__init__(self, jid, account, resource)
-
- self.show = show
- self.status = status
- self.name = name
-
- self.client_caps = client_caps or caps_cache.NullClientCaps()
-
- # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
- # we keep track of xep85 support with the peer by three extra states:
- # None, False and 'ask'
- # None if no info about peer
- # False if peer does not support xep85
- # 'ask' if we sent the first 'active' chatstate and are waiting for reply
- # this holds what WE SEND to contact (our current chatstate)
- self.our_chatstate = our_chatstate
- # tell which XEP we're using for composing state
- # None = have to ask, XEP-0022 = use this xep,
- # XEP-0085 = use this xep, False = no composing support
- self.composing_xep = composing_xep
- # this is contact's chatstate
- self.chatstate = chatstate
-
- def get_full_jid(self):
- raise NotImplementedError
-
- def get_shown_name(self):
- raise NotImplementedError
-
- def supports(self, requested_feature):
- """
- Return True if the contact has advertised to support the feature
- identified by the given namespace. False otherwise.
- """
- if self.show == 'offline':
- # Unfortunately, if all resources are offline, the contact
- # includes the last resource that was online. Check for its
- # show, so we can be sure it's existant. Otherwise, we still
- # return caps for a contact that has no resources left.
- return False
- else:
- return caps_cache.client_supports(self.client_caps, requested_feature)
+ def __init__(self, jid, account, resource, show, status, name,
+ our_chatstate, composing_xep, chatstate, client_caps=None):
+
+ XMPPEntity.__init__(self, jid, account, resource)
+
+ self.show = show
+ self.status = status
+ self.name = name
+
+ self.client_caps = client_caps or caps_cache.NullClientCaps()
+
+ # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
+ # we keep track of xep85 support with the peer by three extra states:
+ # None, False and 'ask'
+ # None if no info about peer
+ # False if peer does not support xep85
+ # 'ask' if we sent the first 'active' chatstate and are waiting for reply
+ # this holds what WE SEND to contact (our current chatstate)
+ self.our_chatstate = our_chatstate
+ # tell which XEP we're using for composing state
+ # None = have to ask, XEP-0022 = use this xep,
+ # XEP-0085 = use this xep, False = no composing support
+ self.composing_xep = composing_xep
+ # this is contact's chatstate
+ self.chatstate = chatstate
+
+ def get_full_jid(self):
+ raise NotImplementedError
+
+ def get_shown_name(self):
+ raise NotImplementedError
+
+ def supports(self, requested_feature):
+ """
+ Return True if the contact has advertised to support the feature
+ identified by the given namespace. False otherwise.
+ """
+ if self.show == 'offline':
+ # Unfortunately, if all resources are offline, the contact
+ # includes the last resource that was online. Check for its
+ # show, so we can be sure it's existant. Otherwise, we still
+ # return caps for a contact that has no resources left.
+ return False
+ else:
+ return caps_cache.client_supports(self.client_caps, requested_feature)
class Contact(CommonContact):
- """
- Information concerning a contact
- """
- def __init__(self, jid, account, name='', groups=[], show='', status='',
- sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
- our_chatstate=None, chatstate=None, last_status_time=None, msg_id=
- None, composing_xep=None, last_activity_time=None):
-
- CommonContact.__init__(self, jid, account, resource, show, status, name,
- our_chatstate, composing_xep, chatstate, client_caps=client_caps)
-
- self.contact_name = '' # nick choosen by contact
- self.groups = [i for i in set(groups)] # filter duplicate values
-
- self.sub = sub
- self.ask = ask
-
- self.priority = priority
- self.keyID = keyID
- self.msg_id = msg_id
- self.last_status_time = last_status_time
- self.last_activity_time = last_activity_time
-
- self.pep = {}
-
- def get_full_jid(self):
- if self.resource:
- return self.jid + '/' + self.resource
- return self.jid
-
- def get_shown_name(self):
- if self.name:
- return self.name
- if self.contact_name:
- return self.contact_name
- return self.jid.split('@')[0]
-
- def get_shown_groups(self):
- if self.is_observer():
- return [_('Observers')]
- elif self.is_groupchat():
- return [_('Groupchats')]
- elif self.is_transport():
- return [_('Transports')]
- elif not self.groups:
- return [_('General')]
- else:
- return self.groups
-
- def is_hidden_from_roster(self):
- """
- If contact should not be visible in roster
- """
- # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
- if self.is_transport():
- return False
- if self.sub in ('both', 'to'):
- return False
- if self.sub in ('none', 'from') and self.ask == 'subscribe':
- return False
- if self.sub in ('none', 'from') and (self.name or len(self.groups)):
- return False
- if _('Not in Roster') in self.groups:
- return False
- return True
-
- def is_observer(self):
- # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
- is_observer = False
- if self.sub == 'from' and not self.is_transport()\
- and self.is_hidden_from_roster():
- is_observer = True
- return is_observer
-
- def is_groupchat(self):
- for account in common.gajim.gc_connected:
- if self.jid in common.gajim.gc_connected[account]:
- return True
- return False
-
- def is_transport(self):
- # if not '@' or '@' starts the jid then contact is transport
- return self.jid.find('@') <= 0
+ """
+ Information concerning a contact
+ """
+ def __init__(self, jid, account, name='', groups=[], show='', status='',
+ sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
+ our_chatstate=None, chatstate=None, last_status_time=None, msg_id=
+ None, composing_xep=None, last_activity_time=None):
+
+ CommonContact.__init__(self, jid, account, resource, show, status, name,
+ our_chatstate, composing_xep, chatstate, client_caps=client_caps)
+
+ self.contact_name = '' # nick choosen by contact
+ self.groups = [i for i in set(groups)] # filter duplicate values
+
+ self.sub = sub
+ self.ask = ask
+
+ self.priority = priority
+ self.keyID = keyID
+ self.msg_id = msg_id
+ self.last_status_time = last_status_time
+ self.last_activity_time = last_activity_time
+
+ self.pep = {}
+
+ def get_full_jid(self):
+ if self.resource:
+ return self.jid + '/' + self.resource
+ return self.jid
+
+ def get_shown_name(self):
+ if self.name:
+ return self.name
+ if self.contact_name:
+ return self.contact_name
+ return self.jid.split('@')[0]
+
+ def get_shown_groups(self):
+ if self.is_observer():
+ return [_('Observers')]
+ elif self.is_groupchat():
+ return [_('Groupchats')]
+ elif self.is_transport():
+ return [_('Transports')]
+ elif not self.groups:
+ return [_('General')]
+ else:
+ return self.groups
+
+ def is_hidden_from_roster(self):
+ """
+ If contact should not be visible in roster
+ """
+ # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
+ if self.is_transport():
+ return False
+ if self.sub in ('both', 'to'):
+ return False
+ if self.sub in ('none', 'from') and self.ask == 'subscribe':
+ return False
+ if self.sub in ('none', 'from') and (self.name or len(self.groups)):
+ return False
+ if _('Not in Roster') in self.groups:
+ return False
+ return True
+
+ def is_observer(self):
+ # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
+ is_observer = False
+ if self.sub == 'from' and not self.is_transport()\
+ and self.is_hidden_from_roster():
+ is_observer = True
+ return is_observer
+
+ def is_groupchat(self):
+ for account in common.gajim.gc_connected:
+ if self.jid in common.gajim.gc_connected[account]:
+ return True
+ return False
+
+ def is_transport(self):
+ # if not '@' or '@' starts the jid then contact is transport
+ return self.jid.find('@') <= 0
class GC_Contact(CommonContact):
- """
- Information concerning each groupchat contact
- """
+ """
+ Information concerning each groupchat contact
+ """
- def __init__(self, room_jid, account, name='', show='', status='', role='',
- affiliation='', jid='', resource='', our_chatstate=None,
- composing_xep=None, chatstate=None):
+ def __init__(self, room_jid, account, name='', show='', status='', role='',
+ affiliation='', jid='', resource='', our_chatstate=None,
+ composing_xep=None, chatstate=None):
- CommonContact.__init__(self, jid, account, resource, show, status, name,
- our_chatstate, composing_xep, chatstate)
+ CommonContact.__init__(self, jid, account, resource, show, status, name,
+ our_chatstate, composing_xep, chatstate)
- self.room_jid = room_jid
- self.role = role
- self.affiliation = affiliation
+ self.room_jid = room_jid
+ self.role = role
+ self.affiliation = affiliation
- def get_full_jid(self):
- return self.room_jid + '/' + self.name
+ def get_full_jid(self):
+ return self.room_jid + '/' + self.name
- def get_shown_name(self):
- return self.name
+ def get_shown_name(self):
+ return self.name
- def as_contact(self):
- """
- Create a Contact instance from this GC_Contact instance
- """
- return Contact(jid=self.get_full_jid(), account=self.account,
- name=self.name, groups=[], show=self.show, status=self.status,
- sub='none', client_caps=self.client_caps)
+ def as_contact(self):
+ """
+ Create a Contact instance from this GC_Contact instance
+ """
+ return Contact(jid=self.get_full_jid(), account=self.account,
+ name=self.name, groups=[], show=self.show, status=self.status,
+ sub='none', client_caps=self.client_caps)
class LegacyContactsAPI:
- """
- This is a GOD class for accessing contact and groupchat information.
- The API has several flaws:
-
- * it mixes concerns because it deals with contacts, groupchats,
- groupchat contacts and metacontacts
- * some methods like get_contact() may return None. This leads to
- a lot of duplication all over Gajim because it is not sure
- if we receive a proper contact or just None.
-
- It is a long way to cleanup this API. Therefore just stick with it
- and use it as before. We will try to figure out a migration path.
- """
- def __init__(self):
- self._metacontact_manager = MetacontactManager(self)
- self._accounts = {}
-
- def change_account_name(self, old_name, new_name):
- self._accounts[new_name] = self._accounts[old_name]
- self._accounts[new_name].name = new_name
- del self._accounts[old_name]
-
- self._metacontact_manager.change_account_name(old_name, new_name)
-
- def add_account(self, account_name):
- self._accounts[account_name] = Account(account_name, Contacts(),
- GC_Contacts())
- self._metacontact_manager.add_account(account_name)
-
- def get_accounts(self):
- return self._accounts.keys()
-
- def remove_account(self, account):
- del self._accounts[account]
- self._metacontact_manager.remove_account(account)
-
- def create_contact(self, jid, account, name='', groups=[], show='',
- status='', sub='', ask='', resource='', priority=0, keyID='',
- client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None,
- composing_xep=None, last_activity_time=None):
- # Use Account object if available
- account = self._accounts.get(account, account)
- return Contact(jid=jid, account=account, name=name, groups=groups,
- show=show, status=status, sub=sub, ask=ask, resource=resource,
- priority=priority, keyID=keyID, client_caps=client_caps,
- our_chatstate=our_chatstate, chatstate=chatstate,
- last_status_time=last_status_time, composing_xep=composing_xep,
- last_activity_time=last_activity_time)
-
- def create_self_contact(self, jid, account, resource, show, status, priority,
- name='', keyID=''):
- conn = common.gajim.connections[account]
- nick = name or common.gajim.nicks[account]
- account = self._accounts.get(account, account) # Use Account object if available
- self_contact = self.create_contact(jid=jid, account=account,
- name=nick, groups=['self_contact'], show=show, status=status,
- sub='both', ask='none', priority=priority, keyID=keyID,
- resource=resource)
- self_contact.pep = conn.pep
- return self_contact
-
- def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''):
- account = self._accounts.get(account, account) # Use Account object if available
- return self.create_contact(jid=jid, account=account, resource=resource,
- name=name, groups=[_('Not in Roster')], show='not in roster',
- status='', sub='none', keyID=keyID)
-
- def copy_contact(self, contact):
- return self.create_contact(contact.jid, contact.account,
- name=contact.name, groups=contact.groups, show=contact.show,
- status=contact.status, sub=contact.sub, ask=contact.ask,
- resource=contact.resource, priority=contact.priority,
- keyID=contact.keyID, client_caps=contact.client_caps,
- our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
- last_status_time=contact.last_status_time,
- composing_xep=contact.composing_xep,
- last_activity_time=contact.last_activity_time)
-
- def add_contact(self, account, contact):
- if account not in self._accounts:
- self.add_account(account)
- return self._accounts[account].contacts.add_contact(contact)
-
- def remove_contact(self, account, contact):
- if account not in self._accounts:
- return
- return self._accounts[account].contacts.remove_contact(contact)
-
- def remove_jid(self, account, jid, remove_meta=True):
- self._accounts[account].contacts.remove_jid(jid)
- if remove_meta:
- self._metacontact_manager.remove_metacontact(account, jid)
-
- def get_contacts(self, account, jid):
- return self._accounts[account].contacts.get_contacts(jid)
-
- def get_contact(self, account, jid, resource=None):
- return self._accounts[account].contacts.get_contact(jid, resource=resource)
-
- def iter_contacts(self, account):
- for contact in self._accounts[account].contacts.iter_contacts():
- yield contact
-
- def get_contact_from_full_jid(self, account, fjid):
- return self._accounts[account].contacts.get_contact_from_full_jid(fjid)
-
- def get_first_contact_from_jid(self, account, jid):
- return self._accounts[account].contacts.get_first_contact_from_jid(jid)
-
- def get_contacts_from_group(self, account, group):
- return self._accounts[account].contacts.get_contacts_from_group(group)
-
- def get_contacts_jid_list(self, account):
- return self._accounts[account].contacts.get_contacts_jid_list()
-
- def get_jid_list(self, account):
- return self._accounts[account].contacts.get_jid_list()
-
- def change_contact_jid(self, old_jid, new_jid, account):
- return self._accounts[account].change_contact_jid(old_jid, new_jid)
-
- def get_highest_prio_contact_from_contacts(self, contacts):
- if not contacts:
- return None
- prim_contact = contacts[0]
- for contact in contacts[1:]:
- if int(contact.priority) > int(prim_contact.priority):
- prim_contact = contact
- return prim_contact
-
- def get_contact_with_highest_priority(self, account, jid):
- contacts = self.get_contacts(account, jid)
- if not contacts and '/' in jid:
- # jid may be a fake jid, try it
- room, nick = jid.split('/', 1)
- contact = self.get_gc_contact(account, room, nick)
- return contact
- return self.get_highest_prio_contact_from_contacts(contacts)
-
- def get_nb_online_total_contacts(self, accounts=[], groups=[]):
- """
- Return the number of online contacts and the total number of contacts
- """
- if accounts == []:
- accounts = self.get_accounts()
- nbr_online = 0
- nbr_total = 0
- for account in accounts:
- our_jid = common.gajim.get_jid_from_account(account)
- for jid in self.get_jid_list(account):
- if jid == our_jid:
- continue
- if common.gajim.jid_is_transport(jid) and not \
- _('Transports') in groups:
- # do not count transports
- continue
- if self.has_brother(account, jid, accounts) and not \
- self.is_big_brother(account, jid, accounts):
- # count metacontacts only once
- continue
- contact = self.get_contact_with_highest_priority(account, jid)
- if _('Not in roster') in contact.groups:
- continue
- in_groups = False
- if groups == []:
- in_groups = True
- else:
- for group in groups:
- if group in contact.get_shown_groups():
- in_groups = True
- break
-
- if in_groups:
- if contact.show not in ('offline', 'error'):
- nbr_online += 1
- nbr_total += 1
- return nbr_online, nbr_total
-
- def __getattr__(self, attr_name):
- # Only called if self has no attr_name
- if hasattr(self._metacontact_manager, attr_name):
- return getattr(self._metacontact_manager, attr_name)
- else:
- raise AttributeError(attr_name)
-
- def create_gc_contact(self, room_jid, account, name='', show='', status='',
- role='', affiliation='', jid='', resource=''):
- account = self._accounts.get(account, account) # Use Account object if available
- return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid,
- resource)
-
- def add_gc_contact(self, account, gc_contact):
- return self._accounts[account].gc_contacts.add_gc_contact(gc_contact)
-
- def remove_gc_contact(self, account, gc_contact):
- return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact)
-
- def remove_room(self, account, room_jid):
- return self._accounts[account].gc_contacts.remove_room(room_jid)
-
- def get_gc_list(self, account):
- return self._accounts[account].gc_contacts.get_gc_list()
-
- def get_nick_list(self, account, room_jid):
- return self._accounts[account].gc_contacts.get_nick_list(room_jid)
-
- def get_gc_contact(self, account, room_jid, nick):
- return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick)
-
- def get_nb_role_total_gc_contacts(self, account, room_jid, role):
- return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role)
+ """
+ This is a GOD class for accessing contact and groupchat information.
+ The API has several flaws:
+
+ * it mixes concerns because it deals with contacts, groupchats,
+ groupchat contacts and metacontacts
+ * some methods like get_contact() may return None. This leads to
+ a lot of duplication all over Gajim because it is not sure
+ if we receive a proper contact or just None.
+
+ It is a long way to cleanup this API. Therefore just stick with it
+ and use it as before. We will try to figure out a migration path.
+ """
+ def __init__(self):
+ self._metacontact_manager = MetacontactManager(self)
+ self._accounts = {}
+
+ def change_account_name(self, old_name, new_name):
+ self._accounts[new_name] = self._accounts[old_name]
+ self._accounts[new_name].name = new_name
+ del self._accounts[old_name]
+
+ self._metacontact_manager.change_account_name(old_name, new_name)
+
+ def add_account(self, account_name):
+ self._accounts[account_name] = Account(account_name, Contacts(),
+ GC_Contacts())
+ self._metacontact_manager.add_account(account_name)
+
+ def get_accounts(self):
+ return self._accounts.keys()
+
+ def remove_account(self, account):
+ del self._accounts[account]
+ self._metacontact_manager.remove_account(account)
+
+ def create_contact(self, jid, account, name='', groups=[], show='',
+ status='', sub='', ask='', resource='', priority=0, keyID='',
+ client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None,
+ composing_xep=None, last_activity_time=None):
+ # Use Account object if available
+ account = self._accounts.get(account, account)
+ return Contact(jid=jid, account=account, name=name, groups=groups,
+ show=show, status=status, sub=sub, ask=ask, resource=resource,
+ priority=priority, keyID=keyID, client_caps=client_caps,
+ our_chatstate=our_chatstate, chatstate=chatstate,
+ last_status_time=last_status_time, composing_xep=composing_xep,
+ last_activity_time=last_activity_time)
+
+ def create_self_contact(self, jid, account, resource, show, status, priority,
+ name='', keyID=''):
+ conn = common.gajim.connections[account]
+ nick = name or common.gajim.nicks[account]
+ account = self._accounts.get(account, account) # Use Account object if available
+ self_contact = self.create_contact(jid=jid, account=account,
+ name=nick, groups=['self_contact'], show=show, status=status,
+ sub='both', ask='none', priority=priority, keyID=keyID,
+ resource=resource)
+ self_contact.pep = conn.pep
+ return self_contact
+
+ def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''):
+ account = self._accounts.get(account, account) # Use Account object if available
+ return self.create_contact(jid=jid, account=account, resource=resource,
+ name=name, groups=[_('Not in Roster')], show='not in roster',
+ status='', sub='none', keyID=keyID)
+
+ def copy_contact(self, contact):
+ return self.create_contact(contact.jid, contact.account,
+ name=contact.name, groups=contact.groups, show=contact.show,
+ status=contact.status, sub=contact.sub, ask=contact.ask,
+ resource=contact.resource, priority=contact.priority,
+ keyID=contact.keyID, client_caps=contact.client_caps,
+ our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
+ last_status_time=contact.last_status_time,
+ composing_xep=contact.composing_xep,
+ last_activity_time=contact.last_activity_time)
+
+ def add_contact(self, account, contact):
+ if account not in self._accounts:
+ self.add_account(account)
+ return self._accounts[account].contacts.add_contact(contact)
+
+ def remove_contact(self, account, contact):
+ if account not in self._accounts:
+ return
+ return self._accounts[account].contacts.remove_contact(contact)
+
+ def remove_jid(self, account, jid, remove_meta=True):
+ self._accounts[account].contacts.remove_jid(jid)
+ if remove_meta:
+ self._metacontact_manager.remove_metacontact(account, jid)
+
+ def get_contacts(self, account, jid):
+ return self._accounts[account].contacts.get_contacts(jid)
+
+ def get_contact(self, account, jid, resource=None):
+ return self._accounts[account].contacts.get_contact(jid, resource=resource)
+
+ def iter_contacts(self, account):
+ for contact in self._accounts[account].contacts.iter_contacts():
+ yield contact
+
+ def get_contact_from_full_jid(self, account, fjid):
+ return self._accounts[account].contacts.get_contact_from_full_jid(fjid)
+
+ def get_first_contact_from_jid(self, account, jid):
+ return self._accounts[account].contacts.get_first_contact_from_jid(jid)
+
+ def get_contacts_from_group(self, account, group):
+ return self._accounts[account].contacts.get_contacts_from_group(group)
+
+ def get_contacts_jid_list(self, account):
+ return self._accounts[account].contacts.get_contacts_jid_list()
+
+ def get_jid_list(self, account):
+ return self._accounts[account].contacts.get_jid_list()
+
+ def change_contact_jid(self, old_jid, new_jid, account):
+ return self._accounts[account].change_contact_jid(old_jid, new_jid)
+
+ def get_highest_prio_contact_from_contacts(self, contacts):
+ if not contacts:
+ return None
+ prim_contact = contacts[0]
+ for contact in contacts[1:]:
+ if int(contact.priority) > int(prim_contact.priority):
+ prim_contact = contact
+ return prim_contact
+
+ def get_contact_with_highest_priority(self, account, jid):
+ contacts = self.get_contacts(account, jid)
+ if not contacts and '/' in jid:
+ # jid may be a fake jid, try it
+ room, nick = jid.split('/', 1)
+ contact = self.get_gc_contact(account, room, nick)
+ return contact
+ return self.get_highest_prio_contact_from_contacts(contacts)
+
+ def get_nb_online_total_contacts(self, accounts=[], groups=[]):
+ """
+ Return the number of online contacts and the total number of contacts
+ """
+ if accounts == []:
+ accounts = self.get_accounts()
+ nbr_online = 0
+ nbr_total = 0
+ for account in accounts:
+ our_jid = common.gajim.get_jid_from_account(account)
+ for jid in self.get_jid_list(account):
+ if jid == our_jid:
+ continue
+ if common.gajim.jid_is_transport(jid) and not \
+ _('Transports') in groups:
+ # do not count transports
+ continue
+ if self.has_brother(account, jid, accounts) and not \
+ self.is_big_brother(account, jid, accounts):
+ # count metacontacts only once
+ continue
+ contact = self.get_contact_with_highest_priority(account, jid)
+ if _('Not in roster') in contact.groups:
+ continue
+ in_groups = False
+ if groups == []:
+ in_groups = True
+ else:
+ for group in groups:
+ if group in contact.get_shown_groups():
+ in_groups = True
+ break
+
+ if in_groups:
+ if contact.show not in ('offline', 'error'):
+ nbr_online += 1
+ nbr_total += 1
+ return nbr_online, nbr_total
+
+ def __getattr__(self, attr_name):
+ # Only called if self has no attr_name
+ if hasattr(self._metacontact_manager, attr_name):
+ return getattr(self._metacontact_manager, attr_name)
+ else:
+ raise AttributeError(attr_name)
+
+ def create_gc_contact(self, room_jid, account, name='', show='', status='',
+ role='', affiliation='', jid='', resource=''):
+ account = self._accounts.get(account, account) # Use Account object if available
+ return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid,
+ resource)
+
+ def add_gc_contact(self, account, gc_contact):
+ return self._accounts[account].gc_contacts.add_gc_contact(gc_contact)
+
+ def remove_gc_contact(self, account, gc_contact):
+ return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact)
+
+ def remove_room(self, account, room_jid):
+ return self._accounts[account].gc_contacts.remove_room(room_jid)
+
+ def get_gc_list(self, account):
+ return self._accounts[account].gc_contacts.get_gc_list()
+
+ def get_nick_list(self, account, room_jid):
+ return self._accounts[account].gc_contacts.get_nick_list(room_jid)
+
+ def get_gc_contact(self, account, room_jid, nick):
+ return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick)
+
+ def get_nb_role_total_gc_contacts(self, account, room_jid, role):
+ return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role)
class Contacts():
- """
- This is a breakout of the contact related behavior of the old
- Contacts class (which is not called LegacyContactsAPI)
- """
- def __init__(self):
- # list of contacts {jid1: [C1, C2]}, } one Contact per resource
- self._contacts = {}
-
- def add_contact(self, contact):
- if contact.jid not in self._contacts:
- self._contacts[contact.jid] = [contact]
- return
- contacts = self._contacts[contact.jid]
- # We had only one that was offline, remove it
- if len(contacts) == 1 and contacts[0].show == 'offline':
- # Do not use self.remove_contact: it deteles
- # self._contacts[account][contact.jid]
- contacts.remove(contacts[0])
- # If same JID with same resource already exists, use the new one
- for c in contacts:
- if c.resource == contact.resource:
- self.remove_contact(c)
- break
- contacts.append(contact)
-
- def remove_contact(self, contact):
- if contact.jid not in self._contacts:
- return
- if contact in self._contacts[contact.jid]:
- self._contacts[contact.jid].remove(contact)
- if len(self._contacts[contact.jid]) == 0:
- del self._contacts[contact.jid]
-
- def remove_jid(self, jid):
- """
- Remove all contacts for a given jid
- """
- if jid in self._contacts:
- del self._contacts[jid]
-
- def get_contacts(self, jid):
- """
- Return the list of contact instances for this jid
- """
- return self._contacts.get(jid, [])
-
- def get_contact(self, jid, resource=None):
- ### WARNING ###
- # This function returns a *RANDOM* resource if resource = None!
- # Do *NOT* use if you need to get the contact to which you
- # send a message for example, as a bare JID in Jabber means
- # highest available resource, which this function ignores!
- """
- Return the contact instance for the given resource if it's given else the
- first contact is no resource is given or None if there is not
- """
- if jid in self._contacts:
- if not resource:
- return self._contacts[jid][0]
- for c in self._contacts[jid]:
- if c.resource == resource:
- return c
-
- def iter_contacts(self):
- for jid in self._contacts.keys():
- for contact in self._contacts[jid][:]:
- yield contact
-
- def get_jid_list(self):
- return self._contacts.keys()
-
- def get_contacts_jid_list(self):
- contacts = self._contacts.keys()
- for jid in self._contacts.keys():
- if self._contacts[jid][0].is_groupchat():
- contacts.remove(jid)
- return contacts
-
- def get_contact_from_full_jid(self, fjid):
- """
- Get Contact object for specific resource of given jid
- """
- barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid)
- return self.get_contact(barejid, resource)
-
- def get_first_contact_from_jid(self, jid):
- if jid in self._contacts:
- return self._contacts[jid][0]
-
- def get_contacts_from_group(self, group):
- """
- Return all contacts in the given group
- """
- group_contacts = []
- for jid in self._contacts:
- contacts = self.get_contacts(jid)
- if group in contacts[0].groups:
- group_contacts += contacts
- return group_contacts
-
- def change_contact_jid(self, old_jid, new_jid):
- if old_jid not in self._contacts:
- return
- self._contacts[new_jid] = []
- for _contact in self._contacts[old_jid]:
- _contact.jid = new_jid
- self._contacts[new_jid].append(_contact)
- del self._contacts[old_jid]
+ """
+ This is a breakout of the contact related behavior of the old
+ Contacts class (which is not called LegacyContactsAPI)
+ """
+ def __init__(self):
+ # list of contacts {jid1: [C1, C2]}, } one Contact per resource
+ self._contacts = {}
+
+ def add_contact(self, contact):
+ if contact.jid not in self._contacts:
+ self._contacts[contact.jid] = [contact]
+ return
+ contacts = self._contacts[contact.jid]
+ # We had only one that was offline, remove it
+ if len(contacts) == 1 and contacts[0].show == 'offline':
+ # Do not use self.remove_contact: it deteles
+ # self._contacts[account][contact.jid]
+ contacts.remove(contacts[0])
+ # If same JID with same resource already exists, use the new one
+ for c in contacts:
+ if c.resource == contact.resource:
+ self.remove_contact(c)
+ break
+ contacts.append(contact)
+
+ def remove_contact(self, contact):
+ if contact.jid not in self._contacts:
+ return
+ if contact in self._contacts[contact.jid]:
+ self._contacts[contact.jid].remove(contact)
+ if len(self._contacts[contact.jid]) == 0:
+ del self._contacts[contact.jid]
+
+ def remove_jid(self, jid):
+ """
+ Remove all contacts for a given jid
+ """
+ if jid in self._contacts:
+ del self._contacts[jid]
+
+ def get_contacts(self, jid):
+ """
+ Return the list of contact instances for this jid
+ """
+ return self._contacts.get(jid, [])
+
+ def get_contact(self, jid, resource=None):
+ ### WARNING ###
+ # This function returns a *RANDOM* resource if resource = None!
+ # Do *NOT* use if you need to get the contact to which you
+ # send a message for example, as a bare JID in Jabber means
+ # highest available resource, which this function ignores!
+ """
+ Return the contact instance for the given resource if it's given else the
+ first contact is no resource is given or None if there is not
+ """
+ if jid in self._contacts:
+ if not resource:
+ return self._contacts[jid][0]
+ for c in self._contacts[jid]:
+ if c.resource == resource:
+ return c
+
+ def iter_contacts(self):
+ for jid in self._contacts.keys():
+ for contact in self._contacts[jid][:]:
+ yield contact
+
+ def get_jid_list(self):
+ return self._contacts.keys()
+
+ def get_contacts_jid_list(self):
+ contacts = self._contacts.keys()
+ for jid in self._contacts.keys():
+ if self._contacts[jid][0].is_groupchat():
+ contacts.remove(jid)
+ return contacts
+
+ def get_contact_from_full_jid(self, fjid):
+ """
+ Get Contact object for specific resource of given jid
+ """
+ barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid)
+ return self.get_contact(barejid, resource)
+
+ def get_first_contact_from_jid(self, jid):
+ if jid in self._contacts:
+ return self._contacts[jid][0]
+
+ def get_contacts_from_group(self, group):
+ """
+ Return all contacts in the given group
+ """
+ group_contacts = []
+ for jid in self._contacts:
+ contacts = self.get_contacts(jid)
+ if group in contacts[0].groups:
+ group_contacts += contacts
+ return group_contacts
+
+ def change_contact_jid(self, old_jid, new_jid):
+ if old_jid not in self._contacts:
+ return
+ self._contacts[new_jid] = []
+ for _contact in self._contacts[old_jid]:
+ _contact.jid = new_jid
+ self._contacts[new_jid].append(_contact)
+ del self._contacts[old_jid]
class GC_Contacts():
- def __init__(self):
- # list of contacts that are in gc {room_jid: {nick: C}}}
- self._rooms = {}
-
- def add_gc_contact(self, gc_contact):
- if gc_contact.room_jid not in self._rooms:
- self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact}
- else:
- self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact
-
- def remove_gc_contact(self, gc_contact):
- if gc_contact.room_jid not in self._rooms:
- return
- if gc_contact.name not in self._rooms[gc_contact.room_jid]:
- return
- del self._rooms[gc_contact.room_jid][gc_contact.name]
- # It was the last nick in room ?
- if not len(self._rooms[gc_contact.room_jid]):
- del self._rooms[gc_contact.room_jid]
-
- def remove_room(self, room_jid):
- if room_jid in self._rooms:
- del self._rooms[room_jid]
-
- def get_gc_list(self):
- return self._rooms.keys()
-
- def get_nick_list(self, room_jid):
- gc_list = self.get_gc_list()
- if not room_jid in gc_list:
- return []
- return self._rooms[room_jid].keys()
-
- def get_gc_contact(self, room_jid, nick):
- nick_list = self.get_nick_list(room_jid)
- if not nick in nick_list:
- return None
- return self._rooms[room_jid][nick]
-
- def get_nb_role_total_gc_contacts(self, room_jid, role):
- """
- Return the number of group chat contacts for the given role and the total
- number of group chat contacts
- """
- if room_jid not in self._rooms:
- return 0, 0
- nb_role = nb_total = 0
- for nick in self._rooms[room_jid]:
- if self._rooms[room_jid][nick].role == role:
- nb_role += 1
- nb_total += 1
- return nb_role, nb_total
+ def __init__(self):
+ # list of contacts that are in gc {room_jid: {nick: C}}}
+ self._rooms = {}
+
+ def add_gc_contact(self, gc_contact):
+ if gc_contact.room_jid not in self._rooms:
+ self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact}
+ else:
+ self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact
+
+ def remove_gc_contact(self, gc_contact):
+ if gc_contact.room_jid not in self._rooms:
+ return
+ if gc_contact.name not in self._rooms[gc_contact.room_jid]:
+ return
+ del self._rooms[gc_contact.room_jid][gc_contact.name]
+ # It was the last nick in room ?
+ if not len(self._rooms[gc_contact.room_jid]):
+ del self._rooms[gc_contact.room_jid]
+
+ def remove_room(self, room_jid):
+ if room_jid in self._rooms:
+ del self._rooms[room_jid]
+
+ def get_gc_list(self):
+ return self._rooms.keys()
+
+ def get_nick_list(self, room_jid):
+ gc_list = self.get_gc_list()
+ if not room_jid in gc_list:
+ return []
+ return self._rooms[room_jid].keys()
+
+ def get_gc_contact(self, room_jid, nick):
+ nick_list = self.get_nick_list(room_jid)
+ if not nick in nick_list:
+ return None
+ return self._rooms[room_jid][nick]
+
+ def get_nb_role_total_gc_contacts(self, room_jid, role):
+ """
+ Return the number of group chat contacts for the given role and the total
+ number of group chat contacts
+ """
+ if room_jid not in self._rooms:
+ return 0, 0
+ nb_role = nb_total = 0
+ for nick in self._rooms[room_jid]:
+ if self._rooms[room_jid][nick].role == role:
+ nb_role += 1
+ nb_total += 1
+ return nb_role, nb_total
class MetacontactManager():
- def __init__(self, contacts):
- self._metacontacts_tags = {}
- self._contacts = contacts
-
- def change_account_name(self, old_name, new_name):
- self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name]
- del self._metacontacts_tags[old_name]
-
- def add_account(self, account):
- if account not in self._metacontacts_tags:
- self._metacontacts_tags[account] = {}
-
- def remove_account(self, account):
- del self._metacontacts_tags[account]
-
- def define_metacontacts(self, account, tags_list):
- self._metacontacts_tags[account] = tags_list
-
- def _get_new_metacontacts_tag(self, jid):
- if not jid in self._metacontacts_tags:
- return jid
- #FIXME: can this append ?
- assert False
-
- def iter_metacontacts_families(self, account):
- for tag in self._metacontacts_tags[account]:
- family = self._get_metacontacts_family_from_tag(account, tag)
- yield family
-
- def _get_metacontacts_tag(self, account, jid):
- """
- Return the tag of a jid
- """
- if not account in self._metacontacts_tags:
- return None
- for tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- if data['jid'] == jid:
- return tag
- return None
-
- def add_metacontact(self, brother_account, brother_jid, account, jid, order=None):
- tag = self._get_metacontacts_tag(brother_account, brother_jid)
- if not tag:
- tag = self._get_new_metacontacts_tag(brother_jid)
- self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid,
- 'tag': tag}]
- if brother_account != account:
- common.gajim.connections[brother_account].store_metacontacts(
- self._metacontacts_tags[brother_account])
- # be sure jid has no other tag
- old_tag = self._get_metacontacts_tag(account, jid)
- while old_tag:
- self.remove_metacontact(account, jid)
- old_tag = self._get_metacontacts_tag(account, jid)
- if tag not in self._metacontacts_tags[account]:
- self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}]
- else:
- if order:
- self._metacontacts_tags[account][tag].append({'jid': jid,
- 'tag': tag, 'order': order})
- else:
- self._metacontacts_tags[account][tag].append({'jid': jid,
- 'tag': tag})
- common.gajim.connections[account].store_metacontacts(
- self._metacontacts_tags[account])
-
- def remove_metacontact(self, account, jid):
- if not account in self._metacontacts_tags:
- return
-
- found = None
- for tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- if data['jid'] == jid:
- found = data
- break
- if found:
- self._metacontacts_tags[account][tag].remove(found)
- common.gajim.connections[account].store_metacontacts(
- self._metacontacts_tags[account])
- break
-
- def has_brother(self, account, jid, accounts):
- tag = self._get_metacontacts_tag(account, jid)
- if not tag:
- return False
- meta_jids = self._get_metacontacts_jids(tag, accounts)
- return len(meta_jids) > 1 or len(meta_jids[account]) > 1
-
- def is_big_brother(self, account, jid, accounts):
- family = self.get_metacontacts_family(account, jid)
- if family:
- nearby_family = [data for data in family
- if account in accounts]
- bb_data = self._get_metacontacts_big_brother(nearby_family)
- if bb_data['jid'] == jid and bb_data['account'] == account:
- return True
- return False
-
- def _get_metacontacts_jids(self, tag, accounts):
- """
- Return all jid for the given tag in the form {acct: [jid1, jid2],.}
- """
- answers = {}
- for account in self._metacontacts_tags:
- if tag in self._metacontacts_tags[account]:
- if account not in accounts:
- continue
- answers[account] = []
- for data in self._metacontacts_tags[account][tag]:
- answers[account].append(data['jid'])
- return answers
-
- def get_metacontacts_family(self, account, jid):
- """
- Return the family of the given jid, including jid in the form:
- [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional
- """
- tag = self._get_metacontacts_tag(account, jid)
- return self._get_metacontacts_family_from_tag(account, tag)
-
- def _get_metacontacts_family_from_tag(self, account, tag):
- if not tag:
- return []
- answers = []
- for account in self._metacontacts_tags:
- if tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- data['account'] = account
- answers.append(data)
- return answers
-
- def _compare_metacontacts(self, data1, data2):
- """
- Compare 2 metacontacts
-
- Data is {'jid': jid, 'account': account, 'order': order} order is
- optional
- """
- jid1 = data1['jid']
- jid2 = data2['jid']
- account1 = data1['account']
- account2 = data2['account']
- contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1)
- contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2)
- show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd',
- 'xa', 'away', 'chat', 'online', 'requested', 'message']
- # contact can be null when a jid listed in the metacontact data
- # is not in our roster
- if not contact1:
- if contact2:
- return -1 # prefer the known contact
- else:
- show1 = 0
- priority1 = 0
- else:
- show1 = show_list.index(contact1.show)
- priority1 = contact1.priority
- if not contact2:
- if contact1:
- return 1 # prefer the known contact
- else:
- show2 = 0
- priority2 = 0
- else:
- show2 = show_list.index(contact2.show)
- priority2 = contact2.priority
- # If only one is offline, it's always second
- if show1 > 2 and show2 < 3:
- return 1
- if show2 > 2 and show1 < 3:
- return -1
- if 'order' in data1 and 'order' in data2:
- if data1['order'] > data2['order']:
- return 1
- if data1['order'] < data2['order']:
- return -1
- if 'order' in data1:
- return 1
- if 'order' in data2:
- return -1
- transport1 = common.gajim.get_transport_name_from_jid(jid1)
- transport2 = common.gajim.get_transport_name_from_jid(jid2)
- if transport2 and not transport1:
- return 1
- if transport1 and not transport2:
- return -1
- if show1 > show2:
- return 1
- if show2 > show1:
- return -1
- if priority1 > priority2:
- return 1
- if priority2 > priority1:
- return -1
- server1 = common.gajim.get_server_from_jid(jid1)
- server2 = common.gajim.get_server_from_jid(jid2)
- myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname')
- myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname')
- if server1 == myserver1:
- if server2 != myserver2:
- return 1
- elif server2 == myserver2:
- return -1
- if jid1 > jid2:
- return 1
- if jid2 > jid1:
- return -1
- # If all is the same, compare accounts, they can't be the same
- if account1 > account2:
- return 1
- if account2 > account1:
- return -1
- return 0
-
- def get_nearby_family_and_big_brother(self, family, account):
- """
- Return the nearby family and its Big Brother
-
- Nearby family is the part of the family that is grouped with the
- metacontact. A metacontact may be over different accounts. If accounts
- are not merged then the given family is split account wise.
-
- (nearby_family, big_brother_jid, big_brother_account)
- """
- if common.gajim.config.get('mergeaccounts'):
- # group all together
- nearby_family = family
- else:
- # we want one nearby_family per account
- nearby_family = [data for data in family if account == data['account']]
-
- big_brother_data = self._get_metacontacts_big_brother(nearby_family)
- big_brother_jid = big_brother_data['jid']
- big_brother_account = big_brother_data['account']
-
- return (nearby_family, big_brother_jid, big_brother_account)
-
- def _get_metacontacts_big_brother(self, family):
- """
- Which of the family will be the big brother under wich all others will be
- ?
- """
- family.sort(cmp=self._compare_metacontacts)
- return family[-1]
-
-# vim: se ts=3:
+ def __init__(self, contacts):
+ self._metacontacts_tags = {}
+ self._contacts = contacts
+
+ def change_account_name(self, old_name, new_name):
+ self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name]
+ del self._metacontacts_tags[old_name]
+
+ def add_account(self, account):
+ if account not in self._metacontacts_tags:
+ self._metacontacts_tags[account] = {}
+
+ def remove_account(self, account):
+ del self._metacontacts_tags[account]
+
+ def define_metacontacts(self, account, tags_list):
+ self._metacontacts_tags[account] = tags_list
+
+ def _get_new_metacontacts_tag(self, jid):
+ if not jid in self._metacontacts_tags:
+ return jid
+ #FIXME: can this append ?
+ assert False
+
+ def iter_metacontacts_families(self, account):
+ for tag in self._metacontacts_tags[account]:
+ family = self._get_metacontacts_family_from_tag(account, tag)
+ yield family
+
+ def _get_metacontacts_tag(self, account, jid):
+ """
+ Return the tag of a jid
+ """
+ if not account in self._metacontacts_tags:
+ return None
+ for tag in self._metacontacts_tags[account]:
+ for data in self._metacontacts_tags[account][tag]:
+ if data['jid'] == jid:
+ return tag
+ return None
+
+ def add_metacontact(self, brother_account, brother_jid, account, jid, order=None):
+ tag = self._get_metacontacts_tag(brother_account, brother_jid)
+ if not tag:
+ tag = self._get_new_metacontacts_tag(brother_jid)
+ self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid,
+ 'tag': tag}]
+ if brother_account != account:
+ common.gajim.connections[brother_account].store_metacontacts(
+ self._metacontacts_tags[brother_account])
+ # be sure jid has no other tag
+ old_tag = self._get_metacontacts_tag(account, jid)
+ while old_tag:
+ self.remove_metacontact(account, jid)
+ old_tag = self._get_metacontacts_tag(account, jid)
+ if tag not in self._metacontacts_tags[account]:
+ self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}]
+ else:
+ if order:
+ self._metacontacts_tags[account][tag].append({'jid': jid,
+ 'tag': tag, 'order': order})
+ else:
+ self._metacontacts_tags[account][tag].append({'jid': jid,
+ 'tag': tag})
+ common.gajim.connections[account].store_metacontacts(
+ self._metacontacts_tags[account])
+
+ def remove_metacontact(self, account, jid):
+ if not account in self._metacontacts_tags:
+ return
+
+ found = None
+ for tag in self._metacontacts_tags[account]:
+ for data in self._metacontacts_tags[account][tag]:
+ if data['jid'] == jid:
+ found = data
+ break
+ if found:
+ self._metacontacts_tags[account][tag].remove(found)
+ common.gajim.connections[account].store_metacontacts(
+ self._metacontacts_tags[account])
+ break
+
+ def has_brother(self, account, jid, accounts):
+ tag = self._get_metacontacts_tag(account, jid)
+ if not tag:
+ return False
+ meta_jids = self._get_metacontacts_jids(tag, accounts)
+ return len(meta_jids) > 1 or len(meta_jids[account]) > 1
+
+ def is_big_brother(self, account, jid, accounts):
+ family = self.get_metacontacts_family(account, jid)
+ if family:
+ nearby_family = [data for data in family
+ if account in accounts]
+ bb_data = self._get_metacontacts_big_brother(nearby_family)
+ if bb_data['jid'] == jid and bb_data['account'] == account:
+ return True
+ return False
+
+ def _get_metacontacts_jids(self, tag, accounts):
+ """
+ Return all jid for the given tag in the form {acct: [jid1, jid2],.}
+ """
+ answers = {}
+ for account in self._metacontacts_tags:
+ if tag in self._metacontacts_tags[account]:
+ if account not in accounts:
+ continue
+ answers[account] = []
+ for data in self._metacontacts_tags[account][tag]:
+ answers[account].append(data['jid'])
+ return answers
+
+ def get_metacontacts_family(self, account, jid):
+ """
+ Return the family of the given jid, including jid in the form:
+ [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional
+ """
+ tag = self._get_metacontacts_tag(account, jid)
+ return self._get_metacontacts_family_from_tag(account, tag)
+
+ def _get_metacontacts_family_from_tag(self, account, tag):
+ if not tag:
+ return []
+ answers = []
+ for account in self._metacontacts_tags:
+ if tag in self._metacontacts_tags[account]:
+ for data in self._metacontacts_tags[account][tag]:
+ data['account'] = account
+ answers.append(data)
+ return answers
+
+ def _compare_metacontacts(self, data1, data2):
+ """
+ Compare 2 metacontacts
+
+ Data is {'jid': jid, 'account': account, 'order': order} order is
+ optional
+ """
+ jid1 = data1['jid']
+ jid2 = data2['jid']
+ account1 = data1['account']
+ account2 = data2['account']
+ contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1)
+ contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2)
+ show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd',
+ 'xa', 'away', 'chat', 'online', 'requested', 'message']
+ # contact can be null when a jid listed in the metacontact data
+ # is not in our roster
+ if not contact1:
+ if contact2:
+ return -1 # prefer the known contact
+ else:
+ show1 = 0
+ priority1 = 0
+ else:
+ show1 = show_list.index(contact1.show)
+ priority1 = contact1.priority
+ if not contact2:
+ if contact1:
+ return 1 # prefer the known contact
+ else:
+ show2 = 0
+ priority2 = 0
+ else:
+ show2 = show_list.index(contact2.show)
+ priority2 = contact2.priority
+ # If only one is offline, it's always second
+ if show1 > 2 and show2 < 3:
+ return 1
+ if show2 > 2 and show1 < 3:
+ return -1
+ if 'order' in data1 and 'order' in data2:
+ if data1['order'] > data2['order']:
+ return 1
+ if data1['order'] < data2['order']:
+ return -1
+ if 'order' in data1:
+ return 1
+ if 'order' in data2:
+ return -1
+ transport1 = common.gajim.get_transport_name_from_jid(jid1)
+ transport2 = common.gajim.get_transport_name_from_jid(jid2)
+ if transport2 and not transport1:
+ return 1
+ if transport1 and not transport2:
+ return -1
+ if show1 > show2:
+ return 1
+ if show2 > show1:
+ return -1
+ if priority1 > priority2:
+ return 1
+ if priority2 > priority1:
+ return -1
+ server1 = common.gajim.get_server_from_jid(jid1)
+ server2 = common.gajim.get_server_from_jid(jid2)
+ myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname')
+ myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname')
+ if server1 == myserver1:
+ if server2 != myserver2:
+ return 1
+ elif server2 == myserver2:
+ return -1
+ if jid1 > jid2:
+ return 1
+ if jid2 > jid1:
+ return -1
+ # If all is the same, compare accounts, they can't be the same
+ if account1 > account2:
+ return 1
+ if account2 > account1:
+ return -1
+ return 0
+
+ def get_nearby_family_and_big_brother(self, family, account):
+ """
+ Return the nearby family and its Big Brother
+
+ Nearby family is the part of the family that is grouped with the
+ metacontact. A metacontact may be over different accounts. If accounts
+ are not merged then the given family is split account wise.
+
+ (nearby_family, big_brother_jid, big_brother_account)
+ """
+ if common.gajim.config.get('mergeaccounts'):
+ # group all together
+ nearby_family = family
+ else:
+ # we want one nearby_family per account
+ nearby_family = [data for data in family if account == data['account']]
+
+ big_brother_data = self._get_metacontacts_big_brother(nearby_family)
+ big_brother_jid = big_brother_data['jid']
+ big_brother_account = big_brother_data['account']
+
+ return (nearby_family, big_brother_jid, big_brother_account)
+
+ def _get_metacontacts_big_brother(self, family):
+ """
+ Which of the family will be the big brother under wich all others will be
+ ?
+ """
+ family.sort(cmp=self._compare_metacontacts)
+ return family[-1]
diff --git a/src/common/crypto.py b/src/common/crypto.py
index 785b753bb..c787df6aa 100644
--- a/src/common/crypto.py
+++ b/src/common/crypto.py
@@ -26,82 +26,80 @@ from hashlib import sha256 as SHA256
# convert a large integer to a big-endian bitstring
def encode_mpi(n):
- if n >= 256:
- return encode_mpi(n / 256) + chr(n % 256)
- else:
- return chr(n)
+ if n >= 256:
+ return encode_mpi(n / 256) + chr(n % 256)
+ else:
+ return chr(n)
# convert a large integer to a big-endian bitstring, padded with \x00s to
# a multiple of 16 bytes
def encode_mpi_with_padding(n):
- return pad_to_multiple(encode_mpi(n), 16, '\x00', True)
+ return pad_to_multiple(encode_mpi(n), 16, '\x00', True)
# pad 'string' to a multiple of 'multiple_of' with 'char'.
# pad on the left if 'left', otherwise pad on the right.
def pad_to_multiple(string, multiple_of, char, left):
- mod = len(string) % multiple_of
- if mod == 0:
- return string
- else:
- padding = (multiple_of - mod) * char
+ mod = len(string) % multiple_of
+ if mod == 0:
+ return string
+ else:
+ padding = (multiple_of - mod) * char
- if left:
- return padding + string
- else:
- return string + padding
+ if left:
+ return padding + string
+ else:
+ return string + padding
# convert a big-endian bitstring to an integer
def decode_mpi(s):
- if len(s) == 0:
- return 0
- else:
- return 256 * decode_mpi(s[:-1]) + ord(s[-1])
+ if len(s) == 0:
+ return 0
+ else:
+ return 256 * decode_mpi(s[:-1]) + ord(s[-1])
def sha256(string):
- sh = SHA256()
- sh.update(string)
- return sh.digest()
+ sh = SHA256()
+ sh.update(string)
+ return sh.digest()
base28_chr = "acdefghikmopqruvwxy123456789"
def sas_28x5(m_a, form_b):
- sha = sha256(m_a + form_b + 'Short Authentication String')
- lsb24 = decode_mpi(sha[-3:])
- return base28(lsb24)
+ sha = sha256(m_a + form_b + 'Short Authentication String')
+ lsb24 = decode_mpi(sha[-3:])
+ return base28(lsb24)
def base28(n):
- if n >= 28:
- return base28(n / 28) + base28_chr[n % 28]
- else:
- return base28_chr[n]
+ if n >= 28:
+ return base28(n / 28) + base28_chr[n % 28]
+ else:
+ return base28_chr[n]
def random_bytes(bytes_):
- return os.urandom(bytes_)
+ return os.urandom(bytes_)
def generate_nonce():
- return random_bytes(8)
+ return random_bytes(8)
# generate a random number between 'bottom' and 'top'
def srand(bottom, top):
- # minimum number of bytes needed to represent that range
- bytes = int(math.ceil(math.log(top - bottom, 256)))
+ # minimum number of bytes needed to represent that range
+ bytes = int(math.ceil(math.log(top - bottom, 256)))
- # in retrospect, this is horribly inadequate.
- return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom
+ # in retrospect, this is horribly inadequate.
+ return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom
# a faster version of (base ** exp) % mod
-# taken from <http://lists.danga.com/pipermail/yadis/2005-September/001445.html>
+# taken from <http://lists.danga.com/pipermail/yadis/2005-September/001445.html>
def powmod(base, exp, mod):
- square = base % mod
- result = 1
+ square = base % mod
+ result = 1
- while exp > 0:
- if exp & 1: # exponent is odd
- result = (result * square) % mod
+ while exp > 0:
+ if exp & 1: # exponent is odd
+ result = (result * square) % mod
- square = (square * square) % mod
- exp /= 2
+ square = (square * square) % mod
+ exp /= 2
- return result
-
-# vim: se ts=3:
+ return result
diff --git a/src/common/dataforms.py b/src/common/dataforms.py
index 048077a74..d3bad1516 100644
--- a/src/common/dataforms.py
+++ b/src/common/dataforms.py
@@ -38,583 +38,581 @@ class WrongFieldValue(Error): pass
# helper class to change class of already existing object
class ExtendedNode(xmpp.Node, object):
- @classmethod
- def __new__(cls, *a, **b):
- if 'extend' not in b.keys() or not b['extend']:
- return object.__new__(cls)
+ @classmethod
+ def __new__(cls, *a, **b):
+ if 'extend' not in b.keys() or not b['extend']:
+ return object.__new__(cls)
- extend = b['extend']
- assert issubclass(cls, extend.__class__)
- extend.__class__ = cls
- return extend
+ extend = b['extend']
+ assert issubclass(cls, extend.__class__)
+ extend.__class__ = cls
+ return extend
# helper decorator to create properties in cleaner way
def nested_property(f):
- ret = f()
- p = {'doc': f.__doc__}
- for v in ('fget', 'fset', 'fdel', 'doc'):
- if v in ret.keys(): p[v]=ret[v]
- return property(**p)
+ ret = f()
+ p = {'doc': f.__doc__}
+ for v in ('fget', 'fset', 'fdel', 'doc'):
+ if v in ret.keys(): p[v]=ret[v]
+ return property(**p)
# helper to create fields from scratch
def Field(typ, **attrs):
- ''' Helper function to create a field of given type. '''
- f = {
- 'boolean': BooleanField,
- 'fixed': StringField,
- 'hidden': StringField,
- 'text-private': StringField,
- 'text-single': StringField,
- 'jid-multi': ListMultiField,
- 'jid-single': ListSingleField,
- 'list-multi': ListMultiField,
- 'list-single': ListSingleField,
- 'text-multi': TextMultiField,
- }[typ](typ=typ, **attrs)
- return f
+ ''' Helper function to create a field of given type. '''
+ f = {
+ 'boolean': BooleanField,
+ 'fixed': StringField,
+ 'hidden': StringField,
+ 'text-private': StringField,
+ 'text-single': StringField,
+ 'jid-multi': ListMultiField,
+ 'jid-single': ListSingleField,
+ 'list-multi': ListMultiField,
+ 'list-single': ListSingleField,
+ 'text-multi': TextMultiField,
+ }[typ](typ=typ, **attrs)
+ return f
def ExtendField(node):
- """
- Helper function to extend a node to field of appropriate type
- """
- # when validation (XEP-122) will go in, we could have another classes
- # like DateTimeField - so that dicts in Field() and ExtendField() will
- # be different...
- typ=node.getAttr('type')
- f = {
- 'boolean': BooleanField,
- 'fixed': StringField,
- 'hidden': StringField,
- 'text-private': StringField,
- 'text-single': StringField,
- 'jid-multi': ListMultiField,
- 'jid-single': ListSingleField,
- 'list-multi': ListMultiField,
- 'list-single': ListSingleField,
- 'text-multi': TextMultiField,
- }
- if typ not in f:
- typ = 'text-single'
- return f[typ](extend=node)
+ """
+ Helper function to extend a node to field of appropriate type
+ """
+ # when validation (XEP-122) will go in, we could have another classes
+ # like DateTimeField - so that dicts in Field() and ExtendField() will
+ # be different...
+ typ=node.getAttr('type')
+ f = {
+ 'boolean': BooleanField,
+ 'fixed': StringField,
+ 'hidden': StringField,
+ 'text-private': StringField,
+ 'text-single': StringField,
+ 'jid-multi': ListMultiField,
+ 'jid-single': ListSingleField,
+ 'list-multi': ListMultiField,
+ 'list-single': ListSingleField,
+ 'text-multi': TextMultiField,
+ }
+ if typ not in f:
+ typ = 'text-single'
+ return f[typ](extend=node)
def ExtendForm(node):
- """
- Helper function to extend a node to form of appropriate type
- """
- if node.getTag('reported') is not None:
- return MultipleDataForm(extend=node)
- else:
- return SimpleDataForm(extend=node)
+ """
+ Helper function to extend a node to form of appropriate type
+ """
+ if node.getTag('reported') is not None:
+ return MultipleDataForm(extend=node)
+ else:
+ return SimpleDataForm(extend=node)
class DataField(ExtendedNode):
- """
- Keeps data about one field - var, field type, labels, instructions... Base
- class for different kinds of fields. Use Field() function to construct one
- of these
- """
-
- def __init__(self, typ=None, var=None, value=None, label=None, desc=None,
- required=False, options=None, extend=None):
-
- if extend is None:
- ExtendedNode.__init__(self, 'field')
-
- self.type = typ
- self.var = var
- if value is not None:
- self.value = value
- if label is not None:
- self.label = label
- if desc is not None:
- self.desc = desc
- self.required = required
- self.options = options
-
- @nested_property
- def type():
- """
- Type of field. Recognized values are: 'boolean', 'fixed', 'hidden',
- 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi',
- 'text-private', 'text-single'. If you set this to something different,
- DataField will store given name, but treat all data as text-single
- """
- def fget(self):
- t = self.getAttr('type')
- if t is None:
- return 'text-single'
- return t
-
- def fset(self, value):
- assert isinstance(value, basestring)
- self.setAttr('type', value)
-
- return locals()
-
- @nested_property
- def var():
- """
- Field identifier
- """
- def fget(self):
- return self.getAttr('var')
-
- def fset(self, value):
- assert isinstance(value, basestring)
- self.setAttr('var', value)
-
- def fdel(self):
- self.delAttr('var')
-
- return locals()
-
- @nested_property
- def label():
- """
- Human-readable field name
- """
- def fget(self):
- l = self.getAttr('label')
- if not l:
- l = self.var
- return l
-
- def fset(self, value):
- assert isinstance(value, basestring)
- self.setAttr('label', value)
-
- def fdel(self):
- if self.getAttr('label'):
- self.delAttr('label')
-
- return locals()
-
- @nested_property
- def description():
- """
- Human-readable description of field meaning
- """
- def fget(self):
- return self.getTagData('desc') or u''
-
- def fset(self, value):
- assert isinstance(value, basestring)
- if value == '':
- fdel(self)
- else:
- self.setTagData('desc', value)
-
- def fdel(self):
- t = self.getTag('desc')
- if t is not None:
- self.delChild(t)
-
- return locals()
-
- @nested_property
- def required():
- """
- Controls whether this field required to fill. Boolean
- """
- def fget(self):
- return bool(self.getTag('required'))
-
- def fset(self, value):
- t = self.getTag('required')
- if t and not value:
- self.delChild(t)
- elif not t and value:
- self.addChild('required')
-
- return locals()
+ """
+ Keeps data about one field - var, field type, labels, instructions... Base
+ class for different kinds of fields. Use Field() function to construct one
+ of these
+ """
+
+ def __init__(self, typ=None, var=None, value=None, label=None, desc=None,
+ required=False, options=None, extend=None):
+
+ if extend is None:
+ ExtendedNode.__init__(self, 'field')
+
+ self.type = typ
+ self.var = var
+ if value is not None:
+ self.value = value
+ if label is not None:
+ self.label = label
+ if desc is not None:
+ self.desc = desc
+ self.required = required
+ self.options = options
+
+ @nested_property
+ def type():
+ """
+ Type of field. Recognized values are: 'boolean', 'fixed', 'hidden',
+ 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi',
+ 'text-private', 'text-single'. If you set this to something different,
+ DataField will store given name, but treat all data as text-single
+ """
+ def fget(self):
+ t = self.getAttr('type')
+ if t is None:
+ return 'text-single'
+ return t
+
+ def fset(self, value):
+ assert isinstance(value, basestring)
+ self.setAttr('type', value)
+
+ return locals()
+
+ @nested_property
+ def var():
+ """
+ Field identifier
+ """
+ def fget(self):
+ return self.getAttr('var')
+
+ def fset(self, value):
+ assert isinstance(value, basestring)
+ self.setAttr('var', value)
+
+ def fdel(self):
+ self.delAttr('var')
+
+ return locals()
+
+ @nested_property
+ def label():
+ """
+ Human-readable field name
+ """
+ def fget(self):
+ l = self.getAttr('label')
+ if not l:
+ l = self.var
+ return l
+
+ def fset(self, value):
+ assert isinstance(value, basestring)
+ self.setAttr('label', value)
+
+ def fdel(self):
+ if self.getAttr('label'):
+ self.delAttr('label')
+
+ return locals()
+
+ @nested_property
+ def description():
+ """
+ Human-readable description of field meaning
+ """
+ def fget(self):
+ return self.getTagData('desc') or u''
+
+ def fset(self, value):
+ assert isinstance(value, basestring)
+ if value == '':
+ fdel(self)
+ else:
+ self.setTagData('desc', value)
+
+ def fdel(self):
+ t = self.getTag('desc')
+ if t is not None:
+ self.delChild(t)
+
+ return locals()
+
+ @nested_property
+ def required():
+ """
+ Controls whether this field required to fill. Boolean
+ """
+ def fget(self):
+ return bool(self.getTag('required'))
+
+ def fset(self, value):
+ t = self.getTag('required')
+ if t and not value:
+ self.delChild(t)
+ elif not t and value:
+ self.addChild('required')
+
+ return locals()
class BooleanField(DataField):
- @nested_property
- def value():
- """
- Value of field. May contain True, False or None
- """
- def fget(self):
- v = self.getTagData('value')
- if v in ('0', 'false'):
- return False
- if v in ('1', 'true'):
- return True
- if v is None:
- return False # default value is False
- raise WrongFieldValue
-
- def fset(self, value):
- self.setTagData('value', value and '1' or '0')
-
- def fdel(self, value):
- t = self.getTag('value')
- if t is not None:
- self.delChild(t)
-
- return locals()
+ @nested_property
+ def value():
+ """
+ Value of field. May contain True, False or None
+ """
+ def fget(self):
+ v = self.getTagData('value')
+ if v in ('0', 'false'):
+ return False
+ if v in ('1', 'true'):
+ return True
+ if v is None:
+ return False # default value is False
+ raise WrongFieldValue
+
+ def fset(self, value):
+ self.setTagData('value', value and '1' or '0')
+
+ def fdel(self, value):
+ t = self.getTag('value')
+ if t is not None:
+ self.delChild(t)
+
+ return locals()
class StringField(DataField):
- """
- Covers fields of types: fixed, hidden, text-private, text-single
- """
-
- @nested_property
- def value():
- """
- Value of field. May be any unicode string
- """
- def fget(self):
- return self.getTagData('value') or u''
-
- def fset(self, value):
- assert isinstance(value, basestring)
- if value == '':
- return fdel(self)
- self.setTagData('value', value)
-
- def fdel(self):
- try:
- self.delChild(self.getTag('value'))
- except ValueError: # if there already were no value tag
- pass
-
- return locals()
+ """
+ Covers fields of types: fixed, hidden, text-private, text-single
+ """
+
+ @nested_property
+ def value():
+ """
+ Value of field. May be any unicode string
+ """
+ def fget(self):
+ return self.getTagData('value') or u''
+
+ def fset(self, value):
+ assert isinstance(value, basestring)
+ if value == '':
+ return fdel(self)
+ self.setTagData('value', value)
+
+ def fdel(self):
+ try:
+ self.delChild(self.getTag('value'))
+ except ValueError: # if there already were no value tag
+ pass
+
+ return locals()
class ListField(DataField):
- """
- Covers fields of types: jid-multi, jid-single, list-multi, list-single
- """
-
- @nested_property
- def options():
- """
- Options
- """
- def fget(self):
- options = []
- for element in self.getTags('option'):
- v = element.getTagData('value')
- if v is None:
- raise WrongFieldValue
- l = element.getAttr('label')
- if not l:
- l = v
- options.append((l, v))
- return options
-
- def fset(self, values):
- fdel(self)
- for value, label in values:
- self.addChild('option', {'label': label}).setTagData('value', value)
-
- def fdel(self):
- for element in self.getTags('option'):
- self.delChild(element)
-
- return locals()
-
- def iter_options(self):
- for element in self.iterTags('option'):
- v = element.getTagData('value')
- if v is None:
- raise WrongFieldValue
- l = element.getAttr('label')
- if not l:
- l = v
- yield (v, l)
+ """
+ Covers fields of types: jid-multi, jid-single, list-multi, list-single
+ """
+
+ @nested_property
+ def options():
+ """
+ Options
+ """
+ def fget(self):
+ options = []
+ for element in self.getTags('option'):
+ v = element.getTagData('value')
+ if v is None:
+ raise WrongFieldValue
+ l = element.getAttr('label')
+ if not l:
+ l = v
+ options.append((l, v))
+ return options
+
+ def fset(self, values):
+ fdel(self)
+ for value, label in values:
+ self.addChild('option', {'label': label}).setTagData('value', value)
+
+ def fdel(self):
+ for element in self.getTags('option'):
+ self.delChild(element)
+
+ return locals()
+
+ def iter_options(self):
+ for element in self.iterTags('option'):
+ v = element.getTagData('value')
+ if v is None:
+ raise WrongFieldValue
+ l = element.getAttr('label')
+ if not l:
+ l = v
+ yield (v, l)
class ListSingleField(ListField, StringField):
- """
- Covers list-single and jid-single fields
- """
- pass
+ """
+ Covers list-single and jid-single fields
+ """
+ pass
class ListMultiField(ListField):
- """
- Covers list-multi and jid-multi fields
- """
-
- @nested_property
- def values():
- """
- Values held in field
- """
- def fget(self):
- values = []
- for element in self.getTags('value'):
- values.append(element.getData())
- return values
-
- def fset(self, values):
- fdel(self)
- for value in values:
- self.addChild('value').setData(value)
-
- def fdel(self):
- for element in self.getTags('value'):
- self.delChild(element)
-
- return locals()
-
- def iter_values(self):
- for element in self.getTags('value'):
- yield element.getData()
+ """
+ Covers list-multi and jid-multi fields
+ """
+
+ @nested_property
+ def values():
+ """
+ Values held in field
+ """
+ def fget(self):
+ values = []
+ for element in self.getTags('value'):
+ values.append(element.getData())
+ return values
+
+ def fset(self, values):
+ fdel(self)
+ for value in values:
+ self.addChild('value').setData(value)
+
+ def fdel(self):
+ for element in self.getTags('value'):
+ self.delChild(element)
+
+ return locals()
+
+ def iter_values(self):
+ for element in self.getTags('value'):
+ yield element.getData()
class TextMultiField(DataField):
- @nested_property
- def value():
- """
- Value held in field
- """
- def fget(self):
- value = u''
- for element in self.iterTags('value'):
- value += '\n' + element.getData()
- return value[1:]
-
- def fset(self, value):
- fdel(self)
- if value == '':
- return
- for line in value.split('\n'):
- self.addChild('value').setData(line)
-
- def fdel(self):
- for element in self.getTags('value'):
- self.delChild(element)
-
- return locals()
+ @nested_property
+ def value():
+ """
+ Value held in field
+ """
+ def fget(self):
+ value = u''
+ for element in self.iterTags('value'):
+ value += '\n' + element.getData()
+ return value[1:]
+
+ def fset(self, value):
+ fdel(self)
+ if value == '':
+ return
+ for line in value.split('\n'):
+ self.addChild('value').setData(line)
+
+ def fdel(self):
+ for element in self.getTags('value'):
+ self.delChild(element)
+
+ return locals()
class DataRecord(ExtendedNode):
- """
- The container for data fields - an xml element which has DataField elements
- as children
- """
- def __init__(self, fields=None, associated=None, extend=None):
- self.associated = associated
- self.vars = {}
- if extend is None:
- # we have to build this object from scratch
- xmpp.Node.__init__(self)
-
- if fields is not None:
- self.fields = fields
- else:
- # we already have xmpp.Node inside - try to convert all
- # fields into DataField objects
- if fields is None:
- for field in self.iterTags('field'):
- if not isinstance(field, DataField):
- ExtendField(field)
- self.vars[field.var] = field
- else:
- for field in self.getTags('field'):
- self.delChild(field)
- self.fields = fields
-
- @nested_property
- def fields():
- """
- List of fields in this record
- """
- def fget(self):
- return self.getTags('field')
-
- def fset(self, fields):
- fdel(self)
- for field in fields:
- if not isinstance(field, DataField):
- ExtendField(extend=field)
- self.addChild(node=field)
-
- def fdel(self):
- for element in self.getTags('field'):
- self.delChild(element)
-
- return locals()
-
- def iter_fields(self):
- """
- Iterate over fields in this record. Do not take associated into account
- """
- for field in self.iterTags('field'):
- yield field
-
- def iter_with_associated(self):
- """
- Iterate over associated, yielding both our field and associated one
- together
- """
- for field in self.associated.iter_fields():
- yield self[field.var], field
-
- def __getitem__(self, item):
- return self.vars[item]
+ """
+ The container for data fields - an xml element which has DataField elements
+ as children
+ """
+ def __init__(self, fields=None, associated=None, extend=None):
+ self.associated = associated
+ self.vars = {}
+ if extend is None:
+ # we have to build this object from scratch
+ xmpp.Node.__init__(self)
+
+ if fields is not None:
+ self.fields = fields
+ else:
+ # we already have xmpp.Node inside - try to convert all
+ # fields into DataField objects
+ if fields is None:
+ for field in self.iterTags('field'):
+ if not isinstance(field, DataField):
+ ExtendField(field)
+ self.vars[field.var] = field
+ else:
+ for field in self.getTags('field'):
+ self.delChild(field)
+ self.fields = fields
+
+ @nested_property
+ def fields():
+ """
+ List of fields in this record
+ """
+ def fget(self):
+ return self.getTags('field')
+
+ def fset(self, fields):
+ fdel(self)
+ for field in fields:
+ if not isinstance(field, DataField):
+ ExtendField(extend=field)
+ self.addChild(node=field)
+
+ def fdel(self):
+ for element in self.getTags('field'):
+ self.delChild(element)
+
+ return locals()
+
+ def iter_fields(self):
+ """
+ Iterate over fields in this record. Do not take associated into account
+ """
+ for field in self.iterTags('field'):
+ yield field
+
+ def iter_with_associated(self):
+ """
+ Iterate over associated, yielding both our field and associated one
+ together
+ """
+ for field in self.associated.iter_fields():
+ yield self[field.var], field
+
+ def __getitem__(self, item):
+ return self.vars[item]
class DataForm(ExtendedNode):
- def __init__(self, type_=None, title=None, instructions=None, extend=None):
- if extend is None:
- # we have to build form from scratch
- xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA})
-
- if type_ is not None:
- self.type_=type_
- if title is not None:
- self.title=title
- if instructions is not None:
- self.instructions=instructions
-
- @nested_property
- def type():
- """
- Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
- 'form' - this form is to be filled in; you will be able soon to do:
- filledform = DataForm(replyto=thisform)
- """
- def fget(self):
- return self.getAttr('type')
-
- def fset(self, type_):
- assert type_ in ('form', 'submit', 'cancel', 'result')
- self.setAttr('type', type_)
-
- return locals()
-
- @nested_property
- def title():
- """
- Title of the form
-
- Human-readable, should not contain any \\r\\n.
- """
- def fget(self):
- return self.getTagData('title')
-
- def fset(self, title):
- self.setTagData('title', title)
-
- def fdel(self):
- try:
- self.delChild('title')
- except ValueError:
- pass
-
- return locals()
-
- @nested_property
- def instructions():
- """
- Instructions for this form
-
- Human-readable, may contain \\r\\n.
- """
- # TODO: the same code is in TextMultiField. join them
- def fget(self):
- value = u''
- for valuenode in self.getTags('instructions'):
- value += '\n' + valuenode.getData()
- return value[1:]
-
- def fset(self, value):
- fdel(self)
- if value == '': return
- for line in value.split('\n'):
- self.addChild('instructions').setData(line)
-
- def fdel(self):
- for value in self.getTags('instructions'):
- self.delChild(value)
-
- return locals()
+ def __init__(self, type_=None, title=None, instructions=None, extend=None):
+ if extend is None:
+ # we have to build form from scratch
+ xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA})
+
+ if type_ is not None:
+ self.type_=type_
+ if title is not None:
+ self.title=title
+ if instructions is not None:
+ self.instructions=instructions
+
+ @nested_property
+ def type():
+ """
+ Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
+ 'form' - this form is to be filled in; you will be able soon to do:
+ filledform = DataForm(replyto=thisform)
+ """
+ def fget(self):
+ return self.getAttr('type')
+
+ def fset(self, type_):
+ assert type_ in ('form', 'submit', 'cancel', 'result')
+ self.setAttr('type', type_)
+
+ return locals()
+
+ @nested_property
+ def title():
+ """
+ Title of the form
+
+ Human-readable, should not contain any \\r\\n.
+ """
+ def fget(self):
+ return self.getTagData('title')
+
+ def fset(self, title):
+ self.setTagData('title', title)
+
+ def fdel(self):
+ try:
+ self.delChild('title')
+ except ValueError:
+ pass
+
+ return locals()
+
+ @nested_property
+ def instructions():
+ """
+ Instructions for this form
+
+ Human-readable, may contain \\r\\n.
+ """
+ # TODO: the same code is in TextMultiField. join them
+ def fget(self):
+ value = u''
+ for valuenode in self.getTags('instructions'):
+ value += '\n' + valuenode.getData()
+ return value[1:]
+
+ def fset(self, value):
+ fdel(self)
+ if value == '': return
+ for line in value.split('\n'):
+ self.addChild('instructions').setData(line)
+
+ def fdel(self):
+ for value in self.getTags('instructions'):
+ self.delChild(value)
+
+ return locals()
class SimpleDataForm(DataForm, DataRecord):
- def __init__(self, type_=None, title=None, instructions=None, fields=None, \
- extend=None):
- DataForm.__init__(self, type_=type_, title=title,
- instructions=instructions, extend=extend)
- DataRecord.__init__(self, fields=fields, extend=self, associated=self)
-
- def get_purged(self):
- c = SimpleDataForm(extend=self)
- del c.title
- c.instructions = ''
- to_be_removed = []
- for f in c.iter_fields():
- if f.required:
- # Keep all required fields
- continue
- if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \
- len(f.values) == 0):
- to_be_removed.append(f)
- else:
- del f.label
- del f.description
- for f in to_be_removed:
- c.delChild(f)
- return c
+ def __init__(self, type_=None, title=None, instructions=None, fields=None, \
+ extend=None):
+ DataForm.__init__(self, type_=type_, title=title,
+ instructions=instructions, extend=extend)
+ DataRecord.__init__(self, fields=fields, extend=self, associated=self)
+
+ def get_purged(self):
+ c = SimpleDataForm(extend=self)
+ del c.title
+ c.instructions = ''
+ to_be_removed = []
+ for f in c.iter_fields():
+ if f.required:
+ # Keep all required fields
+ continue
+ if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \
+ len(f.values) == 0):
+ to_be_removed.append(f)
+ else:
+ del f.label
+ del f.description
+ for f in to_be_removed:
+ c.delChild(f)
+ return c
class MultipleDataForm(DataForm):
- def __init__(self, type_=None, title=None, instructions=None, items=None,
- extend=None):
- DataForm.__init__(self, type_=type_, title=title,
- instructions=instructions, extend=extend)
- # all records, recorded into DataRecords
- if extend is None:
- if items is not None:
- self.items = items
- else:
- # we already have xmpp.Node inside - try to convert all
- # fields into DataField objects
- if items is None:
- self.items = list(self.iterTags('item'))
- else:
- for item in self.getTags('item'):
- self.delChild(item)
- self.items = items
- reported_tag = self.getTag('reported')
- self.reported = DataRecord(extend=reported_tag)
-
- @nested_property
- def items():
- """
- A list of all records
- """
- def fget(self):
- return list(self.iter_records())
-
- def fset(self, records):
- fdel(self)
- for record in records:
- if not isinstance(record, DataRecord):
- DataRecord(extend=record)
- self.addChild(node=record)
-
- def fdel(self):
- for record in self.getTags('item'):
- self.delChild(record)
-
- return locals()
-
- def iter_records(self):
- for record in self.getTags('item'):
- yield record
-
-# @nested_property
-# def reported():
-# """
-# DataRecord that contains descriptions of fields in records
-# """
-# def fget(self):
-# return self.getTag('reported')
-# def fset(self, record):
-# try:
-# self.delChild('reported')
-# except:
-# pass
+ def __init__(self, type_=None, title=None, instructions=None, items=None,
+ extend=None):
+ DataForm.__init__(self, type_=type_, title=title,
+ instructions=instructions, extend=extend)
+ # all records, recorded into DataRecords
+ if extend is None:
+ if items is not None:
+ self.items = items
+ else:
+ # we already have xmpp.Node inside - try to convert all
+ # fields into DataField objects
+ if items is None:
+ self.items = list(self.iterTags('item'))
+ else:
+ for item in self.getTags('item'):
+ self.delChild(item)
+ self.items = items
+ reported_tag = self.getTag('reported')
+ self.reported = DataRecord(extend=reported_tag)
+
+ @nested_property
+ def items():
+ """
+ A list of all records
+ """
+ def fget(self):
+ return list(self.iter_records())
+
+ def fset(self, records):
+ fdel(self)
+ for record in records:
+ if not isinstance(record, DataRecord):
+ DataRecord(extend=record)
+ self.addChild(node=record)
+
+ def fdel(self):
+ for record in self.getTags('item'):
+ self.delChild(record)
+
+ return locals()
+
+ def iter_records(self):
+ for record in self.getTags('item'):
+ yield record
+
+# @nested_property
+# def reported():
+# """
+# DataRecord that contains descriptions of fields in records
+# """
+# def fget(self):
+# return self.getTag('reported')
+# def fset(self, record):
+# try:
+# self.delChild('reported')
+# except:
+# pass
#
-# record.setName('reported')
-# self.addChild(node=record)
-# return locals()
+# record.setName('reported')
+# self.addChild(node=record)
+# return locals()
-
-# vim: se ts=3:
diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py
index 4762b474d..d1e8fe381 100644
--- a/src/common/dbus_support.py
+++ b/src/common/dbus_support.py
@@ -32,152 +32,150 @@ from common import exceptions
_GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error'
try:
- import dbus
- import dbus.glib
+ import dbus
+ import dbus.glib
except ImportError:
- supported = False
- if not os.name == 'nt': # only say that to non Windows users
- print _('D-Bus python bindings are missing in this computer')
- print _('D-Bus capabilities of Gajim cannot be used')
+ supported = False
+ if not os.name == 'nt': # only say that to non Windows users
+ print _('D-Bus python bindings are missing in this computer')
+ print _('D-Bus capabilities of Gajim cannot be used')
else:
- try:
- # test if dbus-x11 is installed
- bus = dbus.SessionBus()
- supported = True # does user have D-Bus bindings?
- except dbus.DBusException:
- supported = False
- if not os.name == 'nt': # only say that to non Windows users
- print _('D-Bus does not run correctly on this machine')
- print _('D-Bus capabilities of Gajim cannot be used')
+ try:
+ # test if dbus-x11 is installed
+ bus = dbus.SessionBus()
+ supported = True # does user have D-Bus bindings?
+ except dbus.DBusException:
+ supported = False
+ if not os.name == 'nt': # only say that to non Windows users
+ print _('D-Bus does not run correctly on this machine')
+ print _('D-Bus capabilities of Gajim cannot be used')
class SystemBus:
- """
- A Singleton for the DBus SystemBus
- """
-
- def __init__(self):
- self.system_bus = None
-
- def SystemBus(self):
- if not supported:
- raise exceptions.DbusNotSupported
-
- if not self.present():
- raise exceptions.SystemBusNotPresent
- return self.system_bus
-
- def bus(self):
- return self.SystemBus()
-
- def present(self):
- if not supported:
- return False
- if self.system_bus is None:
- try:
- self.system_bus = dbus.SystemBus()
- except dbus.DBusException:
- self.system_bus = None
- return False
- if self.system_bus is None:
- return False
- # Don't exit Gajim when dbus is stopped
- self.system_bus.set_exit_on_disconnect(False)
- return True
+ """
+ A Singleton for the DBus SystemBus
+ """
+
+ def __init__(self):
+ self.system_bus = None
+
+ def SystemBus(self):
+ if not supported:
+ raise exceptions.DbusNotSupported
+
+ if not self.present():
+ raise exceptions.SystemBusNotPresent
+ return self.system_bus
+
+ def bus(self):
+ return self.SystemBus()
+
+ def present(self):
+ if not supported:
+ return False
+ if self.system_bus is None:
+ try:
+ self.system_bus = dbus.SystemBus()
+ except dbus.DBusException:
+ self.system_bus = None
+ return False
+ if self.system_bus is None:
+ return False
+ # Don't exit Gajim when dbus is stopped
+ self.system_bus.set_exit_on_disconnect(False)
+ return True
system_bus = SystemBus()
class SessionBus:
- """
- A Singleton for the D-Bus SessionBus
- """
-
- def __init__(self):
- self.session_bus = None
-
- def SessionBus(self):
- if not supported:
- raise exceptions.DbusNotSupported
-
- if not self.present():
- raise exceptions.SessionBusNotPresent
- return self.session_bus
-
- def bus(self):
- return self.SessionBus()
-
- def present(self):
- if not supported:
- return False
- if self.session_bus is None:
- try:
- self.session_bus = dbus.SessionBus()
- except dbus.DBusException:
- self.session_bus = None
- return False
- if self.session_bus is None:
- return False
- return True
+ """
+ A Singleton for the D-Bus SessionBus
+ """
+
+ def __init__(self):
+ self.session_bus = None
+
+ def SessionBus(self):
+ if not supported:
+ raise exceptions.DbusNotSupported
+
+ if not self.present():
+ raise exceptions.SessionBusNotPresent
+ return self.session_bus
+
+ def bus(self):
+ return self.SessionBus()
+
+ def present(self):
+ if not supported:
+ return False
+ if self.session_bus is None:
+ try:
+ self.session_bus = dbus.SessionBus()
+ except dbus.DBusException:
+ self.session_bus = None
+ return False
+ if self.session_bus is None:
+ return False
+ return True
session_bus = SessionBus()
def get_interface(interface, path, start_service=True):
- """
- Get an interface on the current SessionBus. If the interface isn't running,
- try to start it first
- """
- if not supported:
- return None
- if session_bus.present():
- bus = session_bus.SessionBus()
- else:
- return None
- try:
- obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
- dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
- running_services = dbus_iface.ListNames()
- started = True
- if interface not in running_services:
- # try to start the service
- if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1:
- started = True
- else:
- started = False
- if not started:
- return None
- obj = bus.get_object(interface, path)
- return dbus.Interface(obj, interface)
- except Exception, e:
- gajim.log.debug(str(e))
- return None
+ """
+ Get an interface on the current SessionBus. If the interface isn't running,
+ try to start it first
+ """
+ if not supported:
+ return None
+ if session_bus.present():
+ bus = session_bus.SessionBus()
+ else:
+ return None
+ try:
+ obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
+ dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
+ running_services = dbus_iface.ListNames()
+ started = True
+ if interface not in running_services:
+ # try to start the service
+ if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1:
+ started = True
+ else:
+ started = False
+ if not started:
+ return None
+ obj = bus.get_object(interface, path)
+ return dbus.Interface(obj, interface)
+ except Exception, e:
+ gajim.log.debug(str(e))
+ return None
def get_notifications_interface(notif=None):
- """
- Get the notifications interface
-
- :param notif: DesktopNotification instance
- """
- # try to see if KDE notifications are available
- iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications',
- start_service=False)
- if iface != None:
- if notif != None:
- notif.kde_notifications = True
- return iface
- # KDE notifications don't seem to be available, falling back to
- # notification-daemon
- else:
- if notif != None:
- notif.kde_notifications = False
- return get_interface('org.freedesktop.Notifications',
- '/org/freedesktop/Notifications')
+ """
+ Get the notifications interface
+
+ :param notif: DesktopNotification instance
+ """
+ # try to see if KDE notifications are available
+ iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications',
+ start_service=False)
+ if iface != None:
+ if notif != None:
+ notif.kde_notifications = True
+ return iface
+ # KDE notifications don't seem to be available, falling back to
+ # notification-daemon
+ else:
+ if notif != None:
+ notif.kde_notifications = False
+ return get_interface('org.freedesktop.Notifications',
+ '/org/freedesktop/Notifications')
if supported:
- class MissingArgument(dbus.DBusException):
- _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument'
+ class MissingArgument(dbus.DBusException):
+ _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument'
- class InvalidArgument(dbus.DBusException):
- '''Raised when one of the provided arguments is invalid.'''
- _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument'
-
-# vim: se ts=3:
+ class InvalidArgument(dbus.DBusException):
+ '''Raised when one of the provided arguments is invalid.'''
+ _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument'
diff --git a/src/common/defs.py b/src/common/defs.py
index 615c825b2..348010bff 100644
--- a/src/common/defs.py
+++ b/src/common/defs.py
@@ -31,6 +31,4 @@ version = '0.13.10.2-dev'
import sys, os.path
for base in ('.', 'common'):
- sys.path.append(os.path.join(base, '.libs'))
-
-# vim: se ts=3:
+ sys.path.append(os.path.join(base, '.libs'))
diff --git a/src/common/dh.py b/src/common/dh.py
index b13cdefc0..31f43202d 100644
--- a/src/common/dh.py
+++ b/src/common/dh.py
@@ -28,28 +28,28 @@ These constants have been obtained from RFC2409 and RFC3526.
import string
-generators = [ None, # one to get the right offset
- 2,
- 2,
- None,
- None,
- 2,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- 2, # group 14
- 2,
- 2,
- 2,
- 2,
- ]
-
-hex_primes = [ None,
+generators = [ None, # one to get the right offset
+ 2,
+ 2,
+ None,
+ None,
+ 2,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ 2, # group 14
+ 2,
+ 2,
+ 2,
+ 2,
+ ]
+
+hex_primes = [ None,
# group 1
'''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
@@ -222,11 +222,9 @@ B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92
all_ascii = ''.join(map(chr, range(256)))
def hex_to_decimal(stripee):
- if not stripee:
- return None
+ if not stripee:
+ return None
- return int(stripee.translate(all_ascii, string.whitespace), 16)
+ return int(stripee.translate(all_ascii, string.whitespace), 16)
primes = map(hex_to_decimal, hex_primes)
-
-# vim: se ts=3:
diff --git a/src/common/events.py b/src/common/events.py
index ccf5744f1..9e8ae059f 100644
--- a/src/common/events.py
+++ b/src/common/events.py
@@ -27,310 +27,308 @@
import time
class Event:
- """
- Information concerning each event
- """
-
- def __init__(self, type_, time_, parameters, show_in_roster=False,
- show_in_systray=True):
- """
- type_ in chat, normal, file-request, file-error, file-completed,
- file-request-error, file-send-error, file-stopped, gc_msg, pm,
- printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm,
- gc-invitation, subscription_request, unsubscribedm jingle-incoming
-
- parameters is (per type_):
- chat, normal, pm: [message, subject, kind, time, encrypted, resource,
- msg_id]
- where kind in error, incoming
- file-*: file_props
- gc_msg: None
- printed_chat: control
- printed_*: None
- messages that are already printed in chat, but not read
- gc-invitation: [room_jid, reason, password, is_continued]
- subscription_request: [text, nick]
- unsubscribed: contact
- jingle-incoming: (fulljid, sessionid, content_types)
- """
- self.type_ = type_
- self.time_ = time_
- self.parameters = parameters
- self.show_in_roster = show_in_roster
- self.show_in_systray = show_in_systray
- # Set when adding the event
- self.jid = None
- self.account = None
+ """
+ Information concerning each event
+ """
+
+ def __init__(self, type_, time_, parameters, show_in_roster=False,
+ show_in_systray=True):
+ """
+ type_ in chat, normal, file-request, file-error, file-completed,
+ file-request-error, file-send-error, file-stopped, gc_msg, pm,
+ printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm,
+ gc-invitation, subscription_request, unsubscribedm jingle-incoming
+
+ parameters is (per type_):
+ chat, normal, pm: [message, subject, kind, time, encrypted, resource,
+ msg_id]
+ where kind in error, incoming
+ file-*: file_props
+ gc_msg: None
+ printed_chat: control
+ printed_*: None
+ messages that are already printed in chat, but not read
+ gc-invitation: [room_jid, reason, password, is_continued]
+ subscription_request: [text, nick]
+ unsubscribed: contact
+ jingle-incoming: (fulljid, sessionid, content_types)
+ """
+ self.type_ = type_
+ self.time_ = time_
+ self.parameters = parameters
+ self.show_in_roster = show_in_roster
+ self.show_in_systray = show_in_systray
+ # Set when adding the event
+ self.jid = None
+ self.account = None
class Events:
- """
- Information concerning all events
- """
-
- def __init__(self):
- self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
- self._event_added_listeners = []
- self._event_removed_listeners = []
-
- def event_added_subscribe(self, listener):
- """
- Add a listener when an event is added to the queue
- """
- if not listener in self._event_added_listeners:
- self._event_added_listeners.append(listener)
-
- def event_added_unsubscribe(self, listener):
- """
- Remove a listener when an event is added to the queue
- """
- if listener in self._event_added_listeners:
- self._event_added_listeners.remove(listener)
-
- def event_removed_subscribe(self, listener):
- """
- Add a listener when an event is removed from the queue
- """
- if not listener in self._event_removed_listeners:
- self._event_removed_listeners.append(listener)
-
- def event_removed_unsubscribe(self, listener):
- """
- Remove a listener when an event is removed from the queue
- """
- if listener in self._event_removed_listeners:
- self._event_removed_listeners.remove(listener)
-
- def fire_event_added(self, event):
- for listener in self._event_added_listeners:
- listener(event)
-
- def fire_event_removed(self, event_list):
- for listener in self._event_removed_listeners:
- listener(event_list)
-
- def change_account_name(self, old_name, new_name):
- if old_name in self._events:
- self._events[new_name] = self._events[old_name]
- del self._events[old_name]
-
- def add_account(self, account):
- self._events[account] = {}
-
- def get_accounts(self):
- return self._events.keys()
-
- def remove_account(self, account):
- del self._events[account]
-
- def create_event(self, type_, parameters, time_ = time.time(),
- show_in_roster = False, show_in_systray = True):
- return Event(type_, time_, parameters, show_in_roster,
- show_in_systray)
-
- def add_event(self, account, jid, event):
- # No such account before ?
- if account not in self._events:
- self._events[account] = {jid: [event]}
- # no such jid before ?
- elif jid not in self._events[account]:
- self._events[account][jid] = [event]
- else:
- self._events[account][jid].append(event)
- event.jid = jid
- event.account = account
- self.fire_event_added(event)
-
- def remove_events(self, account, jid, event = None, types = []):
- """
- If event is not specified, remove all events from this jid, optionally
- only from given type return True if no such event found
- """
- if account not in self._events:
- return True
- if jid not in self._events[account]:
- return True
- if event: # remove only one event
- if event in self._events[account][jid]:
- if len(self._events[account][jid]) == 1:
- del self._events[account][jid]
- else:
- self._events[account][jid].remove(event)
- self.fire_event_removed([event])
- return
- else:
- return True
- if types:
- new_list = [] # list of events to keep
- removed_list = [] # list of removed events
- for ev in self._events[account][jid]:
- if ev.type_ not in types:
- new_list.append(ev)
- else:
- removed_list.append(ev)
- if len(new_list) == len(self._events[account][jid]):
- return True
- if new_list:
- self._events[account][jid] = new_list
- else:
- del self._events[account][jid]
- self.fire_event_removed(removed_list)
- return
- # no event nor type given, remove them all
- self.fire_event_removed(self._events[account][jid])
- del self._events[account][jid]
-
- def change_jid(self, account, old_jid, new_jid):
- if account not in self._events:
- return
- if old_jid not in self._events[account]:
- return
- if new_jid in self._events[account]:
- self._events[account][new_jid] += self._events[account][old_jid]
- else:
- self._events[account][new_jid] = self._events[account][old_jid]
- del self._events[account][old_jid]
-
- def get_nb_events(self, types = [], account = None):
- return self._get_nb_events(types = types, account = account)
-
- def get_events(self, account, jid = None, types = []):
- """
- Return all events from the given account of the form {jid1: [], jid2:
- []}. If jid is given, returns all events from the given jid in a list: []
- optionally only from given type
- """
- if account not in self._events:
- return []
- if not jid:
- events_list = {} # list of events
- for jid_ in self._events[account]:
- events = []
- for ev in self._events[account][jid_]:
- if not types or ev.type_ in types:
- events.append(ev)
- if events:
- events_list[jid_] = events
- return events_list
- if jid not in self._events[account]:
- return []
- events_list = [] # list of events
- for ev in self._events[account][jid]:
- if not types or ev.type_ in types:
- events_list.append(ev)
- return events_list
-
- def get_first_event(self, account, jid = None, type_ = None):
- """
- Return the first event of type type_ if given
- """
- events_list = self.get_events(account, jid, type_)
- # be sure it's bigger than latest event
- first_event_time = time.time() + 1
- first_event = None
- for event in events_list:
- if event.time_ < first_event_time:
- first_event_time = event.time_
- first_event = event
- return first_event
-
- def _get_nb_events(self, account = None, jid = None, attribute = None, types
- = []):
- """
- Return the number of pending events
- """
- nb = 0
- if account:
- accounts = [account]
- else:
- accounts = self._events.keys()
- for acct in accounts:
- if acct not in self._events:
- continue
- if jid:
- jids = [jid]
- else:
- jids = self._events[acct].keys()
- for j in jids:
- if j not in self._events[acct]:
- continue
- for event in self._events[acct][j]:
- if types and event.type_ not in types:
- continue
- if not attribute or \
- attribute == 'systray' and event.show_in_systray or \
- attribute == 'roster' and event.show_in_roster:
- nb += 1
- return nb
-
- def _get_some_events(self, attribute):
- """
- Attribute in systray, roster
- """
- events = {}
- for account in self._events:
- events[account] = {}
- for jid in self._events[account]:
- events[account][jid] = []
- for event in self._events[account][jid]:
- if attribute == 'systray' and event.show_in_systray or \
- attribute == 'roster' and event.show_in_roster:
- events[account][jid].append(event)
- if not events[account][jid]:
- del events[account][jid]
- if not events[account]:
- del events[account]
- return events
-
- def _get_first_event_with_attribute(self, events):
- """
- Get the first event
-
- events is in the form {account1: {jid1: [ev1, ev2], },. }
- """
- # be sure it's bigger than latest event
- first_event_time = time.time() + 1
- first_account = None
- first_jid = None
- first_event = None
- for account in events:
- for jid in events[account]:
- for event in events[account][jid]:
- if event.time_ < first_event_time:
- first_event_time = event.time_
- first_account = account
- first_jid = jid
- first_event = event
- return first_account, first_jid, first_event
-
- def get_nb_systray_events(self, types = []):
- """
- Return the number of events displayed in roster
- """
- return self._get_nb_events(attribute = 'systray', types = types)
-
- def get_systray_events(self):
- """
- Return all events that must be displayed in systray:
- {account1: {jid1: [ev1, ev2], },. }
- """
- return self._get_some_events('systray')
-
- def get_first_systray_event(self):
- events = self.get_systray_events()
- return self._get_first_event_with_attribute(events)
-
- def get_nb_roster_events(self, account = None, jid = None, types = []):
- """
- Return the number of events displayed in roster
- """
- return self._get_nb_events(attribute = 'roster', account = account,
- jid = jid, types = types)
-
- def get_roster_events(self):
- """
- Return all events that must be displayed in roster:
- {account1: {jid1: [ev1, ev2], },. }
- """
- return self._get_some_events('roster')
-
-# vim: se ts=3:
+ """
+ Information concerning all events
+ """
+
+ def __init__(self):
+ self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
+ self._event_added_listeners = []
+ self._event_removed_listeners = []
+
+ def event_added_subscribe(self, listener):
+ """
+ Add a listener when an event is added to the queue
+ """
+ if not listener in self._event_added_listeners:
+ self._event_added_listeners.append(listener)
+
+ def event_added_unsubscribe(self, listener):
+ """
+ Remove a listener when an event is added to the queue
+ """
+ if listener in self._event_added_listeners:
+ self._event_added_listeners.remove(listener)
+
+ def event_removed_subscribe(self, listener):
+ """
+ Add a listener when an event is removed from the queue
+ """
+ if not listener in self._event_removed_listeners:
+ self._event_removed_listeners.append(listener)
+
+ def event_removed_unsubscribe(self, listener):
+ """
+ Remove a listener when an event is removed from the queue
+ """
+ if listener in self._event_removed_listeners:
+ self._event_removed_listeners.remove(listener)
+
+ def fire_event_added(self, event):
+ for listener in self._event_added_listeners:
+ listener(event)
+
+ def fire_event_removed(self, event_list):
+ for listener in self._event_removed_listeners:
+ listener(event_list)
+
+ def change_account_name(self, old_name, new_name):
+ if old_name in self._events:
+ self._events[new_name] = self._events[old_name]
+ del self._events[old_name]
+
+ def add_account(self, account):
+ self._events[account] = {}
+
+ def get_accounts(self):
+ return self._events.keys()
+
+ def remove_account(self, account):
+ del self._events[account]
+
+ def create_event(self, type_, parameters, time_ = time.time(),
+ show_in_roster = False, show_in_systray = True):
+ return Event(type_, time_, parameters, show_in_roster,
+ show_in_systray)
+
+ def add_event(self, account, jid, event):
+ # No such account before ?
+ if account not in self._events:
+ self._events[account] = {jid: [event]}
+ # no such jid before ?
+ elif jid not in self._events[account]:
+ self._events[account][jid] = [event]
+ else:
+ self._events[account][jid].append(event)
+ event.jid = jid
+ event.account = account
+ self.fire_event_added(event)
+
+ def remove_events(self, account, jid, event = None, types = []):
+ """
+ If event is not specified, remove all events from this jid, optionally
+ only from given type return True if no such event found
+ """
+ if account not in self._events:
+ return True
+ if jid not in self._events[account]:
+ return True
+ if event: # remove only one event
+ if event in self._events[account][jid]:
+ if len(self._events[account][jid]) == 1:
+ del self._events[account][jid]
+ else:
+ self._events[account][jid].remove(event)
+ self.fire_event_removed([event])
+ return
+ else:
+ return True
+ if types:
+ new_list = [] # list of events to keep
+ removed_list = [] # list of removed events
+ for ev in self._events[account][jid]:
+ if ev.type_ not in types:
+ new_list.append(ev)
+ else:
+ removed_list.append(ev)
+ if len(new_list) == len(self._events[account][jid]):
+ return True
+ if new_list:
+ self._events[account][jid] = new_list
+ else:
+ del self._events[account][jid]
+ self.fire_event_removed(removed_list)
+ return
+ # no event nor type given, remove them all
+ self.fire_event_removed(self._events[account][jid])
+ del self._events[account][jid]
+
+ def change_jid(self, account, old_jid, new_jid):
+ if account not in self._events:
+ return
+ if old_jid not in self._events[account]:
+ return
+ if new_jid in self._events[account]:
+ self._events[account][new_jid] += self._events[account][old_jid]
+ else:
+ self._events[account][new_jid] = self._events[account][old_jid]
+ del self._events[account][old_jid]
+
+ def get_nb_events(self, types = [], account = None):
+ return self._get_nb_events(types = types, account = account)
+
+ def get_events(self, account, jid = None, types = []):
+ """
+ Return all events from the given account of the form {jid1: [], jid2:
+ []}. If jid is given, returns all events from the given jid in a list: []
+ optionally only from given type
+ """
+ if account not in self._events:
+ return []
+ if not jid:
+ events_list = {} # list of events
+ for jid_ in self._events[account]:
+ events = []
+ for ev in self._events[account][jid_]:
+ if not types or ev.type_ in types:
+ events.append(ev)
+ if events:
+ events_list[jid_] = events
+ return events_list
+ if jid not in self._events[account]:
+ return []
+ events_list = [] # list of events
+ for ev in self._events[account][jid]:
+ if not types or ev.type_ in types:
+ events_list.append(ev)
+ return events_list
+
+ def get_first_event(self, account, jid = None, type_ = None):
+ """
+ Return the first event of type type_ if given
+ """
+ events_list = self.get_events(account, jid, type_)
+ # be sure it's bigger than latest event
+ first_event_time = time.time() + 1
+ first_event = None
+ for event in events_list:
+ if event.time_ < first_event_time:
+ first_event_time = event.time_
+ first_event = event
+ return first_event
+
+ def _get_nb_events(self, account = None, jid = None, attribute = None, types
+ = []):
+ """
+ Return the number of pending events
+ """
+ nb = 0
+ if account:
+ accounts = [account]
+ else:
+ accounts = self._events.keys()
+ for acct in accounts:
+ if acct not in self._events:
+ continue
+ if jid:
+ jids = [jid]
+ else:
+ jids = self._events[acct].keys()
+ for j in jids:
+ if j not in self._events[acct]:
+ continue
+ for event in self._events[acct][j]:
+ if types and event.type_ not in types:
+ continue
+ if not attribute or \
+ attribute == 'systray' and event.show_in_systray or \
+ attribute == 'roster' and event.show_in_roster:
+ nb += 1
+ return nb
+
+ def _get_some_events(self, attribute):
+ """
+ Attribute in systray, roster
+ """
+ events = {}
+ for account in self._events:
+ events[account] = {}
+ for jid in self._events[account]:
+ events[account][jid] = []
+ for event in self._events[account][jid]:
+ if attribute == 'systray' and event.show_in_systray or \
+ attribute == 'roster' and event.show_in_roster:
+ events[account][jid].append(event)
+ if not events[account][jid]:
+ del events[account][jid]
+ if not events[account]:
+ del events[account]
+ return events
+
+ def _get_first_event_with_attribute(self, events):
+ """
+ Get the first event
+
+ events is in the form {account1: {jid1: [ev1, ev2], },. }
+ """
+ # be sure it's bigger than latest event
+ first_event_time = time.time() + 1
+ first_account = None
+ first_jid = None
+ first_event = None
+ for account in events:
+ for jid in events[account]:
+ for event in events[account][jid]:
+ if event.time_ < first_event_time:
+ first_event_time = event.time_
+ first_account = account
+ first_jid = jid
+ first_event = event
+ return first_account, first_jid, first_event
+
+ def get_nb_systray_events(self, types = []):
+ """
+ Return the number of events displayed in roster
+ """
+ return self._get_nb_events(attribute = 'systray', types = types)
+
+ def get_systray_events(self):
+ """
+ Return all events that must be displayed in systray:
+ {account1: {jid1: [ev1, ev2], },. }
+ """
+ return self._get_some_events('systray')
+
+ def get_first_systray_event(self):
+ events = self.get_systray_events()
+ return self._get_first_event_with_attribute(events)
+
+ def get_nb_roster_events(self, account = None, jid = None, types = []):
+ """
+ Return the number of events displayed in roster
+ """
+ return self._get_nb_events(attribute = 'roster', account = account,
+ jid = jid, types = types)
+
+ def get_roster_events(self):
+ """
+ Return all events that must be displayed in roster:
+ {account1: {jid1: [ev1, ev2], },. }
+ """
+ return self._get_some_events('roster')
diff --git a/src/common/exceptions.py b/src/common/exceptions.py
index 090255768..6e1df2e36 100644
--- a/src/common/exceptions.py
+++ b/src/common/exceptions.py
@@ -22,114 +22,112 @@
##
class PysqliteOperationalError(Exception):
- """
- Sqlite2 raised pysqlite2.dbapi2.OperationalError
- """
+ """
+ Sqlite2 raised pysqlite2.dbapi2.OperationalError
+ """
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
+ def __init__(self, text=''):
+ Exception.__init__(self)
+ self.text = text
- def __str__(self):
- return self.text
+ def __str__(self):
+ return self.text
class DatabaseMalformed(Exception):
- """
- The databas can't be read
- """
+ """
+ The databas can't be read
+ """
- def __init__(self):
- Exception.__init__(self)
+ def __init__(self):
+ Exception.__init__(self)
- def __str__(self):
- return _('Database cannot be read.')
+ def __str__(self):
+ return _('Database cannot be read.')
class ServiceNotAvailable(Exception):
- """
- This exception is raised when we cannot use Gajim remotely'
- """
+ """
+ This exception is raised when we cannot use Gajim remotely'
+ """
- def __init__(self):
- Exception.__init__(self)
+ def __init__(self):
+ Exception.__init__(self)
- def __str__(self):
- return _('Service not available: Gajim is not running, or remote_control is False')
+ def __str__(self):
+ return _('Service not available: Gajim is not running, or remote_control is False')
class DbusNotSupported(Exception):
- """
- D-Bus is not installed or python bindings are missing
- """
+ """
+ D-Bus is not installed or python bindings are missing
+ """
- def __init__(self):
- Exception.__init__(self)
+ def __init__(self):
+ Exception.__init__(self)
- def __str__(self):
- return _('D-Bus is not present on this machine or python module is missing')
+ def __str__(self):
+ return _('D-Bus is not present on this machine or python module is missing')
class SessionBusNotPresent(Exception):
- """
- This exception indicates that there is no session daemon
- """
+ """
+ This exception indicates that there is no session daemon
+ """
- def __init__(self):
- Exception.__init__(self)
+ def __init__(self):
+ Exception.__init__(self)
- def __str__(self):
- return _('Session bus is not available.\nTry reading %(url)s') % \
- {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
+ def __str__(self):
+ return _('Session bus is not available.\nTry reading %(url)s') % \
+ {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
class SystemBusNotPresent(Exception):
- """
- This exception indicates that there is no session daemon
- """
+ """
+ This exception indicates that there is no session daemon
+ """
- def __init__(self):
- Exception.__init__(self)
+ def __init__(self):
+ Exception.__init__(self)
- def __str__(self):
- return _('System bus is not available.\nTry reading %(url)s') % \
- {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
+ def __str__(self):
+ return _('System bus is not available.\nTry reading %(url)s') % \
+ {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
class NegotiationError(Exception):
- """
- A session negotiation failed
- """
- pass
+ """
+ A session negotiation failed
+ """
+ pass
class DecryptionError(Exception):
- """
- A message couldn't be decrypted into usable XML
- """
- pass
+ """
+ A message couldn't be decrypted into usable XML
+ """
+ pass
class Cancelled(Exception):
- """
- The user cancelled an operation
- """
- pass
+ """
+ The user cancelled an operation
+ """
+ pass
class LatexError(Exception):
- """
- LaTeX processing failed for some reason
- """
+ """
+ LaTeX processing failed for some reason
+ """
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
+ def __init__(self, text=''):
+ Exception.__init__(self)
+ self.text = text
- def __str__(self):
- return self.text
+ def __str__(self):
+ return self.text
class GajimGeneralException(Exception):
- """
- This exception is our general exception
- """
+ """
+ This exception is our general exception
+ """
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
+ def __init__(self, text=''):
+ Exception.__init__(self)
+ self.text = text
- def __str__(self):
- return self.text
-
-# vim: se ts=3:
+ def __str__(self):
+ return self.text
diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py
index 3adad66d1..f84d63c3d 100755
--- a/src/common/fuzzyclock.py
+++ b/src/common/fuzzyclock.py
@@ -35,39 +35,37 @@ So most of the credit goes to this guys, thanks :-)
import time
class FuzzyClock:
- HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'),
- _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'),
- _('eleven') ]
+ HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'),
+ _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'),
+ _('eleven') ]
- #Strings to use for the output. %(0)s will be replaced with the preceding hour
- #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). '''
- FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'),
- _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'),
- _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'),
- _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ]
+ #Strings to use for the output. %(0)s will be replaced with the preceding hour
+ #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). '''
+ FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'),
+ _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'),
+ _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'),
+ _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ]
- FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'),
- _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'),
- _('Late evening'), _('Night') ]
+ FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'),
+ _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'),
+ _('Late evening'), _('Night') ]
- FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'),
- _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ]
+ FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'),
+ _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ]
- def fuzzy_time(self, fuzzyness, now):
- if fuzzyness == 1 or fuzzyness == 2:
- if fuzzyness == 1:
- sector = int(round(now.tm_min / 5.0))
- else:
- sector = int(round(now.tm_min / 15.0)) * 3
+ def fuzzy_time(self, fuzzyness, now):
+ if fuzzyness == 1 or fuzzyness == 2:
+ if fuzzyness == 1:
+ sector = int(round(now.tm_min / 5.0))
+ else:
+ sector = int(round(now.tm_min / 15.0)) * 3
- return self.FUZZY_TIME[sector] % {
- '0': self.HOUR_NAMES[now.tm_hour % 12],
- '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]}
+ return self.FUZZY_TIME[sector] % {
+ '0': self.HOUR_NAMES[now.tm_hour % 12],
+ '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]}
- elif fuzzyness == 3:
- return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))]
+ elif fuzzyness == 3:
+ return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))]
- else:
- return self.FUZZY_WEEK[now.tm_wday]
-
-# vim: se ts=3:
+ else:
+ return self.FUZZY_WEEK[now.tm_wday]
diff --git a/src/common/gajim.py b/src/common/gajim.py
index 6ef66d6b9..552b86f8d 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -35,30 +35,30 @@ import config
import xmpp
try:
- import defs
+ import defs
except ImportError:
- print >> sys.stderr, '''defs.py is missing!
+ print >> sys.stderr, '''defs.py is missing!
If you start gajim from svn:
- * Make sure you have GNU autotools installed.
- This includes the following packages:
- automake >= 1.8
- autoconf >= 2.59
- intltool-0.35
- libtool
- * Run
- $ sh autogen.sh
- * Optionally, install gajim
- $ make
- $ sudo make install
+* Make sure you have GNU autotools installed.
+This includes the following packages:
+automake >= 1.8
+autoconf >= 2.59
+intltool-0.35
+libtool
+* Run
+$ sh autogen.sh
+* Optionally, install gajim
+$ make
+$ sudo make install
**** Note for translators ****
- You can get the latest string updates, by running:
- $ cd po/
- $ make update-po
+You can get the latest string updates, by running:
+$ cd po/
+$ make update-po
'''
- sys.exit(1)
+ sys.exit(1)
interface = None # The actual interface (the gtk one for the moment)
thread_interface = None # Interface to run a thread and then a callback
@@ -90,14 +90,14 @@ ICONS_DIR = gajimpaths['ICONS']
HOME_DIR = gajimpaths['HOME']
try:
- LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
+ LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
except (ValueError, locale.Error):
- # unknown locale, use en is better than fail
- LANG = None
+ # unknown locale, use en is better than fail
+ LANG = None
if LANG is None:
- LANG = 'en'
+ LANG = 'en'
else:
- LANG = LANG[:2] # en, fr, el etc..
+ LANG = LANG[:2] # en, fr, el etc..
os_info = None # used to cache os information
@@ -109,7 +109,7 @@ gmail_domains = ['gmail.com', 'googlemail.com']
transport_type = {} # list the type of transport
last_message_time = {} # list of time of the latest incomming message
- # {acct1: {jid1: time1, jid2: time2}, }
+ # {acct1: {jid1: time1, jid2: time2}, }
encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
contacts = LegacyContactsAPI()
@@ -147,35 +147,35 @@ transport_avatar = {} # {transport_jid: [jid_list]}
# Is Gnome configured to activate on single click ?
single_click = False
SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible', 'error']
+ 'invisible', 'error']
# zeroconf account name
ZEROCONF_ACC_NAME = 'Local'
HAVE_ZEROCONF = True
try:
- import avahi
+ import avahi
except ImportError:
- try:
- import pybonjour
- except Exception: # Linux raises ImportError, Windows raises WindowsError
- HAVE_ZEROCONF = False
+ try:
+ import pybonjour
+ except Exception: # Linux raises ImportError, Windows raises WindowsError
+ HAVE_ZEROCONF = False
HAVE_PYCRYPTO = True
try:
- import Crypto
+ import Crypto
except ImportError:
- HAVE_PYCRYPTO = False
+ HAVE_PYCRYPTO = False
HAVE_GPG = True
try:
- import GnuPGInterface
+ import GnuPGInterface
except ImportError:
- HAVE_GPG = False
+ HAVE_GPG = False
else:
- from os import system
- if system('gpg -h >/dev/null 2>&1'):
- HAVE_GPG = False
+ from os import system
+ if system('gpg -h >/dev/null 2>&1'):
+ HAVE_GPG = False
# Depends on use_latex option. Will be correctly set after we config options are
# read.
@@ -183,23 +183,23 @@ HAVE_LATEX = False
HAVE_INDICATOR = True
try:
- import indicate
+ import indicate
except ImportError:
- HAVE_INDICATOR = False
+ HAVE_INDICATOR = False
HAVE_FARSIGHT = True
try:
- import farsight, gst
+ import farsight, gst
except ImportError:
- HAVE_FARSIGHT = False
+ HAVE_FARSIGHT = False
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE,
- xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER,
- xmpp.NS_MUC_CONFIG, xmpp.NS_COMMANDS, xmpp.NS_DISCO_INFO, 'ipv6',
- 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE,
- xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog',
- 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN,
- xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX]
+ xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER,
+ xmpp.NS_MUC_CONFIG, xmpp.NS_COMMANDS, xmpp.NS_DISCO_INFO, 'ipv6',
+ 'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE,
+ xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog',
+ 'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN,
+ xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX]
# Optional features gajim supports per account
gajim_optional_features = {}
@@ -211,224 +211,222 @@ import caps_cache
caps_cache.initialize(logger)
def get_nick_from_jid(jid):
- pos = jid.find('@')
- return jid[:pos]
+ pos = jid.find('@')
+ return jid[:pos]
def get_server_from_jid(jid):
- pos = jid.find('@') + 1 # after @
- return jid[pos:]
+ pos = jid.find('@') + 1 # after @
+ return jid[pos:]
def get_name_and_server_from_jid(jid):
- name = get_nick_from_jid(jid)
- server = get_server_from_jid(jid)
- return name, server
+ name = get_nick_from_jid(jid)
+ server = get_server_from_jid(jid)
+ return name, server
def get_room_and_nick_from_fjid(jid):
- # fake jid is the jid for a contact in a room
- # gaim@conference.jabber.no/nick/nick-continued
- # return ('gaim@conference.jabber.no', 'nick/nick-continued')
- l = jid.split('/', 1)
- if len(l) == 1: # No nick
- l.append('')
- return l
+ # fake jid is the jid for a contact in a room
+ # gaim@conference.jabber.no/nick/nick-continued
+ # return ('gaim@conference.jabber.no', 'nick/nick-continued')
+ l = jid.split('/', 1)
+ if len(l) == 1: # No nick
+ l.append('')
+ return l
def get_real_jid_from_fjid(account, fjid):
- """
- Return real jid or returns None, if we don't know the real jid
- """
- room_jid, nick = get_room_and_nick_from_fjid(fjid)
- if not nick: # It's not a fake_jid, it is a real jid
- return fjid # we return the real jid
- real_jid = fjid
- if interface.msg_win_mgr.get_gc_control(room_jid, account):
- # It's a pm, so if we have real jid it's in contact.jid
- gc_contact = contacts.get_gc_contact(account, room_jid, nick)
- if not gc_contact:
- return
- # gc_contact.jid is None when it's not a real jid (we don't know real jid)
- real_jid = gc_contact.jid
- return real_jid
+ """
+ Return real jid or returns None, if we don't know the real jid
+ """
+ room_jid, nick = get_room_and_nick_from_fjid(fjid)
+ if not nick: # It's not a fake_jid, it is a real jid
+ return fjid # we return the real jid
+ real_jid = fjid
+ if interface.msg_win_mgr.get_gc_control(room_jid, account):
+ # It's a pm, so if we have real jid it's in contact.jid
+ gc_contact = contacts.get_gc_contact(account, room_jid, nick)
+ if not gc_contact:
+ return
+ # gc_contact.jid is None when it's not a real jid (we don't know real jid)
+ real_jid = gc_contact.jid
+ return real_jid
def get_room_from_fjid(jid):
- return get_room_and_nick_from_fjid(jid)[0]
+ return get_room_and_nick_from_fjid(jid)[0]
def get_contact_name_from_jid(account, jid):
- c = contacts.get_first_contact_from_jid(account, jid)
- return c.name
+ c = contacts.get_first_contact_from_jid(account, jid)
+ return c.name
def get_jid_without_resource(jid):
- return jid.split('/')[0]
+ return jid.split('/')[0]
def construct_fjid(room_jid, nick):
- """
- Nick is in UTF-8 (taken from treeview); room_jid is in unicode
- """
- # fake jid is the jid for a contact in a room
- # gaim@conference.jabber.org/nick
- if isinstance(nick, str):
- nick = unicode(nick, 'utf-8')
- return room_jid + '/' + nick
+ """
+ Nick is in UTF-8 (taken from treeview); room_jid is in unicode
+ """
+ # fake jid is the jid for a contact in a room
+ # gaim@conference.jabber.org/nick
+ if isinstance(nick, str):
+ nick = unicode(nick, 'utf-8')
+ return room_jid + '/' + nick
def get_resource_from_jid(jid):
- jids = jid.split('/', 1)
- if len(jids) > 1:
- return jids[1] # abc@doremi.org/res/res-continued
- else:
- return ''
+ jids = jid.split('/', 1)
+ if len(jids) > 1:
+ return jids[1] # abc@doremi.org/res/res-continued
+ else:
+ return ''
def get_number_of_accounts():
- """
- Return the number of ALL accounts
- """
- return len(connections.keys())
+ """
+ Return the number of ALL accounts
+ """
+ return len(connections.keys())
def get_number_of_connected_accounts(accounts_list = None):
- """
- Returns the number of CONNECTED accounts. Uou can optionally pass an
- accounts_list and if you do those will be checked, else all will be checked
- """
- connected_accounts = 0
- if accounts_list is None:
- accounts = connections.keys()
- else:
- accounts = accounts_list
- for account in accounts:
- if account_is_connected(account):
- connected_accounts = connected_accounts + 1
- return connected_accounts
+ """
+ Returns the number of CONNECTED accounts. Uou can optionally pass an
+ accounts_list and if you do those will be checked, else all will be checked
+ """
+ connected_accounts = 0
+ if accounts_list is None:
+ accounts = connections.keys()
+ else:
+ accounts = accounts_list
+ for account in accounts:
+ if account_is_connected(account):
+ connected_accounts = connected_accounts + 1
+ return connected_accounts
def account_is_connected(account):
- if account not in connections:
- return False
- if connections[account].connected > 1: # 0 is offline, 1 is connecting
- return True
- else:
- return False
+ if account not in connections:
+ return False
+ if connections[account].connected > 1: # 0 is offline, 1 is connecting
+ return True
+ else:
+ return False
def account_is_disconnected(account):
- return not account_is_connected(account)
+ return not account_is_connected(account)
def zeroconf_is_connected():
- return account_is_connected(ZEROCONF_ACC_NAME) and \
- config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
+ return account_is_connected(ZEROCONF_ACC_NAME) and \
+ config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
def get_number_of_securely_connected_accounts():
- """
- Return the number of the accounts that are SSL/TLS connected
- """
- num_of_secured = 0
- for account in connections.keys():
- if account_is_securely_connected(account):
- num_of_secured += 1
- return num_of_secured
+ """
+ Return the number of the accounts that are SSL/TLS connected
+ """
+ num_of_secured = 0
+ for account in connections.keys():
+ if account_is_securely_connected(account):
+ num_of_secured += 1
+ return num_of_secured
def account_is_securely_connected(account):
- if account_is_connected(account) and \
- account in con_types and con_types[account] in ('tls', 'ssl'):
- return True
- else:
- return False
+ if account_is_connected(account) and \
+ account in con_types and con_types[account] in ('tls', 'ssl'):
+ return True
+ else:
+ return False
def get_transport_name_from_jid(jid, use_config_setting = True):
- """
- Returns 'aim', 'gg', 'irc' etc
-
- If JID is not from transport returns None.
- """
- #FIXME: jid can be None! one TB I saw had this problem:
- # in the code block # it is a groupchat presence in handle_event_notify
- # jid was None. Yann why?
- if not jid or (use_config_setting and not config.get('use_transports_iconsets')):
- return
-
- host = get_server_from_jid(jid)
- if host in transport_type:
- return transport_type[host]
-
- # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports)
- host_splitted = host.split('.')
- if len(host_splitted) != 0:
- # now we support both 'icq.' and 'icq' but not icqsucks.org
- host = host_splitted[0]
-
- if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo',
- 'mrim', 'facebook'):
- return host
- elif host == 'gg':
- return 'gadu-gadu'
- elif host == 'jit':
- return 'icq'
- elif host == 'facebook':
- return 'facebook'
- else:
- return None
+ """
+ Returns 'aim', 'gg', 'irc' etc
+
+ If JID is not from transport returns None.
+ """
+ #FIXME: jid can be None! one TB I saw had this problem:
+ # in the code block # it is a groupchat presence in handle_event_notify
+ # jid was None. Yann why?
+ if not jid or (use_config_setting and not config.get('use_transports_iconsets')):
+ return
+
+ host = get_server_from_jid(jid)
+ if host in transport_type:
+ return transport_type[host]
+
+ # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports)
+ host_splitted = host.split('.')
+ if len(host_splitted) != 0:
+ # now we support both 'icq.' and 'icq' but not icqsucks.org
+ host = host_splitted[0]
+
+ if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo',
+ 'mrim', 'facebook'):
+ return host
+ elif host == 'gg':
+ return 'gadu-gadu'
+ elif host == 'jit':
+ return 'icq'
+ elif host == 'facebook':
+ return 'facebook'
+ else:
+ return None
def jid_is_transport(jid):
- # if not '@' or '@' starts the jid then it is transport
- if jid.find('@') <= 0:
- return True
- return False
+ # if not '@' or '@' starts the jid then it is transport
+ if jid.find('@') <= 0:
+ return True
+ return False
def get_jid_from_account(account_name):
- """
- Return the jid we use in the given account
- """
- name = config.get_per('accounts', account_name, 'name')
- hostname = config.get_per('accounts', account_name, 'hostname')
- jid = name + '@' + hostname
- return jid
+ """
+ Return the jid we use in the given account
+ """
+ name = config.get_per('accounts', account_name, 'name')
+ hostname = config.get_per('accounts', account_name, 'hostname')
+ jid = name + '@' + hostname
+ return jid
def get_our_jids():
- """
- Returns a list of the jids we use in our accounts
- """
- our_jids = list()
- for account in contacts.get_accounts():
- our_jids.append(get_jid_from_account(account))
- return our_jids
+ """
+ Returns a list of the jids we use in our accounts
+ """
+ our_jids = list()
+ for account in contacts.get_accounts():
+ our_jids.append(get_jid_from_account(account))
+ return our_jids
def get_hostname_from_account(account_name, use_srv = False):
- """
- Returns hostname (if custom hostname is used, that is returned)
- """
- if use_srv and connections[account_name].connected_hostname:
- return connections[account_name].connected_hostname
- if config.get_per('accounts', account_name, 'use_custom_host'):
- return config.get_per('accounts', account_name, 'custom_host')
- return config.get_per('accounts', account_name, 'hostname')
+ """
+ Returns hostname (if custom hostname is used, that is returned)
+ """
+ if use_srv and connections[account_name].connected_hostname:
+ return connections[account_name].connected_hostname
+ if config.get_per('accounts', account_name, 'use_custom_host'):
+ return config.get_per('accounts', account_name, 'custom_host')
+ return config.get_per('accounts', account_name, 'hostname')
def get_notification_image_prefix(jid):
- """
- Returns the prefix for the notification images
- """
- transport_name = get_transport_name_from_jid(jid)
- if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'):
- prefix = transport_name
- else:
- prefix = 'jabber'
- return prefix
+ """
+ Returns the prefix for the notification images
+ """
+ transport_name = get_transport_name_from_jid(jid)
+ if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'):
+ prefix = transport_name
+ else:
+ prefix = 'jabber'
+ return prefix
def get_name_from_jid(account, jid):
- """
- Return from JID's shown name and if no contact returns jids
- """
- contact = contacts.get_first_contact_from_jid(account, jid)
- if contact:
- actor = contact.get_shown_name()
- else:
- actor = jid
- return actor
+ """
+ Return from JID's shown name and if no contact returns jids
+ """
+ contact = contacts.get_first_contact_from_jid(account, jid)
+ if contact:
+ actor = contact.get_shown_name()
+ else:
+ actor = jid
+ return actor
def get_priority(account, show):
- """
- Return the priority an account must have
- """
- if not show:
- show = 'online'
-
- if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \
- config.get_per('accounts', account, 'adjust_priority_with_status'):
- return config.get_per('accounts', account, 'autopriority_' + show)
- return config.get_per('accounts', account, 'priority')
-
-# vim: se ts=3:
+ """
+ Return the priority an account must have
+ """
+ if not show:
+ show = 'online'
+
+ if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \
+ config.get_per('accounts', account, 'adjust_priority_with_status'):
+ return config.get_per('accounts', account, 'autopriority_' + show)
+ return config.get_per('accounts', account, 'priority')
diff --git a/src/common/ged.py b/src/common/ged.py
index 02aa1d66e..989c4c923 100644
--- a/src/common/ged.py
+++ b/src/common/ged.py
@@ -33,32 +33,32 @@ POSTCORE = 50
class GlobalEventsDispatcher(object):
- def __init__(self):
- self.handlers = {}
+ def __init__(self):
+ self.handlers = {}
- def register_event_handler(self, event_name, priority, handler):
- if event_name in self.handlers:
- handlers_list = self.handlers[event_name]
- i = 0
- for i,h in enumerate(handlers_list):
- if priority < h[0]:
- break
+ def register_event_handler(self, event_name, priority, handler):
+ if event_name in self.handlers:
+ handlers_list = self.handlers[event_name]
+ i = 0
+ for i,h in enumerate(handlers_list):
+ if priority < h[0]:
+ break
- handlers_list.insert(i, (priority, handler))
- else:
- self.handlers[event_name] = [(priority, handler)]
+ handlers_list.insert(i, (priority, handler))
+ else:
+ self.handlers[event_name] = [(priority, handler)]
- def remove_event_handler(self, event_name, priority, handler):
- if event_name in self.handlers:
- try:
- self.handlers[event_name].remove((priority, handler))
- except ValueError, error:
- log.warn('''Function (%s) with priority "%s" never registered
- as handler of event "%s". Couldn\'t remove. Error: %s'''
- %(handler, priority, event_name, error))
+ def remove_event_handler(self, event_name, priority, handler):
+ if event_name in self.handlers:
+ try:
+ self.handlers[event_name].remove((priority, handler))
+ except ValueError, error:
+ log.warn('''Function (%s) with priority "%s" never registered
+ as handler of event "%s". Couldn\'t remove. Error: %s'''
+ %(handler, priority, event_name, error))
- def raise_event(self, event_name, *args, **kwargs):
- log.debug('%s\nArgs: %s'%(event_name, str(args)))
- if event_name in self.handlers:
- for priority, handler in self.handlers[event_name]:
- handler(*args, **kwargs)
+ def raise_event(self, event_name, *args, **kwargs):
+ log.debug('%s\nArgs: %s'%(event_name, str(args)))
+ if event_name in self.handlers:
+ for priority, handler in self.handlers[event_name]:
+ handler(*args, **kwargs)
diff --git a/src/common/helpers.py b/src/common/helpers.py
index d3caa4266..3951a4710 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -47,1311 +47,1309 @@ from i18n import Q_
from i18n import ngettext
try:
- import winsound # windows-only built-in module for playing wav
- import win32api
- import win32con
+ import winsound # windows-only built-in module for playing wav
+ import win32api
+ import win32con
except Exception:
- pass
+ pass
special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats'))
class InvalidFormat(Exception):
- pass
+ pass
def decompose_jid(jidstring):
- user = None
- server = None
- resource = None
-
- # Search for delimiters
- user_sep = jidstring.find('@')
- res_sep = jidstring.find('/')
-
- if user_sep == -1:
- if res_sep == -1:
- # host
- server = jidstring
- else:
- # host/resource
- server = jidstring[0:res_sep]
- resource = jidstring[res_sep + 1:] or None
- else:
- if res_sep == -1:
- # user@host
- user = jidstring[0:user_sep] or None
- server = jidstring[user_sep + 1:]
- else:
- if user_sep < res_sep:
- # user@host/resource
- user = jidstring[0:user_sep] or None
- server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)]
- resource = jidstring[res_sep + 1:] or None
- else:
- # server/resource (with an @ in resource)
- server = jidstring[0:res_sep]
- resource = jidstring[res_sep + 1:] or None
- return user, server, resource
+ user = None
+ server = None
+ resource = None
+
+ # Search for delimiters
+ user_sep = jidstring.find('@')
+ res_sep = jidstring.find('/')
+
+ if user_sep == -1:
+ if res_sep == -1:
+ # host
+ server = jidstring
+ else:
+ # host/resource
+ server = jidstring[0:res_sep]
+ resource = jidstring[res_sep + 1:] or None
+ else:
+ if res_sep == -1:
+ # user@host
+ user = jidstring[0:user_sep] or None
+ server = jidstring[user_sep + 1:]
+ else:
+ if user_sep < res_sep:
+ # user@host/resource
+ user = jidstring[0:user_sep] or None
+ server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)]
+ resource = jidstring[res_sep + 1:] or None
+ else:
+ # server/resource (with an @ in resource)
+ server = jidstring[0:res_sep]
+ resource = jidstring[res_sep + 1:] or None
+ return user, server, resource
def parse_jid(jidstring):
- """
- Perform stringprep on all JID fragments from a string and return the full
- jid
- """
- # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
+ """
+ Perform stringprep on all JID fragments from a string and return the full
+ jid
+ """
+ # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
- return prep(*decompose_jid(jidstring))
+ return prep(*decompose_jid(jidstring))
def idn_to_ascii(host):
- """
- Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible
- encoding)
- """
- from encodings import idna
- labels = idna.dots.split(host)
- converted_labels = []
- for label in labels:
- converted_labels.append(idna.ToASCII(label))
- return ".".join(converted_labels)
+ """
+ Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible
+ encoding)
+ """
+ from encodings import idna
+ labels = idna.dots.split(host)
+ converted_labels = []
+ for label in labels:
+ converted_labels.append(idna.ToASCII(label))
+ return ".".join(converted_labels)
def ascii_to_idn(host):
- """
- Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain
- Names)
- """
- from encodings import idna
- labels = idna.dots.split(host)
- converted_labels = []
- for label in labels:
- converted_labels.append(idna.ToUnicode(label))
- return ".".join(converted_labels)
+ """
+ Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain
+ Names)
+ """
+ from encodings import idna
+ labels = idna.dots.split(host)
+ converted_labels = []
+ for label in labels:
+ converted_labels.append(idna.ToUnicode(label))
+ return ".".join(converted_labels)
def parse_resource(resource):
- """
- Perform stringprep on resource and return it
- """
- if resource:
- try:
- from xmpp.stringprepare import resourceprep
- return resourceprep.prepare(unicode(resource))
- except UnicodeError:
- raise InvalidFormat, 'Invalid character in resource.'
+ """
+ Perform stringprep on resource and return it
+ """
+ if resource:
+ try:
+ from xmpp.stringprepare import resourceprep
+ return resourceprep.prepare(unicode(resource))
+ except UnicodeError:
+ raise InvalidFormat, 'Invalid character in resource.'
def prep(user, server, resource):
- """
- Perform stringprep on all JID fragments and return the full jid
- """
- # This function comes from
- #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
- if user:
- try:
- from xmpp.stringprepare import nodeprep
- user = nodeprep.prepare(unicode(user))
- except UnicodeError:
- raise InvalidFormat, _('Invalid character in username.')
- else:
- user = None
-
- if not server:
- raise InvalidFormat, _('Server address required.')
- else:
- try:
- from xmpp.stringprepare import nameprep
- server = nameprep.prepare(unicode(server))
- except UnicodeError:
- raise InvalidFormat, _('Invalid character in hostname.')
-
- if resource:
- try:
- from xmpp.stringprepare import resourceprep
- resource = resourceprep.prepare(unicode(resource))
- except UnicodeError:
- raise InvalidFormat, _('Invalid character in resource.')
- else:
- resource = None
-
- if user:
- if resource:
- return '%s@%s/%s' % (user, server, resource)
- else:
- return '%s@%s' % (user, server)
- else:
- if resource:
- return '%s/%s' % (server, resource)
- else:
- return server
+ """
+ Perform stringprep on all JID fragments and return the full jid
+ """
+ # This function comes from
+ #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
+ if user:
+ try:
+ from xmpp.stringprepare import nodeprep
+ user = nodeprep.prepare(unicode(user))
+ except UnicodeError:
+ raise InvalidFormat, _('Invalid character in username.')
+ else:
+ user = None
+
+ if not server:
+ raise InvalidFormat, _('Server address required.')
+ else:
+ try:
+ from xmpp.stringprepare import nameprep
+ server = nameprep.prepare(unicode(server))
+ except UnicodeError:
+ raise InvalidFormat, _('Invalid character in hostname.')
+
+ if resource:
+ try:
+ from xmpp.stringprepare import resourceprep
+ resource = resourceprep.prepare(unicode(resource))
+ except UnicodeError:
+ raise InvalidFormat, _('Invalid character in resource.')
+ else:
+ resource = None
+
+ if user:
+ if resource:
+ return '%s@%s/%s' % (user, server, resource)
+ else:
+ return '%s@%s' % (user, server)
+ else:
+ if resource:
+ return '%s/%s' % (server, resource)
+ else:
+ return server
def windowsify(s):
- if os.name == 'nt':
- return s.capitalize()
- return s
+ if os.name == 'nt':
+ return s.capitalize()
+ return s
def temp_failure_retry(func, *args, **kwargs):
- while True:
- try:
- return func(*args, **kwargs)
- except (os.error, IOError, select.error), ex:
- if ex.errno == errno.EINTR:
- continue
- else:
- raise
+ while True:
+ try:
+ return func(*args, **kwargs)
+ except (os.error, IOError, select.error), ex:
+ if ex.errno == errno.EINTR:
+ continue
+ else:
+ raise
def get_uf_show(show, use_mnemonic = False):
- """
- Return a userfriendly string for dnd/xa/chat and make all strings
- translatable
-
- If use_mnemonic is True, it adds _ so GUI should call with True for
- accessibility issues
- """
- if show == 'dnd':
- if use_mnemonic:
- uf_show = _('_Busy')
- else:
- uf_show = _('Busy')
- elif show == 'xa':
- if use_mnemonic:
- uf_show = _('_Not Available')
- else:
- uf_show = _('Not Available')
- elif show == 'chat':
- if use_mnemonic:
- uf_show = _('_Free for Chat')
- else:
- uf_show = _('Free for Chat')
- elif show == 'online':
- if use_mnemonic:
- uf_show = _('_Available')
- else:
- uf_show = _('Available')
- elif show == 'connecting':
- uf_show = _('Connecting')
- elif show == 'away':
- if use_mnemonic:
- uf_show = _('A_way')
- else:
- uf_show = _('Away')
- elif show == 'offline':
- if use_mnemonic:
- uf_show = _('_Offline')
- else:
- uf_show = _('Offline')
- elif show == 'invisible':
- if use_mnemonic:
- uf_show = _('_Invisible')
- else:
- uf_show = _('Invisible')
- elif show == 'not in roster':
- uf_show = _('Not in Roster')
- elif show == 'requested':
- uf_show = Q_('?contact has status:Unknown')
- else:
- uf_show = Q_('?contact has status:Has errors')
- return unicode(uf_show)
+ """
+ Return a userfriendly string for dnd/xa/chat and make all strings
+ translatable
+
+ If use_mnemonic is True, it adds _ so GUI should call with True for
+ accessibility issues
+ """
+ if show == 'dnd':
+ if use_mnemonic:
+ uf_show = _('_Busy')
+ else:
+ uf_show = _('Busy')
+ elif show == 'xa':
+ if use_mnemonic:
+ uf_show = _('_Not Available')
+ else:
+ uf_show = _('Not Available')
+ elif show == 'chat':
+ if use_mnemonic:
+ uf_show = _('_Free for Chat')
+ else:
+ uf_show = _('Free for Chat')
+ elif show == 'online':
+ if use_mnemonic:
+ uf_show = _('_Available')
+ else:
+ uf_show = _('Available')
+ elif show == 'connecting':
+ uf_show = _('Connecting')
+ elif show == 'away':
+ if use_mnemonic:
+ uf_show = _('A_way')
+ else:
+ uf_show = _('Away')
+ elif show == 'offline':
+ if use_mnemonic:
+ uf_show = _('_Offline')
+ else:
+ uf_show = _('Offline')
+ elif show == 'invisible':
+ if use_mnemonic:
+ uf_show = _('_Invisible')
+ else:
+ uf_show = _('Invisible')
+ elif show == 'not in roster':
+ uf_show = _('Not in Roster')
+ elif show == 'requested':
+ uf_show = Q_('?contact has status:Unknown')
+ else:
+ uf_show = Q_('?contact has status:Has errors')
+ return unicode(uf_show)
def get_uf_sub(sub):
- if sub == 'none':
- uf_sub = Q_('?Subscription we already have:None')
- elif sub == 'to':
- uf_sub = _('To')
- elif sub == 'from':
- uf_sub = _('From')
- elif sub == 'both':
- uf_sub = _('Both')
- else:
- uf_sub = sub
-
- return unicode(uf_sub)
+ if sub == 'none':
+ uf_sub = Q_('?Subscription we already have:None')
+ elif sub == 'to':
+ uf_sub = _('To')
+ elif sub == 'from':
+ uf_sub = _('From')
+ elif sub == 'both':
+ uf_sub = _('Both')
+ else:
+ uf_sub = sub
+
+ return unicode(uf_sub)
def get_uf_ask(ask):
- if ask is None:
- uf_ask = Q_('?Ask (for Subscription):None')
- elif ask == 'subscribe':
- uf_ask = _('Subscribe')
- else:
- uf_ask = ask
+ if ask is None:
+ uf_ask = Q_('?Ask (for Subscription):None')
+ elif ask == 'subscribe':
+ uf_ask = _('Subscribe')
+ else:
+ uf_ask = ask
- return unicode(uf_ask)
+ return unicode(uf_ask)
def get_uf_role(role, plural = False):
- ''' plural determines if you get Moderators or Moderator'''
- if role == 'none':
- role_name = Q_('?Group Chat Contact Role:None')
- elif role == 'moderator':
- if plural:
- role_name = _('Moderators')
- else:
- role_name = _('Moderator')
- elif role == 'participant':
- if plural:
- role_name = _('Participants')
- else:
- role_name = _('Participant')
- elif role == 'visitor':
- if plural:
- role_name = _('Visitors')
- else:
- role_name = _('Visitor')
- return role_name
+ ''' plural determines if you get Moderators or Moderator'''
+ if role == 'none':
+ role_name = Q_('?Group Chat Contact Role:None')
+ elif role == 'moderator':
+ if plural:
+ role_name = _('Moderators')
+ else:
+ role_name = _('Moderator')
+ elif role == 'participant':
+ if plural:
+ role_name = _('Participants')
+ else:
+ role_name = _('Participant')
+ elif role == 'visitor':
+ if plural:
+ role_name = _('Visitors')
+ else:
+ role_name = _('Visitor')
+ return role_name
def get_uf_affiliation(affiliation):
- '''Get a nice and translated affilition for muc'''
- if affiliation == 'none':
- affiliation_name = Q_('?Group Chat Contact Affiliation:None')
- elif affiliation == 'owner':
- affiliation_name = _('Owner')
- elif affiliation == 'admin':
- affiliation_name = _('Administrator')
- elif affiliation == 'member':
- affiliation_name = _('Member')
- else: # Argl ! An unknown affiliation !
- affiliation_name = affiliation.capitalize()
- return affiliation_name
+ '''Get a nice and translated affilition for muc'''
+ if affiliation == 'none':
+ affiliation_name = Q_('?Group Chat Contact Affiliation:None')
+ elif affiliation == 'owner':
+ affiliation_name = _('Owner')
+ elif affiliation == 'admin':
+ affiliation_name = _('Administrator')
+ elif affiliation == 'member':
+ affiliation_name = _('Member')
+ else: # Argl ! An unknown affiliation !
+ affiliation_name = affiliation.capitalize()
+ return affiliation_name
def get_sorted_keys(adict):
- keys = sorted(adict.keys())
- return keys
+ keys = sorted(adict.keys())
+ return keys
def to_one_line(msg):
- msg = msg.replace('\\', '\\\\')
- msg = msg.replace('\n', '\\n')
- # s1 = 'test\ntest\\ntest'
- # s11 = s1.replace('\\', '\\\\')
- # s12 = s11.replace('\n', '\\n')
- # s12
- # 'test\\ntest\\\\ntest'
- return msg
+ msg = msg.replace('\\', '\\\\')
+ msg = msg.replace('\n', '\\n')
+ # s1 = 'test\ntest\\ntest'
+ # s11 = s1.replace('\\', '\\\\')
+ # s12 = s11.replace('\n', '\\n')
+ # s12
+ # 'test\\ntest\\\\ntest'
+ return msg
def from_one_line(msg):
- # (?<!\\) is a lookbehind assertion which asks anything but '\'
- # to match the regexp that follows it
-
- # So here match '\\n' but not if you have a '\' before that
- expr = re.compile(r'(?<!\\)\\n')
- msg = expr.sub('\n', msg)
- msg = msg.replace('\\\\', '\\')
- # s12 = 'test\\ntest\\\\ntest'
- # s13 = re.sub('\n', s12)
- # s14 s13.replace('\\\\', '\\')
- # s14
- # 'test\ntest\\ntest'
- return msg
+ # (?<!\\) is a lookbehind assertion which asks anything but '\'
+ # to match the regexp that follows it
+
+ # So here match '\\n' but not if you have a '\' before that
+ expr = re.compile(r'(?<!\\)\\n')
+ msg = expr.sub('\n', msg)
+ msg = msg.replace('\\\\', '\\')
+ # s12 = 'test\\ntest\\\\ntest'
+ # s13 = re.sub('\n', s12)
+ # s14 s13.replace('\\\\', '\\')
+ # s14
+ # 'test\ntest\\ntest'
+ return msg
def get_uf_chatstate(chatstate):
- """
- Remove chatstate jargon and returns user friendly messages
- """
- if chatstate == 'active':
- return _('is paying attention to the conversation')
- elif chatstate == 'inactive':
- return _('is doing something else')
- elif chatstate == 'composing':
- return _('is composing a message...')
- elif chatstate == 'paused':
- #paused means he or she was composing but has stopped for a while
- return _('paused composing a message')
- elif chatstate == 'gone':
- return _('has closed the chat window or tab')
- return ''
+ """
+ Remove chatstate jargon and returns user friendly messages
+ """
+ if chatstate == 'active':
+ return _('is paying attention to the conversation')
+ elif chatstate == 'inactive':
+ return _('is doing something else')
+ elif chatstate == 'composing':
+ return _('is composing a message...')
+ elif chatstate == 'paused':
+ #paused means he or she was composing but has stopped for a while
+ return _('paused composing a message')
+ elif chatstate == 'gone':
+ return _('has closed the chat window or tab')
+ return ''
def is_in_path(command, return_abs_path=False):
- """
- Return True if 'command' is found in one of the directories in the user's
- path. If 'return_abs_path' is True, return the absolute path of the first
- found command instead. Return False otherwise and on errors
- """
- for directory in os.getenv('PATH').split(os.pathsep):
- try:
- if command in os.listdir(directory):
- if return_abs_path:
- return os.path.join(directory, command)
- else:
- return True
- except OSError:
- # If the user has non directories in his path
- pass
- return False
+ """
+ Return True if 'command' is found in one of the directories in the user's
+ path. If 'return_abs_path' is True, return the absolute path of the first
+ found command instead. Return False otherwise and on errors
+ """
+ for directory in os.getenv('PATH').split(os.pathsep):
+ try:
+ if command in os.listdir(directory):
+ if return_abs_path:
+ return os.path.join(directory, command)
+ else:
+ return True
+ except OSError:
+ # If the user has non directories in his path
+ pass
+ return False
def exec_command(command):
- subprocess.Popen('%s &' % command, shell=True).wait()
+ subprocess.Popen('%s &' % command, shell=True).wait()
def build_command(executable, parameter):
- # we add to the parameter (can hold path with spaces)
- # "" so we have good parsing from shell
- parameter = parameter.replace('"', '\\"') # but first escape "
- command = '%s "%s"' % (executable, parameter)
- return command
+ # we add to the parameter (can hold path with spaces)
+ # "" so we have good parsing from shell
+ parameter = parameter.replace('"', '\\"') # but first escape "
+ command = '%s "%s"' % (executable, parameter)
+ return command
def get_file_path_from_dnd_dropped_uri(uri):
- path = urllib.unquote(uri) # escape special chars
- path = path.strip('\r\n\x00') # remove \r\n and NULL
- # get the path to file
- if re.match('^file:///[a-zA-Z]:/', path): # windows
- path = path[8:] # 8 is len('file:///')
- elif path.startswith('file://'): # nautilus, rox
- path = path[7:] # 7 is len('file://')
- elif path.startswith('file:'): # xffm
- path = path[5:] # 5 is len('file:')
- return path
+ path = urllib.unquote(uri) # escape special chars
+ path = path.strip('\r\n\x00') # remove \r\n and NULL
+ # get the path to file
+ if re.match('^file:///[a-zA-Z]:/', path): # windows
+ path = path[8:] # 8 is len('file:///')
+ elif path.startswith('file://'): # nautilus, rox
+ path = path[7:] # 7 is len('file://')
+ elif path.startswith('file:'): # xffm
+ path = path[5:] # 5 is len('file:')
+ return path
def from_xs_boolean_to_python_boolean(value):
- # this is xs:boolean so 'true', 'false', '1', '0'
- # convert those to True/False (python booleans)
- if value in ('1', 'true'):
- val = True
- else: # '0', 'false' or anything else
- val = False
+ # this is xs:boolean so 'true', 'false', '1', '0'
+ # convert those to True/False (python booleans)
+ if value in ('1', 'true'):
+ val = True
+ else: # '0', 'false' or anything else
+ val = False
- return val
+ return val
def get_xmpp_show(show):
- if show in ('online', 'offline'):
- return None
- return show
+ if show in ('online', 'offline'):
+ return None
+ return show
def get_output_of_command(command):
- try:
- child_stdin, child_stdout = os.popen2(command)
- except ValueError:
- return None
+ try:
+ child_stdin, child_stdout = os.popen2(command)
+ except ValueError:
+ return None
- output = child_stdout.readlines()
- child_stdout.close()
- child_stdin.close()
+ output = child_stdout.readlines()
+ child_stdout.close()
+ child_stdin.close()
- return output
+ return output
def decode_string(string):
- """
- Try to decode (to make it Unicode instance) given string
- """
- if isinstance(string, unicode):
- return string
- # by the time we go to iso15 it better be the one else we show bad characters
- encodings = (locale.getpreferredencoding(), 'utf-8', 'iso-8859-15')
- for encoding in encodings:
- try:
- string = string.decode(encoding)
- except UnicodeError:
- continue
- break
-
- return string
+ """
+ Try to decode (to make it Unicode instance) given string
+ """
+ if isinstance(string, unicode):
+ return string
+ # by the time we go to iso15 it better be the one else we show bad characters
+ encodings = (locale.getpreferredencoding(), 'utf-8', 'iso-8859-15')
+ for encoding in encodings:
+ try:
+ string = string.decode(encoding)
+ except UnicodeError:
+ continue
+ break
+
+ return string
def ensure_utf8_string(string):
- """
- Make sure string is in UTF-8
- """
- try:
- string = decode_string(string).encode('utf-8')
- except Exception:
- pass
- return string
+ """
+ Make sure string is in UTF-8
+ """
+ try:
+ string = decode_string(string).encode('utf-8')
+ except Exception:
+ pass
+ return string
def get_windows_reg_env(varname, default=''):
- """
- Ask for paths commonly used but not exposed as ENVs in english Windows 2003
- those are:
- 'AppData' = %USERPROFILE%\Application Data (also an ENV)
- 'Desktop' = %USERPROFILE%\Desktop
- 'Favorites' = %USERPROFILE%\Favorites
- 'NetHood' = %USERPROFILE%\NetHood
- 'Personal' = D:\My Documents (PATH TO MY DOCUMENTS)
- 'PrintHood' = %USERPROFILE%\PrintHood
- 'Programs' = %USERPROFILE%\Start Menu\Programs
- 'Recent' = %USERPROFILE%\Recent
- 'SendTo' = %USERPROFILE%\SendTo
- 'Start Menu' = %USERPROFILE%\Start Menu
- 'Startup' = %USERPROFILE%\Start Menu\Programs\Startup
- 'Templates' = %USERPROFILE%\Templates
- 'My Pictures' = D:\My Documents\My Pictures
- 'Local Settings' = %USERPROFILE%\Local Settings
- 'Local AppData' = %USERPROFILE%\Local Settings\Application Data
- 'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files
- 'Cookies' = %USERPROFILE%\Cookies
- 'History' = %USERPROFILE%\Local Settings\History
- """
- if os.name != 'nt':
- return ''
-
- val = default
- try:
- rkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,
+ """
+ Ask for paths commonly used but not exposed as ENVs in english Windows 2003
+ those are:
+ 'AppData' = %USERPROFILE%\Application Data (also an ENV)
+ 'Desktop' = %USERPROFILE%\Desktop
+ 'Favorites' = %USERPROFILE%\Favorites
+ 'NetHood' = %USERPROFILE%\NetHood
+ 'Personal' = D:\My Documents (PATH TO MY DOCUMENTS)
+ 'PrintHood' = %USERPROFILE%\PrintHood
+ 'Programs' = %USERPROFILE%\Start Menu\Programs
+ 'Recent' = %USERPROFILE%\Recent
+ 'SendTo' = %USERPROFILE%\SendTo
+ 'Start Menu' = %USERPROFILE%\Start Menu
+ 'Startup' = %USERPROFILE%\Start Menu\Programs\Startup
+ 'Templates' = %USERPROFILE%\Templates
+ 'My Pictures' = D:\My Documents\My Pictures
+ 'Local Settings' = %USERPROFILE%\Local Settings
+ 'Local AppData' = %USERPROFILE%\Local Settings\Application Data
+ 'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files
+ 'Cookies' = %USERPROFILE%\Cookies
+ 'History' = %USERPROFILE%\Local Settings\History
+ """
+ if os.name != 'nt':
+ return ''
+
+ val = default
+ try:
+ rkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,
r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders')
- try:
- val = str(win32api.RegQueryValueEx(rkey, varname)[0])
- val = win32api.ExpandEnvironmentStrings(val) # expand using environ
- except Exception:
- pass
- finally:
- win32api.RegCloseKey(rkey)
- return val
+ try:
+ val = str(win32api.RegQueryValueEx(rkey, varname)[0])
+ val = win32api.ExpandEnvironmentStrings(val) # expand using environ
+ except Exception:
+ pass
+ finally:
+ win32api.RegCloseKey(rkey)
+ return val
def get_my_pictures_path():
- """
- Windows-only atm
- """
- return get_windows_reg_env('My Pictures')
+ """
+ Windows-only atm
+ """
+ return get_windows_reg_env('My Pictures')
def get_desktop_path():
- if os.name == 'nt':
- path = get_windows_reg_env('Desktop')
- else:
- path = os.path.join(os.path.expanduser('~'), 'Desktop')
- return path
+ if os.name == 'nt':
+ path = get_windows_reg_env('Desktop')
+ else:
+ path = os.path.join(os.path.expanduser('~'), 'Desktop')
+ return path
def get_documents_path():
- if os.name == 'nt':
- path = get_windows_reg_env('Personal')
- else:
- path = os.path.expanduser('~')
- return path
+ if os.name == 'nt':
+ path = get_windows_reg_env('Personal')
+ else:
+ path = os.path.expanduser('~')
+ return path
def sanitize_filename(filename):
- """
- Make sure the filename we will write does contain only acceptable and latin
- characters, and is not too long (in that case hash it)
- """
- # 48 is the limit
- if len(filename) > 48:
- hash = hashlib.md5(filename)
- filename = base64.b64encode(hash.digest())
-
- filename = punycode_encode(filename) # make it latin chars only
- filename = filename.replace('/', '_')
- if os.name == 'nt':
- filename = filename.replace('?', '_').replace(':', '_')\
- .replace('\\', '_').replace('"', "'").replace('|', '_')\
- .replace('*', '_').replace('<', '_').replace('>', '_')
-
- return filename
+ """
+ Make sure the filename we will write does contain only acceptable and latin
+ characters, and is not too long (in that case hash it)
+ """
+ # 48 is the limit
+ if len(filename) > 48:
+ hash = hashlib.md5(filename)
+ filename = base64.b64encode(hash.digest())
+
+ filename = punycode_encode(filename) # make it latin chars only
+ filename = filename.replace('/', '_')
+ if os.name == 'nt':
+ filename = filename.replace('?', '_').replace(':', '_')\
+ .replace('\\', '_').replace('"', "'").replace('|', '_')\
+ .replace('*', '_').replace('<', '_').replace('>', '_')
+
+ return filename
def reduce_chars_newlines(text, max_chars = 0, max_lines = 0):
- """
- Cut the chars after 'max_chars' on each line and show only the first
- 'max_lines'
-
- If any of the params is not present (None or 0) the action on it is not
- performed
- """
- def _cut_if_long(string):
- if len(string) > max_chars:
- string = string[:max_chars - 3] + '...'
- return string
-
- if isinstance(text, str):
- text = text.decode('utf-8')
-
- if max_lines == 0:
- lines = text.split('\n')
- else:
- lines = text.split('\n', max_lines)[:max_lines]
- if max_chars > 0:
- if lines:
- lines = [_cut_if_long(e) for e in lines]
- if lines:
- reduced_text = '\n'.join(lines)
- if reduced_text != text:
- reduced_text += '...'
- else:
- reduced_text = ''
- return reduced_text
+ """
+ Cut the chars after 'max_chars' on each line and show only the first
+ 'max_lines'
+
+ If any of the params is not present (None or 0) the action on it is not
+ performed
+ """
+ def _cut_if_long(string):
+ if len(string) > max_chars:
+ string = string[:max_chars - 3] + '...'
+ return string
+
+ if isinstance(text, str):
+ text = text.decode('utf-8')
+
+ if max_lines == 0:
+ lines = text.split('\n')
+ else:
+ lines = text.split('\n', max_lines)[:max_lines]
+ if max_chars > 0:
+ if lines:
+ lines = [_cut_if_long(e) for e in lines]
+ if lines:
+ reduced_text = '\n'.join(lines)
+ if reduced_text != text:
+ reduced_text += '...'
+ else:
+ reduced_text = ''
+ return reduced_text
def get_account_status(account):
- status = reduce_chars_newlines(account['status_line'], 100, 1)
- return status
+ status = reduce_chars_newlines(account['status_line'], 100, 1)
+ return status
def get_avatar_path(prefix):
- """
- Return the filename of the avatar, distinguishes between user- and contact-
- provided one. Return None if no avatar was found at all. prefix is the path
- to the requested avatar just before the ".png" or ".jpeg"
- """
- # First, scan for a local, user-set avatar
- for type_ in ('jpeg', 'png'):
- file_ = prefix + '_local.' + type_
- if os.path.exists(file_):
- return file_
- # If none available, scan for a contact-provided avatar
- for type_ in ('jpeg', 'png'):
- file_ = prefix + '.' + type_
- if os.path.exists(file_):
- return file_
- return None
+ """
+ Return the filename of the avatar, distinguishes between user- and contact-
+ provided one. Return None if no avatar was found at all. prefix is the path
+ to the requested avatar just before the ".png" or ".jpeg"
+ """
+ # First, scan for a local, user-set avatar
+ for type_ in ('jpeg', 'png'):
+ file_ = prefix + '_local.' + type_
+ if os.path.exists(file_):
+ return file_
+ # If none available, scan for a contact-provided avatar
+ for type_ in ('jpeg', 'png'):
+ file_ = prefix + '.' + type_
+ if os.path.exists(file_):
+ return file_
+ return None
def datetime_tuple(timestamp):
- """
- Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S
-
- Because of various datetime formats are used the following exceptions
- are handled:
- - Optional milliseconds appened to the string are removed
- - Optional Z (that means UTC) appened to the string are removed
- - XEP-082 datetime strings have all '-' cahrs removed to meet
- the above format.
- """
- timestamp = timestamp.split('.')[0]
- timestamp = timestamp.replace('-', '')
- timestamp = timestamp.replace('z', '')
- timestamp = timestamp.replace('Z', '')
- from time import strptime
- return strptime(timestamp, '%Y%m%dT%H:%M:%S')
+ """
+ Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S
+
+ Because of various datetime formats are used the following exceptions
+ are handled:
+ - Optional milliseconds appened to the string are removed
+ - Optional Z (that means UTC) appened to the string are removed
+ - XEP-082 datetime strings have all '-' cahrs removed to meet
+ the above format.
+ """
+ timestamp = timestamp.split('.')[0]
+ timestamp = timestamp.replace('-', '')
+ timestamp = timestamp.replace('z', '')
+ timestamp = timestamp.replace('Z', '')
+ from time import strptime
+ return strptime(timestamp, '%Y%m%dT%H:%M:%S')
# import gajim only when needed (after decode_string is defined) see #4764
import gajim
def convert_bytes(string):
- suffix = ''
- # IEC standard says KiB = 1024 bytes KB = 1000 bytes
- # but do we use the standard?
- use_kib_mib = gajim.config.get('use_kib_mib')
- align = 1024.
- bytes = float(string)
- if bytes >= align:
- bytes = round(bytes/align, 1)
- if bytes >= align:
- bytes = round(bytes/align, 1)
- if bytes >= align:
- bytes = round(bytes/align, 1)
- if use_kib_mib:
- #GiB means gibibyte
- suffix = _('%s GiB')
- else:
- #GB means gigabyte
- suffix = _('%s GB')
- else:
- if use_kib_mib:
- #MiB means mibibyte
- suffix = _('%s MiB')
- else:
- #MB means megabyte
- suffix = _('%s MB')
- else:
- if use_kib_mib:
- #KiB means kibibyte
- suffix = _('%s KiB')
- else:
- #KB means kilo bytes
- suffix = _('%s KB')
- else:
- #B means bytes
- suffix = _('%s B')
- return suffix % unicode(bytes)
+ suffix = ''
+ # IEC standard says KiB = 1024 bytes KB = 1000 bytes
+ # but do we use the standard?
+ use_kib_mib = gajim.config.get('use_kib_mib')
+ align = 1024.
+ bytes = float(string)
+ if bytes >= align:
+ bytes = round(bytes/align, 1)
+ if bytes >= align:
+ bytes = round(bytes/align, 1)
+ if bytes >= align:
+ bytes = round(bytes/align, 1)
+ if use_kib_mib:
+ #GiB means gibibyte
+ suffix = _('%s GiB')
+ else:
+ #GB means gigabyte
+ suffix = _('%s GB')
+ else:
+ if use_kib_mib:
+ #MiB means mibibyte
+ suffix = _('%s MiB')
+ else:
+ #MB means megabyte
+ suffix = _('%s MB')
+ else:
+ if use_kib_mib:
+ #KiB means kibibyte
+ suffix = _('%s KiB')
+ else:
+ #KB means kilo bytes
+ suffix = _('%s KB')
+ else:
+ #B means bytes
+ suffix = _('%s B')
+ return suffix % unicode(bytes)
def get_contact_dict_for_account(account):
- """
- Create a dict of jid, nick -> contact with all contacts of account.
-
- Can be used for completion lists
- """
- contacts_dict = {}
- for jid in gajim.contacts.get_jid_list(account):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- contacts_dict[jid] = contact
- name = contact.name
- if name in contacts_dict:
- contact1 = contacts_dict[name]
- del contacts_dict[name]
- contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1
- contacts_dict['%s (%s)' % (name, jid)] = contact
- else:
- if contact.name == gajim.get_nick_from_jid(jid):
- del contacts_dict[jid]
- contacts_dict[name] = contact
- return contacts_dict
+ """
+ Create a dict of jid, nick -> contact with all contacts of account.
+
+ Can be used for completion lists
+ """
+ contacts_dict = {}
+ for jid in gajim.contacts.get_jid_list(account):
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ contacts_dict[jid] = contact
+ name = contact.name
+ if name in contacts_dict:
+ contact1 = contacts_dict[name]
+ del contacts_dict[name]
+ contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1
+ contacts_dict['%s (%s)' % (name, jid)] = contact
+ else:
+ if contact.name == gajim.get_nick_from_jid(jid):
+ del contacts_dict[jid]
+ contacts_dict[name] = contact
+ return contacts_dict
def launch_browser_mailer(kind, uri):
- #kind = 'url' or 'mail'
- if os.name == 'nt':
- try:
- os.startfile(uri) # if pywin32 is installed we open
- except Exception:
- pass
-
- else:
- if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'):
- uri = 'mailto:' + uri
-
- if kind == 'url' and uri.startswith('www.'):
- uri = 'http://' + uri
-
- if gajim.config.get('openwith') == 'gnome-open':
- command = 'gnome-open'
- elif gajim.config.get('openwith') == 'kfmclient exec':
- command = 'kfmclient exec'
- elif gajim.config.get('openwith') == 'exo-open':
- command = 'exo-open'
- elif gajim.config.get('openwith') == 'custom':
- if kind == 'url':
- command = gajim.config.get('custombrowser')
- elif kind in ('mail', 'sth_at_sth'):
- command = gajim.config.get('custommailapp')
- if command == '': # if no app is configured
- return
-
- command = build_command(command, uri)
- try:
- exec_command(command)
- except Exception:
- pass
+ #kind = 'url' or 'mail'
+ if os.name == 'nt':
+ try:
+ os.startfile(uri) # if pywin32 is installed we open
+ except Exception:
+ pass
+
+ else:
+ if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'):
+ uri = 'mailto:' + uri
+
+ if kind == 'url' and uri.startswith('www.'):
+ uri = 'http://' + uri
+
+ if gajim.config.get('openwith') == 'gnome-open':
+ command = 'gnome-open'
+ elif gajim.config.get('openwith') == 'kfmclient exec':
+ command = 'kfmclient exec'
+ elif gajim.config.get('openwith') == 'exo-open':
+ command = 'exo-open'
+ elif gajim.config.get('openwith') == 'custom':
+ if kind == 'url':
+ command = gajim.config.get('custombrowser')
+ elif kind in ('mail', 'sth_at_sth'):
+ command = gajim.config.get('custommailapp')
+ if command == '': # if no app is configured
+ return
+
+ command = build_command(command, uri)
+ try:
+ exec_command(command)
+ except Exception:
+ pass
def launch_file_manager(path_to_open):
- if os.name == 'nt':
- try:
- os.startfile(path_to_open) # if pywin32 is installed we open
- except Exception:
- pass
- else:
- if gajim.config.get('openwith') == 'gnome-open':
- command = 'gnome-open'
- elif gajim.config.get('openwith') == 'kfmclient exec':
- command = 'kfmclient exec'
- elif gajim.config.get('openwith') == 'exo-open':
- command = 'exo-open'
- elif gajim.config.get('openwith') == 'custom':
- command = gajim.config.get('custom_file_manager')
- if command == '': # if no app is configured
- return
- command = build_command(command, path_to_open)
- try:
- exec_command(command)
- except Exception:
- pass
+ if os.name == 'nt':
+ try:
+ os.startfile(path_to_open) # if pywin32 is installed we open
+ except Exception:
+ pass
+ else:
+ if gajim.config.get('openwith') == 'gnome-open':
+ command = 'gnome-open'
+ elif gajim.config.get('openwith') == 'kfmclient exec':
+ command = 'kfmclient exec'
+ elif gajim.config.get('openwith') == 'exo-open':
+ command = 'exo-open'
+ elif gajim.config.get('openwith') == 'custom':
+ command = gajim.config.get('custom_file_manager')
+ if command == '': # if no app is configured
+ return
+ command = build_command(command, path_to_open)
+ try:
+ exec_command(command)
+ except Exception:
+ pass
def play_sound(event):
- if not gajim.config.get('sounds_on'):
- return
- path_to_soundfile = gajim.config.get_per('soundevents', event, 'path')
- play_sound_file(path_to_soundfile)
+ if not gajim.config.get('sounds_on'):
+ return
+ path_to_soundfile = gajim.config.get_per('soundevents', event, 'path')
+ play_sound_file(path_to_soundfile)
def check_soundfile_path(file, dirs=(gajim.gajimpaths.data_root,
gajim.DATA_DIR)):
- """
- Check if the sound file exists
-
- :param file: the file to check, absolute or relative to 'dirs' path
- :param dirs: list of knows paths to fallback if the file doesn't exists
- (eg: ~/.gajim/sounds/, DATADIR/sounds...).
- :return the path to file or None if it doesn't exists.
- """
- if not file:
- return None
- elif os.path.exists(file):
- return file
-
- for d in dirs:
- d = os.path.join(d, 'sounds', file)
- if os.path.exists(d):
- return d
- return None
+ """
+ Check if the sound file exists
+
+ :param file: the file to check, absolute or relative to 'dirs' path
+ :param dirs: list of knows paths to fallback if the file doesn't exists
+ (eg: ~/.gajim/sounds/, DATADIR/sounds...).
+ :return the path to file or None if it doesn't exists.
+ """
+ if not file:
+ return None
+ elif os.path.exists(file):
+ return file
+
+ for d in dirs:
+ d = os.path.join(d, 'sounds', file)
+ if os.path.exists(d):
+ return d
+ return None
def strip_soundfile_path(file, dirs=(gajim.gajimpaths.data_root,
gajim.DATA_DIR), abs=True):
- """
- Remove knowns paths from a sound file
-
- Filechooser returns absolute path. If path is a known fallback path, we remove it.
- So config have no hardcoded path to DATA_DIR and text in textfield is shorther.
- param: file: the filename to strip.
- param: dirs: list of knowns paths from which the filename should be stripped.
- param: abs: force absolute path on dirs
- """
- if not file:
- return None
-
- name = os.path.basename(file)
- for d in dirs:
- d = os.path.join(d, 'sounds', name)
- if abs:
- d = os.path.abspath(d)
- if file == d:
- return name
- return file
+ """
+ Remove knowns paths from a sound file
+
+ Filechooser returns absolute path. If path is a known fallback path, we remove it.
+ So config have no hardcoded path to DATA_DIR and text in textfield is shorther.
+ param: file: the filename to strip.
+ param: dirs: list of knowns paths from which the filename should be stripped.
+ param: abs: force absolute path on dirs
+ """
+ if not file:
+ return None
+
+ name = os.path.basename(file)
+ for d in dirs:
+ d = os.path.join(d, 'sounds', name)
+ if abs:
+ d = os.path.abspath(d)
+ if file == d:
+ return name
+ return file
def play_sound_file(path_to_soundfile):
- if path_to_soundfile == 'beep':
- exec_command('beep')
- return
- path_to_soundfile = check_soundfile_path(path_to_soundfile)
- if path_to_soundfile is None:
- return
- elif os.name == 'nt':
- try:
- winsound.PlaySound(path_to_soundfile,
- winsound.SND_FILENAME|winsound.SND_ASYNC)
- except Exception:
- pass
- elif os.name == 'posix':
- if gajim.config.get('soundplayer') == '':
- return
- player = gajim.config.get('soundplayer')
- command = build_command(player, path_to_soundfile)
- exec_command(command)
+ if path_to_soundfile == 'beep':
+ exec_command('beep')
+ return
+ path_to_soundfile = check_soundfile_path(path_to_soundfile)
+ if path_to_soundfile is None:
+ return
+ elif os.name == 'nt':
+ try:
+ winsound.PlaySound(path_to_soundfile,
+ winsound.SND_FILENAME|winsound.SND_ASYNC)
+ except Exception:
+ pass
+ elif os.name == 'posix':
+ if gajim.config.get('soundplayer') == '':
+ return
+ player = gajim.config.get('soundplayer')
+ command = build_command(player, path_to_soundfile)
+ exec_command(command)
def get_global_show():
- maxi = 0
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- connected = gajim.connections[account].connected
- if connected > maxi:
- maxi = connected
- return gajim.SHOW_LIST[maxi]
+ maxi = 0
+ for account in gajim.connections:
+ if not gajim.config.get_per('accounts', account,
+ 'sync_with_global_status'):
+ continue
+ connected = gajim.connections[account].connected
+ if connected > maxi:
+ maxi = connected
+ return gajim.SHOW_LIST[maxi]
def get_global_status():
- maxi = 0
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- connected = gajim.connections[account].connected
- if connected > maxi:
- maxi = connected
- status = gajim.connections[account].status
- return status
+ maxi = 0
+ for account in gajim.connections:
+ if not gajim.config.get_per('accounts', account,
+ 'sync_with_global_status'):
+ continue
+ connected = gajim.connections[account].connected
+ if connected > maxi:
+ maxi = connected
+ status = gajim.connections[account].status
+ return status
def statuses_unified():
- """
- Test if all statuses are the same
- """
- reference = None
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- if reference is None:
- reference = gajim.connections[account].connected
- elif reference != gajim.connections[account].connected:
- return False
- return True
+ """
+ Test if all statuses are the same
+ """
+ reference = None
+ for account in gajim.connections:
+ if not gajim.config.get_per('accounts', account,
+ 'sync_with_global_status'):
+ continue
+ if reference is None:
+ reference = gajim.connections[account].connected
+ elif reference != gajim.connections[account].connected:
+ return False
+ return True
def get_icon_name_to_show(contact, account = None):
- """
- Get the icon name to show in online, away, requested, etc
- """
- if account and gajim.events.get_nb_roster_events(account, contact.jid):
- return 'event'
- if account and gajim.events.get_nb_roster_events(account,
- contact.get_full_jid()):
- return 'event'
- if account and account in gajim.interface.minimized_controls and \
- contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\
- minimized_controls[account][contact.jid].get_nb_unread_pm() > 0:
- return 'event'
- if account and contact.jid in gajim.gc_connected[account]:
- if gajim.gc_connected[account][contact.jid]:
- return 'muc_active'
- else:
- return 'muc_inactive'
- if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
- return contact.show
- if contact.sub in ('both', 'to'):
- return contact.show
- if contact.ask == 'subscribe':
- return 'requested'
- transport = gajim.get_transport_name_from_jid(contact.jid)
- if transport:
- return contact.show
- if contact.show in gajim.SHOW_LIST:
- return contact.show
- return 'not in roster'
+ """
+ Get the icon name to show in online, away, requested, etc
+ """
+ if account and gajim.events.get_nb_roster_events(account, contact.jid):
+ return 'event'
+ if account and gajim.events.get_nb_roster_events(account,
+ contact.get_full_jid()):
+ return 'event'
+ if account and account in gajim.interface.minimized_controls and \
+ contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\
+ minimized_controls[account][contact.jid].get_nb_unread_pm() > 0:
+ return 'event'
+ if account and contact.jid in gajim.gc_connected[account]:
+ if gajim.gc_connected[account][contact.jid]:
+ return 'muc_active'
+ else:
+ return 'muc_inactive'
+ if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
+ return contact.show
+ if contact.sub in ('both', 'to'):
+ return contact.show
+ if contact.ask == 'subscribe':
+ return 'requested'
+ transport = gajim.get_transport_name_from_jid(contact.jid)
+ if transport:
+ return contact.show
+ if contact.show in gajim.SHOW_LIST:
+ return contact.show
+ return 'not in roster'
def get_full_jid_from_iq(iq_obj):
- """
- Return the full jid (with resource) from an iq as unicode
- """
- return parse_jid(str(iq_obj.getFrom()))
+ """
+ Return the full jid (with resource) from an iq as unicode
+ """
+ return parse_jid(str(iq_obj.getFrom()))
def get_jid_from_iq(iq_obj):
- """
- Return the jid (without resource) from an iq as unicode
- """
- jid = get_full_jid_from_iq(iq_obj)
- return gajim.get_jid_without_resource(jid)
+ """
+ Return the jid (without resource) from an iq as unicode
+ """
+ jid = get_full_jid_from_iq(iq_obj)
+ return gajim.get_jid_without_resource(jid)
def get_auth_sha(sid, initiator, target):
- """
- Return sha of sid + initiator + target used for proxy auth
- """
- return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest()
+ """
+ Return sha of sid + initiator + target used for proxy auth
+ """
+ return hashlib.sha1("%s%s%s" % (sid, initiator, target)).hexdigest()
def remove_invalid_xml_chars(string):
- if string:
- string = re.sub(gajim.interface.invalid_XML_chars_re, '', string)
- return string
+ if string:
+ string = re.sub(gajim.interface.invalid_XML_chars_re, '', string)
+ return string
distro_info = {
- 'Arch Linux': '/etc/arch-release',
- 'Aurox Linux': '/etc/aurox-release',
- 'Conectiva Linux': '/etc/conectiva-release',
- 'CRUX': '/usr/bin/crux',
- 'Debian GNU/Linux': '/etc/debian_release',
- 'Debian GNU/Linux': '/etc/debian_version',
- 'Fedora Linux': '/etc/fedora-release',
- 'Gentoo Linux': '/etc/gentoo-release',
- 'Linux from Scratch': '/etc/lfs-release',
- 'Mandrake Linux': '/etc/mandrake-release',
- 'Slackware Linux': '/etc/slackware-release',
- 'Slackware Linux': '/etc/slackware-version',
- 'Solaris/Sparc': '/etc/release',
- 'Source Mage': '/etc/sourcemage_version',
- 'SUSE Linux': '/etc/SuSE-release',
- 'Sun JDS': '/etc/sun-release',
- 'PLD Linux': '/etc/pld-release',
- 'Yellow Dog Linux': '/etc/yellowdog-release',
- # many distros use the /etc/redhat-release for compatibility
- # so Redhat is the last
- 'Redhat Linux': '/etc/redhat-release'
+ 'Arch Linux': '/etc/arch-release',
+ 'Aurox Linux': '/etc/aurox-release',
+ 'Conectiva Linux': '/etc/conectiva-release',
+ 'CRUX': '/usr/bin/crux',
+ 'Debian GNU/Linux': '/etc/debian_release',
+ 'Debian GNU/Linux': '/etc/debian_version',
+ 'Fedora Linux': '/etc/fedora-release',
+ 'Gentoo Linux': '/etc/gentoo-release',
+ 'Linux from Scratch': '/etc/lfs-release',
+ 'Mandrake Linux': '/etc/mandrake-release',
+ 'Slackware Linux': '/etc/slackware-release',
+ 'Slackware Linux': '/etc/slackware-version',
+ 'Solaris/Sparc': '/etc/release',
+ 'Source Mage': '/etc/sourcemage_version',
+ 'SUSE Linux': '/etc/SuSE-release',
+ 'Sun JDS': '/etc/sun-release',
+ 'PLD Linux': '/etc/pld-release',
+ 'Yellow Dog Linux': '/etc/yellowdog-release',
+ # many distros use the /etc/redhat-release for compatibility
+ # so Redhat is the last
+ 'Redhat Linux': '/etc/redhat-release'
}
def get_random_string_16():
- """
- Create random string of length 16
- """
- rng = range(65, 90)
- rng.extend(range(48, 57))
- char_sequence = [chr(e) for e in rng]
- from random import sample
- return ''.join(sample(char_sequence, 16))
+ """
+ Create random string of length 16
+ """
+ rng = range(65, 90)
+ rng.extend(range(48, 57))
+ char_sequence = [chr(e) for e in rng]
+ from random import sample
+ return ''.join(sample(char_sequence, 16))
def get_os_info():
- if gajim.os_info:
- return gajim.os_info
- if os.name == 'nt':
- # platform.release() seems to return the name of the windows
- ver = sys.getwindowsversion()
- ver_format = ver[3], ver[0], ver[1]
- win_version = {
- (1, 4, 0): '95',
- (1, 4, 10): '98',
- (1, 4, 90): 'ME',
- (2, 4, 0): 'NT',
- (2, 5, 0): '2000',
- (2, 5, 1): 'XP',
- (2, 5, 2): '2003',
- (2, 6, 0): 'Vista',
- (2, 6, 1): '7',
- }
- if ver_format in win_version:
- os_info = 'Windows' + ' ' + win_version[ver_format]
- else:
- os_info = 'Windows'
- gajim.os_info = os_info
- return os_info
- elif os.name == 'posix':
- executable = 'lsb_release'
- params = ' --description --codename --release --short'
- full_path_to_executable = is_in_path(executable, return_abs_path = True)
- if full_path_to_executable:
- command = executable + params
- p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, close_fds=True)
- p.wait()
- output = temp_failure_retry(p.stdout.readline).strip()
- # some distros put n/a in places, so remove those
- output = output.replace('n/a', '').replace('N/A', '')
- gajim.os_info = output
- return output
-
- # lsb_release executable not available, so parse files
- for distro_name in distro_info:
- path_to_file = distro_info[distro_name]
- if os.path.exists(path_to_file):
- if os.access(path_to_file, os.X_OK):
- # the file is executable (f.e. CRUX)
- # yes, then run it and get the first line of output.
- text = get_output_of_command(path_to_file)[0]
- else:
- fd = open(path_to_file)
- text = fd.readline().strip() # get only first line
- fd.close()
- if path_to_file.endswith('version'):
- # sourcemage_version and slackware-version files
- # have all the info we need (name and version of distro)
- if not os.path.basename(path_to_file).startswith(
- 'sourcemage') or not\
- os.path.basename(path_to_file).startswith('slackware'):
- text = distro_name + ' ' + text
- elif path_to_file.endswith('aurox-release') or \
- path_to_file.endswith('arch-release'):
- # file doesn't have version
- text = distro_name
- elif path_to_file.endswith('lfs-release'): # file just has version
- text = distro_name + ' ' + text
- os_info = text.replace('\n', '')
- gajim.os_info = os_info
- return os_info
-
- # our last chance, ask uname and strip it
- uname_output = get_output_of_command('uname -sr')
- if uname_output is not None:
- os_info = uname_output[0] # only first line
- gajim.os_info = os_info
- return os_info
- os_info = 'N/A'
- gajim.os_info = os_info
- return os_info
+ if gajim.os_info:
+ return gajim.os_info
+ if os.name == 'nt':
+ # platform.release() seems to return the name of the windows
+ ver = sys.getwindowsversion()
+ ver_format = ver[3], ver[0], ver[1]
+ win_version = {
+ (1, 4, 0): '95',
+ (1, 4, 10): '98',
+ (1, 4, 90): 'ME',
+ (2, 4, 0): 'NT',
+ (2, 5, 0): '2000',
+ (2, 5, 1): 'XP',
+ (2, 5, 2): '2003',
+ (2, 6, 0): 'Vista',
+ (2, 6, 1): '7',
+ }
+ if ver_format in win_version:
+ os_info = 'Windows' + ' ' + win_version[ver_format]
+ else:
+ os_info = 'Windows'
+ gajim.os_info = os_info
+ return os_info
+ elif os.name == 'posix':
+ executable = 'lsb_release'
+ params = ' --description --codename --release --short'
+ full_path_to_executable = is_in_path(executable, return_abs_path = True)
+ if full_path_to_executable:
+ command = executable + params
+ p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, close_fds=True)
+ p.wait()
+ output = temp_failure_retry(p.stdout.readline).strip()
+ # some distros put n/a in places, so remove those
+ output = output.replace('n/a', '').replace('N/A', '')
+ gajim.os_info = output
+ return output
+
+ # lsb_release executable not available, so parse files
+ for distro_name in distro_info:
+ path_to_file = distro_info[distro_name]
+ if os.path.exists(path_to_file):
+ if os.access(path_to_file, os.X_OK):
+ # the file is executable (f.e. CRUX)
+ # yes, then run it and get the first line of output.
+ text = get_output_of_command(path_to_file)[0]
+ else:
+ fd = open(path_to_file)
+ text = fd.readline().strip() # get only first line
+ fd.close()
+ if path_to_file.endswith('version'):
+ # sourcemage_version and slackware-version files
+ # have all the info we need (name and version of distro)
+ if not os.path.basename(path_to_file).startswith(
+ 'sourcemage') or not\
+ os.path.basename(path_to_file).startswith('slackware'):
+ text = distro_name + ' ' + text
+ elif path_to_file.endswith('aurox-release') or \
+ path_to_file.endswith('arch-release'):
+ # file doesn't have version
+ text = distro_name
+ elif path_to_file.endswith('lfs-release'): # file just has version
+ text = distro_name + ' ' + text
+ os_info = text.replace('\n', '')
+ gajim.os_info = os_info
+ return os_info
+
+ # our last chance, ask uname and strip it
+ uname_output = get_output_of_command('uname -sr')
+ if uname_output is not None:
+ os_info = uname_output[0] # only first line
+ gajim.os_info = os_info
+ return os_info
+ os_info = 'N/A'
+ gajim.os_info = os_info
+ return os_info
def allow_showing_notification(account, type_ = 'notify_on_new_message',
- advanced_notif_num = None, is_first_message = True):
- """
- Is it allowed to show nofication?
-
- Check OUR status and if we allow notifications for that status type is the
- option that need to be True e.g.: notify_on_signing is_first_message: set it
- to false when it's not the first message
- """
- if advanced_notif_num is not None:
- popup = gajim.config.get_per('notifications', str(advanced_notif_num),
- 'popup')
- if popup == 'yes':
- return True
- if popup == 'no':
- return False
- if type_ and (not gajim.config.get(type_) or not is_first_message):
- return False
- if gajim.config.get('autopopupaway'): # always show notification
- return True
- if gajim.connections[account].connected in (2, 3): # we're online or chat
- return True
- return False
+ advanced_notif_num = None, is_first_message = True):
+ """
+ Is it allowed to show nofication?
+
+ Check OUR status and if we allow notifications for that status type is the
+ option that need to be True e.g.: notify_on_signing is_first_message: set it
+ to false when it's not the first message
+ """
+ if advanced_notif_num is not None:
+ popup = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'popup')
+ if popup == 'yes':
+ return True
+ if popup == 'no':
+ return False
+ if type_ and (not gajim.config.get(type_) or not is_first_message):
+ return False
+ if gajim.config.get('autopopupaway'): # always show notification
+ return True
+ if gajim.connections[account].connected in (2, 3): # we're online or chat
+ return True
+ return False
def allow_popup_window(account, advanced_notif_num = None):
- """
- Is it allowed to popup windows?
- """
- if advanced_notif_num is not None:
- popup = gajim.config.get_per('notifications', str(advanced_notif_num),
- 'auto_open')
- if popup == 'yes':
- return True
- if popup == 'no':
- return False
- autopopup = gajim.config.get('autopopup')
- autopopupaway = gajim.config.get('autopopupaway')
- if autopopup and (autopopupaway or \
- gajim.connections[account].connected in (2, 3)): # we're online or chat
- return True
- return False
+ """
+ Is it allowed to popup windows?
+ """
+ if advanced_notif_num is not None:
+ popup = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'auto_open')
+ if popup == 'yes':
+ return True
+ if popup == 'no':
+ return False
+ autopopup = gajim.config.get('autopopup')
+ autopopupaway = gajim.config.get('autopopupaway')
+ if autopopup and (autopopupaway or \
+ gajim.connections[account].connected in (2, 3)): # we're online or chat
+ return True
+ return False
def allow_sound_notification(account, sound_event, advanced_notif_num=None):
- if advanced_notif_num is not None:
- sound = gajim.config.get_per('notifications', str(advanced_notif_num),
- 'sound')
- if sound == 'yes':
- return True
- if sound == 'no':
- return False
- if gajim.config.get('sounddnd') or gajim.connections[account].connected != \
- gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents',
- sound_event, 'enabled'):
- return True
- return False
+ if advanced_notif_num is not None:
+ sound = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'sound')
+ if sound == 'yes':
+ return True
+ if sound == 'no':
+ return False
+ if gajim.config.get('sounddnd') or gajim.connections[account].connected != \
+ gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents',
+ sound_event, 'enabled'):
+ return True
+ return False
def get_chat_control(account, contact):
- full_jid_with_resource = contact.jid
- if contact.resource:
- full_jid_with_resource += '/' + contact.resource
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- account, contact.jid)
-
- # Look for a chat control that has the given resource, or default to
- # one without resource
- ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
- account)
-
- if ctrl:
- return ctrl
- elif highest_contact and highest_contact.resource and \
- contact.resource != highest_contact.resource:
- return None
- else:
- # unknown contact or offline message
- return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
+ full_jid_with_resource = contact.jid
+ if contact.resource:
+ full_jid_with_resource += '/' + contact.resource
+ highest_contact = gajim.contacts.get_contact_with_highest_priority(
+ account, contact.jid)
+
+ # Look for a chat control that has the given resource, or default to
+ # one without resource
+ ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
+ account)
+
+ if ctrl:
+ return ctrl
+ elif highest_contact and highest_contact.resource and \
+ contact.resource != highest_contact.resource:
+ return None
+ else:
+ # unknown contact or offline message
+ return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
def get_notification_icon_tooltip_dict():
- """
- Return a dict of the form {acct: {'show': show, 'message': message,
- 'event_lines': [list of text lines to show in tooltip]}
- """
- # How many events must there be before they're shown summarized, not per-user
- max_ungrouped_events = 10
-
- accounts = get_accounts_info()
-
- # Gather events. (With accounts, when there are more.)
- for account in accounts:
- account_name = account['name']
- account['event_lines'] = []
- # Gather events per-account
- pending_events = gajim.events.get_events(account = account_name)
- messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0
- for jid in pending_events:
- for event in pending_events[jid]:
- if event.type_.count('file') > 0:
- # This is a non-messagee event.
- messages[jid] = non_messages.get(jid, 0) + 1
- total_non_messages = total_non_messages + 1
- else:
- # This is a message.
- messages[jid] = messages.get(jid, 0) + 1
- total_messages = total_messages + 1
- # Display unread messages numbers, if any
- if total_messages > 0:
- if total_messages > max_ungrouped_events:
- text = ngettext(
- '%d message pending',
- '%d messages pending',
- total_messages, total_messages, total_messages)
- account['event_lines'].append(text)
- else:
- for jid in messages.keys():
- text = ngettext(
- '%d message pending',
- '%d messages pending',
- messages[jid], messages[jid], messages[jid])
- contact = gajim.contacts.get_first_contact_from_jid(
- account['name'], jid)
- if jid in gajim.gc_connected[account['name']]:
- text += _(' from room %s') % (jid)
- elif contact:
- name = contact.get_shown_name()
- text += _(' from user %s') % (name)
- else:
- text += _(' from %s') % (jid)
- account['event_lines'].append(text)
-
- # Display unseen events numbers, if any
- if total_non_messages > 0:
- if total_non_messages > max_ungrouped_events:
- text = ngettext(
- '%d event pending',
- '%d events pending',
- total_non_messages, total_non_messages, total_non_messages)
- account['event_lines'].append(text)
- else:
- for jid in non_messages.keys():
- text = ngettext(
- '%d event pending',
- '%d events pending',
- non_messages[jid], non_messages[jid], non_messages[jid])
- text += _(' from user %s') % (jid)
- account[account]['event_lines'].append(text)
-
- return accounts
+ """
+ Return a dict of the form {acct: {'show': show, 'message': message,
+ 'event_lines': [list of text lines to show in tooltip]}
+ """
+ # How many events must there be before they're shown summarized, not per-user
+ max_ungrouped_events = 10
+
+ accounts = get_accounts_info()
+
+ # Gather events. (With accounts, when there are more.)
+ for account in accounts:
+ account_name = account['name']
+ account['event_lines'] = []
+ # Gather events per-account
+ pending_events = gajim.events.get_events(account = account_name)
+ messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0
+ for jid in pending_events:
+ for event in pending_events[jid]:
+ if event.type_.count('file') > 0:
+ # This is a non-messagee event.
+ messages[jid] = non_messages.get(jid, 0) + 1
+ total_non_messages = total_non_messages + 1
+ else:
+ # This is a message.
+ messages[jid] = messages.get(jid, 0) + 1
+ total_messages = total_messages + 1
+ # Display unread messages numbers, if any
+ if total_messages > 0:
+ if total_messages > max_ungrouped_events:
+ text = ngettext(
+ '%d message pending',
+ '%d messages pending',
+ total_messages, total_messages, total_messages)
+ account['event_lines'].append(text)
+ else:
+ for jid in messages.keys():
+ text = ngettext(
+ '%d message pending',
+ '%d messages pending',
+ messages[jid], messages[jid], messages[jid])
+ contact = gajim.contacts.get_first_contact_from_jid(
+ account['name'], jid)
+ if jid in gajim.gc_connected[account['name']]:
+ text += _(' from room %s') % (jid)
+ elif contact:
+ name = contact.get_shown_name()
+ text += _(' from user %s') % (name)
+ else:
+ text += _(' from %s') % (jid)
+ account['event_lines'].append(text)
+
+ # Display unseen events numbers, if any
+ if total_non_messages > 0:
+ if total_non_messages > max_ungrouped_events:
+ text = ngettext(
+ '%d event pending',
+ '%d events pending',
+ total_non_messages, total_non_messages, total_non_messages)
+ account['event_lines'].append(text)
+ else:
+ for jid in non_messages.keys():
+ text = ngettext(
+ '%d event pending',
+ '%d events pending',
+ non_messages[jid], non_messages[jid], non_messages[jid])
+ text += _(' from user %s') % (jid)
+ account[account]['event_lines'].append(text)
+
+ return accounts
def get_notification_icon_tooltip_text():
- text = None
- # How many events must there be before they're shown summarized, not per-user
- # max_ungrouped_events = 10
- # Character which should be used to indent in the tooltip.
- indent_with = ' '
-
- accounts = get_notification_icon_tooltip_dict()
-
- if len(accounts) == 0:
- # No configured account
- return _('Gajim')
-
- # at least one account present
-
- # Is there more that one account?
- if len(accounts) == 1:
- show_more_accounts = False
- else:
- show_more_accounts = True
-
- # If there is only one account, its status is shown on the first line.
- if show_more_accounts:
- text = _('Gajim')
- else:
- text = _('Gajim - %s') % (get_account_status(accounts[0]))
-
- # Gather and display events. (With accounts, when there are more.)
- for account in accounts:
- account_name = account['name']
- # Set account status, if not set above
- if (show_more_accounts):
- message = '\n' + indent_with + ' %s - %s'
- text += message % (account_name, get_account_status(account))
- # Account list shown, messages need to be indented more
- indent_how = 2
- else:
- # If no account list is shown, messages could have default indenting.
- indent_how = 1
- for line in account['event_lines']:
- text += '\n' + indent_with * indent_how + ' '
- text += line
- return text
+ text = None
+ # How many events must there be before they're shown summarized, not per-user
+ # max_ungrouped_events = 10
+ # Character which should be used to indent in the tooltip.
+ indent_with = ' '
+
+ accounts = get_notification_icon_tooltip_dict()
+
+ if len(accounts) == 0:
+ # No configured account
+ return _('Gajim')
+
+ # at least one account present
+
+ # Is there more that one account?
+ if len(accounts) == 1:
+ show_more_accounts = False
+ else:
+ show_more_accounts = True
+
+ # If there is only one account, its status is shown on the first line.
+ if show_more_accounts:
+ text = _('Gajim')
+ else:
+ text = _('Gajim - %s') % (get_account_status(accounts[0]))
+
+ # Gather and display events. (With accounts, when there are more.)
+ for account in accounts:
+ account_name = account['name']
+ # Set account status, if not set above
+ if (show_more_accounts):
+ message = '\n' + indent_with + ' %s - %s'
+ text += message % (account_name, get_account_status(account))
+ # Account list shown, messages need to be indented more
+ indent_how = 2
+ else:
+ # If no account list is shown, messages could have default indenting.
+ indent_how = 1
+ for line in account['event_lines']:
+ text += '\n' + indent_with * indent_how + ' '
+ text += line
+ return text
def get_accounts_info():
- """
- Helper for notification icon tooltip
- """
- accounts = []
- accounts_list = sorted(gajim.contacts.get_accounts())
- for account in accounts_list:
- status_idx = gajim.connections[account].connected
- # uncomment the following to hide offline accounts
- # if status_idx == 0: continue
- status = gajim.SHOW_LIST[status_idx]
- message = gajim.connections[account].status
- single_line = get_uf_show(status)
- if message is None:
- message = ''
- else:
- message = message.strip()
- if message != '':
- single_line += ': ' + message
- accounts.append({'name': account, 'status_line': single_line,
- 'show': status, 'message': message})
- return accounts
+ """
+ Helper for notification icon tooltip
+ """
+ accounts = []
+ accounts_list = sorted(gajim.contacts.get_accounts())
+ for account in accounts_list:
+ status_idx = gajim.connections[account].connected
+ # uncomment the following to hide offline accounts
+ # if status_idx == 0: continue
+ status = gajim.SHOW_LIST[status_idx]
+ message = gajim.connections[account].status
+ single_line = get_uf_show(status)
+ if message is None:
+ message = ''
+ else:
+ message = message.strip()
+ if message != '':
+ single_line += ': ' + message
+ accounts.append({'name': account, 'status_line': single_line,
+ 'show': status, 'message': message})
+ return accounts
def get_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)):
- return os.path.join(gajim.DATA_DIR, 'iconsets', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)):
- return os.path.join(gajim.MY_ICONSETS_PATH, iconset)
+ if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)):
+ return os.path.join(gajim.DATA_DIR, 'iconsets', iconset)
+ elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)):
+ return os.path.join(gajim.MY_ICONSETS_PATH, iconset)
def get_mood_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)):
- return os.path.join(gajim.DATA_DIR, 'moods', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)):
- return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)
+ if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)):
+ return os.path.join(gajim.DATA_DIR, 'moods', iconset)
+ elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)):
+ return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)
def get_activity_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)):
- return os.path.join(gajim.DATA_DIR, 'activities', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH,
- iconset)):
- return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset)
+ if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)):
+ return os.path.join(gajim.DATA_DIR, 'activities', iconset)
+ elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH,
+ iconset)):
+ return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset)
def get_transport_path(transport):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports',
- transport)):
- return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport)
- elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports',
- transport)):
- return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport)
- # No transport folder found, use default jabber one
- return get_iconset_path(gajim.config.get('iconset'))
+ if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports',
+ transport)):
+ return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport)
+ elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports',
+ transport)):
+ return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport)
+ # No transport folder found, use default jabber one
+ return get_iconset_path(gajim.config.get('iconset'))
def prepare_and_validate_gpg_keyID(account, jid, keyID):
- """
- Return an eight char long keyID that can be used with for GPG encryption
- with this contact
-
- If the given keyID is None, return UNKNOWN; if the key does not match the
- assigned key XXXXXXXXMISMATCH is returned. If the key is trusted and not yet
- assigned, assign it.
- """
- if gajim.connections[account].USE_GPG:
- if keyID and len(keyID) == 16:
- keyID = keyID[8:]
-
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
-
- if jid in attached_keys and keyID:
- attachedkeyID = attached_keys[attached_keys.index(jid) + 1]
- if attachedkeyID != keyID:
- # Mismatch! Another gpg key was expected
- keyID += 'MISMATCH'
- elif jid in attached_keys:
- # An unsigned presence, just use the assigned key
- keyID = attached_keys[attached_keys.index(jid) + 1]
- elif keyID:
- public_keys = gajim.connections[account].ask_gpg_keys()
- # Assign the corresponding key, if we have it in our keyring
- if keyID in public_keys:
- for u in gajim.contacts.get_contacts(account, jid):
- u.keyID = keyID
- keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys')
- keys_str += jid + ' ' + keyID + ' '
- gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str)
- elif keyID is None:
- keyID = 'UNKNOWN'
- return keyID
+ """
+ Return an eight char long keyID that can be used with for GPG encryption
+ with this contact
+
+ If the given keyID is None, return UNKNOWN; if the key does not match the
+ assigned key XXXXXXXXMISMATCH is returned. If the key is trusted and not yet
+ assigned, assign it.
+ """
+ if gajim.connections[account].USE_GPG:
+ if keyID and len(keyID) == 16:
+ keyID = keyID[8:]
+
+ attached_keys = gajim.config.get_per('accounts', account,
+ 'attached_gpg_keys').split()
+
+ if jid in attached_keys and keyID:
+ attachedkeyID = attached_keys[attached_keys.index(jid) + 1]
+ if attachedkeyID != keyID:
+ # Mismatch! Another gpg key was expected
+ keyID += 'MISMATCH'
+ elif jid in attached_keys:
+ # An unsigned presence, just use the assigned key
+ keyID = attached_keys[attached_keys.index(jid) + 1]
+ elif keyID:
+ public_keys = gajim.connections[account].ask_gpg_keys()
+ # Assign the corresponding key, if we have it in our keyring
+ if keyID in public_keys:
+ for u in gajim.contacts.get_contacts(account, jid):
+ u.keyID = keyID
+ keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys')
+ keys_str += jid + ' ' + keyID + ' '
+ gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str)
+ elif keyID is None:
+ keyID = 'UNKNOWN'
+ return keyID
def update_optional_features(account = None):
- import xmpp
- if account:
- accounts = [account]
- else:
- accounts = [a for a in gajim.connections]
- for a in accounts:
- gajim.gajim_optional_features[a] = []
- if gajim.config.get_per('accounts', a, 'subscribe_mood'):
- gajim.gajim_optional_features[a].append(xmpp.NS_MOOD + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_activity'):
- gajim.gajim_optional_features[a].append(xmpp.NS_ACTIVITY + '+notify')
- if gajim.config.get_per('accounts', a, 'publish_tune'):
- gajim.gajim_optional_features[a].append(xmpp.NS_TUNE)
- if gajim.config.get_per('accounts', a, 'publish_location'):
- gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION)
- if gajim.config.get_per('accounts', a, 'subscribe_tune'):
- gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_nick'):
- gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_location'):
- gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION + '+notify')
- if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
- gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES)
- if not gajim.config.get('ignore_incoming_xhtml'):
- gajim.gajim_optional_features[a].append(xmpp.NS_XHTML_IM)
- if gajim.HAVE_PYCRYPTO \
- and gajim.config.get_per('accounts', a, 'enable_esessions'):
- gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION)
- if gajim.config.get_per('accounts', a, 'answer_receipts'):
- gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS)
- if gajim.HAVE_FARSIGHT:
- gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE)
- gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP)
- gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO)
- gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
- gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP)
- gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity],
- gajim.gajim_common_features + gajim.gajim_optional_features[a])
- # re-send presence with new hash
- connected = gajim.connections[a].connected
- if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
- gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
- gajim.connections[a].status)
+ import xmpp
+ if account:
+ accounts = [account]
+ else:
+ accounts = [a for a in gajim.connections]
+ for a in accounts:
+ gajim.gajim_optional_features[a] = []
+ if gajim.config.get_per('accounts', a, 'subscribe_mood'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_MOOD + '+notify')
+ if gajim.config.get_per('accounts', a, 'subscribe_activity'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_ACTIVITY + '+notify')
+ if gajim.config.get_per('accounts', a, 'publish_tune'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_TUNE)
+ if gajim.config.get_per('accounts', a, 'publish_location'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION)
+ if gajim.config.get_per('accounts', a, 'subscribe_tune'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_TUNE + '+notify')
+ if gajim.config.get_per('accounts', a, 'subscribe_nick'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_NICK + '+notify')
+ if gajim.config.get_per('accounts', a, 'subscribe_location'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_LOCATION + '+notify')
+ if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
+ gajim.gajim_optional_features[a].append(xmpp.NS_CHATSTATES)
+ if not gajim.config.get('ignore_incoming_xhtml'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_XHTML_IM)
+ if gajim.HAVE_PYCRYPTO \
+ and gajim.config.get_per('accounts', a, 'enable_esessions'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION)
+ if gajim.config.get_per('accounts', a, 'answer_receipts'):
+ gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS)
+ if gajim.HAVE_FARSIGHT:
+ gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE)
+ gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP)
+ gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO)
+ gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
+ gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP)
+ gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity],
+ gajim.gajim_common_features + gajim.gajim_optional_features[a])
+ # re-send presence with new hash
+ connected = gajim.connections[a].connected
+ if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
+ gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
+ gajim.connections[a].status)
def jid_is_blocked(account, jid):
- return ((jid in gajim.connections[account].blocked_contacts) or \
- gajim.connections[account].blocked_all)
+ return ((jid in gajim.connections[account].blocked_contacts) or \
+ gajim.connections[account].blocked_all)
def group_is_blocked(account, group):
- return ((group in gajim.connections[account].blocked_groups) or \
- gajim.connections[account].blocked_all)
+ return ((group in gajim.connections[account].blocked_groups) or \
+ gajim.connections[account].blocked_all)
def get_subscription_request_msg(account=None):
- s = gajim.config.get_per('accounts', account, 'subscription_request_msg')
- if s:
- return s
- s = _('I would like to add you to my contact list.')
- if account:
- s = _('Hello, I am $name.') + ' ' + s
- our_jid = gajim.get_jid_from_account(account)
- vcard = gajim.connections[account].get_cached_vcard(our_jid)
- name = ''
- if 'N' in vcard:
- if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']:
- name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY']
- if not name:
- if 'FN' in vcard:
- name = vcard['FN']
- nick = gajim.nicks[account]
- if name and nick:
- name += ' (%s)' % nick
- elif nick:
- name = nick
- s = Template(s).safe_substitute({'name': name})
- return s
-# vim: se ts=3:
+ s = gajim.config.get_per('accounts', account, 'subscription_request_msg')
+ if s:
+ return s
+ s = _('I would like to add you to my contact list.')
+ if account:
+ s = _('Hello, I am $name.') + ' ' + s
+ our_jid = gajim.get_jid_from_account(account)
+ vcard = gajim.connections[account].get_cached_vcard(our_jid)
+ name = ''
+ if 'N' in vcard:
+ if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']:
+ name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY']
+ if not name:
+ if 'FN' in vcard:
+ name = vcard['FN']
+ nick = gajim.nicks[account]
+ if name and nick:
+ name += ' (%s)' % nick
+ elif nick:
+ name = nick
+ s = Template(s).safe_substitute({'name': name})
diff --git a/src/common/i18n.py b/src/common/i18n.py
index 7b9e8dab3..4497a56f0 100644
--- a/src/common/i18n.py
+++ b/src/common/i18n.py
@@ -29,20 +29,20 @@ import defs
import unicodedata
def paragraph_direction_mark(text):
- """
- Determine paragraph writing direction according to
- http://www.unicode.org/reports/tr9/#The_Paragraph_Level
+ """
+ Determine paragraph writing direction according to
+ http://www.unicode.org/reports/tr9/#The_Paragraph_Level
- Returns either Unicode LTR mark or RTL mark.
- """
- for char in text:
- bidi = unicodedata.bidirectional(char)
- if bidi == 'L':
- return u'\u200E'
- elif bidi == 'AL' or bidi == 'R':
- return u'\u200F'
+ Returns either Unicode LTR mark or RTL mark.
+ """
+ for char in text:
+ bidi = unicodedata.bidirectional(char)
+ if bidi == 'L':
+ return u'\u200E'
+ elif bidi == 'AL' or bidi == 'R':
+ return u'\u200F'
- return u'\u200E'
+ return u'\u200E'
APP = 'gajim'
DIR = defs.localedir
@@ -53,48 +53,46 @@ locale.setlocale(locale.LC_ALL, '')
## For windows: set, if needed, a value in LANG environmental variable ##
if os.name == 'nt':
- lang = os.getenv('LANG')
- if lang is None:
- default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
- if default_lang:
- lang = default_lang
+ lang = os.getenv('LANG')
+ if lang is None:
+ default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
+ if default_lang:
+ lang = default_lang
- if lang:
- os.environ['LANG'] = lang
+ if lang:
+ os.environ['LANG'] = lang
gettext.install(APP, DIR, unicode = True)
if gettext._translations:
- _translation = gettext._translations.values()[0]
+ _translation = gettext._translations.values()[0]
else:
- _translation = gettext.NullTranslations()
+ _translation = gettext.NullTranslations()
def Q_(s):
- # Qualified translatable strings
- # Some strings are too ambiguous to be easily translated.
- # so we must use as:
- # s = Q_('?vcard:Unknown')
- # widget.set_text(s)
- # Q_() removes the ?vcard:
- # but gettext while parsing the file detects ?vcard:Unknown as a whole string.
- # translator can either put the ?vcard: part or no (easier for him or her to no)
- # nothing fails
- s = _(s)
- if s[0] == '?':
- s = s[s.find(':')+1:] # remove ?abc: part
- return s
+ # Qualified translatable strings
+ # Some strings are too ambiguous to be easily translated.
+ # so we must use as:
+ # s = Q_('?vcard:Unknown')
+ # widget.set_text(s)
+ # Q_() removes the ?vcard:
+ # but gettext while parsing the file detects ?vcard:Unknown as a whole string.
+ # translator can either put the ?vcard: part or no (easier for him or her to no)
+ # nothing fails
+ s = _(s)
+ if s[0] == '?':
+ s = s[s.find(':')+1:] # remove ?abc: part
+ return s
def ngettext(s_sing, s_plural, n, replace_sing = None, replace_plural = None):
- """
- Use as:
- i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c')
+ """
+ Use as:
+ i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c')
- In other words this is a hack to ngettext() to support %s %d etc..
- """
- text = _translation.ungettext(s_sing, s_plural, n)
- if n == 1 and replace_sing is not None:
- text = text % replace_sing
- elif n > 1 and replace_plural is not None:
- text = text % replace_plural
- return text
-
-# vim: se ts=3:
+ In other words this is a hack to ngettext() to support %s %d etc..
+ """
+ text = _translation.ungettext(s_sing, s_plural, n)
+ if n == 1 and replace_sing is not None:
+ text = text % replace_sing
+ elif n > 1 and replace_plural is not None:
+ text = text % replace_plural
+ return text
diff --git a/src/common/idle.py b/src/common/idle.py
index d3ff4dff7..5e1bf4c0f 100644
--- a/src/common/idle.py
+++ b/src/common/idle.py
@@ -20,14 +20,14 @@ import ctypes
import ctypes.util
class XScreenSaverInfo(ctypes.Structure):
- _fields_ = [
- ('window', ctypes.c_ulong),
- ('state', ctypes.c_int),
- ('kind', ctypes.c_int),
- ('til_or_since', ctypes.c_ulong),
- ('idle', ctypes.c_ulong),
- ('eventMask', ctypes.c_ulong)
- ]
+ _fields_ = [
+ ('window', ctypes.c_ulong),
+ ('state', ctypes.c_int),
+ ('kind', ctypes.c_int),
+ ('til_or_since', ctypes.c_ulong),
+ ('idle', ctypes.c_ulong),
+ ('eventMask', ctypes.c_ulong)
+ ]
XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo)
display_p = ctypes.c_void_p
@@ -35,65 +35,65 @@ xid = ctypes.c_ulong
c_int_p = ctypes.POINTER(ctypes.c_int)
try:
- libX11path = ctypes.util.find_library('X11')
- if libX11path == None:
- raise OSError('libX11 could not be found.')
- libX11 = ctypes.cdll.LoadLibrary(libX11path)
- libX11.XOpenDisplay.restype = display_p
- libX11.XOpenDisplay.argtypes = ctypes.c_char_p,
- libX11.XDefaultRootWindow.restype = xid
- libX11.XDefaultRootWindow.argtypes = display_p,
+ libX11path = ctypes.util.find_library('X11')
+ if libX11path == None:
+ raise OSError('libX11 could not be found.')
+ libX11 = ctypes.cdll.LoadLibrary(libX11path)
+ libX11.XOpenDisplay.restype = display_p
+ libX11.XOpenDisplay.argtypes = ctypes.c_char_p,
+ libX11.XDefaultRootWindow.restype = xid
+ libX11.XDefaultRootWindow.argtypes = display_p,
- libXsspath = ctypes.util.find_library('Xss')
- if libXsspath == None:
- raise OSError('libXss could not be found.')
- libXss = ctypes.cdll.LoadLibrary(libXsspath)
- libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p
- libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p
- libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p)
+ libXsspath = ctypes.util.find_library('Xss')
+ if libXsspath == None:
+ raise OSError('libXss could not be found.')
+ libXss = ctypes.cdll.LoadLibrary(libXsspath)
+ libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p
+ libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p
+ libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p)
- dpy_p = libX11.XOpenDisplay(None)
- if dpy_p == None:
- raise OSError('Could not open X Display.')
+ dpy_p = libX11.XOpenDisplay(None)
+ if dpy_p == None:
+ raise OSError('Could not open X Display.')
- _event_basep = ctypes.c_int()
- _error_basep = ctypes.c_int()
- if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep),
- ctypes.byref(_error_basep)) == 0:
- raise OSError('XScreenSaver Extension not available on display.')
+ _event_basep = ctypes.c_int()
+ _error_basep = ctypes.c_int()
+ if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep),
+ ctypes.byref(_error_basep)) == 0:
+ raise OSError('XScreenSaver Extension not available on display.')
- xss_info_p = libXss.XScreenSaverAllocInfo()
- if xss_info_p == None:
- raise OSError('XScreenSaverAllocInfo: Out of Memory.')
+ xss_info_p = libXss.XScreenSaverAllocInfo()
+ if xss_info_p == None:
+ raise OSError('XScreenSaverAllocInfo: Out of Memory.')
- rootwindow = libX11.XDefaultRootWindow(dpy_p)
- xss_available = True
+ rootwindow = libX11.XDefaultRootWindow(dpy_p)
+ xss_available = True
except OSError, e:
- # Logging?
- xss_available = False
+ # Logging?
+ xss_available = False
def getIdleSec():
- global xss_available
- """
- Return the idle time in seconds
- """
- if not xss_available:
- return 0
- if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0:
- return 0
- else:
- return int(xss_info_p.contents.idle) / 1000
+ global xss_available
+ """
+ Return the idle time in seconds
+ """
+ if not xss_available:
+ return 0
+ if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0:
+ return 0
+ else:
+ return int(xss_info_p.contents.idle) / 1000
def close():
- global xss_available
- if xss_available:
- libX11.XFree(xss_info_p)
- libX11.XCloseDisplay(dpy_p)
- xss_available = False
+ global xss_available
+ if xss_available:
+ libX11.XFree(xss_info_p)
+ libX11.XCloseDisplay(dpy_p)
+ xss_available = False
if __name__ == '__main__':
- import time
- time.sleep(2.1)
- print getIdleSec()
- close()
- print getIdleSec()
+ import time
+ time.sleep(2.1)
+ print getIdleSec()
+ close()
+ print getIdleSec()
diff --git a/src/common/jingle.py b/src/common/jingle.py
index 73745592a..8f87504c8 100644
--- a/src/common/jingle.py
+++ b/src/common/jingle.py
@@ -38,111 +38,109 @@ from jingle_rtp import JingleAudio, JingleVideo
class ConnectionJingle(object):
- """
- This object depends on that it is a part of Connection class.
- """
-
- def __init__(self):
- # dictionary: (jid, sessionid) => JingleSession object
- self.__sessions = {}
-
- # dictionary: (jid, iq stanza id) => JingleSession object,
- # one time callbacks
- self.__iq_responses = {}
-
- def add_jingle(self, jingle):
- """
- Add a jingle session to a jingle stanza dispatcher
-
- jingle - a JingleSession object.
- """
- self.__sessions[(jingle.peerjid, jingle.sid)] = jingle
-
- def delete_jingle_session(self, peerjid, sid):
- """
- Remove a jingle session from a jingle stanza dispatcher
- """
- key = (peerjid, sid)
- if key in self.__sessions:
- #FIXME: Move this elsewhere?
- for content in self.__sessions[key].contents.values():
- content.destroy()
- self.__sessions[key].callbacks = []
- del self.__sessions[key]
-
- def _JingleCB(self, con, stanza):
- """
- The jingle stanza dispatcher
-
- Route jingle stanza to proper JingleSession object, or create one if it
- is a new session.
-
- TODO: Also check if the stanza isn't an error stanza, if so route it
- adequatelly.
- """
- # get data
- jid = helpers.get_full_jid_from_iq(stanza)
- id = stanza.getID()
-
- if (jid, id) in self.__iq_responses.keys():
- self.__iq_responses[(jid, id)].on_stanza(stanza)
- del self.__iq_responses[(jid, id)]
- raise xmpp.NodeProcessed
-
- jingle = stanza.getTag('jingle')
- if not jingle: return
- sid = jingle.getAttr('sid')
-
- # do we need to create a new jingle object
- if (jid, sid) not in self.__sessions:
- #TODO: tie-breaking and other things...
- newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
- self.add_jingle(newjingle)
-
- # we already have such session in dispatcher...
- self.__sessions[(jid, sid)].on_stanza(stanza)
-
- raise xmpp.NodeProcessed
-
- def start_audio(self, jid):
- if self.get_jingle_session(jid, media='audio'):
- return self.get_jingle_session(jid, media='audio').sid
- jingle = self.get_jingle_session(jid, media='video')
- if jingle:
- jingle.add_content('voice', JingleAudio(jingle))
- else:
- jingle = JingleSession(self, weinitiate=True, jid=jid)
- self.add_jingle(jingle)
- jingle.add_content('voice', JingleAudio(jingle))
- jingle.start_session()
- return jingle.sid
-
- def start_video(self, jid):
- if self.get_jingle_session(jid, media='video'):
- return self.get_jingle_session(jid, media='video').sid
- jingle = self.get_jingle_session(jid, media='audio')
- if jingle:
- jingle.add_content('video', JingleVideo(jingle))
- else:
- jingle = JingleSession(self, weinitiate=True, jid=jid)
- self.add_jingle(jingle)
- jingle.add_content('video', JingleVideo(jingle))
- jingle.start_session()
- return jingle.sid
-
- def get_jingle_session(self, jid, sid=None, media=None):
- if sid:
- if (jid, sid) in self.__sessions:
- return self.__sessions[(jid, sid)]
- else:
- return None
- elif media:
- if media not in ('audio', 'video'):
- return None
- for session in self.__sessions.values():
- if session.peerjid == jid and session.get_content(media):
- return session
-
- return None
-
-# vim: se ts=3: \ No newline at end of file
+ """
+ This object depends on that it is a part of Connection class.
+ """
+
+ def __init__(self):
+ # dictionary: (jid, sessionid) => JingleSession object
+ self.__sessions = {}
+
+ # dictionary: (jid, iq stanza id) => JingleSession object,
+ # one time callbacks
+ self.__iq_responses = {}
+
+ def add_jingle(self, jingle):
+ """
+ Add a jingle session to a jingle stanza dispatcher
+
+ jingle - a JingleSession object.
+ """
+ self.__sessions[(jingle.peerjid, jingle.sid)] = jingle
+
+ def delete_jingle_session(self, peerjid, sid):
+ """
+ Remove a jingle session from a jingle stanza dispatcher
+ """
+ key = (peerjid, sid)
+ if key in self.__sessions:
+ #FIXME: Move this elsewhere?
+ for content in self.__sessions[key].contents.values():
+ content.destroy()
+ self.__sessions[key].callbacks = []
+ del self.__sessions[key]
+
+ def _JingleCB(self, con, stanza):
+ """
+ The jingle stanza dispatcher
+
+ Route jingle stanza to proper JingleSession object, or create one if it
+ is a new session.
+
+ TODO: Also check if the stanza isn't an error stanza, if so route it
+ adequatelly.
+ """
+ # get data
+ jid = helpers.get_full_jid_from_iq(stanza)
+ id = stanza.getID()
+
+ if (jid, id) in self.__iq_responses.keys():
+ self.__iq_responses[(jid, id)].on_stanza(stanza)
+ del self.__iq_responses[(jid, id)]
+ raise xmpp.NodeProcessed
+
+ jingle = stanza.getTag('jingle')
+ if not jingle: return
+ sid = jingle.getAttr('sid')
+
+ # do we need to create a new jingle object
+ if (jid, sid) not in self.__sessions:
+ #TODO: tie-breaking and other things...
+ newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
+ self.add_jingle(newjingle)
+
+ # we already have such session in dispatcher...
+ self.__sessions[(jid, sid)].on_stanza(stanza)
+
+ raise xmpp.NodeProcessed
+
+ def start_audio(self, jid):
+ if self.get_jingle_session(jid, media='audio'):
+ return self.get_jingle_session(jid, media='audio').sid
+ jingle = self.get_jingle_session(jid, media='video')
+ if jingle:
+ jingle.add_content('voice', JingleAudio(jingle))
+ else:
+ jingle = JingleSession(self, weinitiate=True, jid=jid)
+ self.add_jingle(jingle)
+ jingle.add_content('voice', JingleAudio(jingle))
+ jingle.start_session()
+ return jingle.sid
+
+ def start_video(self, jid):
+ if self.get_jingle_session(jid, media='video'):
+ return self.get_jingle_session(jid, media='video').sid
+ jingle = self.get_jingle_session(jid, media='audio')
+ if jingle:
+ jingle.add_content('video', JingleVideo(jingle))
+ else:
+ jingle = JingleSession(self, weinitiate=True, jid=jid)
+ self.add_jingle(jingle)
+ jingle.add_content('video', JingleVideo(jingle))
+ jingle.start_session()
+ return jingle.sid
+
+ def get_jingle_session(self, jid, sid=None, media=None):
+ if sid:
+ if (jid, sid) in self.__sessions:
+ return self.__sessions[(jid, sid)]
+ else:
+ return None
+ elif media:
+ if media not in ('audio', 'video'):
+ return None
+ for session in self.__sessions.values():
+ if session.peerjid == jid and session.get_content(media):
+ return session
+
+ return None
diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py
index 7b0e3f8fe..f4022cc8e 100644
--- a/src/common/jingle_content.py
+++ b/src/common/jingle_content.py
@@ -20,123 +20,121 @@ import xmpp
contents = {}
def get_jingle_content(node):
- namespace = node.getNamespace()
- if namespace in contents:
- return contents[namespace](node)
+ namespace = node.getNamespace()
+ if namespace in contents:
+ return contents[namespace](node)
class JingleContentSetupException(Exception):
- """
- Exception that should be raised when a content fails to setup.
- """
+ """
+ Exception that should be raised when a content fails to setup.
+ """
class JingleContent(object):
- """
- An abstraction of content in Jingle sessions
- """
-
- def __init__(self, session, transport):
- self.session = session
- self.transport = transport
- # will be filled by JingleSession.add_content()
- # don't uncomment these lines, we will catch more buggy code then
- # (a JingleContent not added to session shouldn't send anything)
- #self.creator = None
- #self.name = None
- self.accepted = False
- self.sent = False
- self.negotiated = False
-
- self.media = None
-
- self.senders = 'both' #FIXME
- self.allow_sending = True # Used for stream direction, attribute 'senders'
-
- self.callbacks = {
- # these are called when *we* get stanzas
- 'content-accept': [self.__on_transport_info],
- 'content-add': [self.__on_transport_info],
- 'content-modify': [],
- 'content-reject': [],
- 'content-remove': [],
- 'description-info': [],
- 'security-info': [],
- 'session-accept': [self.__on_transport_info],
- 'session-info': [],
- 'session-initiate': [self.__on_transport_info],
- 'session-terminate': [],
- 'transport-info': [self.__on_transport_info],
- 'transport-replace': [],
- 'transport-accept': [],
- 'transport-reject': [],
- 'iq-result': [],
- 'iq-error': [],
- # these are called when *we* sent these stanzas
- 'content-accept-sent': [self.__fill_jingle_stanza],
- 'content-add-sent': [self.__fill_jingle_stanza],
- 'session-initiate-sent': [self.__fill_jingle_stanza],
- 'session-accept-sent': [self.__fill_jingle_stanza],
- 'session-terminate-sent': [],
- }
-
- def is_ready(self):
- return self.accepted and not self.sent
-
- def on_negotiated(self):
- if self.accepted:
- self.negotiated = True
- self.session.content_negotiated(self.media)
-
- def add_remote_candidates(self, candidates):
- """
- Add a list of candidates to the list of remote candidates
- """
- pass
-
- def on_stanza(self, stanza, content, error, action):
- """
- Called when something related to our content was sent by peer
- """
- if action in self.callbacks:
- for callback in self.callbacks[action]:
- callback(stanza, content, error, action)
-
- def __on_transport_info(self, stanza, content, error, action):
- """
- Got a new transport candidate
- """
- candidates = self.transport.parse_transport_stanza(
- content.getTag('transport'))
- if candidates:
- self.add_remote_candidates(candidates)
-
- def __content(self, payload=[]):
- """
- Build a XML content-wrapper for our data
- """
- return xmpp.Node('content',
- attrs={'name': self.name, 'creator': self.creator},
- payload=payload)
-
- def send_candidate(self, candidate):
- """
- Send a transport candidate for a previously defined transport.
- """
- content = self.__content()
- content.addChild(node=self.transport.make_transport([candidate]))
- self.session.send_transport_info(content)
-
- def __fill_jingle_stanza(self, stanza, content, error, action):
- """
- Add our things to session-initiate stanza
- """
- self._fill_content(content)
- self.sent = True
- content.addChild(node=self.transport.make_transport())
-
- def destroy(self):
- self.callbacks = None
- del self.session.contents[(self.creator, self.name)]
-
-# vim: se ts=3:
+ """
+ An abstraction of content in Jingle sessions
+ """
+
+ def __init__(self, session, transport):
+ self.session = session
+ self.transport = transport
+ # will be filled by JingleSession.add_content()
+ # don't uncomment these lines, we will catch more buggy code then
+ # (a JingleContent not added to session shouldn't send anything)
+ #self.creator = None
+ #self.name = None
+ self.accepted = False
+ self.sent = False
+ self.negotiated = False
+
+ self.media = None
+
+ self.senders = 'both' #FIXME
+ self.allow_sending = True # Used for stream direction, attribute 'senders'
+
+ self.callbacks = {
+ # these are called when *we* get stanzas
+ 'content-accept': [self.__on_transport_info],
+ 'content-add': [self.__on_transport_info],
+ 'content-modify': [],
+ 'content-reject': [],
+ 'content-remove': [],
+ 'description-info': [],
+ 'security-info': [],
+ 'session-accept': [self.__on_transport_info],
+ 'session-info': [],
+ 'session-initiate': [self.__on_transport_info],
+ 'session-terminate': [],
+ 'transport-info': [self.__on_transport_info],
+ 'transport-replace': [],
+ 'transport-accept': [],
+ 'transport-reject': [],
+ 'iq-result': [],
+ 'iq-error': [],
+ # these are called when *we* sent these stanzas
+ 'content-accept-sent': [self.__fill_jingle_stanza],
+ 'content-add-sent': [self.__fill_jingle_stanza],
+ 'session-initiate-sent': [self.__fill_jingle_stanza],
+ 'session-accept-sent': [self.__fill_jingle_stanza],
+ 'session-terminate-sent': [],
+ }
+
+ def is_ready(self):
+ return self.accepted and not self.sent
+
+ def on_negotiated(self):
+ if self.accepted:
+ self.negotiated = True
+ self.session.content_negotiated(self.media)
+
+ def add_remote_candidates(self, candidates):
+ """
+ Add a list of candidates to the list of remote candidates
+ """
+ pass
+
+ def on_stanza(self, stanza, content, error, action):
+ """
+ Called when something related to our content was sent by peer
+ """
+ if action in self.callbacks:
+ for callback in self.callbacks[action]:
+ callback(stanza, content, error, action)
+
+ def __on_transport_info(self, stanza, content, error, action):
+ """
+ Got a new transport candidate
+ """
+ candidates = self.transport.parse_transport_stanza(
+ content.getTag('transport'))
+ if candidates:
+ self.add_remote_candidates(candidates)
+
+ def __content(self, payload=[]):
+ """
+ Build a XML content-wrapper for our data
+ """
+ return xmpp.Node('content',
+ attrs={'name': self.name, 'creator': self.creator},
+ payload=payload)
+
+ def send_candidate(self, candidate):
+ """
+ Send a transport candidate for a previously defined transport.
+ """
+ content = self.__content()
+ content.addChild(node=self.transport.make_transport([candidate]))
+ self.session.send_transport_info(content)
+
+ def __fill_jingle_stanza(self, stanza, content, error, action):
+ """
+ Add our things to session-initiate stanza
+ """
+ self._fill_content(content)
+ self.sent = True
+ content.addChild(node=self.transport.make_transport())
+
+ def destroy(self):
+ self.callbacks = None
+ del self.session.contents[(self.creator, self.name)]
diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py
index ce37370f4..32d9398a9 100644
--- a/src/common/jingle_rtp.py
+++ b/src/common/jingle_rtp.py
@@ -29,325 +29,323 @@ from jingle_content import contents, JingleContent, JingleContentSetupException
class JingleRTPContent(JingleContent):
- def __init__(self, session, media, transport=None):
- if transport is None:
- transport = JingleTransportICEUDP()
- JingleContent.__init__(self, session, transport)
- self.media = media
- self._dtmf_running = False
- self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO,
- 'video': farsight.MEDIA_TYPE_VIDEO}[media]
-
- self.candidates_ready = False # True when local candidates are prepared
-
- self.callbacks['session-initiate'] += [self.__on_remote_codecs]
- self.callbacks['content-add'] += [self.__on_remote_codecs]
- self.callbacks['description-info'] += [self.__on_remote_codecs]
- self.callbacks['content-accept'] += [self.__on_remote_codecs,
- self.__on_content_accept]
- self.callbacks['session-accept'] += [self.__on_remote_codecs,
- self.__on_content_accept]
- self.callbacks['session-accept-sent'] += [self.__on_content_accept]
- self.callbacks['content-accept-sent'] += [self.__on_content_accept]
- self.callbacks['session-terminate'] += [self.__stop]
- self.callbacks['session-terminate-sent'] += [self.__stop]
-
- def setup_stream(self):
- # pipeline and bus
- self.pipeline = gst.Pipeline()
- bus = self.pipeline.get_bus()
- bus.add_signal_watch()
- bus.connect('message', self._on_gst_message)
-
- # conference
- self.conference = gst.element_factory_make('fsrtpconference')
- self.conference.set_property('sdes-cname', self.session.ourjid)
- self.pipeline.add(self.conference)
- self.funnel = None
-
- self.p2psession = self.conference.new_session(self.farsight_media)
-
- participant = self.conference.new_participant(self.session.peerjid)
- # FIXME: Consider a workaround, here...
- # pidgin and telepathy-gabble don't follow the XEP, and it won't work
- # due to bad controlling-mode
- params = {'controlling-mode': self.session.weinitiate, 'debug': False}
- if gajim.config.get('use_stun_server'):
- stun_server = gajim.config.get('stun_server')
- if not stun_server and self.session.connection._stun_servers:
- stun_server = self.session.connection._stun_servers[0]['host']
- if stun_server:
- try:
- ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
- socket.SOCK_STREAM)[0][4][0]
- except socket.gaierror, (errnum, errstr):
- log.warn('Lookup of stun ip failed: %s' % errstr)
- else:
- params['stun-ip'] = ip
-
- self.p2pstream = self.p2psession.new_stream(participant,
- farsight.DIRECTION_RECV, 'nice', params)
-
- def is_ready(self):
- return (JingleContent.is_ready(self) and self.candidates_ready
- and self.p2psession.get_property('codecs-ready'))
-
- def make_bin_from_config(self, config_key, pipeline, text):
- pipeline = pipeline % gajim.config.get(config_key)
- try:
- bin = gst.parse_bin_from_description(pipeline, True)
- return bin
- except GError, error_str:
- self.session.connection.dispatch('ERROR',
- (_("%s configuration error") % text.capitalize(),
- _("Couldn't setup %s. Check your configuration.\n\n"
- "Pipeline was:\n%s\n\n"
- "Error was:\n%s") % (text, pipeline, error_str)))
- raise JingleContentSetupException
-
- def add_remote_candidates(self, candidates):
- JingleContent.add_remote_candidates(self, candidates)
- # FIXME: connectivity should not be etablished yet
- # Instead, it should be etablished after session-accept!
- if self.sent:
- self.p2pstream.set_remote_candidates(candidates)
-
- def batch_dtmf(self, events):
- """
- Send several DTMF tones
- """
- if self._dtmf_running:
- raise Exception # TODO: Proper exception
- self._dtmf_running = True
- self._start_dtmf(events.pop(0))
- gobject.timeout_add(500, self._next_dtmf, events)
-
- def _next_dtmf(self, events):
- self._stop_dtmf()
- if events:
- self._start_dtmf(events.pop(0))
- gobject.timeout_add(500, self._next_dtmf, events)
- else:
- self._dtmf_running = False
-
- def _start_dtmf(self, event):
- if event in ('*', '#'):
- event = {'*': farsight.DTMF_EVENT_STAR,
- '#': farsight.DTMF_EVENT_POUND}[event]
- else:
- event = int(event)
- self.p2psession.start_telephony_event(event, 2,
- farsight.DTMF_METHOD_RTP_RFC4733)
-
- def _stop_dtmf(self):
- self.p2psession.stop_telephony_event(farsight.DTMF_METHOD_RTP_RFC4733)
-
- def _fill_content(self, content):
- content.addChild(xmpp.NS_JINGLE_RTP + ' description',
- attrs={'media': self.media}, payload=self.iter_codecs())
-
- def _setup_funnel(self):
- self.funnel = gst.element_factory_make('fsfunnel')
- self.pipeline.add(self.funnel)
- self.funnel.set_state(gst.STATE_PLAYING)
- self.sink.set_state(gst.STATE_PLAYING)
- self.funnel.link(self.sink)
-
- def _on_src_pad_added(self, stream, pad, codec):
- if not self.funnel:
- self._setup_funnel()
- pad.link(self.funnel.get_pad('sink%d'))
-
- def _on_gst_message(self, bus, message):
- if message.type == gst.MESSAGE_ELEMENT:
- name = message.structure.get_name()
- if name == 'farsight-new-active-candidate-pair':
- pass
- elif name == 'farsight-recv-codecs-changed':
- pass
- elif name == 'farsight-codecs-changed':
- if self.is_ready():
- self.session.on_session_state_changed(self)
- # TODO: description-info
- elif name == 'farsight-local-candidates-prepared':
- self.candidates_ready = True
- if self.is_ready():
- self.session.on_session_state_changed(self)
- elif name == 'farsight-new-local-candidate':
- candidate = message.structure['candidate']
- self.transport.candidates.append(candidate)
- if self.candidates_ready:
- # FIXME: Is this case even possible?
- self.send_candidate(candidate)
- elif name == 'farsight-component-state-changed':
- state = message.structure['state']
- print message.structure['component'], state
- if state == farsight.STREAM_STATE_FAILED:
- reason = xmpp.Node('reason')
- reason.setTag('failed-transport')
- self.session._session_terminate(reason)
- elif name == 'farsight-error':
- print 'Farsight error #%d!' % message.structure['error-no']
- print 'Message: %s' % message.structure['error-msg']
- print 'Debug: %s' % message.structure['debug-msg']
- else:
- print name
-
- def __on_content_accept(self, stanza, content, error, action):
- if self.accepted:
- if self.transport.remote_candidates:
- self.p2pstream.set_remote_candidates(self.transport.remote_candidates)
- self.transport.remote_candidates = []
- # TODO: farsight.DIRECTION_BOTH only if senders='both'
- self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH)
- self.on_negotiated()
-
- def __on_remote_codecs(self, stanza, content, error, action):
- """
- Get peer codecs from what we get from peer
- """
-
- codecs = []
- for codec in content.getTag('description').iterTags('payload-type'):
- c = farsight.Codec(int(codec['id']), codec['name'],
- self.farsight_media, int(codec['clockrate']))
- if 'channels' in codec:
- c.channels = int(codec['channels'])
- else:
- c.channels = 1
- c.optional_params = [(str(p['name']), str(p['value'])) for p in \
- codec.iterTags('parameter')]
- codecs.append(c)
-
- if codecs:
- # FIXME: Handle this case:
- # glib.GError: There was no intersection between the remote codecs and
- # the local ones
- self.p2pstream.set_remote_codecs(codecs)
-
- def iter_codecs(self):
- codecs = self.p2psession.get_property('codecs')
- for codec in codecs:
- attrs = {'name': codec.encoding_name,
- 'id': codec.id,
- 'channels': codec.channels}
- if codec.clock_rate:
- attrs['clockrate'] = codec.clock_rate
- if codec.optional_params:
- payload = (xmpp.Node('parameter', {'name': name, 'value': value})
- for name, value in codec.optional_params)
- else:
- payload = ()
- yield xmpp.Node('payload-type', attrs, payload)
-
- def __stop(self, *things):
- self.pipeline.set_state(gst.STATE_NULL)
-
- def __del__(self):
- self.__stop()
-
- def destroy(self):
- JingleContent.destroy(self)
- self.p2pstream.disconnect_by_func(self._on_src_pad_added)
- self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
+ def __init__(self, session, media, transport=None):
+ if transport is None:
+ transport = JingleTransportICEUDP()
+ JingleContent.__init__(self, session, transport)
+ self.media = media
+ self._dtmf_running = False
+ self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO,
+ 'video': farsight.MEDIA_TYPE_VIDEO}[media]
+
+ self.candidates_ready = False # True when local candidates are prepared
+
+ self.callbacks['session-initiate'] += [self.__on_remote_codecs]
+ self.callbacks['content-add'] += [self.__on_remote_codecs]
+ self.callbacks['description-info'] += [self.__on_remote_codecs]
+ self.callbacks['content-accept'] += [self.__on_remote_codecs,
+ self.__on_content_accept]
+ self.callbacks['session-accept'] += [self.__on_remote_codecs,
+ self.__on_content_accept]
+ self.callbacks['session-accept-sent'] += [self.__on_content_accept]
+ self.callbacks['content-accept-sent'] += [self.__on_content_accept]
+ self.callbacks['session-terminate'] += [self.__stop]
+ self.callbacks['session-terminate-sent'] += [self.__stop]
+
+ def setup_stream(self):
+ # pipeline and bus
+ self.pipeline = gst.Pipeline()
+ bus = self.pipeline.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message', self._on_gst_message)
+
+ # conference
+ self.conference = gst.element_factory_make('fsrtpconference')
+ self.conference.set_property('sdes-cname', self.session.ourjid)
+ self.pipeline.add(self.conference)
+ self.funnel = None
+
+ self.p2psession = self.conference.new_session(self.farsight_media)
+
+ participant = self.conference.new_participant(self.session.peerjid)
+ # FIXME: Consider a workaround, here...
+ # pidgin and telepathy-gabble don't follow the XEP, and it won't work
+ # due to bad controlling-mode
+ params = {'controlling-mode': self.session.weinitiate, 'debug': False}
+ if gajim.config.get('use_stun_server'):
+ stun_server = gajim.config.get('stun_server')
+ if not stun_server and self.session.connection._stun_servers:
+ stun_server = self.session.connection._stun_servers[0]['host']
+ if stun_server:
+ try:
+ ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
+ socket.SOCK_STREAM)[0][4][0]
+ except socket.gaierror, (errnum, errstr):
+ log.warn('Lookup of stun ip failed: %s' % errstr)
+ else:
+ params['stun-ip'] = ip
+
+ self.p2pstream = self.p2psession.new_stream(participant,
+ farsight.DIRECTION_RECV, 'nice', params)
+
+ def is_ready(self):
+ return (JingleContent.is_ready(self) and self.candidates_ready
+ and self.p2psession.get_property('codecs-ready'))
+
+ def make_bin_from_config(self, config_key, pipeline, text):
+ pipeline = pipeline % gajim.config.get(config_key)
+ try:
+ bin = gst.parse_bin_from_description(pipeline, True)
+ return bin
+ except GError, error_str:
+ self.session.connection.dispatch('ERROR',
+ (_("%s configuration error") % text.capitalize(),
+ _("Couldn't setup %s. Check your configuration.\n\n"
+ "Pipeline was:\n%s\n\n"
+ "Error was:\n%s") % (text, pipeline, error_str)))
+ raise JingleContentSetupException
+
+ def add_remote_candidates(self, candidates):
+ JingleContent.add_remote_candidates(self, candidates)
+ # FIXME: connectivity should not be etablished yet
+ # Instead, it should be etablished after session-accept!
+ if self.sent:
+ self.p2pstream.set_remote_candidates(candidates)
+
+ def batch_dtmf(self, events):
+ """
+ Send several DTMF tones
+ """
+ if self._dtmf_running:
+ raise Exception # TODO: Proper exception
+ self._dtmf_running = True
+ self._start_dtmf(events.pop(0))
+ gobject.timeout_add(500, self._next_dtmf, events)
+
+ def _next_dtmf(self, events):
+ self._stop_dtmf()
+ if events:
+ self._start_dtmf(events.pop(0))
+ gobject.timeout_add(500, self._next_dtmf, events)
+ else:
+ self._dtmf_running = False
+
+ def _start_dtmf(self, event):
+ if event in ('*', '#'):
+ event = {'*': farsight.DTMF_EVENT_STAR,
+ '#': farsight.DTMF_EVENT_POUND}[event]
+ else:
+ event = int(event)
+ self.p2psession.start_telephony_event(event, 2,
+ farsight.DTMF_METHOD_RTP_RFC4733)
+
+ def _stop_dtmf(self):
+ self.p2psession.stop_telephony_event(farsight.DTMF_METHOD_RTP_RFC4733)
+
+ def _fill_content(self, content):
+ content.addChild(xmpp.NS_JINGLE_RTP + ' description',
+ attrs={'media': self.media}, payload=self.iter_codecs())
+
+ def _setup_funnel(self):
+ self.funnel = gst.element_factory_make('fsfunnel')
+ self.pipeline.add(self.funnel)
+ self.funnel.set_state(gst.STATE_PLAYING)
+ self.sink.set_state(gst.STATE_PLAYING)
+ self.funnel.link(self.sink)
+
+ def _on_src_pad_added(self, stream, pad, codec):
+ if not self.funnel:
+ self._setup_funnel()
+ pad.link(self.funnel.get_pad('sink%d'))
+
+ def _on_gst_message(self, bus, message):
+ if message.type == gst.MESSAGE_ELEMENT:
+ name = message.structure.get_name()
+ if name == 'farsight-new-active-candidate-pair':
+ pass
+ elif name == 'farsight-recv-codecs-changed':
+ pass
+ elif name == 'farsight-codecs-changed':
+ if self.is_ready():
+ self.session.on_session_state_changed(self)
+ # TODO: description-info
+ elif name == 'farsight-local-candidates-prepared':
+ self.candidates_ready = True
+ if self.is_ready():
+ self.session.on_session_state_changed(self)
+ elif name == 'farsight-new-local-candidate':
+ candidate = message.structure['candidate']
+ self.transport.candidates.append(candidate)
+ if self.candidates_ready:
+ # FIXME: Is this case even possible?
+ self.send_candidate(candidate)
+ elif name == 'farsight-component-state-changed':
+ state = message.structure['state']
+ print message.structure['component'], state
+ if state == farsight.STREAM_STATE_FAILED:
+ reason = xmpp.Node('reason')
+ reason.setTag('failed-transport')
+ self.session._session_terminate(reason)
+ elif name == 'farsight-error':
+ print 'Farsight error #%d!' % message.structure['error-no']
+ print 'Message: %s' % message.structure['error-msg']
+ print 'Debug: %s' % message.structure['debug-msg']
+ else:
+ print name
+
+ def __on_content_accept(self, stanza, content, error, action):
+ if self.accepted:
+ if self.transport.remote_candidates:
+ self.p2pstream.set_remote_candidates(self.transport.remote_candidates)
+ self.transport.remote_candidates = []
+ # TODO: farsight.DIRECTION_BOTH only if senders='both'
+ self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH)
+ self.on_negotiated()
+
+ def __on_remote_codecs(self, stanza, content, error, action):
+ """
+ Get peer codecs from what we get from peer
+ """
+
+ codecs = []
+ for codec in content.getTag('description').iterTags('payload-type'):
+ c = farsight.Codec(int(codec['id']), codec['name'],
+ self.farsight_media, int(codec['clockrate']))
+ if 'channels' in codec:
+ c.channels = int(codec['channels'])
+ else:
+ c.channels = 1
+ c.optional_params = [(str(p['name']), str(p['value'])) for p in \
+ codec.iterTags('parameter')]
+ codecs.append(c)
+
+ if codecs:
+ # FIXME: Handle this case:
+ # glib.GError: There was no intersection between the remote codecs and
+ # the local ones
+ self.p2pstream.set_remote_codecs(codecs)
+
+ def iter_codecs(self):
+ codecs = self.p2psession.get_property('codecs')
+ for codec in codecs:
+ attrs = {'name': codec.encoding_name,
+ 'id': codec.id,
+ 'channels': codec.channels}
+ if codec.clock_rate:
+ attrs['clockrate'] = codec.clock_rate
+ if codec.optional_params:
+ payload = (xmpp.Node('parameter', {'name': name, 'value': value})
+ for name, value in codec.optional_params)
+ else:
+ payload = ()
+ yield xmpp.Node('payload-type', attrs, payload)
+
+ def __stop(self, *things):
+ self.pipeline.set_state(gst.STATE_NULL)
+
+ def __del__(self):
+ self.__stop()
+
+ def destroy(self):
+ JingleContent.destroy(self)
+ self.p2pstream.disconnect_by_func(self._on_src_pad_added)
+ self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
class JingleAudio(JingleRTPContent):
- """
- Jingle VoIP sessions consist of audio content transported over an ICE UDP
- protocol
- """
+ """
+ Jingle VoIP sessions consist of audio content transported over an ICE UDP
+ protocol
+ """
- def __init__(self, session, transport=None):
- JingleRTPContent.__init__(self, session, 'audio', transport)
- self.setup_stream()
+ def __init__(self, session, transport=None):
+ JingleRTPContent.__init__(self, session, 'audio', transport)
+ self.setup_stream()
- def set_mic_volume(self, vol):
- """
- vol must be between 0 ans 1
- """
- self.mic_volume.set_property('volume', vol)
+ def set_mic_volume(self, vol):
+ """
+ vol must be between 0 ans 1
+ """
+ self.mic_volume.set_property('volume', vol)
- def set_out_volume(self, vol):
- """
- vol must be between 0 ans 1
- """
- self.out_volume.set_property('volume', vol)
+ def set_out_volume(self, vol):
+ """
+ vol must be between 0 ans 1
+ """
+ self.out_volume.set_property('volume', vol)
- def setup_stream(self):
- JingleRTPContent.setup_stream(self)
+ def setup_stream(self):
+ JingleRTPContent.setup_stream(self)
- # Configure SPEEX
- # Workaround for psi (not needed since rev
- # 147aedcea39b43402fe64c533d1866a25449888a):
- # place 16kHz before 8kHz, as buggy psi versions will take in
- # account only the first codec
+ # Configure SPEEX
+ # Workaround for psi (not needed since rev
+ # 147aedcea39b43402fe64c533d1866a25449888a):
+ # place 16kHz before 8kHz, as buggy psi versions will take in
+ # account only the first codec
- codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
- farsight.MEDIA_TYPE_AUDIO, 16000),
- farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
- farsight.MEDIA_TYPE_AUDIO, 8000)]
- self.p2psession.set_codec_preferences(codecs)
+ codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
+ farsight.MEDIA_TYPE_AUDIO, 16000),
+ farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
+ farsight.MEDIA_TYPE_AUDIO, 8000)]
+ self.p2psession.set_codec_preferences(codecs)
- # the local parts
- # TODO: Add queues?
- src_bin = self.make_bin_from_config('audio_input_device',
- '%s ! audioconvert', _("audio input"))
+ # the local parts
+ # TODO: Add queues?
+ src_bin = self.make_bin_from_config('audio_input_device',
+ '%s ! audioconvert', _("audio input"))
- self.sink = self.make_bin_from_config('audio_output_device',
- 'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output"))
+ self.sink = self.make_bin_from_config('audio_output_device',
+ 'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output"))
- self.mic_volume = src_bin.get_by_name('gajim_vol')
- self.out_volume = self.sink.get_by_name('gajim_out_vol')
+ self.mic_volume = src_bin.get_by_name('gajim_vol')
+ self.out_volume = self.sink.get_by_name('gajim_out_vol')
- # link gst elements
- self.pipeline.add(self.sink, src_bin)
+ # link gst elements
+ self.pipeline.add(self.sink, src_bin)
- src_bin.get_pad('src').link(self.p2psession.get_property(
- 'sink-pad'))
- self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
+ src_bin.get_pad('src').link(self.p2psession.get_property(
+ 'sink-pad'))
+ self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
- # The following is needed for farsight to process ICE requests:
- self.pipeline.set_state(gst.STATE_PLAYING)
+ # The following is needed for farsight to process ICE requests:
+ self.pipeline.set_state(gst.STATE_PLAYING)
class JingleVideo(JingleRTPContent):
- def __init__(self, session, transport=None):
- JingleRTPContent.__init__(self, session, 'video', transport)
- self.setup_stream()
+ def __init__(self, session, transport=None):
+ JingleRTPContent.__init__(self, session, 'video', transport)
+ self.setup_stream()
- def setup_stream(self):
- # TODO: Everything is not working properly:
- # sometimes, one window won't show up,
- # sometimes it'll freeze...
- JingleRTPContent.setup_stream(self)
+ def setup_stream(self):
+ # TODO: Everything is not working properly:
+ # sometimes, one window won't show up,
+ # sometimes it'll freeze...
+ JingleRTPContent.setup_stream(self)
- # the local parts
- src_bin = self.make_bin_from_config('video_input_device',
- '%s ! videoscale ! ffmpegcolorspace', _("video input"))
- #caps = gst.element_factory_make('capsfilter')
- #caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
+ # the local parts
+ src_bin = self.make_bin_from_config('video_input_device',
+ '%s ! videoscale ! ffmpegcolorspace', _("video input"))
+ #caps = gst.element_factory_make('capsfilter')
+ #caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
- self.pipeline.add(src_bin)#, caps)
- #src_bin.link(caps)
+ self.pipeline.add(src_bin)#, caps)
+ #src_bin.link(caps)
- self.sink = self.make_bin_from_config('video_output_device',
- 'videoscale ! ffmpegcolorspace ! %s', _("video output"))
- self.pipeline.add(self.sink)
+ self.sink = self.make_bin_from_config('video_output_device',
+ 'videoscale ! ffmpegcolorspace ! %s', _("video output"))
+ self.pipeline.add(self.sink)
- src_bin.get_pad('src').link(self.p2psession.get_property('sink-pad'))
- self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
+ src_bin.get_pad('src').link(self.p2psession.get_property('sink-pad'))
+ self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
- # The following is needed for farsight to process ICE requests:
- self.pipeline.set_state(gst.STATE_PLAYING)
+ # The following is needed for farsight to process ICE requests:
+ self.pipeline.set_state(gst.STATE_PLAYING)
def get_content(desc):
- if desc['media'] == 'audio':
- return JingleAudio
- elif desc['media'] == 'video':
- return JingleVideo
+ if desc['media'] == 'audio':
+ return JingleAudio
+ elif desc['media'] == 'video':
+ return JingleVideo
contents[xmpp.NS_JINGLE_RTP] = get_content
-
-# vim: se ts=3:
diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py
index 3e2392739..fd5fec1ef 100644
--- a/src/common/jingle_session.py
+++ b/src/common/jingle_session.py
@@ -33,624 +33,622 @@ from jingle_content import get_jingle_content, JingleContentSetupException
# FIXME: Move it to JingleSession.States?
class JingleStates(object):
- """
- States in which jingle session may exist
- """
- ended = 0
- pending = 1
- active = 2
+ """
+ States in which jingle session may exist
+ """
+ ended = 0
+ pending = 1
+ active = 2
class OutOfOrder(Exception):
- """
- Exception that should be raised when an action is received when in the wrong
- state
- """
+ """
+ Exception that should be raised when an action is received when in the wrong
+ state
+ """
class TieBreak(Exception):
- """
- Exception that should be raised in case of a tie, when we overrule the other
- action
- """
+ """
+ Exception that should be raised in case of a tie, when we overrule the other
+ action
+ """
class JingleSession(object):
- """
- This represents one jingle session, that is, one or more content types
- negotiated between an initiator and a responder.
- """
-
- def __init__(self, con, weinitiate, jid, sid=None):
- """
- con -- connection object,
- weinitiate -- boolean, are we the initiator?
- jid - jid of the other entity
- """
- self.contents = {} # negotiated contents
- self.connection = con # connection to use
- # our full jid
- #FIXME: Get rid of gajim here?
- self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \
- con.server_resource
- self.peerjid = jid # jid we connect to
- # jid we use as the initiator
- self.initiator = weinitiate and self.ourjid or self.peerjid
- # jid we use as the responder
- self.responder = weinitiate and self.peerjid or self.ourjid
- # are we an initiator?
- self.weinitiate = weinitiate
- # what state is session in? (one from JingleStates)
- self.state = JingleStates.ended
- if not sid:
- sid = con.connection.getAnID()
- self.sid = sid # sessionid
-
- self.accepted = True # is this session accepted by user
-
- # callbacks to call on proper contents
- # use .prepend() to add new callbacks, especially when you're going
- # to send error instead of ack
- self.callbacks = {
- 'content-accept': [self.__on_content_accept, self.__broadcast,
- self.__ack],
- 'content-add': [self.__on_content_add, self.__broadcast,
- self.__ack], #TODO
- 'content-modify': [self.__ack], #TODO
- 'content-reject': [self.__ack, self.__on_content_remove], #TODO
- 'content-remove': [self.__ack, self.__on_content_remove],
- 'description-info': [self.__broadcast, self.__ack], #TODO
- 'security-info': [self.__ack], #TODO
- 'session-accept': [self.__on_session_accept, self.__on_content_accept,
- self.__broadcast, self.__ack],
- 'session-info': [self.__broadcast, self.__on_session_info, self.__ack],
- 'session-initiate': [self.__on_session_initiate, self.__broadcast,
- self.__ack],
- 'session-terminate': [self.__on_session_terminate, self.__broadcast_all,
- self.__ack],
- 'transport-info': [self.__broadcast, self.__ack],
- 'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO
- 'transport-accept': [self.__ack], #TODO
- 'transport-reject': [self.__ack], #TODO
- 'iq-result': [],
- 'iq-error': [self.__on_error],
- }
-
- def approve_session(self):
- """
- Called when user accepts session in UI (when we aren't the initiator)
- """
- self.accept_session()
-
- def decline_session(self):
- """
- Called when user declines session in UI (when we aren't the initiator)
- """
- reason = xmpp.Node('reason')
- reason.addChild('decline')
- self._session_terminate(reason)
-
- def approve_content(self, media):
- content = self.get_content(media)
- if content:
- content.accepted = True
- self.on_session_state_changed(content)
-
- def reject_content(self, media):
- content = self.get_content(media)
- if content:
- if self.state == JingleStates.active:
- self.__content_reject(content)
- content.destroy()
- self.on_session_state_changed()
-
- def end_session(self):
- """
- Called when user stops or cancel session in UI
- """
- reason = xmpp.Node('reason')
- if self.state == JingleStates.active:
- reason.addChild('success')
- else:
- reason.addChild('cancel')
- self._session_terminate(reason)
-
- def get_content(self, media=None):
- if media is None:
- return
-
- for content in self.contents.values():
- if content.media == media:
- return content
-
- def add_content(self, name, content, creator='we'):
- """
- Add new content to session. If the session is active, this will send
- proper stanza to update session
-
- Creator must be one of ('we', 'peer', 'initiator', 'responder')
- """
- assert creator in ('we', 'peer', 'initiator', 'responder')
-
- if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \
- not self.weinitiate):
- creator = 'initiator'
- elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \
- not self.weinitiate):
- creator = 'responder'
- content.creator = creator
- content.name = name
- self.contents[(creator, name)] = content
-
- if (creator == 'initiator') == self.weinitiate:
- # The content is from us, accept it
- content.accepted = True
-
- def remove_content(self, creator, name):
- """
- We do not need this now
- """
- #TODO:
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- if len(self.contents) > 1:
- self.__content_remove(content)
- self.contents[(creator, name)].destroy()
- if len(self.contents) == 0:
- self.end_session()
-
- def modify_content(self, creator, name, *someother):
- """
- We do not need this now
- """
- pass
-
- def on_session_state_changed(self, content=None):
- if self.state == JingleStates.ended:
- # Session not yet started, only one action possible: session-initiate
- if self.is_ready() and self.weinitiate:
- self.__session_initiate()
- elif self.state == JingleStates.pending:
- # We can either send a session-accept or a content-add
- if self.is_ready() and not self.weinitiate:
- self.__session_accept()
- elif content and (content.creator == 'initiator') == self.weinitiate:
- self.__content_add(content)
- elif content and self.weinitiate:
- self.__content_accept(content)
- elif self.state == JingleStates.active:
- # We can either send a content-add or a content-accept
- if not content:
- return
- if (content.creator == 'initiator') == self.weinitiate:
- # We initiated this content. It's a pending content-add.
- self.__content_add(content)
- else:
- # The other side created this content, we accept it.
- self.__content_accept(content)
-
- def is_ready(self):
- """
- Return True when all codecs and candidates are ready (for all contents)
- """
- return (all((content.is_ready() for content in self.contents.itervalues()))
- and self.accepted)
-
- def accept_session(self):
- """
- Mark the session as accepted
- """
- self.accepted = True
- self.on_session_state_changed()
-
- def start_session(self):
- """
- Mark the session as ready to be started
- """
- self.accepted = True
- self.on_session_state_changed()
-
- def send_session_info(self):
- pass
-
- def send_content_accept(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-accept')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- def send_transport_info(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('transport-info')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- def on_stanza(self, stanza):
- """
- A callback for ConnectionJingle. It gets stanza, then tries to send it to
- all internally registered callbacks. First one to raise
- xmpp.NodeProcessed breaks function
- """
- jingle = stanza.getTag('jingle')
- error = stanza.getTag('error')
- if error:
- # it's an iq-error stanza
- action = 'iq-error'
- elif jingle:
- # it's a jingle action
- action = jingle.getAttr('action')
- if action not in self.callbacks:
- self.__send_error(stanza, 'bad_request')
- return
- # FIXME: If we aren't initiated and it's not a session-initiate...
- if action != 'session-initiate' and self.state == JingleStates.ended:
- self.__send_error(stanza, 'item-not-found', 'unknown-session')
- return
- else:
- # it's an iq-result (ack) stanza
- action = 'iq-result'
-
- callables = self.callbacks[action]
-
- try:
- for callable in callables:
- callable(stanza=stanza, jingle=jingle, error=error, action=action)
- except xmpp.NodeProcessed:
- pass
- except TieBreak:
- self.__send_error(stanza, 'conflict', 'tiebreak')
- except OutOfOrder:
- # FIXME
- self.__send_error(stanza, 'unexpected-request', 'out-of-order')
-
- def __ack(self, stanza, jingle, error, action):
- """
- Default callback for action stanzas -- simple ack and stop processing
- """
- response = stanza.buildReply('result')
- self.connection.connection.send(response)
-
- def __on_error(self, stanza, jingle, error, action):
- # FIXME
- text = error.getTagData('text')
- jingle_error = None
- xmpp_error = None
- for child in error.getChildren():
- if child.getNamespace() == xmpp.NS_JINGLE_ERRORS:
- jingle_error = child.getName()
- elif child.getNamespace() == xmpp.NS_STANZAS:
- xmpp_error = child.getName()
- self.__dispatch_error(xmpp_error, jingle_error, text)
- # FIXME: Not sure when we would want to do that...
- if xmpp_error == 'item-not-found':
- self.connection.delete_jingle_session(self.peerjid, self.sid)
-
- def __on_transport_replace(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- transport_ns = content.getTag('transport').getNamespace()
- if transport_ns == xmpp.JINGLE_ICE_UDP:
- # FIXME: We don't manage anything else than ICE-UDP now...
- # What was the previous transport?!?
- # Anyway, content's transport is not modifiable yet
- pass
- else:
- stanza, jingle = self.__make_jingle('transport-reject')
- content = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- content.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise xmpp.NodeProcessed
- else:
- # FIXME: This ressource is unknown to us, what should we do?
- # For now, reject the transport
- stanza, jingle = self.__make_jingle('transport-reject')
- c = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- c.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise xmpp.NodeProcessed
-
- def __on_session_info(self, stanza, jingle, error, action):
- # TODO: ringing, active, (un)hold, (un)mute
- payload = jingle.getPayload()
- if payload:
- self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info')
- raise xmpp.NodeProcessed
-
- def __on_content_remove(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- # TODO: this will fail if content is not an RTP content
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, content.media, 'removed'))
- content.destroy()
- if not self.contents:
- reason = xmpp.Node('reason')
- reason.setTag('success')
- self._session_terminate(reason)
-
- def __on_session_accept(self, stanza, jingle, error, action):
- # FIXME
- if self.state != JingleStates.pending:
- raise OutOfOrder
- self.state = JingleStates.active
-
- def __on_content_accept(self, stanza, jingle, error, action):
- """
- Called when we get content-accept stanza or equivalent one (like
- session-accept)
- """
- # check which contents are accepted
- for content in jingle.iterTags('content'):
- creator = content['creator']
- # TODO
- name = content['name']
-
- def __on_content_add(self, stanza, jingle, error, action):
- if self.state == JingleStates.ended:
- raise OutOfOrder
-
- parse_result = self.__parse_contents(jingle)
- contents = parse_result[0]
- rejected_contents = parse_result[1]
-
- for name, creator in rejected_contents:
- # TODO
- content = JingleContent()
- self.add_content(name, content, creator)
- self.__content_reject(content)
- self.contents[(content.creator, content.name)].destroy()
-
- self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
- contents))
-
- def __on_session_initiate(self, stanza, jingle, error, action):
- """
- We got a jingle session request from other entity, therefore we are the
- receiver... Unpack the data, inform the user
- """
- if self.state != JingleStates.ended:
- raise OutOfOrder
-
- self.initiator = jingle['initiator']
- self.responder = self.ourjid
- self.peerjid = self.initiator
- self.accepted = False # user did not accept this session yet
-
- # TODO: If the initiator is unknown to the receiver (e.g., via presence
- # subscription) and the receiver has a policy of not communicating via
- # Jingle with unknown entities, it SHOULD return a <service-unavailable/>
- # error.
-
- # Lets check what kind of jingle session does the peer want
- contents, contents_rejected, reason = self.__parse_contents(jingle)
-
- # If there's no content we understand...
- if not contents:
- # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
- reason = xmpp.Node('reason')
- reason.setTag(reason)
- self.__ack(stanza, jingle, error, action)
- self._session_terminate(reason)
- raise xmpp.NodeProcessed
-
- self.state = JingleStates.pending
-
- # Send event about starting a session
- self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
- contents))
-
- def __broadcast(self, stanza, jingle, error, action):
- """
- Broadcast the stanza contents to proper content handlers
- """
- for content in jingle.iterTags('content'):
- name = content['name']
- creator = content['creator']
- cn = self.contents[(creator, name)]
- cn.on_stanza(stanza, content, error, action)
-
- def __on_session_terminate(self, stanza, jingle, error, action):
- self.connection.delete_jingle_session(self.peerjid, self.sid)
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- # TODO
- text = reason
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, None, text))
-
- def __broadcast_all(self, stanza, jingle, error, action):
- """
- Broadcast the stanza to all content handlers
- """
- for content in self.contents.itervalues():
- content.on_stanza(stanza, None, error, action)
-
- def __parse_contents(self, jingle):
- # TODO: Needs some reworking
- contents = []
- contents_rejected = []
- reasons = set()
-
- for element in jingle.iterTags('content'):
- transport = get_jingle_transport(element.getTag('transport'))
- content_type = get_jingle_content(element.getTag('description'))
- if content_type:
- try:
- if transport:
- content = content_type(self, transport)
- self.add_content(element['name'],
- content, 'peer')
- contents.append((content.media,))
- else:
- reasons.add('unsupported-transports')
- contents_rejected.append((element['name'], 'peer'))
- except JingleContentSetupException:
- reasons.add('failed-application')
- else:
- contents_rejected.append((element['name'], 'peer'))
- failed.add('unsupported-applications')
-
- failure_reason = None
-
- # Store the first reason of failure
- for reason in ('failed-application', 'unsupported-transports',
- 'unsupported-applications'):
- if reason in reasons:
- failure_reason = reason
- break
-
- return (contents, contents_rejected, failure_reason)
-
- def __dispatch_error(self, error, jingle_error=None, text=None):
- if jingle_error:
- error = jingle_error
- if text:
- text = '%s (%s)' % (error, text)
- else:
- text = error
- self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text))
-
- def __reason_from_stanza(self, stanza):
- reason = 'success'
- reasons = ['success', 'busy', 'cancel', 'connectivity-error',
- 'decline', 'expired', 'failed-application', 'failed-transport',
- 'general-error', 'gone', 'incompatible-parameters', 'media-error',
- 'security-error', 'timeout', 'unsupported-applications',
- 'unsupported-transports']
- tag = stanza.getTag('reason')
- if tag:
- text = tag.getTagData('text')
- for r in reasons:
- if tag.getTag(r):
- reason = r
- break
- return (reason, text)
-
- def __make_jingle(self, action):
- stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
- attrs = {'action': action,
- 'sid': self.sid}
- if action == 'session-initiate':
- attrs['initiator'] = self.initiator
- elif action == 'session-accept':
- attrs['responder'] = self.responder
- jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
- return stanza, jingle
-
- def __send_error(self, stanza, error, jingle_error=None, text=None):
- err = xmpp.Error(stanza, '%s %s' % (xmpp.NS_STANZAS, error))
- if jingle_error:
- err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS)
- if text:
- err.setTagData('text', text)
- self.connection.connection.send(err)
- self.__dispatch_error(error, jingle_error, text)
-
- def __append_content(self, jingle, content):
- """
- Append <content/> element to <jingle/> element, with (full=True) or
- without (full=False) <content/> children
- """
- jingle.addChild('content',
- attrs={'name': content.name, 'creator': content.creator})
-
- def __append_contents(self, jingle):
- """
- Append all <content/> elements to <jingle/>
- """
- # TODO: integrate with __appendContent?
- # TODO: parameters 'name', 'content'?
- for content in self.contents.values():
- self.__append_content(jingle, content)
-
- def __session_initiate(self):
- assert self.state == JingleStates.ended
- stanza, jingle = self.__make_jingle('session-initiate')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
- self.connection.connection.send(stanza)
- self.state = JingleStates.pending
-
- def __session_accept(self):
- assert self.state == JingleStates.pending
- stanza, jingle = self.__make_jingle('session-accept')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'session-accept-sent')
- self.connection.connection.send(stanza)
- self.state = JingleStates.active
-
- def __session_info(self, payload=None):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('session-info')
- if payload:
- jingle.addChild(node=payload)
- self.connection.connection.send(stanza)
-
- def _session_terminate(self, reason=None):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('session-terminate')
- if reason is not None:
- jingle.addChild(node=reason)
- self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent')
- self.connection.connection.send(stanza)
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- text = reason
- self.connection.delete_jingle_session(self.peerjid, self.sid)
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, None, text))
-
- def __content_add(self, content):
- # TODO: test
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-add')
- self.__append_content(jingle, content)
- self.__broadcast(stanza, jingle, None, 'content-add-sent')
- self.connection.connection.send(stanza)
-
- def __content_accept(self, content):
- # TODO: test
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-accept')
- self.__append_content(jingle, content)
- self.__broadcast(stanza, jingle, None, 'content-accept-sent')
- self.connection.connection.send(stanza)
-
- def __content_reject(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-reject')
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- # TODO: this will fail if content is not an RTP content
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, content.media, 'rejected'))
-
- def __content_modify(self):
- assert self.state != JingleStates.ended
-
- def __content_remove(self, content):
- assert self.state != JingleStates.ended
- stanza, jingle = self.__make_jingle('content-remove')
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- # TODO: this will fail if content is not an RTP content
- self.connection.dispatch('JINGLE_DISCONNECTED',
- (self.peerjid, self.sid, content.media, 'removed'))
-
- def content_negotiated(self, media):
- self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid,
- media))
-
-# vim: se ts=3:
+ """
+ This represents one jingle session, that is, one or more content types
+ negotiated between an initiator and a responder.
+ """
+
+ def __init__(self, con, weinitiate, jid, sid=None):
+ """
+ con -- connection object,
+ weinitiate -- boolean, are we the initiator?
+ jid - jid of the other entity
+ """
+ self.contents = {} # negotiated contents
+ self.connection = con # connection to use
+ # our full jid
+ #FIXME: Get rid of gajim here?
+ self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \
+ con.server_resource
+ self.peerjid = jid # jid we connect to
+ # jid we use as the initiator
+ self.initiator = weinitiate and self.ourjid or self.peerjid
+ # jid we use as the responder
+ self.responder = weinitiate and self.peerjid or self.ourjid
+ # are we an initiator?
+ self.weinitiate = weinitiate
+ # what state is session in? (one from JingleStates)
+ self.state = JingleStates.ended
+ if not sid:
+ sid = con.connection.getAnID()
+ self.sid = sid # sessionid
+
+ self.accepted = True # is this session accepted by user
+
+ # callbacks to call on proper contents
+ # use .prepend() to add new callbacks, especially when you're going
+ # to send error instead of ack
+ self.callbacks = {
+ 'content-accept': [self.__on_content_accept, self.__broadcast,
+ self.__ack],
+ 'content-add': [self.__on_content_add, self.__broadcast,
+ self.__ack], #TODO
+ 'content-modify': [self.__ack], #TODO
+ 'content-reject': [self.__ack, self.__on_content_remove], #TODO
+ 'content-remove': [self.__ack, self.__on_content_remove],
+ 'description-info': [self.__broadcast, self.__ack], #TODO
+ 'security-info': [self.__ack], #TODO
+ 'session-accept': [self.__on_session_accept, self.__on_content_accept,
+ self.__broadcast, self.__ack],
+ 'session-info': [self.__broadcast, self.__on_session_info, self.__ack],
+ 'session-initiate': [self.__on_session_initiate, self.__broadcast,
+ self.__ack],
+ 'session-terminate': [self.__on_session_terminate, self.__broadcast_all,
+ self.__ack],
+ 'transport-info': [self.__broadcast, self.__ack],
+ 'transport-replace': [self.__broadcast, self.__on_transport_replace], #TODO
+ 'transport-accept': [self.__ack], #TODO
+ 'transport-reject': [self.__ack], #TODO
+ 'iq-result': [],
+ 'iq-error': [self.__on_error],
+ }
+
+ def approve_session(self):
+ """
+ Called when user accepts session in UI (when we aren't the initiator)
+ """
+ self.accept_session()
+
+ def decline_session(self):
+ """
+ Called when user declines session in UI (when we aren't the initiator)
+ """
+ reason = xmpp.Node('reason')
+ reason.addChild('decline')
+ self._session_terminate(reason)
+
+ def approve_content(self, media):
+ content = self.get_content(media)
+ if content:
+ content.accepted = True
+ self.on_session_state_changed(content)
+
+ def reject_content(self, media):
+ content = self.get_content(media)
+ if content:
+ if self.state == JingleStates.active:
+ self.__content_reject(content)
+ content.destroy()
+ self.on_session_state_changed()
+
+ def end_session(self):
+ """
+ Called when user stops or cancel session in UI
+ """
+ reason = xmpp.Node('reason')
+ if self.state == JingleStates.active:
+ reason.addChild('success')
+ else:
+ reason.addChild('cancel')
+ self._session_terminate(reason)
+
+ def get_content(self, media=None):
+ if media is None:
+ return
+
+ for content in self.contents.values():
+ if content.media == media:
+ return content
+
+ def add_content(self, name, content, creator='we'):
+ """
+ Add new content to session. If the session is active, this will send
+ proper stanza to update session
+
+ Creator must be one of ('we', 'peer', 'initiator', 'responder')
+ """
+ assert creator in ('we', 'peer', 'initiator', 'responder')
+
+ if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \
+ not self.weinitiate):
+ creator = 'initiator'
+ elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \
+ not self.weinitiate):
+ creator = 'responder'
+ content.creator = creator
+ content.name = name
+ self.contents[(creator, name)] = content
+
+ if (creator == 'initiator') == self.weinitiate:
+ # The content is from us, accept it
+ content.accepted = True
+
+ def remove_content(self, creator, name):
+ """
+ We do not need this now
+ """
+ #TODO:
+ if (creator, name) in self.contents:
+ content = self.contents[(creator, name)]
+ if len(self.contents) > 1:
+ self.__content_remove(content)
+ self.contents[(creator, name)].destroy()
+ if len(self.contents) == 0:
+ self.end_session()
+
+ def modify_content(self, creator, name, *someother):
+ """
+ We do not need this now
+ """
+ pass
+
+ def on_session_state_changed(self, content=None):
+ if self.state == JingleStates.ended:
+ # Session not yet started, only one action possible: session-initiate
+ if self.is_ready() and self.weinitiate:
+ self.__session_initiate()
+ elif self.state == JingleStates.pending:
+ # We can either send a session-accept or a content-add
+ if self.is_ready() and not self.weinitiate:
+ self.__session_accept()
+ elif content and (content.creator == 'initiator') == self.weinitiate:
+ self.__content_add(content)
+ elif content and self.weinitiate:
+ self.__content_accept(content)
+ elif self.state == JingleStates.active:
+ # We can either send a content-add or a content-accept
+ if not content:
+ return
+ if (content.creator == 'initiator') == self.weinitiate:
+ # We initiated this content. It's a pending content-add.
+ self.__content_add(content)
+ else:
+ # The other side created this content, we accept it.
+ self.__content_accept(content)
+
+ def is_ready(self):
+ """
+ Return True when all codecs and candidates are ready (for all contents)
+ """
+ return (all((content.is_ready() for content in self.contents.itervalues()))
+ and self.accepted)
+
+ def accept_session(self):
+ """
+ Mark the session as accepted
+ """
+ self.accepted = True
+ self.on_session_state_changed()
+
+ def start_session(self):
+ """
+ Mark the session as ready to be started
+ """
+ self.accepted = True
+ self.on_session_state_changed()
+
+ def send_session_info(self):
+ pass
+
+ def send_content_accept(self, content):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-accept')
+ jingle.addChild(node=content)
+ self.connection.connection.send(stanza)
+
+ def send_transport_info(self, content):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('transport-info')
+ jingle.addChild(node=content)
+ self.connection.connection.send(stanza)
+
+ def on_stanza(self, stanza):
+ """
+ A callback for ConnectionJingle. It gets stanza, then tries to send it to
+ all internally registered callbacks. First one to raise
+ xmpp.NodeProcessed breaks function
+ """
+ jingle = stanza.getTag('jingle')
+ error = stanza.getTag('error')
+ if error:
+ # it's an iq-error stanza
+ action = 'iq-error'
+ elif jingle:
+ # it's a jingle action
+ action = jingle.getAttr('action')
+ if action not in self.callbacks:
+ self.__send_error(stanza, 'bad_request')
+ return
+ # FIXME: If we aren't initiated and it's not a session-initiate...
+ if action != 'session-initiate' and self.state == JingleStates.ended:
+ self.__send_error(stanza, 'item-not-found', 'unknown-session')
+ return
+ else:
+ # it's an iq-result (ack) stanza
+ action = 'iq-result'
+
+ callables = self.callbacks[action]
+
+ try:
+ for callable in callables:
+ callable(stanza=stanza, jingle=jingle, error=error, action=action)
+ except xmpp.NodeProcessed:
+ pass
+ except TieBreak:
+ self.__send_error(stanza, 'conflict', 'tiebreak')
+ except OutOfOrder:
+ # FIXME
+ self.__send_error(stanza, 'unexpected-request', 'out-of-order')
+
+ def __ack(self, stanza, jingle, error, action):
+ """
+ Default callback for action stanzas -- simple ack and stop processing
+ """
+ response = stanza.buildReply('result')
+ self.connection.connection.send(response)
+
+ def __on_error(self, stanza, jingle, error, action):
+ # FIXME
+ text = error.getTagData('text')
+ jingle_error = None
+ xmpp_error = None
+ for child in error.getChildren():
+ if child.getNamespace() == xmpp.NS_JINGLE_ERRORS:
+ jingle_error = child.getName()
+ elif child.getNamespace() == xmpp.NS_STANZAS:
+ xmpp_error = child.getName()
+ self.__dispatch_error(xmpp_error, jingle_error, text)
+ # FIXME: Not sure when we would want to do that...
+ if xmpp_error == 'item-not-found':
+ self.connection.delete_jingle_session(self.peerjid, self.sid)
+
+ def __on_transport_replace(self, stanza, jingle, error, action):
+ for content in jingle.iterTags('content'):
+ creator = content['creator']
+ name = content['name']
+ if (creator, name) in self.contents:
+ transport_ns = content.getTag('transport').getNamespace()
+ if transport_ns == xmpp.JINGLE_ICE_UDP:
+ # FIXME: We don't manage anything else than ICE-UDP now...
+ # What was the previous transport?!?
+ # Anyway, content's transport is not modifiable yet
+ pass
+ else:
+ stanza, jingle = self.__make_jingle('transport-reject')
+ content = jingle.setTag('content', attrs={'creator': creator,
+ 'name': name})
+ content.setTag('transport', namespace=transport_ns)
+ self.connection.connection.send(stanza)
+ raise xmpp.NodeProcessed
+ else:
+ # FIXME: This ressource is unknown to us, what should we do?
+ # For now, reject the transport
+ stanza, jingle = self.__make_jingle('transport-reject')
+ c = jingle.setTag('content', attrs={'creator': creator,
+ 'name': name})
+ c.setTag('transport', namespace=transport_ns)
+ self.connection.connection.send(stanza)
+ raise xmpp.NodeProcessed
+
+ def __on_session_info(self, stanza, jingle, error, action):
+ # TODO: ringing, active, (un)hold, (un)mute
+ payload = jingle.getPayload()
+ if payload:
+ self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info')
+ raise xmpp.NodeProcessed
+
+ def __on_content_remove(self, stanza, jingle, error, action):
+ for content in jingle.iterTags('content'):
+ creator = content['creator']
+ name = content['name']
+ if (creator, name) in self.contents:
+ content = self.contents[(creator, name)]
+ # TODO: this will fail if content is not an RTP content
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, content.media, 'removed'))
+ content.destroy()
+ if not self.contents:
+ reason = xmpp.Node('reason')
+ reason.setTag('success')
+ self._session_terminate(reason)
+
+ def __on_session_accept(self, stanza, jingle, error, action):
+ # FIXME
+ if self.state != JingleStates.pending:
+ raise OutOfOrder
+ self.state = JingleStates.active
+
+ def __on_content_accept(self, stanza, jingle, error, action):
+ """
+ Called when we get content-accept stanza or equivalent one (like
+ session-accept)
+ """
+ # check which contents are accepted
+ for content in jingle.iterTags('content'):
+ creator = content['creator']
+ # TODO
+ name = content['name']
+
+ def __on_content_add(self, stanza, jingle, error, action):
+ if self.state == JingleStates.ended:
+ raise OutOfOrder
+
+ parse_result = self.__parse_contents(jingle)
+ contents = parse_result[0]
+ rejected_contents = parse_result[1]
+
+ for name, creator in rejected_contents:
+ # TODO
+ content = JingleContent()
+ self.add_content(name, content, creator)
+ self.__content_reject(content)
+ self.contents[(content.creator, content.name)].destroy()
+
+ self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
+ contents))
+
+ def __on_session_initiate(self, stanza, jingle, error, action):
+ """
+ We got a jingle session request from other entity, therefore we are the
+ receiver... Unpack the data, inform the user
+ """
+ if self.state != JingleStates.ended:
+ raise OutOfOrder
+
+ self.initiator = jingle['initiator']
+ self.responder = self.ourjid
+ self.peerjid = self.initiator
+ self.accepted = False # user did not accept this session yet
+
+ # TODO: If the initiator is unknown to the receiver (e.g., via presence
+ # subscription) and the receiver has a policy of not communicating via
+ # Jingle with unknown entities, it SHOULD return a <service-unavailable/>
+ # error.
+
+ # Lets check what kind of jingle session does the peer want
+ contents, contents_rejected, reason = self.__parse_contents(jingle)
+
+ # If there's no content we understand...
+ if not contents:
+ # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
+ reason = xmpp.Node('reason')
+ reason.setTag(reason)
+ self.__ack(stanza, jingle, error, action)
+ self._session_terminate(reason)
+ raise xmpp.NodeProcessed
+
+ self.state = JingleStates.pending
+
+ # Send event about starting a session
+ self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
+ contents))
+
+ def __broadcast(self, stanza, jingle, error, action):
+ """
+ Broadcast the stanza contents to proper content handlers
+ """
+ for content in jingle.iterTags('content'):
+ name = content['name']
+ creator = content['creator']
+ cn = self.contents[(creator, name)]
+ cn.on_stanza(stanza, content, error, action)
+
+ def __on_session_terminate(self, stanza, jingle, error, action):
+ self.connection.delete_jingle_session(self.peerjid, self.sid)
+ reason, text = self.__reason_from_stanza(jingle)
+ if reason not in ('success', 'cancel', 'decline'):
+ self.__dispatch_error(reason, reason, text)
+ if text:
+ text = '%s (%s)' % (reason, text)
+ else:
+ # TODO
+ text = reason
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, None, text))
+
+ def __broadcast_all(self, stanza, jingle, error, action):
+ """
+ Broadcast the stanza to all content handlers
+ """
+ for content in self.contents.itervalues():
+ content.on_stanza(stanza, None, error, action)
+
+ def __parse_contents(self, jingle):
+ # TODO: Needs some reworking
+ contents = []
+ contents_rejected = []
+ reasons = set()
+
+ for element in jingle.iterTags('content'):
+ transport = get_jingle_transport(element.getTag('transport'))
+ content_type = get_jingle_content(element.getTag('description'))
+ if content_type:
+ try:
+ if transport:
+ content = content_type(self, transport)
+ self.add_content(element['name'],
+ content, 'peer')
+ contents.append((content.media,))
+ else:
+ reasons.add('unsupported-transports')
+ contents_rejected.append((element['name'], 'peer'))
+ except JingleContentSetupException:
+ reasons.add('failed-application')
+ else:
+ contents_rejected.append((element['name'], 'peer'))
+ failed.add('unsupported-applications')
+
+ failure_reason = None
+
+ # Store the first reason of failure
+ for reason in ('failed-application', 'unsupported-transports',
+ 'unsupported-applications'):
+ if reason in reasons:
+ failure_reason = reason
+ break
+
+ return (contents, contents_rejected, failure_reason)
+
+ def __dispatch_error(self, error, jingle_error=None, text=None):
+ if jingle_error:
+ error = jingle_error
+ if text:
+ text = '%s (%s)' % (error, text)
+ else:
+ text = error
+ self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text))
+
+ def __reason_from_stanza(self, stanza):
+ reason = 'success'
+ reasons = ['success', 'busy', 'cancel', 'connectivity-error',
+ 'decline', 'expired', 'failed-application', 'failed-transport',
+ 'general-error', 'gone', 'incompatible-parameters', 'media-error',
+ 'security-error', 'timeout', 'unsupported-applications',
+ 'unsupported-transports']
+ tag = stanza.getTag('reason')
+ if tag:
+ text = tag.getTagData('text')
+ for r in reasons:
+ if tag.getTag(r):
+ reason = r
+ break
+ return (reason, text)
+
+ def __make_jingle(self, action):
+ stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
+ attrs = {'action': action,
+ 'sid': self.sid}
+ if action == 'session-initiate':
+ attrs['initiator'] = self.initiator
+ elif action == 'session-accept':
+ attrs['responder'] = self.responder
+ jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
+ return stanza, jingle
+
+ def __send_error(self, stanza, error, jingle_error=None, text=None):
+ err = xmpp.Error(stanza, '%s %s' % (xmpp.NS_STANZAS, error))
+ if jingle_error:
+ err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS)
+ if text:
+ err.setTagData('text', text)
+ self.connection.connection.send(err)
+ self.__dispatch_error(error, jingle_error, text)
+
+ def __append_content(self, jingle, content):
+ """
+ Append <content/> element to <jingle/> element, with (full=True) or
+ without (full=False) <content/> children
+ """
+ jingle.addChild('content',
+ attrs={'name': content.name, 'creator': content.creator})
+
+ def __append_contents(self, jingle):
+ """
+ Append all <content/> elements to <jingle/>
+ """
+ # TODO: integrate with __appendContent?
+ # TODO: parameters 'name', 'content'?
+ for content in self.contents.values():
+ self.__append_content(jingle, content)
+
+ def __session_initiate(self):
+ assert self.state == JingleStates.ended
+ stanza, jingle = self.__make_jingle('session-initiate')
+ self.__append_contents(jingle)
+ self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
+ self.connection.connection.send(stanza)
+ self.state = JingleStates.pending
+
+ def __session_accept(self):
+ assert self.state == JingleStates.pending
+ stanza, jingle = self.__make_jingle('session-accept')
+ self.__append_contents(jingle)
+ self.__broadcast(stanza, jingle, None, 'session-accept-sent')
+ self.connection.connection.send(stanza)
+ self.state = JingleStates.active
+
+ def __session_info(self, payload=None):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('session-info')
+ if payload:
+ jingle.addChild(node=payload)
+ self.connection.connection.send(stanza)
+
+ def _session_terminate(self, reason=None):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('session-terminate')
+ if reason is not None:
+ jingle.addChild(node=reason)
+ self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent')
+ self.connection.connection.send(stanza)
+ reason, text = self.__reason_from_stanza(jingle)
+ if reason not in ('success', 'cancel', 'decline'):
+ self.__dispatch_error(reason, reason, text)
+ if text:
+ text = '%s (%s)' % (reason, text)
+ else:
+ text = reason
+ self.connection.delete_jingle_session(self.peerjid, self.sid)
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, None, text))
+
+ def __content_add(self, content):
+ # TODO: test
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-add')
+ self.__append_content(jingle, content)
+ self.__broadcast(stanza, jingle, None, 'content-add-sent')
+ self.connection.connection.send(stanza)
+
+ def __content_accept(self, content):
+ # TODO: test
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-accept')
+ self.__append_content(jingle, content)
+ self.__broadcast(stanza, jingle, None, 'content-accept-sent')
+ self.connection.connection.send(stanza)
+
+ def __content_reject(self, content):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-reject')
+ self.__append_content(jingle, content)
+ self.connection.connection.send(stanza)
+ # TODO: this will fail if content is not an RTP content
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, content.media, 'rejected'))
+
+ def __content_modify(self):
+ assert self.state != JingleStates.ended
+
+ def __content_remove(self, content):
+ assert self.state != JingleStates.ended
+ stanza, jingle = self.__make_jingle('content-remove')
+ self.__append_content(jingle, content)
+ self.connection.connection.send(stanza)
+ # TODO: this will fail if content is not an RTP content
+ self.connection.dispatch('JINGLE_DISCONNECTED',
+ (self.peerjid, self.sid, content.media, 'removed'))
+
+ def content_negotiated(self, media):
+ self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid,
+ media))
diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py
index 82a0cb987..521564e87 100644
--- a/src/common/jingle_transport.py
+++ b/src/common/jingle_transport.py
@@ -20,131 +20,129 @@ import xmpp
transports = {}
def get_jingle_transport(node):
- namespace = node.getNamespace()
- if namespace in transports:
- return transports[namespace]()
+ namespace = node.getNamespace()
+ if namespace in transports:
+ return transports[namespace]()
class TransportType(object):
- """
- Possible types of a JingleTransport
- """
- datagram = 1
- streaming = 2
+ """
+ Possible types of a JingleTransport
+ """
+ datagram = 1
+ streaming = 2
class JingleTransport(object):
- """
- An abstraction of a transport in Jingle sessions
- """
-
- def __init__(self, type_):
- self.type = type_
- self.candidates = []
- self.remote_candidates = []
-
- def _iter_candidates(self):
- for candidate in self.candidates:
- yield self.make_candidate(candidate)
-
- def make_candidate(self, candidate):
- """
- Build a candidate stanza for the given candidate
- """
- pass
-
- def make_transport(self, candidates=None):
- """
- Build a transport stanza with the given candidates (or self.candidates if
- candidates is None)
- """
- if not candidates:
- candidates = self._iter_candidates()
- else:
- candidates = (self.make_candidate(candidate) for candidate in candidates)
- transport = xmpp.Node('transport', payload=candidates)
- return transport
-
- def parse_transport_stanza(self, transport):
- """
- Return the list of transport candidates from a transport stanza
- """
- return []
+ """
+ An abstraction of a transport in Jingle sessions
+ """
+
+ def __init__(self, type_):
+ self.type = type_
+ self.candidates = []
+ self.remote_candidates = []
+
+ def _iter_candidates(self):
+ for candidate in self.candidates:
+ yield self.make_candidate(candidate)
+
+ def make_candidate(self, candidate):
+ """
+ Build a candidate stanza for the given candidate
+ """
+ pass
+
+ def make_transport(self, candidates=None):
+ """
+ Build a transport stanza with the given candidates (or self.candidates if
+ candidates is None)
+ """
+ if not candidates:
+ candidates = self._iter_candidates()
+ else:
+ candidates = (self.make_candidate(candidate) for candidate in candidates)
+ transport = xmpp.Node('transport', payload=candidates)
+ return transport
+
+ def parse_transport_stanza(self, transport):
+ """
+ Return the list of transport candidates from a transport stanza
+ """
+ return []
import farsight
class JingleTransportICEUDP(JingleTransport):
- def __init__(self):
- JingleTransport.__init__(self, TransportType.datagram)
-
- def make_candidate(self, candidate):
- types = {farsight.CANDIDATE_TYPE_HOST: 'host',
- farsight.CANDIDATE_TYPE_SRFLX: 'srflx',
- farsight.CANDIDATE_TYPE_PRFLX: 'prflx',
- farsight.CANDIDATE_TYPE_RELAY: 'relay',
- farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'}
- attrs = {
- 'component': candidate.component_id,
- 'foundation': '1', # hack
- 'generation': '0',
- 'ip': candidate.ip,
- 'network': '0',
- 'port': candidate.port,
- 'priority': int(candidate.priority), # hack
- }
- if candidate.type in types:
- attrs['type'] = types[candidate.type]
- if candidate.proto == farsight.NETWORK_PROTOCOL_UDP:
- attrs['protocol'] = 'udp'
- else:
- # we actually don't handle properly different tcp options in jingle
- attrs['protocol'] = 'tcp'
- return xmpp.Node('candidate', attrs=attrs)
-
- def make_transport(self, candidates=None):
- transport = JingleTransport.make_transport(self, candidates)
- transport.setNamespace(xmpp.NS_JINGLE_ICE_UDP)
- if self.candidates and self.candidates[0].username and \
- self.candidates[0].password:
- transport.setAttr('ufrag', self.candidates[0].username)
- transport.setAttr('pwd', self.candidates[0].password)
- return transport
-
- def parse_transport_stanza(self, transport):
- candidates = []
- for candidate in transport.iterTags('candidate'):
- cand = farsight.Candidate()
- cand.component_id = int(candidate['component'])
- cand.ip = str(candidate['ip'])
- cand.port = int(candidate['port'])
- cand.foundation = str(candidate['foundation'])
- #cand.type = farsight.CANDIDATE_TYPE_LOCAL
- cand.priority = int(candidate['priority'])
-
- if candidate['protocol'] == 'udp':
- cand.proto = farsight.NETWORK_PROTOCOL_UDP
- else:
- # we actually don't handle properly different tcp options in jingle
- cand.proto = farsight.NETWORK_PROTOCOL_TCP
-
- cand.username = str(transport['ufrag'])
- cand.password = str(transport['pwd'])
-
- #FIXME: huh?
- types = {'host': farsight.CANDIDATE_TYPE_HOST,
- 'srflx': farsight.CANDIDATE_TYPE_SRFLX,
- 'prflx': farsight.CANDIDATE_TYPE_PRFLX,
- 'relay': farsight.CANDIDATE_TYPE_RELAY,
- 'multicast': farsight.CANDIDATE_TYPE_MULTICAST}
- if 'type' in candidate and candidate['type'] in types:
- cand.type = types[candidate['type']]
- else:
- print 'Unknown type %s', candidate['type']
- candidates.append(cand)
- self.remote_candidates.extend(candidates)
- return candidates
+ def __init__(self):
+ JingleTransport.__init__(self, TransportType.datagram)
+
+ def make_candidate(self, candidate):
+ types = {farsight.CANDIDATE_TYPE_HOST: 'host',
+ farsight.CANDIDATE_TYPE_SRFLX: 'srflx',
+ farsight.CANDIDATE_TYPE_PRFLX: 'prflx',
+ farsight.CANDIDATE_TYPE_RELAY: 'relay',
+ farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'}
+ attrs = {
+ 'component': candidate.component_id,
+ 'foundation': '1', # hack
+ 'generation': '0',
+ 'ip': candidate.ip,
+ 'network': '0',
+ 'port': candidate.port,
+ 'priority': int(candidate.priority), # hack
+ }
+ if candidate.type in types:
+ attrs['type'] = types[candidate.type]
+ if candidate.proto == farsight.NETWORK_PROTOCOL_UDP:
+ attrs['protocol'] = 'udp'
+ else:
+ # we actually don't handle properly different tcp options in jingle
+ attrs['protocol'] = 'tcp'
+ return xmpp.Node('candidate', attrs=attrs)
+
+ def make_transport(self, candidates=None):
+ transport = JingleTransport.make_transport(self, candidates)
+ transport.setNamespace(xmpp.NS_JINGLE_ICE_UDP)
+ if self.candidates and self.candidates[0].username and \
+ self.candidates[0].password:
+ transport.setAttr('ufrag', self.candidates[0].username)
+ transport.setAttr('pwd', self.candidates[0].password)
+ return transport
+
+ def parse_transport_stanza(self, transport):
+ candidates = []
+ for candidate in transport.iterTags('candidate'):
+ cand = farsight.Candidate()
+ cand.component_id = int(candidate['component'])
+ cand.ip = str(candidate['ip'])
+ cand.port = int(candidate['port'])
+ cand.foundation = str(candidate['foundation'])
+ #cand.type = farsight.CANDIDATE_TYPE_LOCAL
+ cand.priority = int(candidate['priority'])
+
+ if candidate['protocol'] == 'udp':
+ cand.proto = farsight.NETWORK_PROTOCOL_UDP
+ else:
+ # we actually don't handle properly different tcp options in jingle
+ cand.proto = farsight.NETWORK_PROTOCOL_TCP
+
+ cand.username = str(transport['ufrag'])
+ cand.password = str(transport['pwd'])
+
+ #FIXME: huh?
+ types = {'host': farsight.CANDIDATE_TYPE_HOST,
+ 'srflx': farsight.CANDIDATE_TYPE_SRFLX,
+ 'prflx': farsight.CANDIDATE_TYPE_PRFLX,
+ 'relay': farsight.CANDIDATE_TYPE_RELAY,
+ 'multicast': farsight.CANDIDATE_TYPE_MULTICAST}
+ if 'type' in candidate and candidate['type'] in types:
+ cand.type = types[candidate['type']]
+ else:
+ print 'Unknown type %s', candidate['type']
+ candidates.append(cand)
+ self.remote_candidates.extend(candidates)
+ return candidates
transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP
-
-# vim: se ts=3:
diff --git a/src/common/kwalletbinding.py b/src/common/kwalletbinding.py
index 842aff8b9..af320c2f8 100644
--- a/src/common/kwalletbinding.py
+++ b/src/common/kwalletbinding.py
@@ -25,56 +25,56 @@ import subprocess
def kwallet_available():
- """
- Return True if kwalletcli can be run, False otherwise
- """
- try:
- p = subprocess.Popen(["kwalletcli", "-qV"])
- except Exception:
- return False
- p.communicate()
- if p.returncode == 0:
- return True
- return False
+ """
+ Return True if kwalletcli can be run, False otherwise
+ """
+ try:
+ p = subprocess.Popen(["kwalletcli", "-qV"])
+ except Exception:
+ return False
+ p.communicate()
+ if p.returncode == 0:
+ return True
+ return False
def kwallet_get(folder, entry):
- """
- Retrieve a passphrase from the KDE Wallet via kwalletcli
+ """
+ Retrieve a passphrase from the KDE Wallet via kwalletcli
- Arguments:
- • folder: The top-level category to use (normally the programme name)
- • entry: The key of the entry to retrieve
+ Arguments:
+ • folder: The top-level category to use (normally the programme name)
+ • entry: The key of the entry to retrieve
- Returns the passphrase as unicode, False if it cannot be found,
- or None if an error occured.
- """
- p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'),
- "-e", entry.encode('utf-8')], stdout=subprocess.PIPE)
- pw = p.communicate()[0]
- if p.returncode == 0:
- return unicode(pw.decode('utf-8'))
- if p.returncode == 1 or p.returncode == 4:
- # ENOENT
- return False
- # error
- return None
+ Returns the passphrase as unicode, False if it cannot be found,
+ or None if an error occured.
+ """
+ p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'),
+ "-e", entry.encode('utf-8')], stdout=subprocess.PIPE)
+ pw = p.communicate()[0]
+ if p.returncode == 0:
+ return unicode(pw.decode('utf-8'))
+ if p.returncode == 1 or p.returncode == 4:
+ # ENOENT
+ return False
+ # error
+ return None
def kwallet_put(folder, entry, passphrase):
- """
- Store a passphrase into the KDE Wallet via kwalletcli
+ """
+ Store a passphrase into the KDE Wallet via kwalletcli
- Arguments:
- • folder: The top-level category to use (normally the programme name)
- • entry: The key of the entry to store
- • passphrase: The value to store
+ Arguments:
+ • folder: The top-level category to use (normally the programme name)
+ • entry: The key of the entry to store
+ • passphrase: The value to store
- Returns True on success, False otherwise.
- """
- p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'),
- "-e", entry.encode('utf-8'), "-P"], stdin=subprocess.PIPE)
- p.communicate(passphrase.encode('utf-8'))
- if p.returncode == 0:
- return True
- return False
+ Returns True on success, False otherwise.
+ """
+ p = subprocess.Popen(["kwalletcli", "-q", "-f", folder.encode('utf-8'),
+ "-e", entry.encode('utf-8'), "-P"], stdin=subprocess.PIPE)
+ p.communicate(passphrase.encode('utf-8'))
+ if p.returncode == 0:
+ return True
+ return False
diff --git a/src/common/latex.py b/src/common/latex.py
index 492f04c39..a95f1f9f7 100644
--- a/src/common/latex.py
+++ b/src/common/latex.py
@@ -41,121 +41,119 @@ import helpers
# some latex commands are really bad
blacklist = ['\\def', '\\let', '\\futurelet',
- '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write',
- '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter',
- '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode',
- '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname',
- '\\newhelp', '\\relax', '\\afterground', '\\afterassignment',
- '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop',
- '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name',
- '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[',
- '\\]']
+ '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write',
+ '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter',
+ '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode',
+ '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname',
+ '\\newhelp', '\\relax', '\\afterground', '\\afterassignment',
+ '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop',
+ '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name',
+ '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[',
+ '\\]']
# True if the string matches the blacklist
def check_blacklist(str_):
- for word in blacklist:
- if word in str_:
- return True
- return False
+ for word in blacklist:
+ if word in str_:
+ return True
+ return False
def get_tmpfile_name():
- random.seed()
- int_ = random.randint(0, 100)
- return os.path.join(gettempdir(), 'gajimtex_' + int_.__str__())
+ random.seed()
+ int_ = random.randint(0, 100)
+ return os.path.join(gettempdir(), 'gajimtex_' + int_.__str__())
def write_latex(filename, str_):
- texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}'
- texstr += '\\usepackage{amsmath}\\usepackage{amssymb}'
- texstr += '\\pagestyle{empty}'
- texstr += '\\begin{document}\\begin{large}\\begin{gather*}'
- texstr += str_
- texstr += '\\end{gather*}\\end{large}\\end{document}'
-
- file_ = open(filename, "w+")
- file_.write(texstr)
- file_.flush()
- file_.close()
+ texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}'
+ texstr += '\\usepackage{amsmath}\\usepackage{amssymb}'
+ texstr += '\\pagestyle{empty}'
+ texstr += '\\begin{document}\\begin{large}\\begin{gather*}'
+ texstr += str_
+ texstr += '\\end{gather*}\\end{large}\\end{document}'
+
+ file_ = open(filename, "w+")
+ file_.write(texstr)
+ file_.flush()
+ file_.close()
# a wrapper for Popen so that no window gets opened on Windows
# (i think this is the reason we're using Popen rather than just system())
# stdout goes to a pipe so that it can be read
def popen_nt_friendly(command):
- if os.name == 'nt':
- # CREATE_NO_WINDOW
- return Popen(command, creationflags=0x08000000, cwd=gettempdir(), stdout=PIPE)
- else:
- return Popen(command, cwd=gettempdir(), stdout=PIPE)
+ if os.name == 'nt':
+ # CREATE_NO_WINDOW
+ return Popen(command, creationflags=0x08000000, cwd=gettempdir(), stdout=PIPE)
+ else:
+ return Popen(command, cwd=gettempdir(), stdout=PIPE)
def check_for_latex_support():
- """
- Check if latex is available and if it can create a picture
- """
- try:
- filename = latex_to_image("test")
- if filename:
- # we have a file, conversion succeeded
- os.remove(filename)
- return True
- return False
- except LatexError:
- return False
+ """
+ Check if latex is available and if it can create a picture
+ """
+ try:
+ filename = latex_to_image("test")
+ if filename:
+ # we have a file, conversion succeeded
+ os.remove(filename)
+ return True
+ return False
+ except LatexError:
+ return False
def try_run(argv):
- try:
- p = popen_nt_friendly(argv)
- out = p.communicate()[0]
- log.info(out)
- return p.wait()
- except Exception, e:
- return _('Error executing "%(command)s": %(error)s') % {
- 'command': " ".join(argv),
- 'error': helpers.decode_string(str(e))}
+ try:
+ p = popen_nt_friendly(argv)
+ out = p.communicate()[0]
+ log.info(out)
+ return p.wait()
+ except Exception, e:
+ return _('Error executing "%(command)s": %(error)s') % {
+ 'command': " ".join(argv),
+ 'error': helpers.decode_string(str(e))}
def latex_to_image(str_):
- result = None
- exitcode = 0
-
- try:
- bg_str, fg_str = gajim.interface.get_bg_fg_colors()
- except:
- # interface may not be available when we test latext at startup
- bg_str, fg_str = 'rgb 1.0 1.0 1.0', 'rgb 0.0 0.0 0.0'
-
- # filter latex code with bad commands
- if check_blacklist(str_):
- # we triggered the blacklist, immediately return None
- return None
-
- tmpfile = get_tmpfile_name()
-
- # build latex string
- write_latex(os.path.join(tmpfile + '.tex'), str_)
-
- # convert TeX to dvi
- exitcode = try_run(['latex', '--interaction=nonstopmode',
- tmpfile + '.tex'])
-
- if exitcode == 0:
- # convert dvi to png
- latex_png_dpi = gajim.config.get('latex_png_dpi')
- exitcode = try_run(['dvipng', '-bg', bg_str, '-fg', fg_str, '-T',
- 'tight', '-D', latex_png_dpi, tmpfile + '.dvi', '-o',
- tmpfile + '.png'])
-
- # remove temp files created by us and TeX
- extensions = ['.tex', '.log', '.aux', '.dvi']
- for ext in extensions:
- try:
- os.remove(tmpfile + ext)
- except Exception:
- pass
-
- if isinstance(exitcode, (unicode, str)):
- raise LatexError(exitcode)
-
- if exitcode == 0:
- result = tmpfile + '.png'
-
- return result
-
-# vim: se ts=3:
+ result = None
+ exitcode = 0
+
+ try:
+ bg_str, fg_str = gajim.interface.get_bg_fg_colors()
+ except:
+ # interface may not be available when we test latext at startup
+ bg_str, fg_str = 'rgb 1.0 1.0 1.0', 'rgb 0.0 0.0 0.0'
+
+ # filter latex code with bad commands
+ if check_blacklist(str_):
+ # we triggered the blacklist, immediately return None
+ return None
+
+ tmpfile = get_tmpfile_name()
+
+ # build latex string
+ write_latex(os.path.join(tmpfile + '.tex'), str_)
+
+ # convert TeX to dvi
+ exitcode = try_run(['latex', '--interaction=nonstopmode',
+ tmpfile + '.tex'])
+
+ if exitcode == 0:
+ # convert dvi to png
+ latex_png_dpi = gajim.config.get('latex_png_dpi')
+ exitcode = try_run(['dvipng', '-bg', bg_str, '-fg', fg_str, '-T',
+ 'tight', '-D', latex_png_dpi, tmpfile + '.dvi', '-o',
+ tmpfile + '.png'])
+
+ # remove temp files created by us and TeX
+ extensions = ['.tex', '.log', '.aux', '.dvi']
+ for ext in extensions:
+ try:
+ os.remove(tmpfile + ext)
+ except Exception:
+ pass
+
+ if isinstance(exitcode, (unicode, str)):
+ raise LatexError(exitcode)
+
+ if exitcode == 0:
+ result = tmpfile + '.png'
+
+ return result
diff --git a/src/common/location_listener.py b/src/common/location_listener.py
index f1ba5b714..37a2e4612 100644
--- a/src/common/location_listener.py
+++ b/src/common/location_listener.py
@@ -22,116 +22,116 @@ from common import gajim
from common import pep
from common import dbus_support
if dbus_support.supported:
- import dbus
- import dbus.glib
+ import dbus
+ import dbus.glib
class LocationListener:
- _instance = None
- @classmethod
- def get(cls):
- if cls._instance is None:
- cls._instance = cls()
- return cls._instance
+ _instance = None
+ @classmethod
+ def get(cls):
+ if cls._instance is None:
+ cls._instance = cls()
+ return cls._instance
- def __init__(self):
- self._data = {}
+ def __init__(self):
+ self._data = {}
- def get_data(self):
- self._get_address()
- self._get_position()
+ def get_data(self):
+ self._get_address()
+ self._get_position()
- def _get_address(self):
- bus = dbus.SessionBus()
- if 'org.freedesktop.Geoclue.Master' not in bus.list_names():
- self._on_geoclue_address_changed()
- return
- obj = bus.get_object('org.freedesktop.Geoclue.Master',
- '/org/freedesktop/Geoclue/Master')
- # get MasterClient path
- path = obj.Create()
- # get MasterClient
- cli = bus.get_object('org.freedesktop.Geoclue.Master', path)
- cli.AddressStart()
- # Check that there is a provider
- name, description, service, path = cli.GetAddressProvider()
- if path:
- timestamp, address, accuracy = cli.GetAddress()
- self._on_geoclue_address_changed(timestamp, address, accuracy)
+ def _get_address(self):
+ bus = dbus.SessionBus()
+ if 'org.freedesktop.Geoclue.Master' not in bus.list_names():
+ self._on_geoclue_address_changed()
+ return
+ obj = bus.get_object('org.freedesktop.Geoclue.Master',
+ '/org/freedesktop/Geoclue/Master')
+ # get MasterClient path
+ path = obj.Create()
+ # get MasterClient
+ cli = bus.get_object('org.freedesktop.Geoclue.Master', path)
+ cli.AddressStart()
+ # Check that there is a provider
+ name, description, service, path = cli.GetAddressProvider()
+ if path:
+ timestamp, address, accuracy = cli.GetAddress()
+ self._on_geoclue_address_changed(timestamp, address, accuracy)
- def _get_position(self):
- bus = dbus.SessionBus()
- if 'org.freedesktop.Geoclue.Master' not in bus.list_names():
- self._on_geoclue_position_changed()
- return
- obj = bus.get_object('org.freedesktop.Geoclue.Master',
- '/org/freedesktop/Geoclue/Master')
- # get MasterClient path
- path = obj.Create()
- # get MasterClient
- cli = bus.get_object('org.freedesktop.Geoclue.Master', path)
- cli.PositionStart()
- # Check that there is a provider
- name, description, service, path = cli.GetPositionProvider()
- if path:
- fields, timestamp, lat, lon, alt, accuray = cli.GetPosition()
- self._on_geoclue_position_changed(fields, timestamp, lat, lon, alt,
- accuracy)
+ def _get_position(self):
+ bus = dbus.SessionBus()
+ if 'org.freedesktop.Geoclue.Master' not in bus.list_names():
+ self._on_geoclue_position_changed()
+ return
+ obj = bus.get_object('org.freedesktop.Geoclue.Master',
+ '/org/freedesktop/Geoclue/Master')
+ # get MasterClient path
+ path = obj.Create()
+ # get MasterClient
+ cli = bus.get_object('org.freedesktop.Geoclue.Master', path)
+ cli.PositionStart()
+ # Check that there is a provider
+ name, description, service, path = cli.GetPositionProvider()
+ if path:
+ fields, timestamp, lat, lon, alt, accuray = cli.GetPosition()
+ self._on_geoclue_position_changed(fields, timestamp, lat, lon, alt,
+ accuracy)
- def start(self):
- self.get_data()
- bus = dbus.SessionBus()
- # Geoclue
- bus.add_signal_receiver(self._on_geoclue_address_changed,
- 'AddressChanged', 'org.freedesktop.Geoclue.Address')
- bus.add_signal_receiver(self._on_geoclue_position_changed,
- 'PositionChanged', 'org.freedesktop.Geoclue.Position')
+ def start(self):
+ self.get_data()
+ bus = dbus.SessionBus()
+ # Geoclue
+ bus.add_signal_receiver(self._on_geoclue_address_changed,
+ 'AddressChanged', 'org.freedesktop.Geoclue.Address')
+ bus.add_signal_receiver(self._on_geoclue_position_changed,
+ 'PositionChanged', 'org.freedesktop.Geoclue.Position')
- def shut_down(self):
- pass
+ def shut_down(self):
+ pass
- def _on_geoclue_address_changed(self, timestamp=None, address={},
- accuracy=None):
- # update data with info we just received
- for field in ['country', 'countrycode', 'locality', 'postalcode',
- 'region', 'street']:
- self._data[field] = address.get(field, None)
- if timestamp:
- self._data['timestamp'] = timestamp
- if accuracy:
- # in PEP it's horizontal accuracy
- self._data['accuracy'] = accuracy[1]
- self._send_location()
+ def _on_geoclue_address_changed(self, timestamp=None, address={},
+ accuracy=None):
+ # update data with info we just received
+ for field in ['country', 'countrycode', 'locality', 'postalcode',
+ 'region', 'street']:
+ self._data[field] = address.get(field, None)
+ if timestamp:
+ self._data['timestamp'] = timestamp
+ if accuracy:
+ # in PEP it's horizontal accuracy
+ self._data['accuracy'] = accuracy[1]
+ self._send_location()
- def _on_geoclue_position_changed(self, fields=[], timestamp=None, lat=None,
- lon=None, alt=None, accuracy=None):
- # update data with info we just received
- _dict = {'lat': lat, 'lon': lon, 'alt': alt}
- for field in _dict:
- if _dict[field] is not None:
- self._data[field] = _dict[field]
- if timestamp:
- self._data['timestamp'] = timestamp
- if accuracy:
- # in PEP it's horizontal accuracy
- self._data['accuracy'] = accuracy[1]
- self._send_location()
+ def _on_geoclue_position_changed(self, fields=[], timestamp=None, lat=None,
+ lon=None, alt=None, accuracy=None):
+ # update data with info we just received
+ _dict = {'lat': lat, 'lon': lon, 'alt': alt}
+ for field in _dict:
+ if _dict[field] is not None:
+ self._data[field] = _dict[field]
+ if timestamp:
+ self._data['timestamp'] = timestamp
+ if accuracy:
+ # in PEP it's horizontal accuracy
+ self._data['accuracy'] = accuracy[1]
+ self._send_location()
- def _send_location(self):
- accounts = gajim.connections.keys()
- for acct in accounts:
- if not gajim.account_is_connected(acct):
- continue
- if not gajim.config.get_per('accounts', acct, 'publish_location'):
- continue
- if gajim.connections[acct].location_info == self._data:
- continue
- gajim.connections[acct].send_location(self._data)
- gajim.connections[acct].location_info = self._data
+ def _send_location(self):
+ accounts = gajim.connections.keys()
+ for acct in accounts:
+ if not gajim.account_is_connected(acct):
+ continue
+ if not gajim.config.get_per('accounts', acct, 'publish_location'):
+ continue
+ if gajim.connections[acct].location_info == self._data:
+ continue
+ gajim.connections[acct].send_location(self._data)
+ gajim.connections[acct].location_info = self._data
def enable():
- listener = LocationListener.get()
- listener.start()
+ listener = LocationListener.get()
+ listener.start()
def disable():
- listener = LocationListener.get()
- listener.shut_down()
+ listener = LocationListener.get()
+ listener.shut_down()
diff --git a/src/common/logger.py b/src/common/logger.py
index a9297c463..b1079690a 100644
--- a/src/common/logger.py
+++ b/src/common/logger.py
@@ -46,1009 +46,1007 @@ LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
CACHE_DB_PATH = configpaths.gajimpaths['CACHE_DB']
class Constants:
- def __init__(self):
- (
- self.JID_NORMAL_TYPE,
- self.JID_ROOM_TYPE
- ) = range(2)
-
- (
- self.KIND_STATUS,
- self.KIND_GCSTATUS,
- self.KIND_GC_MSG,
- self.KIND_SINGLE_MSG_RECV,
- self.KIND_CHAT_MSG_RECV,
- self.KIND_SINGLE_MSG_SENT,
- self.KIND_CHAT_MSG_SENT,
- self.KIND_ERROR
- ) = range(8)
-
- (
- self.SHOW_ONLINE,
- self.SHOW_CHAT,
- self.SHOW_AWAY,
- self.SHOW_XA,
- self.SHOW_DND,
- self.SHOW_OFFLINE
- ) = range(6)
-
- (
- self.TYPE_AIM,
- self.TYPE_GG,
- self.TYPE_HTTP_WS,
- self.TYPE_ICQ,
- self.TYPE_MSN,
- self.TYPE_QQ,
- self.TYPE_SMS,
- self.TYPE_SMTP,
- self.TYPE_TLEN,
- self.TYPE_YAHOO,
- self.TYPE_NEWMAIL,
- self.TYPE_RSS,
- self.TYPE_WEATHER,
- self.TYPE_MRIM,
- ) = range(14)
-
- (
- self.SUBSCRIPTION_NONE,
- self.SUBSCRIPTION_TO,
- self.SUBSCRIPTION_FROM,
- self.SUBSCRIPTION_BOTH,
- ) = range(4)
+ def __init__(self):
+ (
+ self.JID_NORMAL_TYPE,
+ self.JID_ROOM_TYPE
+ ) = range(2)
+
+ (
+ self.KIND_STATUS,
+ self.KIND_GCSTATUS,
+ self.KIND_GC_MSG,
+ self.KIND_SINGLE_MSG_RECV,
+ self.KIND_CHAT_MSG_RECV,
+ self.KIND_SINGLE_MSG_SENT,
+ self.KIND_CHAT_MSG_SENT,
+ self.KIND_ERROR
+ ) = range(8)
+
+ (
+ self.SHOW_ONLINE,
+ self.SHOW_CHAT,
+ self.SHOW_AWAY,
+ self.SHOW_XA,
+ self.SHOW_DND,
+ self.SHOW_OFFLINE
+ ) = range(6)
+
+ (
+ self.TYPE_AIM,
+ self.TYPE_GG,
+ self.TYPE_HTTP_WS,
+ self.TYPE_ICQ,
+ self.TYPE_MSN,
+ self.TYPE_QQ,
+ self.TYPE_SMS,
+ self.TYPE_SMTP,
+ self.TYPE_TLEN,
+ self.TYPE_YAHOO,
+ self.TYPE_NEWMAIL,
+ self.TYPE_RSS,
+ self.TYPE_WEATHER,
+ self.TYPE_MRIM,
+ ) = range(14)
+
+ (
+ self.SUBSCRIPTION_NONE,
+ self.SUBSCRIPTION_TO,
+ self.SUBSCRIPTION_FROM,
+ self.SUBSCRIPTION_BOTH,
+ ) = range(4)
constants = Constants()
class Logger:
- def __init__(self):
- self.jids_already_in = [] # holds jids that we already have in DB
- self.con = None
-
- if not os.path.exists(LOG_DB_PATH):
- # this can happen only the first time (the time we create the db)
- # db is not created here but in src/common/checks_paths.py
- return
- self.init_vars()
- if not os.path.exists(CACHE_DB_PATH):
- # this can happen cache database is not present when gajim is launched
- # db will be created in src/common/checks_paths.py
- return
- self.attach_cache_database()
-
- def close_db(self):
- if self.con:
- self.con.close()
- self.con = None
- self.cur = None
-
- def open_db(self):
- self.close_db()
-
- # FIXME: sqlite3_open wants UTF8 strings. So a path with
- # non-ascii chars doesn't work. See #2812 and
- # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html
- back = os.getcwd()
- os.chdir(LOG_DB_FOLDER)
-
- # if locked, wait up to 20 sec to unlock
- # before raise (hopefully should be enough)
-
- self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0,
- isolation_level='IMMEDIATE')
- os.chdir(back)
- self.cur = self.con.cursor()
- self.set_synchronous(False)
-
- def attach_cache_database(self):
- try:
- self.cur.execute("ATTACH DATABASE '%s' AS cache" % CACHE_DB_PATH)
- except sqlite.Error, e:
- gajim.log.debug("Failed to attach cache database: %s" % str(e))
-
- def set_synchronous(self, sync):
- try:
- if sync:
- self.cur.execute("PRAGMA synchronous = NORMAL")
- else:
- self.cur.execute("PRAGMA synchronous = OFF")
- except sqlite.Error, e:
- gajim.log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e)))
-
- def init_vars(self):
- self.open_db()
- self.get_jids_already_in_db()
-
- def simple_commit(self, sql_to_commit):
- """
- Helper to commit
- """
- self.cur.execute(sql_to_commit)
- try:
- self.con.commit()
- except sqlite.OperationalError, e:
- print >> sys.stderr, str(e)
-
- def get_jids_already_in_db(self):
- try:
- self.cur.execute('SELECT jid FROM jids')
- # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
- rows = self.cur.fetchall()
- except sqlite.DatabaseError:
- raise exceptions.DatabaseMalformed
- self.jids_already_in = []
- for row in rows:
- # row[0] is first item of row (the only result here, the jid)
- if row[0] == '':
- # malformed jid, ignore line
- pass
- else:
- self.jids_already_in.append(row[0])
-
- def get_jids_in_db(self):
- return self.jids_already_in
-
- def jid_is_from_pm(self, jid):
- """
- If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
- is not a normal guy and nkour is not his resource? we ask if gajim@conf
- is already in jids (with type room jid) this fails if user disables
- logging for room and only enables for pm (so higly unlikely) and if we
- fail we do not go chaos (user will see the first pm as if it was message
- in room's public chat) and after that all okay
- """
- if jid.find('/') > -1:
- possible_room_jid = jid.split('/', 1)[0]
- return self.jid_is_room_jid(possible_room_jid)
- else:
- # it's not a full jid, so it's not a pm one
- return False
-
- def jid_is_room_jid(self, jid):
- self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?',
- (jid, constants.JID_ROOM_TYPE))
- row = self.cur.fetchone()
- if row is None:
- return False
- else:
- return True
-
- def get_jid_id(self, jid, typestr=None):
- """
- jids table has jid and jid_id logs table has log_id, jid_id,
- contact_name, time, kind, show, message so to ask logs we need jid_id
- that matches our jid in jids table this method wants jid and returns the
- jid_id for later sql-ing on logs typestr can be 'ROOM' or anything else
- depending on the type of JID and is only needed to be specified when the
- JID is new in DB
- """
- if jid.find('/') != -1: # if it has a /
- jid_is_from_pm = self.jid_is_from_pm(jid)
- if not jid_is_from_pm: # it's normal jid with resource
- jid = jid.split('/', 1)[0] # remove the resource
- if jid in self.jids_already_in: # we already have jids in DB
- self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid])
- row = self.cur.fetchone()
- if row:
- return row[0]
- # oh! a new jid :), we add it now
- if typestr == 'ROOM':
- typ = constants.JID_ROOM_TYPE
- else:
- typ = constants.JID_NORMAL_TYPE
- try:
- self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid,
- typ))
- self.con.commit()
- except sqlite.IntegrityError, e:
- # Jid already in DB, maybe added by another instance. re-read DB
- self.get_jids_already_in_db()
- return self.get_jid_id(jid, typestr)
- except sqlite.OperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
- jid_id = self.cur.lastrowid
- self.jids_already_in.append(jid)
- return jid_id
-
- def convert_human_values_to_db_api_values(self, kind, show):
- """
- Convert from string style to constant ints for db
- """
- if kind == 'status':
- kind_col = constants.KIND_STATUS
- elif kind == 'gcstatus':
- kind_col = constants.KIND_GCSTATUS
- elif kind == 'gc_msg':
- kind_col = constants.KIND_GC_MSG
- elif kind == 'single_msg_recv':
- kind_col = constants.KIND_SINGLE_MSG_RECV
- elif kind == 'single_msg_sent':
- kind_col = constants.KIND_SINGLE_MSG_SENT
- elif kind == 'chat_msg_recv':
- kind_col = constants.KIND_CHAT_MSG_RECV
- elif kind == 'chat_msg_sent':
- kind_col = constants.KIND_CHAT_MSG_SENT
- elif kind == 'error':
- kind_col = constants.KIND_ERROR
-
- if show == 'online':
- show_col = constants.SHOW_ONLINE
- elif show == 'chat':
- show_col = constants.SHOW_CHAT
- elif show == 'away':
- show_col = constants.SHOW_AWAY
- elif show == 'xa':
- show_col = constants.SHOW_XA
- elif show == 'dnd':
- show_col = constants.SHOW_DND
- elif show == 'offline':
- show_col = constants.SHOW_OFFLINE
- elif show is None:
- show_col = None
- else: # invisible in GC when someone goes invisible
- # it's a RFC violation .... but we should not crash
- show_col = 'UNKNOWN'
-
- return kind_col, show_col
-
- def convert_human_transport_type_to_db_api_values(self, type_):
- """
- Convert from string style to constant ints for db
- """
- if type_ == 'aim':
- return constants.TYPE_AIM
- if type_ == 'gadu-gadu':
- return constants.TYPE_GG
- if type_ == 'http-ws':
- return constants.TYPE_HTTP_WS
- if type_ == 'icq':
- return constants.TYPE_ICQ
- if type_ == 'msn':
- return constants.TYPE_MSN
- if type_ == 'qq':
- return constants.TYPE_QQ
- if type_ == 'sms':
- return constants.TYPE_SMS
- if type_ == 'smtp':
- return constants.TYPE_SMTP
- if type_ in ('tlen', 'x-tlen'):
- return constants.TYPE_TLEN
- if type_ == 'yahoo':
- return constants.TYPE_YAHOO
- if type_ == 'newmail':
- return constants.TYPE_NEWMAIL
- if type_ == 'rss':
- return constants.TYPE_RSS
- if type_ == 'weather':
- return constants.TYPE_WEATHER
- if type_ == 'mrim':
- return constants.TYPE_MRIM
- return None
-
- def convert_api_values_to_human_transport_type(self, type_id):
- """
- Convert from constant ints for db to string style
- """
- if type_id == constants.TYPE_AIM:
- return 'aim'
- if type_id == constants.TYPE_GG:
- return 'gadu-gadu'
- if type_id == constants.TYPE_HTTP_WS:
- return 'http-ws'
- if type_id == constants.TYPE_ICQ:
- return 'icq'
- if type_id == constants.TYPE_MSN:
- return 'msn'
- if type_id == constants.TYPE_QQ:
- return 'qq'
- if type_id == constants.TYPE_SMS:
- return 'sms'
- if type_id == constants.TYPE_SMTP:
- return 'smtp'
- if type_id == constants.TYPE_TLEN:
- return 'tlen'
- if type_id == constants.TYPE_YAHOO:
- return 'yahoo'
- if type_id == constants.TYPE_NEWMAIL:
- return 'newmail'
- if type_id == constants.TYPE_RSS:
- return 'rss'
- if type_id == constants.TYPE_WEATHER:
- return 'weather'
- if type_id == constants.TYPE_MRIM:
- return 'mrim'
-
- def convert_human_subscription_values_to_db_api_values(self, sub):
- """
- Convert from string style to constant ints for db
- """
- if sub == 'none':
- return constants.SUBSCRIPTION_NONE
- if sub == 'to':
- return constants.SUBSCRIPTION_TO
- if sub == 'from':
- return constants.SUBSCRIPTION_FROM
- if sub == 'both':
- return constants.SUBSCRIPTION_BOTH
-
- def convert_db_api_values_to_human_subscription_values(self, sub):
- """
- Convert from constant ints for db to string style
- """
- if sub == constants.SUBSCRIPTION_NONE:
- return 'none'
- if sub == constants.SUBSCRIPTION_TO:
- return 'to'
- if sub == constants.SUBSCRIPTION_FROM:
- return 'from'
- if sub == constants.SUBSCRIPTION_BOTH:
- return 'both'
-
- def commit_to_db(self, values, write_unread=False):
- sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show,
- message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)'''
- try:
- self.cur.execute(sql, values)
- except sqlite.DatabaseError:
- raise exceptions.DatabaseMalformed
- except sqlite.OperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
- message_id = None
- try:
- self.con.commit()
- if write_unread:
- message_id = self.cur.lastrowid
- except sqlite.OperationalError, e:
- print >> sys.stderr, str(e)
- if message_id:
- self.insert_unread_events(message_id, values[0])
- return message_id
-
- def insert_unread_events(self, message_id, jid_id):
- """
- Add unread message with id: message_id
- """
- sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id,
- jid_id)
- self.simple_commit(sql)
-
- def set_read_messages(self, message_ids):
- """
- Mark all messages with ids in message_ids as read
- """
- ids = ','.join([str(i) for i in message_ids])
- sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids
- self.simple_commit(sql)
-
- def set_shown_unread_msgs(self, msg_id):
- """
- Mark unread message as shown un GUI
- """
- sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \
- msg_id
- self.simple_commit(sql)
-
- def reset_shown_unread_messages(self):
- """
- Set shown field to False in unread_messages table
- """
- sql = 'UPDATE unread_messages SET shown = 0'
- self.simple_commit(sql)
-
- def get_unread_msgs(self):
- """
- Get all unread messages
- """
- all_messages = []
- try:
- self.cur.execute(
- 'SELECT message_id, shown from unread_messages')
- results = self.cur.fetchall()
- except Exception:
- pass
- for message in results:
- msg_id = message[0]
- shown = message[1]
- # here we get infos for that message, and related jid from jids table
- # do NOT change order of SELECTed things, unless you change function(s)
- # that called this function
- self.cur.execute('''
- SELECT logs.log_line_id, logs.message, logs.time, logs.subject,
- jids.jid
- FROM logs, jids
- WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id
- ''' % msg_id
- )
- results = self.cur.fetchall()
- if len(results) == 0:
- # Log line is no more in logs table. remove it from unread_messages
- self.set_read_messages([msg_id])
- continue
- all_messages.append(results[0] + (shown,))
- return all_messages
-
- def write(self, kind, jid, message=None, show=None, tim=None, subject=None):
- """
- Write a row (status, gcstatus, message etc) to logs database
-
- kind can be status, gcstatus, gc_msg, (we only recv for those 3),
- single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent we cannot
- know if it is pm or normal chat message, we try to guess see
- jid_is_from_pm()
-
- We analyze jid and store it as follows:
- jids.jid text column will hold JID if TC-related, room_jid if GC-related,
- ROOM_JID/nick if pm-related.
- """
-
- if self.jids_already_in == []: # only happens if we just created the db
- self.open_db()
-
- contact_name_col = None # holds nickname for kinds gcstatus, gc_msg
- # message holds the message unless kind is status or gcstatus,
- # then it holds status message
- message_col = message
- subject_col = subject
- if tim:
- time_col = int(float(time.mktime(tim)))
- else:
- time_col = int(float(time.time()))
-
- kind_col, show_col = self.convert_human_values_to_db_api_values(kind,
- show)
-
- write_unread = False
-
- # now we may have need to do extra care for some values in columns
- if kind == 'status': # we store (not None) time, jid, show, msg
- # status for roster items
- try:
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
- if show is None: # show is None (xmpp), but we say that 'online'
- show_col = constants.SHOW_ONLINE
-
- elif kind == 'gcstatus':
- # status in ROOM (for pm status see status)
- if show is None: # show is None (xmpp), but we say that 'online'
- show_col = constants.SHOW_ONLINE
- jid, nick = jid.split('/', 1)
- try:
- # re-get jid_id for the new jid
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
- contact_name_col = nick
-
- elif kind == 'gc_msg':
- if jid.find('/') != -1: # if it has a /
- jid, nick = jid.split('/', 1)
- else:
- # it's server message f.e. error message
- # when user tries to ban someone but he's not allowed to
- nick = None
- try:
- # re-get jid_id for the new jid
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
- contact_name_col = nick
- else:
- try:
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
- if kind == 'chat_msg_recv':
- if not self.jid_is_from_pm(jid):
- # Save in unread table only if it's not a pm
- write_unread = True
-
- if show_col == 'UNKNOWN': # unknown show, do not log
- return
-
- values = (jid_id, contact_name_col, time_col, kind_col, show_col,
- message_col, subject_col)
- return self.commit_to_db(values, write_unread)
-
- def get_last_conversation_lines(self, jid, restore_how_many_rows,
- pending_how_many, timeout, account):
- """
- Accept how many rows to restore and when to time them out (in minutes)
- (mark them as too old) and number of messages that are in queue and are
- already logged but pending to be viewed, returns a list of tupples
- containg time, kind, message, list with empty tupple if nothing found to
- meet our demands
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- # Error trying to create a new jid_id. This means there is no log
- return []
- where_sql = self._build_contact_where(account, jid)
-
- now = int(float(time.time()))
- timed_out = now - (timeout * 60) # before that they are too old
- # so if we ask last 5 lines and we have 2 pending we get
- # 3 - 8 (we avoid the last 2 lines but we still return 5 asked)
- try:
- self.cur.execute('''
- SELECT time, kind, message FROM logs
- WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d
- ORDER BY time DESC LIMIT %d OFFSET %d
- ''' % (where_sql, constants.KIND_SINGLE_MSG_RECV,
- constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT,
- constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR,
- timed_out, restore_how_many_rows, pending_how_many)
- )
-
- results = self.cur.fetchall()
- except sqlite.DatabaseError:
- raise exceptions.DatabaseMalformed
- results.reverse()
- return results
-
- def get_unix_time_from_date(self, year, month, day):
- # year (fe 2005), month (fe 11), day (fe 25)
- # returns time in seconds for the second that starts that date since epoch
- # gimme unixtime from year month day:
- d = datetime.date(year, month, day)
- local_time = d.timetuple() # time tupple (compat with time.localtime())
- # we have time since epoch baby :)
- start_of_day = int(time.mktime(local_time))
- return start_of_day
-
- def get_conversation_for_date(self, jid, year, month, day, account):
- """
- Return contact_name, time, kind, show, message, subject
-
- For each row in a list of tupples, returns list with empty tupple if we
- found nothing to meet our demands
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- # Error trying to create a new jid_id. This means there is no log
- return []
- where_sql = self._build_contact_where(account, jid)
-
- start_of_day = self.get_unix_time_from_date(year, month, day)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_day = start_of_day + seconds_in_a_day - 1
-
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, start_of_day, last_second_of_day))
-
- results = self.cur.fetchall()
- return results
-
- def get_search_results_for_query(self, jid, query, account):
- """
- Returns contact_name, time, kind, show, message
-
- For each row in a list of tupples, returns list with empty tupple if we
- found nothing to meet our demands
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- # Error trying to create a new jid_id. This means there is no log
- return []
-
- if False: # query.startswith('SELECT '): # it's SQL query (FIXME)
- try:
- self.cur.execute(query)
- except sqlite.OperationalError, e:
- results = [('', '', '', '', str(e))]
- return results
-
- else: # user just typed something, we search in message column
- where_sql = self._build_contact_where(account, jid)
- like_sql = '%' + query.replace("'", "''") + '%'
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject FROM logs
- WHERE (%s) AND message LIKE '%s'
- ORDER BY time
- ''' % (where_sql, like_sql))
-
- results = self.cur.fetchall()
- return results
-
- def get_days_with_logs(self, jid, year, month, max_day, account):
- """
- Return the list of days that have logs (not status messages)
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- # Error trying to create a new jid_id. This means there is no log
- return []
- days_with_logs = []
- where_sql = self._build_contact_where(account, jid)
-
- # First select all date of month whith logs we want
- start_of_month = self.get_unix_time_from_date(year, month, 1)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1
-
- # Select times and 'floor' them to time 0:00
- # (by dividing, they are integers)
- # and take only one of the same values (distinct)
- # Now we have timestamps of time 0:00 of every day with logs
- self.cur.execute('''
- SELECT DISTINCT time/(86400)*86400 FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- AND kind NOT IN (%d, %d)
- ORDER BY time
- ''' % (where_sql, start_of_month, last_second_of_month,
- constants.KIND_STATUS, constants.KIND_GCSTATUS))
- result = self.cur.fetchall()
-
- # convert timestamps to day of month
- for line in result:
- days_with_logs[0:0]=[time.gmtime(line[0])[2]]
-
- return days_with_logs
-
- def get_last_date_that_has_logs(self, jid, account=None, is_room=False):
- """
- Return last time (in seconds since EPOCH) for which we had logs
- (excluding statuses)
- """
- where_sql = ''
- if not is_room:
- where_sql = self._build_contact_where(account, jid)
- else:
- try:
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError, e:
- # Error trying to create a new jid_id. This means there is no log
- return None
- where_sql = 'jid_id = %s' % jid_id
- self.cur.execute('''
- SELECT MAX(time) FROM logs
- WHERE (%s)
- AND kind NOT IN (%d, %d)
- ''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS))
-
- results = self.cur.fetchone()
- if results is not None:
- result = results[0]
- else:
- result = None
- return result
-
- def get_room_last_message_time(self, jid):
- """
- Return FASTLY last time (in seconds since EPOCH) for which we had logs
- for that room from rooms_last_message_time table
- """
- try:
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError, e:
- # Error trying to create a new jid_id. This means there is no log
- return None
- where_sql = 'jid_id = %s' % jid_id
- self.cur.execute('''
- SELECT time FROM rooms_last_message_time
- WHERE (%s)
- ''' % (where_sql))
-
- results = self.cur.fetchone()
- if results is not None:
- result = results[0]
- else:
- result = None
- return result
-
- def set_room_last_message_time(self, jid, time):
- """
- Set last time (in seconds since EPOCH) for which we had logs for that
- room in rooms_last_message_time table
- """
- jid_id = self.get_jid_id(jid, 'ROOM')
- # jid_id is unique in this table, create or update :
- sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \
- (jid_id, time)
- self.simple_commit(sql)
-
- def _build_contact_where(self, account, jid):
- """
- Build the where clause for a jid, including metacontacts jid(s) if any
- """
- where_sql = ''
- # will return empty list if jid is not associated with
- # any metacontacts
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- for user in family:
- try:
- jid_id = self.get_jid_id(user['jid'])
- except exceptions.PysqliteOperationalError, e:
- continue
- where_sql += 'jid_id = %s' % jid_id
- if user != family[-1]:
- where_sql += ' OR '
- else: # if jid was not associated with metacontacts
- jid_id = self.get_jid_id(jid)
- where_sql = 'jid_id = %s' % jid_id
- return where_sql
-
- def save_transport_type(self, jid, type_):
- """
- Save the type of the transport in DB
- """
- type_id = self.convert_human_transport_type_to_db_api_values(type_)
- if not type_id:
- # unknown type
- return
- self.cur.execute(
- 'SELECT type from transports_cache WHERE transport = "%s"' % jid)
- results = self.cur.fetchall()
- if results:
- result = results[0][0]
- if result == type_id:
- return
- sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\
- (type_id, jid)
- self.simple_commit(sql)
- return
- sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id)
- self.simple_commit(sql)
-
- def get_transports_type(self):
- """
- Return all the type of the transports in DB
- """
- self.cur.execute(
- 'SELECT * from transports_cache')
- results = self.cur.fetchall()
- if not results:
- return {}
- answer = {}
- for result in results:
- answer[result[0]] = self.convert_api_values_to_human_transport_type(
- result[1])
- return answer
-
- # A longer note here:
- # The database contains a blob field. Pysqlite seems to need special care for
- # such fields.
- # When storing, we need to convert string into buffer object (1).
- # When retrieving, we need to convert it back to a string to decompress it.
- # (2)
- # GzipFile needs a file-like object, StringIO emulates file for plain strings
- def iter_caps_data(self):
- """
- Iterate over caps cache data stored in the database
-
- The iterator values are pairs of (node, ver, ext, identities, features):
- identities == {'category':'foo', 'type':'bar', 'name':'boo'},
- features being a list of feature namespaces.
- """
- # get data from table
- # the data field contains binary object (gzipped data), this is a hack
- # to get that data without trying to convert it to unicode
- try:
- self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;')
- except sqlite.OperationalError:
- # might happen when there's no caps_cache table yet
- # -- there's no data to read anyway then
- return
-
- # list of corrupted entries that will be removed
- to_be_removed = []
- for hash_method, hash_, data in self.cur:
- # for each row: unpack the data field
- # (format: (category, type, name, category, type, name, ...
- # ..., 'FEAT', feature1, feature2, ...).join(' '))
- # NOTE: if there's a need to do more gzip, put that to a function
- try:
- data = GzipFile(fileobj=StringIO(str(data))).read().decode(
- 'utf-8').split('\0')
- except IOError:
- # This data is corrupted. It probably contains non-ascii chars
- to_be_removed.append((hash_method, hash_))
- continue
- i = 0
- identities = list()
- features = list()
- while i < (len(data) - 3) and data[i] != 'FEAT':
- category = data[i]
- type_ = data[i + 1]
- lang = data[i + 2]
- name = data[i + 3]
- identities.append({'category': category, 'type': type_,
- 'xml:lang': lang, 'name': name})
- i += 4
- i+=1
- while i < len(data):
- features.append(data[i])
- i += 1
-
- # yield the row
- yield hash_method, hash_, identities, features
- for hash_method, hash_ in to_be_removed:
- sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND
- hash = "%s"''' % (hash_method, hash_)
- self.simple_commit(sql)
-
- def add_caps_entry(self, hash_method, hash_, identities, features):
- data = []
- for identity in identities:
- # there is no FEAT category
- if identity['category'] == 'FEAT':
- return
- data.extend((identity.get('category'), identity.get('type', ''),
- identity.get('xml:lang', ''), identity.get('name', '')))
- data.append('FEAT')
- data.extend(features)
- data = '\0'.join(data)
- # if there's a need to do more gzip, put that to a function
- string = StringIO()
- gzip = GzipFile(fileobj=string, mode='w')
- data = data.encode('utf-8') # the gzip module can't handle unicode objects
- gzip.write(data)
- gzip.close()
- data = string.getvalue()
- self.cur.execute('''
- INSERT INTO caps_cache ( hash_method, hash, data, last_seen )
- VALUES (?, ?, ?, ?);
- ''', (hash_method, hash_, buffer(data), int(time.time())))
- # (1) -- note above
- try:
- self.con.commit()
- except sqlite.OperationalError, e:
- print >> sys.stderr, str(e)
-
- def update_caps_time(self, method, hash_):
- sql = '''UPDATE caps_cache SET last_seen = %d
- WHERE hash_method = "%s" and hash = "%s"''' % \
- (int(time.time()), method, hash_)
- self.simple_commit(sql)
-
- def clean_caps_table(self):
- """
- Remove caps which was not seen for 3 months
- """
- sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \
- int(time.time() - 3*30*24*3600)
- self.simple_commit(sql)
-
- def replace_roster(self, account_name, roster_version, roster):
- """
- Replace current roster in DB by a new one
-
- accout_name is the name of the account to change.
- roster_version is the version of the new roster.
- roster is the new version.
- """
- # First we must reset roster_version value to ensure that the server
- # sends back all the roster at the next connexion if the replacement
- # didn't work properly.
- gajim.config.set_per('accounts', account_name, 'roster_version', '')
-
- account_jid = gajim.get_jid_from_account(account_name)
- account_jid_id = self.get_jid_id(account_jid)
-
- # Delete old roster
- self.remove_roster(account_jid)
-
- # Fill roster tables with the new roster
- for jid in roster:
- self.add_or_update_contact(account_jid, jid, roster[jid]['name'],
- roster[jid]['subscription'], roster[jid]['ask'],
- roster[jid]['groups'])
-
- # At this point, we are sure the replacement works properly so we can
- # set the new roster_version value.
- gajim.config.set_per('accounts', account_name, 'roster_version',
- roster_version)
-
- def del_contact(self, account_jid, jid):
- """
- Remove jid from account_jid roster
- """
- try:
- account_jid_id = self.get_jid_id(account_jid)
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
- self.cur.execute(
- 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- self.cur.execute(
- 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- self.con.commit()
-
- def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups):
- """
- Add or update a contact from account_jid roster
- """
- if sub == 'remove':
- self.del_contact(account_jid, jid)
- return
-
- try:
- account_jid_id = self.get_jid_id(account_jid)
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError, e:
- raise exceptions.PysqliteOperationalError(str(e))
-
- # Update groups information
- # First we delete all previous groups information
- self.cur.execute(
- 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- # Then we add all new groups information
- for group in groups:
- self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)',
- (account_jid_id, jid_id, group))
-
- if name is None:
- name = ''
-
- self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)',
- (account_jid_id, jid_id, name,
- self.convert_human_subscription_values_to_db_api_values(sub),
- bool(ask)))
- self.con.commit()
-
- def get_roster(self, account_jid):
- """
- Return the accound_jid roster in NonBlockingRoster format
- """
- data = {}
- account_jid_id = self.get_jid_id(account_jid)
-
- # First we fill data with roster_entry informations
- self.cur.execute('''
- SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
- FROM roster_entry re, jids j
- WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
- for jid, jid_id, name, subscription, ask in self.cur:
- data[jid] = {}
- if name:
- data[jid]['name'] = name
- else:
- data[jid]['name'] = None
- data[jid]['subscription'] = \
- self.convert_db_api_values_to_human_subscription_values(
- subscription)
- data[jid]['groups'] = []
- data[jid]['resources'] = {}
- if ask:
- data[jid]['ask'] = 'subscribe'
- else:
- data[jid]['ask'] = None
- data[jid]['id'] = jid_id
-
- # Then we add group for roster entries
- for jid in data:
- self.cur.execute('''
- SELECT group_name FROM roster_group
- WHERE account_jid_id=? AND jid_id=?''',
- (account_jid_id, data[jid]['id']))
- for (group_name,) in self.cur:
- data[jid]['groups'].append(group_name)
- del data[jid]['id']
-
- return data
-
- def remove_roster(self, account_jid):
- """
- Remove all entry from account_jid roster
- """
- account_jid_id = self.get_jid_id(account_jid)
-
- self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?',
- (account_jid_id,))
- self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?',
- (account_jid_id,))
- self.con.commit()
-
-# vim: se ts=3:
+ def __init__(self):
+ self.jids_already_in = [] # holds jids that we already have in DB
+ self.con = None
+
+ if not os.path.exists(LOG_DB_PATH):
+ # this can happen only the first time (the time we create the db)
+ # db is not created here but in src/common/checks_paths.py
+ return
+ self.init_vars()
+ if not os.path.exists(CACHE_DB_PATH):
+ # this can happen cache database is not present when gajim is launched
+ # db will be created in src/common/checks_paths.py
+ return
+ self.attach_cache_database()
+
+ def close_db(self):
+ if self.con:
+ self.con.close()
+ self.con = None
+ self.cur = None
+
+ def open_db(self):
+ self.close_db()
+
+ # FIXME: sqlite3_open wants UTF8 strings. So a path with
+ # non-ascii chars doesn't work. See #2812 and
+ # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html
+ back = os.getcwd()
+ os.chdir(LOG_DB_FOLDER)
+
+ # if locked, wait up to 20 sec to unlock
+ # before raise (hopefully should be enough)
+
+ self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0,
+ isolation_level='IMMEDIATE')
+ os.chdir(back)
+ self.cur = self.con.cursor()
+ self.set_synchronous(False)
+
+ def attach_cache_database(self):
+ try:
+ self.cur.execute("ATTACH DATABASE '%s' AS cache" % CACHE_DB_PATH)
+ except sqlite.Error, e:
+ gajim.log.debug("Failed to attach cache database: %s" % str(e))
+
+ def set_synchronous(self, sync):
+ try:
+ if sync:
+ self.cur.execute("PRAGMA synchronous = NORMAL")
+ else:
+ self.cur.execute("PRAGMA synchronous = OFF")
+ except sqlite.Error, e:
+ gajim.log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e)))
+
+ def init_vars(self):
+ self.open_db()
+ self.get_jids_already_in_db()
+
+ def simple_commit(self, sql_to_commit):
+ """
+ Helper to commit
+ """
+ self.cur.execute(sql_to_commit)
+ try:
+ self.con.commit()
+ except sqlite.OperationalError, e:
+ print >> sys.stderr, str(e)
+
+ def get_jids_already_in_db(self):
+ try:
+ self.cur.execute('SELECT jid FROM jids')
+ # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
+ rows = self.cur.fetchall()
+ except sqlite.DatabaseError:
+ raise exceptions.DatabaseMalformed
+ self.jids_already_in = []
+ for row in rows:
+ # row[0] is first item of row (the only result here, the jid)
+ if row[0] == '':
+ # malformed jid, ignore line
+ pass
+ else:
+ self.jids_already_in.append(row[0])
+
+ def get_jids_in_db(self):
+ return self.jids_already_in
+
+ def jid_is_from_pm(self, jid):
+ """
+ If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
+ is not a normal guy and nkour is not his resource? we ask if gajim@conf
+ is already in jids (with type room jid) this fails if user disables
+ logging for room and only enables for pm (so higly unlikely) and if we
+ fail we do not go chaos (user will see the first pm as if it was message
+ in room's public chat) and after that all okay
+ """
+ if jid.find('/') > -1:
+ possible_room_jid = jid.split('/', 1)[0]
+ return self.jid_is_room_jid(possible_room_jid)
+ else:
+ # it's not a full jid, so it's not a pm one
+ return False
+
+ def jid_is_room_jid(self, jid):
+ self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?',
+ (jid, constants.JID_ROOM_TYPE))
+ row = self.cur.fetchone()
+ if row is None:
+ return False
+ else:
+ return True
+
+ def get_jid_id(self, jid, typestr=None):
+ """
+ jids table has jid and jid_id logs table has log_id, jid_id,
+ contact_name, time, kind, show, message so to ask logs we need jid_id
+ that matches our jid in jids table this method wants jid and returns the
+ jid_id for later sql-ing on logs typestr can be 'ROOM' or anything else
+ depending on the type of JID and is only needed to be specified when the
+ JID is new in DB
+ """
+ if jid.find('/') != -1: # if it has a /
+ jid_is_from_pm = self.jid_is_from_pm(jid)
+ if not jid_is_from_pm: # it's normal jid with resource
+ jid = jid.split('/', 1)[0] # remove the resource
+ if jid in self.jids_already_in: # we already have jids in DB
+ self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid])
+ row = self.cur.fetchone()
+ if row:
+ return row[0]
+ # oh! a new jid :), we add it now
+ if typestr == 'ROOM':
+ typ = constants.JID_ROOM_TYPE
+ else:
+ typ = constants.JID_NORMAL_TYPE
+ try:
+ self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid,
+ typ))
+ self.con.commit()
+ except sqlite.IntegrityError, e:
+ # Jid already in DB, maybe added by another instance. re-read DB
+ self.get_jids_already_in_db()
+ return self.get_jid_id(jid, typestr)
+ except sqlite.OperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+ jid_id = self.cur.lastrowid
+ self.jids_already_in.append(jid)
+ return jid_id
+
+ def convert_human_values_to_db_api_values(self, kind, show):
+ """
+ Convert from string style to constant ints for db
+ """
+ if kind == 'status':
+ kind_col = constants.KIND_STATUS
+ elif kind == 'gcstatus':
+ kind_col = constants.KIND_GCSTATUS
+ elif kind == 'gc_msg':
+ kind_col = constants.KIND_GC_MSG
+ elif kind == 'single_msg_recv':
+ kind_col = constants.KIND_SINGLE_MSG_RECV
+ elif kind == 'single_msg_sent':
+ kind_col = constants.KIND_SINGLE_MSG_SENT
+ elif kind == 'chat_msg_recv':
+ kind_col = constants.KIND_CHAT_MSG_RECV
+ elif kind == 'chat_msg_sent':
+ kind_col = constants.KIND_CHAT_MSG_SENT
+ elif kind == 'error':
+ kind_col = constants.KIND_ERROR
+
+ if show == 'online':
+ show_col = constants.SHOW_ONLINE
+ elif show == 'chat':
+ show_col = constants.SHOW_CHAT
+ elif show == 'away':
+ show_col = constants.SHOW_AWAY
+ elif show == 'xa':
+ show_col = constants.SHOW_XA
+ elif show == 'dnd':
+ show_col = constants.SHOW_DND
+ elif show == 'offline':
+ show_col = constants.SHOW_OFFLINE
+ elif show is None:
+ show_col = None
+ else: # invisible in GC when someone goes invisible
+ # it's a RFC violation .... but we should not crash
+ show_col = 'UNKNOWN'
+
+ return kind_col, show_col
+
+ def convert_human_transport_type_to_db_api_values(self, type_):
+ """
+ Convert from string style to constant ints for db
+ """
+ if type_ == 'aim':
+ return constants.TYPE_AIM
+ if type_ == 'gadu-gadu':
+ return constants.TYPE_GG
+ if type_ == 'http-ws':
+ return constants.TYPE_HTTP_WS
+ if type_ == 'icq':
+ return constants.TYPE_ICQ
+ if type_ == 'msn':
+ return constants.TYPE_MSN
+ if type_ == 'qq':
+ return constants.TYPE_QQ
+ if type_ == 'sms':
+ return constants.TYPE_SMS
+ if type_ == 'smtp':
+ return constants.TYPE_SMTP
+ if type_ in ('tlen', 'x-tlen'):
+ return constants.TYPE_TLEN
+ if type_ == 'yahoo':
+ return constants.TYPE_YAHOO
+ if type_ == 'newmail':
+ return constants.TYPE_NEWMAIL
+ if type_ == 'rss':
+ return constants.TYPE_RSS
+ if type_ == 'weather':
+ return constants.TYPE_WEATHER
+ if type_ == 'mrim':
+ return constants.TYPE_MRIM
+ return None
+
+ def convert_api_values_to_human_transport_type(self, type_id):
+ """
+ Convert from constant ints for db to string style
+ """
+ if type_id == constants.TYPE_AIM:
+ return 'aim'
+ if type_id == constants.TYPE_GG:
+ return 'gadu-gadu'
+ if type_id == constants.TYPE_HTTP_WS:
+ return 'http-ws'
+ if type_id == constants.TYPE_ICQ:
+ return 'icq'
+ if type_id == constants.TYPE_MSN:
+ return 'msn'
+ if type_id == constants.TYPE_QQ:
+ return 'qq'
+ if type_id == constants.TYPE_SMS:
+ return 'sms'
+ if type_id == constants.TYPE_SMTP:
+ return 'smtp'
+ if type_id == constants.TYPE_TLEN:
+ return 'tlen'
+ if type_id == constants.TYPE_YAHOO:
+ return 'yahoo'
+ if type_id == constants.TYPE_NEWMAIL:
+ return 'newmail'
+ if type_id == constants.TYPE_RSS:
+ return 'rss'
+ if type_id == constants.TYPE_WEATHER:
+ return 'weather'
+ if type_id == constants.TYPE_MRIM:
+ return 'mrim'
+
+ def convert_human_subscription_values_to_db_api_values(self, sub):
+ """
+ Convert from string style to constant ints for db
+ """
+ if sub == 'none':
+ return constants.SUBSCRIPTION_NONE
+ if sub == 'to':
+ return constants.SUBSCRIPTION_TO
+ if sub == 'from':
+ return constants.SUBSCRIPTION_FROM
+ if sub == 'both':
+ return constants.SUBSCRIPTION_BOTH
+
+ def convert_db_api_values_to_human_subscription_values(self, sub):
+ """
+ Convert from constant ints for db to string style
+ """
+ if sub == constants.SUBSCRIPTION_NONE:
+ return 'none'
+ if sub == constants.SUBSCRIPTION_TO:
+ return 'to'
+ if sub == constants.SUBSCRIPTION_FROM:
+ return 'from'
+ if sub == constants.SUBSCRIPTION_BOTH:
+ return 'both'
+
+ def commit_to_db(self, values, write_unread=False):
+ sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show,
+ message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)'''
+ try:
+ self.cur.execute(sql, values)
+ except sqlite.DatabaseError:
+ raise exceptions.DatabaseMalformed
+ except sqlite.OperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+ message_id = None
+ try:
+ self.con.commit()
+ if write_unread:
+ message_id = self.cur.lastrowid
+ except sqlite.OperationalError, e:
+ print >> sys.stderr, str(e)
+ if message_id:
+ self.insert_unread_events(message_id, values[0])
+ return message_id
+
+ def insert_unread_events(self, message_id, jid_id):
+ """
+ Add unread message with id: message_id
+ """
+ sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id,
+ jid_id)
+ self.simple_commit(sql)
+
+ def set_read_messages(self, message_ids):
+ """
+ Mark all messages with ids in message_ids as read
+ """
+ ids = ','.join([str(i) for i in message_ids])
+ sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids
+ self.simple_commit(sql)
+
+ def set_shown_unread_msgs(self, msg_id):
+ """
+ Mark unread message as shown un GUI
+ """
+ sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \
+ msg_id
+ self.simple_commit(sql)
+
+ def reset_shown_unread_messages(self):
+ """
+ Set shown field to False in unread_messages table
+ """
+ sql = 'UPDATE unread_messages SET shown = 0'
+ self.simple_commit(sql)
+
+ def get_unread_msgs(self):
+ """
+ Get all unread messages
+ """
+ all_messages = []
+ try:
+ self.cur.execute(
+ 'SELECT message_id, shown from unread_messages')
+ results = self.cur.fetchall()
+ except Exception:
+ pass
+ for message in results:
+ msg_id = message[0]
+ shown = message[1]
+ # here we get infos for that message, and related jid from jids table
+ # do NOT change order of SELECTed things, unless you change function(s)
+ # that called this function
+ self.cur.execute('''
+ SELECT logs.log_line_id, logs.message, logs.time, logs.subject,
+ jids.jid
+ FROM logs, jids
+ WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id
+ ''' % msg_id
+ )
+ results = self.cur.fetchall()
+ if len(results) == 0:
+ # Log line is no more in logs table. remove it from unread_messages
+ self.set_read_messages([msg_id])
+ continue
+ all_messages.append(results[0] + (shown,))
+ return all_messages
+
+ def write(self, kind, jid, message=None, show=None, tim=None, subject=None):
+ """
+ Write a row (status, gcstatus, message etc) to logs database
+
+ kind can be status, gcstatus, gc_msg, (we only recv for those 3),
+ single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent we cannot
+ know if it is pm or normal chat message, we try to guess see
+ jid_is_from_pm()
+
+ We analyze jid and store it as follows:
+ jids.jid text column will hold JID if TC-related, room_jid if GC-related,
+ ROOM_JID/nick if pm-related.
+ """
+
+ if self.jids_already_in == []: # only happens if we just created the db
+ self.open_db()
+
+ contact_name_col = None # holds nickname for kinds gcstatus, gc_msg
+ # message holds the message unless kind is status or gcstatus,
+ # then it holds status message
+ message_col = message
+ subject_col = subject
+ if tim:
+ time_col = int(float(time.mktime(tim)))
+ else:
+ time_col = int(float(time.time()))
+
+ kind_col, show_col = self.convert_human_values_to_db_api_values(kind,
+ show)
+
+ write_unread = False
+
+ # now we may have need to do extra care for some values in columns
+ if kind == 'status': # we store (not None) time, jid, show, msg
+ # status for roster items
+ try:
+ jid_id = self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+ if show is None: # show is None (xmpp), but we say that 'online'
+ show_col = constants.SHOW_ONLINE
+
+ elif kind == 'gcstatus':
+ # status in ROOM (for pm status see status)
+ if show is None: # show is None (xmpp), but we say that 'online'
+ show_col = constants.SHOW_ONLINE
+ jid, nick = jid.split('/', 1)
+ try:
+ # re-get jid_id for the new jid
+ jid_id = self.get_jid_id(jid, 'ROOM')
+ except exceptions.PysqliteOperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+ contact_name_col = nick
+
+ elif kind == 'gc_msg':
+ if jid.find('/') != -1: # if it has a /
+ jid, nick = jid.split('/', 1)
+ else:
+ # it's server message f.e. error message
+ # when user tries to ban someone but he's not allowed to
+ nick = None
+ try:
+ # re-get jid_id for the new jid
+ jid_id = self.get_jid_id(jid, 'ROOM')
+ except exceptions.PysqliteOperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+ contact_name_col = nick
+ else:
+ try:
+ jid_id = self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+ if kind == 'chat_msg_recv':
+ if not self.jid_is_from_pm(jid):
+ # Save in unread table only if it's not a pm
+ write_unread = True
+
+ if show_col == 'UNKNOWN': # unknown show, do not log
+ return
+
+ values = (jid_id, contact_name_col, time_col, kind_col, show_col,
+ message_col, subject_col)
+ return self.commit_to_db(values, write_unread)
+
+ def get_last_conversation_lines(self, jid, restore_how_many_rows,
+ pending_how_many, timeout, account):
+ """
+ Accept how many rows to restore and when to time them out (in minutes)
+ (mark them as too old) and number of messages that are in queue and are
+ already logged but pending to be viewed, returns a list of tupples
+ containg time, kind, message, list with empty tupple if nothing found to
+ meet our demands
+ """
+ try:
+ self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ # Error trying to create a new jid_id. This means there is no log
+ return []
+ where_sql = self._build_contact_where(account, jid)
+
+ now = int(float(time.time()))
+ timed_out = now - (timeout * 60) # before that they are too old
+ # so if we ask last 5 lines and we have 2 pending we get
+ # 3 - 8 (we avoid the last 2 lines but we still return 5 asked)
+ try:
+ self.cur.execute('''
+ SELECT time, kind, message FROM logs
+ WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d
+ ORDER BY time DESC LIMIT %d OFFSET %d
+ ''' % (where_sql, constants.KIND_SINGLE_MSG_RECV,
+ constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT,
+ constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR,
+ timed_out, restore_how_many_rows, pending_how_many)
+ )
+
+ results = self.cur.fetchall()
+ except sqlite.DatabaseError:
+ raise exceptions.DatabaseMalformed
+ results.reverse()
+ return results
+
+ def get_unix_time_from_date(self, year, month, day):
+ # year (fe 2005), month (fe 11), day (fe 25)
+ # returns time in seconds for the second that starts that date since epoch
+ # gimme unixtime from year month day:
+ d = datetime.date(year, month, day)
+ local_time = d.timetuple() # time tupple (compat with time.localtime())
+ # we have time since epoch baby :)
+ start_of_day = int(time.mktime(local_time))
+ return start_of_day
+
+ def get_conversation_for_date(self, jid, year, month, day, account):
+ """
+ Return contact_name, time, kind, show, message, subject
+
+ For each row in a list of tupples, returns list with empty tupple if we
+ found nothing to meet our demands
+ """
+ try:
+ self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ # Error trying to create a new jid_id. This means there is no log
+ return []
+ where_sql = self._build_contact_where(account, jid)
+
+ start_of_day = self.get_unix_time_from_date(year, month, day)
+ seconds_in_a_day = 86400 # 60 * 60 * 24
+ last_second_of_day = start_of_day + seconds_in_a_day - 1
+
+ self.cur.execute('''
+ SELECT contact_name, time, kind, show, message, subject FROM logs
+ WHERE (%s)
+ AND time BETWEEN %d AND %d
+ ORDER BY time
+ ''' % (where_sql, start_of_day, last_second_of_day))
+
+ results = self.cur.fetchall()
+ return results
+
+ def get_search_results_for_query(self, jid, query, account):
+ """
+ Returns contact_name, time, kind, show, message
+
+ For each row in a list of tupples, returns list with empty tupple if we
+ found nothing to meet our demands
+ """
+ try:
+ self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ # Error trying to create a new jid_id. This means there is no log
+ return []
+
+ if False: # query.startswith('SELECT '): # it's SQL query (FIXME)
+ try:
+ self.cur.execute(query)
+ except sqlite.OperationalError, e:
+ results = [('', '', '', '', str(e))]
+ return results
+
+ else: # user just typed something, we search in message column
+ where_sql = self._build_contact_where(account, jid)
+ like_sql = '%' + query.replace("'", "''") + '%'
+ self.cur.execute('''
+ SELECT contact_name, time, kind, show, message, subject FROM logs
+ WHERE (%s) AND message LIKE '%s'
+ ORDER BY time
+ ''' % (where_sql, like_sql))
+
+ results = self.cur.fetchall()
+ return results
+
+ def get_days_with_logs(self, jid, year, month, max_day, account):
+ """
+ Return the list of days that have logs (not status messages)
+ """
+ try:
+ self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ # Error trying to create a new jid_id. This means there is no log
+ return []
+ days_with_logs = []
+ where_sql = self._build_contact_where(account, jid)
+
+ # First select all date of month whith logs we want
+ start_of_month = self.get_unix_time_from_date(year, month, 1)
+ seconds_in_a_day = 86400 # 60 * 60 * 24
+ last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1
+
+ # Select times and 'floor' them to time 0:00
+ # (by dividing, they are integers)
+ # and take only one of the same values (distinct)
+ # Now we have timestamps of time 0:00 of every day with logs
+ self.cur.execute('''
+ SELECT DISTINCT time/(86400)*86400 FROM logs
+ WHERE (%s)
+ AND time BETWEEN %d AND %d
+ AND kind NOT IN (%d, %d)
+ ORDER BY time
+ ''' % (where_sql, start_of_month, last_second_of_month,
+ constants.KIND_STATUS, constants.KIND_GCSTATUS))
+ result = self.cur.fetchall()
+
+ # convert timestamps to day of month
+ for line in result:
+ days_with_logs[0:0]=[time.gmtime(line[0])[2]]
+
+ return days_with_logs
+
+ def get_last_date_that_has_logs(self, jid, account=None, is_room=False):
+ """
+ Return last time (in seconds since EPOCH) for which we had logs
+ (excluding statuses)
+ """
+ where_sql = ''
+ if not is_room:
+ where_sql = self._build_contact_where(account, jid)
+ else:
+ try:
+ jid_id = self.get_jid_id(jid, 'ROOM')
+ except exceptions.PysqliteOperationalError, e:
+ # Error trying to create a new jid_id. This means there is no log
+ return None
+ where_sql = 'jid_id = %s' % jid_id
+ self.cur.execute('''
+ SELECT MAX(time) FROM logs
+ WHERE (%s)
+ AND kind NOT IN (%d, %d)
+ ''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS))
+
+ results = self.cur.fetchone()
+ if results is not None:
+ result = results[0]
+ else:
+ result = None
+ return result
+
+ def get_room_last_message_time(self, jid):
+ """
+ Return FASTLY last time (in seconds since EPOCH) for which we had logs
+ for that room from rooms_last_message_time table
+ """
+ try:
+ jid_id = self.get_jid_id(jid, 'ROOM')
+ except exceptions.PysqliteOperationalError, e:
+ # Error trying to create a new jid_id. This means there is no log
+ return None
+ where_sql = 'jid_id = %s' % jid_id
+ self.cur.execute('''
+ SELECT time FROM rooms_last_message_time
+ WHERE (%s)
+ ''' % (where_sql))
+
+ results = self.cur.fetchone()
+ if results is not None:
+ result = results[0]
+ else:
+ result = None
+ return result
+
+ def set_room_last_message_time(self, jid, time):
+ """
+ Set last time (in seconds since EPOCH) for which we had logs for that
+ room in rooms_last_message_time table
+ """
+ jid_id = self.get_jid_id(jid, 'ROOM')
+ # jid_id is unique in this table, create or update :
+ sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \
+ (jid_id, time)
+ self.simple_commit(sql)
+
+ def _build_contact_where(self, account, jid):
+ """
+ Build the where clause for a jid, including metacontacts jid(s) if any
+ """
+ where_sql = ''
+ # will return empty list if jid is not associated with
+ # any metacontacts
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ for user in family:
+ try:
+ jid_id = self.get_jid_id(user['jid'])
+ except exceptions.PysqliteOperationalError, e:
+ continue
+ where_sql += 'jid_id = %s' % jid_id
+ if user != family[-1]:
+ where_sql += ' OR '
+ else: # if jid was not associated with metacontacts
+ jid_id = self.get_jid_id(jid)
+ where_sql = 'jid_id = %s' % jid_id
+ return where_sql
+
+ def save_transport_type(self, jid, type_):
+ """
+ Save the type of the transport in DB
+ """
+ type_id = self.convert_human_transport_type_to_db_api_values(type_)
+ if not type_id:
+ # unknown type
+ return
+ self.cur.execute(
+ 'SELECT type from transports_cache WHERE transport = "%s"' % jid)
+ results = self.cur.fetchall()
+ if results:
+ result = results[0][0]
+ if result == type_id:
+ return
+ sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\
+ (type_id, jid)
+ self.simple_commit(sql)
+ return
+ sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id)
+ self.simple_commit(sql)
+
+ def get_transports_type(self):
+ """
+ Return all the type of the transports in DB
+ """
+ self.cur.execute(
+ 'SELECT * from transports_cache')
+ results = self.cur.fetchall()
+ if not results:
+ return {}
+ answer = {}
+ for result in results:
+ answer[result[0]] = self.convert_api_values_to_human_transport_type(
+ result[1])
+ return answer
+
+ # A longer note here:
+ # The database contains a blob field. Pysqlite seems to need special care for
+ # such fields.
+ # When storing, we need to convert string into buffer object (1).
+ # When retrieving, we need to convert it back to a string to decompress it.
+ # (2)
+ # GzipFile needs a file-like object, StringIO emulates file for plain strings
+ def iter_caps_data(self):
+ """
+ Iterate over caps cache data stored in the database
+
+ The iterator values are pairs of (node, ver, ext, identities, features):
+ identities == {'category':'foo', 'type':'bar', 'name':'boo'},
+ features being a list of feature namespaces.
+ """
+ # get data from table
+ # the data field contains binary object (gzipped data), this is a hack
+ # to get that data without trying to convert it to unicode
+ try:
+ self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;')
+ except sqlite.OperationalError:
+ # might happen when there's no caps_cache table yet
+ # -- there's no data to read anyway then
+ return
+
+ # list of corrupted entries that will be removed
+ to_be_removed = []
+ for hash_method, hash_, data in self.cur:
+ # for each row: unpack the data field
+ # (format: (category, type, name, category, type, name, ...
+ # ..., 'FEAT', feature1, feature2, ...).join(' '))
+ # NOTE: if there's a need to do more gzip, put that to a function
+ try:
+ data = GzipFile(fileobj=StringIO(str(data))).read().decode(
+ 'utf-8').split('\0')
+ except IOError:
+ # This data is corrupted. It probably contains non-ascii chars
+ to_be_removed.append((hash_method, hash_))
+ continue
+ i = 0
+ identities = list()
+ features = list()
+ while i < (len(data) - 3) and data[i] != 'FEAT':
+ category = data[i]
+ type_ = data[i + 1]
+ lang = data[i + 2]
+ name = data[i + 3]
+ identities.append({'category': category, 'type': type_,
+ 'xml:lang': lang, 'name': name})
+ i += 4
+ i+=1
+ while i < len(data):
+ features.append(data[i])
+ i += 1
+
+ # yield the row
+ yield hash_method, hash_, identities, features
+ for hash_method, hash_ in to_be_removed:
+ sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND
+ hash = "%s"''' % (hash_method, hash_)
+ self.simple_commit(sql)
+
+ def add_caps_entry(self, hash_method, hash_, identities, features):
+ data = []
+ for identity in identities:
+ # there is no FEAT category
+ if identity['category'] == 'FEAT':
+ return
+ data.extend((identity.get('category'), identity.get('type', ''),
+ identity.get('xml:lang', ''), identity.get('name', '')))
+ data.append('FEAT')
+ data.extend(features)
+ data = '\0'.join(data)
+ # if there's a need to do more gzip, put that to a function
+ string = StringIO()
+ gzip = GzipFile(fileobj=string, mode='w')
+ data = data.encode('utf-8') # the gzip module can't handle unicode objects
+ gzip.write(data)
+ gzip.close()
+ data = string.getvalue()
+ self.cur.execute('''
+ INSERT INTO caps_cache ( hash_method, hash, data, last_seen )
+ VALUES (?, ?, ?, ?);
+ ''', (hash_method, hash_, buffer(data), int(time.time())))
+ # (1) -- note above
+ try:
+ self.con.commit()
+ except sqlite.OperationalError, e:
+ print >> sys.stderr, str(e)
+
+ def update_caps_time(self, method, hash_):
+ sql = '''UPDATE caps_cache SET last_seen = %d
+ WHERE hash_method = "%s" and hash = "%s"''' % \
+ (int(time.time()), method, hash_)
+ self.simple_commit(sql)
+
+ def clean_caps_table(self):
+ """
+ Remove caps which was not seen for 3 months
+ """
+ sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \
+ int(time.time() - 3*30*24*3600)
+ self.simple_commit(sql)
+
+ def replace_roster(self, account_name, roster_version, roster):
+ """
+ Replace current roster in DB by a new one
+
+ accout_name is the name of the account to change.
+ roster_version is the version of the new roster.
+ roster is the new version.
+ """
+ # First we must reset roster_version value to ensure that the server
+ # sends back all the roster at the next connexion if the replacement
+ # didn't work properly.
+ gajim.config.set_per('accounts', account_name, 'roster_version', '')
+
+ account_jid = gajim.get_jid_from_account(account_name)
+ account_jid_id = self.get_jid_id(account_jid)
+
+ # Delete old roster
+ self.remove_roster(account_jid)
+
+ # Fill roster tables with the new roster
+ for jid in roster:
+ self.add_or_update_contact(account_jid, jid, roster[jid]['name'],
+ roster[jid]['subscription'], roster[jid]['ask'],
+ roster[jid]['groups'])
+
+ # At this point, we are sure the replacement works properly so we can
+ # set the new roster_version value.
+ gajim.config.set_per('accounts', account_name, 'roster_version',
+ roster_version)
+
+ def del_contact(self, account_jid, jid):
+ """
+ Remove jid from account_jid roster
+ """
+ try:
+ account_jid_id = self.get_jid_id(account_jid)
+ jid_id = self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+ self.cur.execute(
+ 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
+ (account_jid_id, jid_id))
+ self.cur.execute(
+ 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?',
+ (account_jid_id, jid_id))
+ self.con.commit()
+
+ def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups):
+ """
+ Add or update a contact from account_jid roster
+ """
+ if sub == 'remove':
+ self.del_contact(account_jid, jid)
+ return
+
+ try:
+ account_jid_id = self.get_jid_id(account_jid)
+ jid_id = self.get_jid_id(jid)
+ except exceptions.PysqliteOperationalError, e:
+ raise exceptions.PysqliteOperationalError(str(e))
+
+ # Update groups information
+ # First we delete all previous groups information
+ self.cur.execute(
+ 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
+ (account_jid_id, jid_id))
+ # Then we add all new groups information
+ for group in groups:
+ self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)',
+ (account_jid_id, jid_id, group))
+
+ if name is None:
+ name = ''
+
+ self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)',
+ (account_jid_id, jid_id, name,
+ self.convert_human_subscription_values_to_db_api_values(sub),
+ bool(ask)))
+ self.con.commit()
+
+ def get_roster(self, account_jid):
+ """
+ Return the accound_jid roster in NonBlockingRoster format
+ """
+ data = {}
+ account_jid_id = self.get_jid_id(account_jid)
+
+ # First we fill data with roster_entry informations
+ self.cur.execute('''
+ SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
+ FROM roster_entry re, jids j
+ WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
+ for jid, jid_id, name, subscription, ask in self.cur:
+ data[jid] = {}
+ if name:
+ data[jid]['name'] = name
+ else:
+ data[jid]['name'] = None
+ data[jid]['subscription'] = \
+ self.convert_db_api_values_to_human_subscription_values(
+ subscription)
+ data[jid]['groups'] = []
+ data[jid]['resources'] = {}
+ if ask:
+ data[jid]['ask'] = 'subscribe'
+ else:
+ data[jid]['ask'] = None
+ data[jid]['id'] = jid_id
+
+ # Then we add group for roster entries
+ for jid in data:
+ self.cur.execute('''
+ SELECT group_name FROM roster_group
+ WHERE account_jid_id=? AND jid_id=?''',
+ (account_jid_id, data[jid]['id']))
+ for (group_name,) in self.cur:
+ data[jid]['groups'].append(group_name)
+ del data[jid]['id']
+
+ return data
+
+ def remove_roster(self, account_jid):
+ """
+ Remove all entry from account_jid roster
+ """
+ account_jid_id = self.get_jid_id(account_jid)
+
+ self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?',
+ (account_jid_id,))
+ self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?',
+ (account_jid_id,))
+ self.con.commit()
diff --git a/src/common/logging_helpers.py b/src/common/logging_helpers.py
index 733958b2e..1599c49f5 100644
--- a/src/common/logging_helpers.py
+++ b/src/common/logging_helpers.py
@@ -22,163 +22,161 @@ import logging
import i18n
def parseLogLevel(arg):
- """
- Eiter numeric value or level name from logging module
- """
- if arg.isdigit():
- return int(arg)
- elif arg.isupper():
- return getattr(logging, arg)
- else:
- raise ValueError(_('%s is not a valid loglevel'), repr(arg))
+ """
+ Eiter numeric value or level name from logging module
+ """
+ if arg.isdigit():
+ return int(arg)
+ elif arg.isupper():
+ return getattr(logging, arg)
+ else:
+ raise ValueError(_('%s is not a valid loglevel'), repr(arg))
def parseLogTarget(arg):
- """
- [gajim.]c.x.y -> gajim.c.x.y
- .other_logger -> other_logger
- <None> -> gajim
- """
- arg = arg.lower()
- if not arg:
- return 'gajim'
- elif arg.startswith('.'):
- return arg[1:]
- elif arg.startswith('gajim'):
- return arg
- else:
- return 'gajim.' + arg
+ """
+ [gajim.]c.x.y -> gajim.c.x.y
+ .other_logger -> other_logger
+ <None> -> gajim
+ """
+ arg = arg.lower()
+ if not arg:
+ return 'gajim'
+ elif arg.startswith('.'):
+ return arg[1:]
+ elif arg.startswith('gajim'):
+ return arg
+ else:
+ return 'gajim.' + arg
def parseAndSetLogLevels(arg):
- """
- [=]LOGLEVEL -> gajim=LOGLEVEL
- gajim=LOGLEVEL -> gajim=LOGLEVEL
- .other=10 -> other=10
- .=10 -> <nothing>
- c.x.y=c.z=20 -> gajim.c.x.y=20
- gajim.c.z=20
- gajim=10,c.x=20 -> gajim=10
- gajim.c.x=20
- """
- for directive in arg.split(','):
- directive = directive.strip()
- if not directive:
- continue
- if '=' not in directive:
- directive = '=' + directive
- targets, level = directive.rsplit('=', 1)
- level = parseLogLevel(level.strip())
- for target in targets.split('='):
- target = parseLogTarget(target.strip())
- if target:
- logging.getLogger(target).setLevel(level)
- print "Logger %s level set to %d" % (target, level)
+ """
+ [=]LOGLEVEL -> gajim=LOGLEVEL
+ gajim=LOGLEVEL -> gajim=LOGLEVEL
+ .other=10 -> other=10
+ .=10 -> <nothing>
+ c.x.y=c.z=20 -> gajim.c.x.y=20
+ gajim.c.z=20
+ gajim=10,c.x=20 -> gajim=10
+ gajim.c.x=20
+ """
+ for directive in arg.split(','):
+ directive = directive.strip()
+ if not directive:
+ continue
+ if '=' not in directive:
+ directive = '=' + directive
+ targets, level = directive.rsplit('=', 1)
+ level = parseLogLevel(level.strip())
+ for target in targets.split('='):
+ target = parseLogTarget(target.strip())
+ if target:
+ logging.getLogger(target).setLevel(level)
+ print "Logger %s level set to %d" % (target, level)
class colors:
- NONE = chr(27) + "[0m"
- BLACk = chr(27) + "[30m"
- RED = chr(27) + "[31m"
- GREEN = chr(27) + "[32m"
- BROWN = chr(27) + "[33m"
- BLUE = chr(27) + "[34m"
- MAGENTA = chr(27) + "[35m"
- CYAN = chr(27) + "[36m"
- LIGHT_GRAY = chr(27) + "[37m"
- DARK_GRAY = chr(27) + "[30;1m"
- BRIGHT_RED = chr(27) + "[31;1m"
- BRIGHT_GREEN = chr(27) + "[32;1m"
- YELLOW = chr(27) + "[33;1m"
- BRIGHT_BLUE = chr(27) + "[34;1m"
- PURPLE = chr(27) + "[35;1m"
- BRIGHT_CYAN = chr(27) + "[36;1m"
- WHITE = chr(27) + "[37;1m"
+ NONE = chr(27) + "[0m"
+ BLACk = chr(27) + "[30m"
+ RED = chr(27) + "[31m"
+ GREEN = chr(27) + "[32m"
+ BROWN = chr(27) + "[33m"
+ BLUE = chr(27) + "[34m"
+ MAGENTA = chr(27) + "[35m"
+ CYAN = chr(27) + "[36m"
+ LIGHT_GRAY = chr(27) + "[37m"
+ DARK_GRAY = chr(27) + "[30;1m"
+ BRIGHT_RED = chr(27) + "[31;1m"
+ BRIGHT_GREEN = chr(27) + "[32;1m"
+ YELLOW = chr(27) + "[33;1m"
+ BRIGHT_BLUE = chr(27) + "[34;1m"
+ PURPLE = chr(27) + "[35;1m"
+ BRIGHT_CYAN = chr(27) + "[36;1m"
+ WHITE = chr(27) + "[37;1m"
def colorize(text, color):
- return color + text + colors.NONE
+ return color + text + colors.NONE
class FancyFormatter(logging.Formatter):
- """
- An eye-candy formatter with colors
- """
- colors_mapping = {
- 'DEBUG': colors.BLUE,
- 'INFO' : colors.GREEN,
- 'WARNING': colors.BROWN,
- 'ERROR': colors.RED,
- 'CRITICAL': colors.BRIGHT_RED,
- }
-
- def __init__(self, fmt, datefmt=None, use_color=False):
- logging.Formatter.__init__(self, fmt, datefmt)
- self.use_color = use_color
-
- def formatTime(self, record, datefmt=None):
- f = logging.Formatter.formatTime(self, record, datefmt)
- if self.use_color:
- f = colorize(f, colors.DARK_GRAY)
- return f
-
- def format(self, record):
- level = record.levelname
- record.levelname = '(%s)' % level[0]
-
- if self.use_color:
- c = FancyFormatter.colors_mapping.get(level, '')
- record.levelname = colorize(record.levelname, c)
- record.name = colorize(record.name, colors.CYAN)
- else:
- record.name += ':'
-
- return logging.Formatter.format(self, record)
+ """
+ An eye-candy formatter with colors
+ """
+ colors_mapping = {
+ 'DEBUG': colors.BLUE,
+ 'INFO' : colors.GREEN,
+ 'WARNING': colors.BROWN,
+ 'ERROR': colors.RED,
+ 'CRITICAL': colors.BRIGHT_RED,
+ }
+
+ def __init__(self, fmt, datefmt=None, use_color=False):
+ logging.Formatter.__init__(self, fmt, datefmt)
+ self.use_color = use_color
+
+ def formatTime(self, record, datefmt=None):
+ f = logging.Formatter.formatTime(self, record, datefmt)
+ if self.use_color:
+ f = colorize(f, colors.DARK_GRAY)
+ return f
+
+ def format(self, record):
+ level = record.levelname
+ record.levelname = '(%s)' % level[0]
+
+ if self.use_color:
+ c = FancyFormatter.colors_mapping.get(level, '')
+ record.levelname = colorize(record.levelname, c)
+ record.name = colorize(record.name, colors.CYAN)
+ else:
+ record.name += ':'
+
+ return logging.Formatter.format(self, record)
def init(use_color=False):
- """
- Iinitialize the logging system
- """
- consoleloghandler = logging.StreamHandler()
- consoleloghandler.setFormatter(
- FancyFormatter(
- '%(asctime)s %(levelname)s %(name)s %(message)s',
- '%H:%M:%S',
- use_color
- )
- )
-
- # fake the root logger so we have 'gajim' root name instead of 'root'
- root_log = logging.getLogger('gajim')
- root_log.setLevel(logging.WARNING)
- root_log.addHandler(consoleloghandler)
- root_log.propagate = False
+ """
+ Iinitialize the logging system
+ """
+ consoleloghandler = logging.StreamHandler()
+ consoleloghandler.setFormatter(
+ FancyFormatter(
+ '%(asctime)s %(levelname)s %(name)s %(message)s',
+ '%H:%M:%S',
+ use_color
+ )
+ )
+
+ # fake the root logger so we have 'gajim' root name instead of 'root'
+ root_log = logging.getLogger('gajim')
+ root_log.setLevel(logging.WARNING)
+ root_log.addHandler(consoleloghandler)
+ root_log.propagate = False
def set_loglevels(loglevels_string):
- parseAndSetLogLevels(loglevels_string)
+ parseAndSetLogLevels(loglevels_string)
def set_verbose():
- parseAndSetLogLevels('gajim=1')
+ parseAndSetLogLevels('gajim=1')
def set_quiet():
- parseAndSetLogLevels('gajim=CRITICAL')
+ parseAndSetLogLevels('gajim=CRITICAL')
# tests
if __name__ == '__main__':
- init(use_color=True)
-
- set_loglevels('gajim.c=DEBUG,INFO')
-
- log = logging.getLogger('gajim')
- log.debug('debug')
- log.info('info')
- log.warn('warn')
- log.error('error')
- log.critical('critical')
-
- log = logging.getLogger('gajim.c.x.dispatcher')
- log.debug('debug')
- log.info('info')
- log.warn('warn')
- log.error('error')
- log.critical('critical')
-
-# vim: se ts=3:
+ init(use_color=True)
+
+ set_loglevels('gajim.c=DEBUG,INFO')
+
+ log = logging.getLogger('gajim')
+ log.debug('debug')
+ log.info('info')
+ log.warn('warn')
+ log.error('error')
+ log.critical('critical')
+
+ log = logging.getLogger('gajim.c.x.dispatcher')
+ log.debug('debug')
+ log.info('info')
+ log.warn('warn')
+ log.error('error')
+ log.critical('critical')
diff --git a/src/common/multimedia_helpers.py b/src/common/multimedia_helpers.py
index 4a2a4fb2e..b4e574eeb 100644
--- a/src/common/multimedia_helpers.py
+++ b/src/common/multimedia_helpers.py
@@ -15,87 +15,84 @@ import gst
class DeviceManager(object):
- def __init__(self):
- self.devices = {}
-
- def detect(self):
- self.devices = {}
-
- def get_devices(self):
- if not self.devices:
- self.detect()
- return self.devices
-
- def detect_element(self, name, text, pipe='%s'):
- try:
- element = gst.element_factory_make(name, '%spresencetest' % name)
- if isinstance(element, gst.interfaces.PropertyProbe):
- element.set_state(gst.STATE_READY)
- devices = element.probe_get_values_name('device')
- if devices:
- self.devices[text % _(' Default device')] = pipe % name
- for device in devices:
- element.set_property('device', device)
- device_name = element.get_property('device-name')
- self.devices[text % device_name] = pipe % '%s device=%s' % (name, device)
- element.set_state(gst.STATE_NULL)
- else:
- self.devices[text] = pipe % name
- except gst.ElementNotFoundError:
- print 'element \'%s\' not found' % name
+ def __init__(self):
+ self.devices = {}
+
+ def detect(self):
+ self.devices = {}
+
+ def get_devices(self):
+ if not self.devices:
+ self.detect()
+ return self.devices
+
+ def detect_element(self, name, text, pipe='%s'):
+ try:
+ element = gst.element_factory_make(name, '%spresencetest' % name)
+ if isinstance(element, gst.interfaces.PropertyProbe):
+ element.set_state(gst.STATE_READY)
+ devices = element.probe_get_values_name('device')
+ if devices:
+ self.devices[text % _(' Default device')] = pipe % name
+ for device in devices:
+ element.set_property('device', device)
+ device_name = element.get_property('device-name')
+ self.devices[text % device_name] = pipe % '%s device=%s' % (name, device)
+ element.set_state(gst.STATE_NULL)
+ else:
+ self.devices[text] = pipe % name
+ except gst.ElementNotFoundError:
+ print 'element \'%s\' not found' % name
class AudioInputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Test src
- self.detect_element('audiotestsrc', _('Audio test'),
- '%s is-live=true name=gajim_vol')
- # Auto src
- self.detect_element('autoaudiosrc', _('Autodetect'),
- '%s ! volume name=gajim_vol')
- # Alsa src
- self.detect_element('alsasrc', _('ALSA: %s'),
- '%s ! volume name=gajim_vol')
+ def detect(self):
+ self.devices = {}
+ # Test src
+ self.detect_element('audiotestsrc', _('Audio test'),
+ '%s is-live=true name=gajim_vol')
+ # Auto src
+ self.detect_element('autoaudiosrc', _('Autodetect'),
+ '%s ! volume name=gajim_vol')
+ # Alsa src
+ self.detect_element('alsasrc', _('ALSA: %s'),
+ '%s ! volume name=gajim_vol')
class AudioOutputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Fake sink
- self.detect_element('fakesink', _('Fake audio output'))
- # Auto sink
- self.detect_element('autoaudiosink', _('Autodetect'))
- # Alsa sink
- self.detect_element('alsasink', _('ALSA: %s'),
- '%s sync=false')
+ def detect(self):
+ self.devices = {}
+ # Fake sink
+ self.detect_element('fakesink', _('Fake audio output'))
+ # Auto sink
+ self.detect_element('autoaudiosink', _('Autodetect'))
+ # Alsa sink
+ self.detect_element('alsasink', _('ALSA: %s'),
+ '%s sync=false')
class VideoInputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Test src
- self.detect_element('videotestsrc', _('Video test'),
- '%s is-live=true')
- # Auto src
- self.detect_element('autovideosrc', _('Autodetect'))
- # V4L2 src ; TODO: Figure out why it doesn't work
- self.detect_element('v4l2src', _('V4L2: %s'))
- # Funny things, just to test...
- # self.devices['GOOM'] = 'audiotestsrc ! goom'
- # self.devices['screen'] = 'ximagesrc'
+ def detect(self):
+ self.devices = {}
+ # Test src
+ self.detect_element('videotestsrc', _('Video test'),
+ '%s is-live=true')
+ # Auto src
+ self.detect_element('autovideosrc', _('Autodetect'))
+ # V4L2 src ; TODO: Figure out why it doesn't work
+ self.detect_element('v4l2src', _('V4L2: %s'))
+ # Funny things, just to test...
+ # self.devices['GOOM'] = 'audiotestsrc ! goom'
+ # self.devices['screen'] = 'ximagesrc'
class VideoOutputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Fake video output
- self.detect_element('fakesink', _('Fake audio output'))
- # Auto sink
- self.detect_element('autovideosink', _('Autodetect'))
- # xvimage sink
- self.detect_element('xvimagesink', _('X Window System (X11/XShm/Xv): %s'))
- # ximagesink
- self.detect_element('ximagesink', _('X Window System (without Xv)'))
-
-
+ def detect(self):
+ self.devices = {}
+ # Fake video output
+ self.detect_element('fakesink', _('Fake audio output'))
+ # Auto sink
+ self.detect_element('xvimagesink', _('X Window System (X11/XShm/Xv): %s'))
+ # ximagesink
+ self.detect_element('ximagesink', _('X Window System (without Xv)'))
+ self.detect_element('autovideosink', _('Autodetect'))
diff --git a/src/common/optparser.py b/src/common/optparser.py
index b14bdf69e..9c2e51148 100644
--- a/src/common/optparser.py
+++ b/src/common/optparser.py
@@ -39,847 +39,845 @@ import sqlite3 as sqlite
import logger
class OptionsParser:
- def __init__(self, filename):
- self.__filename = filename
- self.old_values = {} # values that are saved in the file and maybe
- # no longer valid
-
- def read(self):
- try:
- fd = open(self.__filename)
- except Exception:
- if os.path.exists(self.__filename):
- #we talk about a file
- print _('error: cannot open %s for reading') % self.__filename
- return False
-
- new_version = gajim.config.get('version')
- new_version = new_version.split('-', 1)[0]
- seen = set()
- regex = re.compile(r"(?P<optname>[^.]+)(?:(?:\.(?P<key>.+))?\.(?P<subname>[^.]+))?\s=\s(?P<value>.*)")
-
- for line in fd:
- try:
- line = line.decode('utf-8')
- except UnicodeDecodeError:
- line = line.decode(locale.getpreferredencoding())
- optname, key, subname, value = regex.match(line).groups()
- if key is None:
- self.old_values[optname] = value
- gajim.config.set(optname, value)
- else:
- if (optname, key) not in seen:
- gajim.config.add_per(optname, key)
- seen.add((optname, key))
- gajim.config.set_per(optname, key, subname, value)
-
- old_version = gajim.config.get('version')
- old_version = old_version.split('-', 1)[0]
-
- self.update_config(old_version, new_version)
- self.old_values = {} # clean mem
-
- fd.close()
- return True
-
- def write_line(self, fd, opt, parents, value):
- if value is None:
- return
- value = value[1]
- # convert to utf8 before writing to file if needed
- if isinstance(value, unicode):
- value = value.encode('utf-8')
- else:
- value = str(value)
- if isinstance(opt, unicode):
- opt = opt.encode('utf-8')
- s = ''
- if parents:
- if len(parents) == 1:
- return
- for p in parents:
- if isinstance(p, unicode):
- p = p.encode('utf-8')
- s += p + '.'
- s += opt
- fd.write(s + ' = ' + value + '\n')
-
- def write(self):
- (base_dir, filename) = os.path.split(self.__filename)
- self.__tempfile = os.path.join(base_dir, '.' + filename)
- try:
- f = open(self.__tempfile, 'w')
- except IOError, e:
- return str(e)
- try:
- gajim.config.foreach(self.write_line, f)
- except IOError, e:
- return str(e)
- f.close()
- if os.path.exists(self.__filename):
- # win32 needs this
- try:
- os.remove(self.__filename)
- except Exception:
- pass
- try:
- os.rename(self.__tempfile, self.__filename)
- except IOError, e:
- return str(e)
- os.chmod(self.__filename, 0600)
-
- def update_config(self, old_version, new_version):
- old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y)
- old = []
- while len(old_version_list):
- old.append(int(old_version_list.pop(0)))
- new_version_list = new_version.split('.')
- new = []
- while len(new_version_list):
- new.append(int(new_version_list.pop(0)))
-
- if old < [0, 9] and new >= [0, 9]:
- self.update_config_x_to_09()
- if old < [0, 10] and new >= [0, 10]:
- self.update_config_09_to_010()
- if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]:
- self.update_config_to_01011()
- if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]:
- self.update_config_to_01012()
- if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]:
- self.update_config_to_01013()
- if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]:
- self.update_config_to_01014()
- if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]:
- self.update_config_to_01015()
- if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]:
- self.update_config_to_01016()
- if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]:
- self.update_config_to_01017()
- if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]:
- self.update_config_to_01018()
- if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]:
- self.update_config_to_01101()
- if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]:
- self.update_config_to_01102()
- if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]:
- self.update_config_to_01111()
- if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]:
- self.update_config_to_01112()
- if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]:
- self.update_config_to_01113()
- if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]:
- self.update_config_to_01114()
- if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]:
- self.update_config_to_01115()
- if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]:
- self.update_config_to_01121()
- if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]:
- self.update_config_to_01141()
- if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]:
- self.update_config_to_01142()
- if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]:
- self.update_config_to_01143()
- if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]:
- self.update_config_to_01144()
- if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]:
- self.update_config_to_01201()
- if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]:
- self.update_config_to_01211()
- if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]:
- self.update_config_to_01212()
- if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]:
- self.update_config_to_01213()
- if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]:
- self.update_config_to_01214()
- if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]:
- self.update_config_to_01215()
- if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]:
- self.update_config_to_01231()
- if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]:
- self.update_config_from_0125()
- self.update_config_to_01251()
- if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]:
- self.update_config_to_01252()
- if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]:
- self.update_config_to_01253()
- if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]:
- self.update_config_to_01254()
- if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]:
- self.update_config_to_01255()
- if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]:
- self.update_config_to_01256()
- if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]:
- self.update_config_to_01257()
- if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]:
- self.update_config_to_01258()
- if old < [0, 13, 10, 0] and new >= [0, 13, 10, 0]:
- self.update_config_to_013100()
- if old < [0, 13, 10, 1] and new >= [0, 13, 10, 1]:
- self.update_config_to_013101()
-
- gajim.logger.init_vars()
- gajim.logger.attach_cache_database()
- gajim.config.set('version', new_version)
-
- caps_cache.capscache.initialize_from_db()
-
- def assert_unread_msgs_table_exists(self):
- """
- Create table unread_messages if there is no such table
- """
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE unread_messages (
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER
- );
- '''
- )
- con.commit()
- gajim.logger.init_vars()
- except sqlite.OperationalError:
- pass
- con.close()
-
- def update_ft_proxies(self, to_remove=[], to_add=[]):
- for account in gajim.config.get_per('accounts'):
- proxies_str = gajim.config.get_per('accounts', account,
- 'file_transfer_proxies')
- proxies = [p.strip() for p in proxies_str.split(',')]
- for wrong_proxy in to_remove:
- if wrong_proxy in proxies:
- proxies.remove(wrong_proxy)
- for new_proxy in to_add:
- if new_proxy not in proxies:
- proxies.append(new_proxy)
- proxies_str = ', '.join(proxies)
- gajim.config.set_per('accounts', account, 'file_transfer_proxies',
- proxies_str)
-
- def update_config_x_to_09(self):
- # Var name that changed:
- # avatar_width /height -> chat_avatar_width / height
- if 'avatar_width' in self.old_values:
- gajim.config.set('chat_avatar_width', self.old_values['avatar_width'])
- if 'avatar_height' in self.old_values:
- gajim.config.set('chat_avatar_height', self.old_values['avatar_height'])
- if 'use_dbus' in self.old_values:
- gajim.config.set('remote_control', self.old_values['use_dbus'])
- # always_compact_view -> always_compact_view_chat / _gc
- if 'always_compact_view' in self.old_values:
- gajim.config.set('always_compact_view_chat',
- self.old_values['always_compact_view'])
- gajim.config.set('always_compact_view_gc',
- self.old_values['always_compact_view'])
- # new theme: grocery, plain
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
- 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
- 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
- 'bannerfontattrs']
- for theme_name in (_('grocery'), _('default')):
- if theme_name not in gajim.config.get_per('themes'):
- gajim.config.add_per('themes', theme_name)
- theme = gajim.config.themes_default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
- # Remove cyan theme if it's not the current theme
- if 'cyan' in gajim.config.get_per('themes'):
- gajim.config.del_per('themes', 'cyan')
- if _('cyan') in gajim.config.get_per('themes'):
- gajim.config.del_per('themes', _('cyan'))
- # If we removed our roster_theme, choose the default green one or another
- # one if doesn't exists in config
- if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'):
- theme = _('green')
- if theme not in gajim.config.get_per('themes'):
- theme = gajim.config.get_per('themes')[0]
- gajim.config.set('roster_theme', theme)
- # new proxies in accounts.name.file_transfer_proxies
- self.update_ft_proxies(to_add=['proxy.netlab.cz'])
-
- gajim.config.set('version', '0.9')
-
- def update_config_09_to_010(self):
- if 'usetabbedchat' in self.old_values and not \
- self.old_values['usetabbedchat']:
- gajim.config.set('one_message_window', 'never')
- if 'autodetect_browser_mailer' in self.old_values and \
- self.old_values['autodetect_browser_mailer'] is True:
- gajim.config.set('autodetect_browser_mailer', False)
- if 'useemoticons' in self.old_values and \
- not self.old_values['useemoticons']:
- gajim.config.set('emoticons_theme', '')
- if 'always_compact_view_chat' in self.old_values and \
- self.old_values['always_compact_view_chat'] != 'False':
- gajim.config.set('always_hide_chat_buttons', True)
- if 'always_compact_view_gc' in self.old_values and \
- self.old_values['always_compact_view_gc'] != 'False':
- gajim.config.set('always_hide_groupchat_buttons', True)
-
- self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl',
- 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de'])
- # create unread_messages table if needed
- self.assert_unread_msgs_table_exists()
-
- gajim.config.set('version', '0.10')
-
- def update_config_to_01011(self):
- if 'print_status_in_muc' in self.old_values and \
- self.old_values['print_status_in_muc'] in (True, False):
- gajim.config.set('print_status_in_muc', 'in_and_out')
- gajim.config.set('version', '0.10.1.1')
-
- def update_config_to_01012(self):
- # See [6456]
- if 'emoticons_theme' in self.old_values and \
- self.old_values['emoticons_theme'] == 'Disabled':
- gajim.config.set('emoticons_theme', '')
- gajim.config.set('version', '0.10.1.2')
-
- def update_config_to_01013(self):
- """
- Create table transports_cache if there is no such table
- """
- # FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE transports_cache (
- transport TEXT UNIQUE,
- type INTEGER
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.10.1.3')
-
- def update_config_to_01014(self):
- """
- Apply indeces to the logs database
- """
- print _('migrating logs database to indices')
- # FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- # apply indeces
- try:
- cur.executescript(
- '''
- CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
- CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
- '''
- )
-
- con.commit()
- except Exception:
- pass
- con.close()
- gajim.config.set('version', '0.10.1.4')
-
- def update_config_to_01015(self):
- """
- Clean show values in logs database
- """
- #FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \
- logger.constants.__dict__.keys() if i.startswith('SHOW_'))
- for show in status:
- cur.execute('update logs set show = ? where show = ?;', (status[show],
- show))
- cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);')
- con.commit()
- cur.close() # remove this in 2007 [pysqlite old versions need this]
- con.close()
- gajim.config.set('version', '0.10.1.5')
-
- def update_config_to_01016(self):
- """
- #2494 : Now we play gc_received_message sound even if
- notify_on_all_muc_messages is false. Keep precedent behaviour
- """
- if 'notify_on_all_muc_messages' in self.old_values and \
- self.old_values['notify_on_all_muc_messages'] == 'False' and \
- gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
- gajim.config.set_per('soundevents',\
- 'muc_message_received', 'enabled', False)
- gajim.config.set('version', '0.10.1.6')
-
- def update_config_to_01017(self):
- """
- trayicon_notification_on_new_messages -> trayicon_notification_on_events
- """
- if 'trayicon_notification_on_new_messages' in self.old_values:
- gajim.config.set('trayicon_notification_on_events',
- self.old_values['trayicon_notification_on_new_messages'])
- gajim.config.set('version', '0.10.1.7')
-
- def update_config_to_01018(self):
- """
- chat_state_notifications -> outgoing_chat_state_notifications
- """
- if 'chat_state_notifications' in self.old_values:
- gajim.config.set('outgoing_chat_state_notifications',
- self.old_values['chat_state_notifications'])
- gajim.config.set('version', '0.10.1.8')
-
- def update_config_to_01101(self):
- """
- Fill time_stamp from before_time and after_time
- """
- if 'before_time' in self.old_values:
- gajim.config.set('time_stamp', '%s%%X%s ' % (
- self.old_values['before_time'], self.old_values['after_time']))
- gajim.config.set('version', '0.11.0.1')
-
- def update_config_to_01102(self):
- """
- Fill time_stamp from before_time and after_time
- """
- if 'ft_override_host_to_send' in self.old_values:
- gajim.config.set('ft_add_hosts_to_send',
- self.old_values['ft_override_host_to_send'])
- gajim.config.set('version', '0.11.0.2')
-
- def update_config_to_01111(self):
- """
- Always_hide_chatbuttons -> compact_view
- """
- if 'always_hide_groupchat_buttons' in self.old_values and \
- 'always_hide_chat_buttons' in self.old_values:
- gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \
- self.old_values['always_hide_chat_buttons'])
- gajim.config.set('version', '0.11.1.1')
-
- def update_config_to_01112(self):
- """
- GTK+ theme is renamed to default
- """
- if 'roster_theme' in self.old_values and \
- self.old_values['roster_theme'] == 'gtk+':
- gajim.config.set('roster_theme', _('default'))
- gajim.config.set('version', '0.11.1.2')
-
- def update_config_to_01113(self):
- # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE caps_cache (
- node TEXT,
- ver TEXT,
- ext TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.1.3')
-
- def update_config_to_01114(self):
- # add default theme if it doesn't exist
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
- 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
- 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
- 'bannerfontattrs']
- theme_name = _('default')
- if theme_name not in gajim.config.get_per('themes'):
- gajim.config.add_per('themes', theme_name)
- if gajim.config.get_per('themes', 'gtk+'):
- # copy from old gtk+ theme
- for o in d:
- val = gajim.config.get_per('themes', 'gtk+', o)
- gajim.config.set_per('themes', theme_name, o, val)
- gajim.config.del_per('themes', 'gtk+')
- else:
- # copy from default theme
- theme = gajim.config.themes_default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
- gajim.config.set('version', '0.11.1.4')
-
- def update_config_to_01115(self):
- # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- DELETE FROM caps_cache;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.1.5')
-
- def update_config_to_01121(self):
- # remove old unencrypted secrets file
- from common.configpaths import gajimpaths
-
- new_file = gajimpaths['SECRETS_FILE']
-
- old_file = os.path.dirname(new_file) + '/secrets'
-
- if os.path.exists(old_file):
- os.remove(old_file)
-
- gajim.config.set('version', '0.11.2.1')
-
- def update_config_to_01141(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS caps_cache (
- node TEXT,
- ver TEXT,
- ext TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.1')
-
- def update_config_to_01142(self):
- """
- next_message_received sound event is splittedin 2 events
- """
- gajim.config.add_per('soundevents', 'next_message_received_focused')
- gajim.config.add_per('soundevents', 'next_message_received_unfocused')
- if gajim.config.get_per('soundevents', 'next_message_received'):
- enabled = gajim.config.get_per('soundevents', 'next_message_received',
- 'enabled')
- path = gajim.config.get_per('soundevents', 'next_message_received',
- 'path')
- gajim.config.del_per('soundevents', 'next_message_received')
- gajim.config.set_per('soundevents', 'next_message_received_focused',
- 'enabled', enabled)
- gajim.config.set_per('soundevents', 'next_message_received_focused',
- 'path', path)
- gajim.config.set('version', '0.11.1.2')
-
- def update_config_to_01143(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS rooms_last_message_time(
- jid_id INTEGER PRIMARY KEY UNIQUE,
- time INTEGER
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.3')
-
- def update_config_to_01144(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript('DROP TABLE caps_cache;')
- con.commit()
- except sqlite.OperationalError:
- pass
- try:
- cur.executescript(
- '''
- CREATE TABLE caps_cache (
- hash_method TEXT,
- hash TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError, e:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.4')
-
- def update_config_to_01201(self):
- if 'uri_schemes' in self.old_values:
- new_values = self.old_values['uri_schemes'].replace(' mailto', '').\
- replace(' xmpp', '')
- gajim.config.set('uri_schemes', new_values)
- gajim.config.set('version', '0.12.0.1')
-
- def update_config_to_01211(self):
- if 'trayicon' in self.old_values:
- if self.old_values['trayicon'] == 'False':
- gajim.config.set('trayicon', 'never')
- else:
- gajim.config.set('trayicon', 'always')
- gajim.config.set('version', '0.12.1.1')
-
- def update_config_to_01212(self):
- for opt in ('ignore_unknown_contacts', 'send_os_info',
- 'log_encrypted_sessions'):
- if opt in self.old_values:
- val = self.old_values[opt]
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, opt, val)
- gajim.config.set('version', '0.12.1.2')
-
- def update_config_to_01213(self):
- msgs = gajim.config.statusmsg_default
- for msg_name in gajim.config.get_per('statusmsg'):
- if msg_name in msgs:
- gajim.config.set_per('statusmsg', msg_name, 'activity',
- msgs[msg_name][1])
- gajim.config.set_per('statusmsg', msg_name, 'subactivity',
- msgs[msg_name][2])
- gajim.config.set_per('statusmsg', msg_name, 'activity_text',
- msgs[msg_name][3])
- gajim.config.set_per('statusmsg', msg_name, 'mood',
- msgs[msg_name][4])
- gajim.config.set_per('statusmsg', msg_name, 'mood_text',
- msgs[msg_name][5])
- gajim.config.set('version', '0.12.1.3')
-
- def update_config_to_01214(self):
- for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible',
- 'offline']:
- if 'last_status_msg_' + status in self.old_values:
- gajim.config.add_per('statusmsg', '_last_' + status)
- gajim.config.set_per('statusmsg', '_last_' + status, 'message',
- self.old_values['last_status_msg_' + status])
- gajim.config.set('version', '0.12.1.4')
-
- def update_config_to_01215(self):
- """
- Remove hardcoded ../data/sounds from config
- """
- dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR)
- for evt in gajim.config.get_per('soundevents'):
- path = gajim.config.get_per('soundevents', evt ,'path')
- # absolute and relative passes are necessary
- path = helpers.strip_soundfile_path(path, dirs, abs=False)
- path = helpers.strip_soundfile_path(path, dirs, abs=True)
- gajim.config.set_per('soundevents', evt, 'path', path)
- gajim.config.set('version', '0.12.1.5')
-
- def update_config_to_01231(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS roster_entry(
- account_jid_id INTEGER,
- jid_id INTEGER,
- name TEXT,
- subscription INTEGER,
- ask BOOLEAN,
- PRIMARY KEY (account_jid_id, jid_id)
- );
-
- CREATE TABLE IF NOT EXISTS roster_group(
- account_jid_id INTEGER,
- jid_id INTEGER,
- group_name TEXT,
- PRIMARY KEY (account_jid_id, jid_id, group_name)
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.12.3.1')
-
- def update_config_from_0125(self):
- # All those functions need to be called for 0.12.5 to 0.13 transition
- self.update_config_to_01211()
- self.update_config_to_01213()
- self.update_config_to_01214()
- self.update_config_to_01215()
- self.update_config_to_01231()
-
- def update_config_to_01251(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE unread_messages
- ADD shown BOOLEAN default 0;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.12.5.1')
-
- def update_config_to_01252(self):
- if 'alwaysauth' in self.old_values:
- val = self.old_values['alwaysauth']
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, 'autoauth', val)
- gajim.config.set('version', '0.12.5.2')
-
- def update_config_to_01253(self):
- if 'enable_zeroconf' in self.old_values:
- val = self.old_values['enable_zeroconf']
- for account in gajim.config.get_per('accounts'):
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- gajim.config.set_per('accounts', account, 'active', val)
- else:
- gajim.config.set_per('accounts', account, 'active', True)
- gajim.config.set('version', '0.12.5.3')
-
- def update_config_to_01254(self):
- vals = {'inmsgcolor': ['#a34526', '#a40000'],
- 'outmsgcolor': ['#164e6f', '#3465a4'],
- 'restored_messages_color': ['grey', '#555753'],
- 'statusmsgcolor': ['#1eaa1e', '#73d216'],
- 'urlmsgcolor': ['#0000ff', '#204a87'],
- 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.4')
-
- def update_config_to_01255(self):
- vals = {'statusmsgcolor': ['#73d216', '#4e9a06'],
- 'outmsgtxtcolor': ['#a2a2a2', '#555753']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.5')
-
- def update_config_to_01256(self):
- vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.6')
-
- def update_config_to_01257(self):
- if 'iconset' in self.old_values:
- if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip',
- 'simplebulb', 'stellar'):
- gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET)
- gajim.config.set('version', '0.12.5.7')
-
- def update_config_to_01258(self):
- self.update_ft_proxies(to_remove=['proxy65.talkonaut.com',
- 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de',
- 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org',
- 'proxy.jabber.ru', 'proxy.jabbim.cz'])
- gajim.config.set('version', '0.12.5.8')
-
- def update_config_to_013100(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE caps_cache
- ADD last_seen INTEGER default %d;
- ''' % int(time())
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.13.10.0')
-
- def update_config_to_013101(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- DROP INDEX IF EXISTS idx_logs_jid_id_kind;
-
- CREATE INDEX IF NOT EXISTS
- idx_logs_jid_id_time ON logs (jid_id, time DESC);
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.13.10.1')
-
-# vim: se ts=3:
+ def __init__(self, filename):
+ self.__filename = filename
+ self.old_values = {} # values that are saved in the file and maybe
+ # no longer valid
+
+ def read(self):
+ try:
+ fd = open(self.__filename)
+ except Exception:
+ if os.path.exists(self.__filename):
+ #we talk about a file
+ print _('error: cannot open %s for reading') % self.__filename
+ return False
+
+ new_version = gajim.config.get('version')
+ new_version = new_version.split('-', 1)[0]
+ seen = set()
+ regex = re.compile(r"(?P<optname>[^.]+)(?:(?:\.(?P<key>.+))?\.(?P<subname>[^.]+))?\s=\s(?P<value>.*)")
+
+ for line in fd:
+ try:
+ line = line.decode('utf-8')
+ except UnicodeDecodeError:
+ line = line.decode(locale.getpreferredencoding())
+ optname, key, subname, value = regex.match(line).groups()
+ if key is None:
+ self.old_values[optname] = value
+ gajim.config.set(optname, value)
+ else:
+ if (optname, key) not in seen:
+ gajim.config.add_per(optname, key)
+ seen.add((optname, key))
+ gajim.config.set_per(optname, key, subname, value)
+
+ old_version = gajim.config.get('version')
+ old_version = old_version.split('-', 1)[0]
+
+ self.update_config(old_version, new_version)
+ self.old_values = {} # clean mem
+
+ fd.close()
+ return True
+
+ def write_line(self, fd, opt, parents, value):
+ if value is None:
+ return
+ value = value[1]
+ # convert to utf8 before writing to file if needed
+ if isinstance(value, unicode):
+ value = value.encode('utf-8')
+ else:
+ value = str(value)
+ if isinstance(opt, unicode):
+ opt = opt.encode('utf-8')
+ s = ''
+ if parents:
+ if len(parents) == 1:
+ return
+ for p in parents:
+ if isinstance(p, unicode):
+ p = p.encode('utf-8')
+ s += p + '.'
+ s += opt
+ fd.write(s + ' = ' + value + '\n')
+
+ def write(self):
+ (base_dir, filename) = os.path.split(self.__filename)
+ self.__tempfile = os.path.join(base_dir, '.' + filename)
+ try:
+ f = open(self.__tempfile, 'w')
+ except IOError, e:
+ return str(e)
+ try:
+ gajim.config.foreach(self.write_line, f)
+ except IOError, e:
+ return str(e)
+ f.close()
+ if os.path.exists(self.__filename):
+ # win32 needs this
+ try:
+ os.remove(self.__filename)
+ except Exception:
+ pass
+ try:
+ os.rename(self.__tempfile, self.__filename)
+ except IOError, e:
+ return str(e)
+ os.chmod(self.__filename, 0600)
+
+ def update_config(self, old_version, new_version):
+ old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y)
+ old = []
+ while len(old_version_list):
+ old.append(int(old_version_list.pop(0)))
+ new_version_list = new_version.split('.')
+ new = []
+ while len(new_version_list):
+ new.append(int(new_version_list.pop(0)))
+
+ if old < [0, 9] and new >= [0, 9]:
+ self.update_config_x_to_09()
+ if old < [0, 10] and new >= [0, 10]:
+ self.update_config_09_to_010()
+ if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]:
+ self.update_config_to_01011()
+ if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]:
+ self.update_config_to_01012()
+ if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]:
+ self.update_config_to_01013()
+ if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]:
+ self.update_config_to_01014()
+ if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]:
+ self.update_config_to_01015()
+ if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]:
+ self.update_config_to_01016()
+ if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]:
+ self.update_config_to_01017()
+ if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]:
+ self.update_config_to_01018()
+ if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]:
+ self.update_config_to_01101()
+ if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]:
+ self.update_config_to_01102()
+ if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]:
+ self.update_config_to_01111()
+ if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]:
+ self.update_config_to_01112()
+ if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]:
+ self.update_config_to_01113()
+ if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]:
+ self.update_config_to_01114()
+ if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]:
+ self.update_config_to_01115()
+ if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]:
+ self.update_config_to_01121()
+ if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]:
+ self.update_config_to_01141()
+ if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]:
+ self.update_config_to_01142()
+ if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]:
+ self.update_config_to_01143()
+ if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]:
+ self.update_config_to_01144()
+ if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]:
+ self.update_config_to_01201()
+ if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]:
+ self.update_config_to_01211()
+ if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]:
+ self.update_config_to_01212()
+ if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]:
+ self.update_config_to_01213()
+ if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]:
+ self.update_config_to_01214()
+ if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]:
+ self.update_config_to_01215()
+ if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]:
+ self.update_config_to_01231()
+ if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]:
+ self.update_config_from_0125()
+ self.update_config_to_01251()
+ if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]:
+ self.update_config_to_01252()
+ if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]:
+ self.update_config_to_01253()
+ if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]:
+ self.update_config_to_01254()
+ if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]:
+ self.update_config_to_01255()
+ if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]:
+ self.update_config_to_01256()
+ if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]:
+ self.update_config_to_01257()
+ if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]:
+ self.update_config_to_01258()
+ if old < [0, 13, 10, 0] and new >= [0, 13, 10, 0]:
+ self.update_config_to_013100()
+ if old < [0, 13, 10, 1] and new >= [0, 13, 10, 1]:
+ self.update_config_to_013101()
+
+ gajim.logger.init_vars()
+ gajim.logger.attach_cache_database()
+ gajim.config.set('version', new_version)
+
+ caps_cache.capscache.initialize_from_db()
+
+ def assert_unread_msgs_table_exists(self):
+ """
+ Create table unread_messages if there is no such table
+ """
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE unread_messages (
+ message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+ jid_id INTEGER
+ );
+ '''
+ )
+ con.commit()
+ gajim.logger.init_vars()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+
+ def update_ft_proxies(self, to_remove=[], to_add=[]):
+ for account in gajim.config.get_per('accounts'):
+ proxies_str = gajim.config.get_per('accounts', account,
+ 'file_transfer_proxies')
+ proxies = [p.strip() for p in proxies_str.split(',')]
+ for wrong_proxy in to_remove:
+ if wrong_proxy in proxies:
+ proxies.remove(wrong_proxy)
+ for new_proxy in to_add:
+ if new_proxy not in proxies:
+ proxies.append(new_proxy)
+ proxies_str = ', '.join(proxies)
+ gajim.config.set_per('accounts', account, 'file_transfer_proxies',
+ proxies_str)
+
+ def update_config_x_to_09(self):
+ # Var name that changed:
+ # avatar_width /height -> chat_avatar_width / height
+ if 'avatar_width' in self.old_values:
+ gajim.config.set('chat_avatar_width', self.old_values['avatar_width'])
+ if 'avatar_height' in self.old_values:
+ gajim.config.set('chat_avatar_height', self.old_values['avatar_height'])
+ if 'use_dbus' in self.old_values:
+ gajim.config.set('remote_control', self.old_values['use_dbus'])
+ # always_compact_view -> always_compact_view_chat / _gc
+ if 'always_compact_view' in self.old_values:
+ gajim.config.set('always_compact_view_chat',
+ self.old_values['always_compact_view'])
+ gajim.config.set('always_compact_view_gc',
+ self.old_values['always_compact_view'])
+ # new theme: grocery, plain
+ d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
+ 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
+ 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
+ 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
+ 'bannerfontattrs']
+ for theme_name in (_('grocery'), _('default')):
+ if theme_name not in gajim.config.get_per('themes'):
+ gajim.config.add_per('themes', theme_name)
+ theme = gajim.config.themes_default[theme_name]
+ for o in d:
+ gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
+ # Remove cyan theme if it's not the current theme
+ if 'cyan' in gajim.config.get_per('themes'):
+ gajim.config.del_per('themes', 'cyan')
+ if _('cyan') in gajim.config.get_per('themes'):
+ gajim.config.del_per('themes', _('cyan'))
+ # If we removed our roster_theme, choose the default green one or another
+ # one if doesn't exists in config
+ if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'):
+ theme = _('green')
+ if theme not in gajim.config.get_per('themes'):
+ theme = gajim.config.get_per('themes')[0]
+ gajim.config.set('roster_theme', theme)
+ # new proxies in accounts.name.file_transfer_proxies
+ self.update_ft_proxies(to_add=['proxy.netlab.cz'])
+
+ gajim.config.set('version', '0.9')
+
+ def update_config_09_to_010(self):
+ if 'usetabbedchat' in self.old_values and not \
+ self.old_values['usetabbedchat']:
+ gajim.config.set('one_message_window', 'never')
+ if 'autodetect_browser_mailer' in self.old_values and \
+ self.old_values['autodetect_browser_mailer'] is True:
+ gajim.config.set('autodetect_browser_mailer', False)
+ if 'useemoticons' in self.old_values and \
+ not self.old_values['useemoticons']:
+ gajim.config.set('emoticons_theme', '')
+ if 'always_compact_view_chat' in self.old_values and \
+ self.old_values['always_compact_view_chat'] != 'False':
+ gajim.config.set('always_hide_chat_buttons', True)
+ if 'always_compact_view_gc' in self.old_values and \
+ self.old_values['always_compact_view_gc'] != 'False':
+ gajim.config.set('always_hide_groupchat_buttons', True)
+
+ self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl',
+ 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de'])
+ # create unread_messages table if needed
+ self.assert_unread_msgs_table_exists()
+
+ gajim.config.set('version', '0.10')
+
+ def update_config_to_01011(self):
+ if 'print_status_in_muc' in self.old_values and \
+ self.old_values['print_status_in_muc'] in (True, False):
+ gajim.config.set('print_status_in_muc', 'in_and_out')
+ gajim.config.set('version', '0.10.1.1')
+
+ def update_config_to_01012(self):
+ # See [6456]
+ if 'emoticons_theme' in self.old_values and \
+ self.old_values['emoticons_theme'] == 'Disabled':
+ gajim.config.set('emoticons_theme', '')
+ gajim.config.set('version', '0.10.1.2')
+
+ def update_config_to_01013(self):
+ """
+ Create table transports_cache if there is no such table
+ """
+ # FIXME see #2812
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE transports_cache (
+ transport TEXT UNIQUE,
+ type INTEGER
+ );
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.10.1.3')
+
+ def update_config_to_01014(self):
+ """
+ Apply indeces to the logs database
+ """
+ print _('migrating logs database to indices')
+ # FIXME see #2812
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ # apply indeces
+ try:
+ cur.executescript(
+ '''
+ CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
+ CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
+ '''
+ )
+
+ con.commit()
+ except Exception:
+ pass
+ con.close()
+ gajim.config.set('version', '0.10.1.4')
+
+ def update_config_to_01015(self):
+ """
+ Clean show values in logs database
+ """
+ #FIXME see #2812
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \
+ logger.constants.__dict__.keys() if i.startswith('SHOW_'))
+ for show in status:
+ cur.execute('update logs set show = ? where show = ?;', (status[show],
+ show))
+ cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);')
+ con.commit()
+ cur.close() # remove this in 2007 [pysqlite old versions need this]
+ con.close()
+ gajim.config.set('version', '0.10.1.5')
+
+ def update_config_to_01016(self):
+ """
+ #2494 : Now we play gc_received_message sound even if
+ notify_on_all_muc_messages is false. Keep precedent behaviour
+ """
+ if 'notify_on_all_muc_messages' in self.old_values and \
+ self.old_values['notify_on_all_muc_messages'] == 'False' and \
+ gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
+ gajim.config.set_per('soundevents',\
+ 'muc_message_received', 'enabled', False)
+ gajim.config.set('version', '0.10.1.6')
+
+ def update_config_to_01017(self):
+ """
+ trayicon_notification_on_new_messages -> trayicon_notification_on_events
+ """
+ if 'trayicon_notification_on_new_messages' in self.old_values:
+ gajim.config.set('trayicon_notification_on_events',
+ self.old_values['trayicon_notification_on_new_messages'])
+ gajim.config.set('version', '0.10.1.7')
+
+ def update_config_to_01018(self):
+ """
+ chat_state_notifications -> outgoing_chat_state_notifications
+ """
+ if 'chat_state_notifications' in self.old_values:
+ gajim.config.set('outgoing_chat_state_notifications',
+ self.old_values['chat_state_notifications'])
+ gajim.config.set('version', '0.10.1.8')
+
+ def update_config_to_01101(self):
+ """
+ Fill time_stamp from before_time and after_time
+ """
+ if 'before_time' in self.old_values:
+ gajim.config.set('time_stamp', '%s%%X%s ' % (
+ self.old_values['before_time'], self.old_values['after_time']))
+ gajim.config.set('version', '0.11.0.1')
+
+ def update_config_to_01102(self):
+ """
+ Fill time_stamp from before_time and after_time
+ """
+ if 'ft_override_host_to_send' in self.old_values:
+ gajim.config.set('ft_add_hosts_to_send',
+ self.old_values['ft_override_host_to_send'])
+ gajim.config.set('version', '0.11.0.2')
+
+ def update_config_to_01111(self):
+ """
+ Always_hide_chatbuttons -> compact_view
+ """
+ if 'always_hide_groupchat_buttons' in self.old_values and \
+ 'always_hide_chat_buttons' in self.old_values:
+ gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \
+ self.old_values['always_hide_chat_buttons'])
+ gajim.config.set('version', '0.11.1.1')
+
+ def update_config_to_01112(self):
+ """
+ GTK+ theme is renamed to default
+ """
+ if 'roster_theme' in self.old_values and \
+ self.old_values['roster_theme'] == 'gtk+':
+ gajim.config.set('roster_theme', _('default'))
+ gajim.config.set('version', '0.11.1.2')
+
+ def update_config_to_01113(self):
+ # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE caps_cache (
+ node TEXT,
+ ver TEXT,
+ ext TEXT,
+ data BLOB
+ );
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.11.1.3')
+
+ def update_config_to_01114(self):
+ # add default theme if it doesn't exist
+ d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
+ 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
+ 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
+ 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
+ 'bannerfontattrs']
+ theme_name = _('default')
+ if theme_name not in gajim.config.get_per('themes'):
+ gajim.config.add_per('themes', theme_name)
+ if gajim.config.get_per('themes', 'gtk+'):
+ # copy from old gtk+ theme
+ for o in d:
+ val = gajim.config.get_per('themes', 'gtk+', o)
+ gajim.config.set_per('themes', theme_name, o, val)
+ gajim.config.del_per('themes', 'gtk+')
+ else:
+ # copy from default theme
+ theme = gajim.config.themes_default[theme_name]
+ for o in d:
+ gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
+ gajim.config.set('version', '0.11.1.4')
+
+ def update_config_to_01115(self):
+ # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ DELETE FROM caps_cache;
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.11.1.5')
+
+ def update_config_to_01121(self):
+ # remove old unencrypted secrets file
+ from common.configpaths import gajimpaths
+
+ new_file = gajimpaths['SECRETS_FILE']
+
+ old_file = os.path.dirname(new_file) + '/secrets'
+
+ if os.path.exists(old_file):
+ os.remove(old_file)
+
+ gajim.config.set('version', '0.11.2.1')
+
+ def update_config_to_01141(self):
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE IF NOT EXISTS caps_cache (
+ node TEXT,
+ ver TEXT,
+ ext TEXT,
+ data BLOB
+ );
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.11.4.1')
+
+ def update_config_to_01142(self):
+ """
+ next_message_received sound event is splittedin 2 events
+ """
+ gajim.config.add_per('soundevents', 'next_message_received_focused')
+ gajim.config.add_per('soundevents', 'next_message_received_unfocused')
+ if gajim.config.get_per('soundevents', 'next_message_received'):
+ enabled = gajim.config.get_per('soundevents', 'next_message_received',
+ 'enabled')
+ path = gajim.config.get_per('soundevents', 'next_message_received',
+ 'path')
+ gajim.config.del_per('soundevents', 'next_message_received')
+ gajim.config.set_per('soundevents', 'next_message_received_focused',
+ 'enabled', enabled)
+ gajim.config.set_per('soundevents', 'next_message_received_focused',
+ 'path', path)
+ gajim.config.set('version', '0.11.1.2')
+
+ def update_config_to_01143(self):
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE IF NOT EXISTS rooms_last_message_time(
+ jid_id INTEGER PRIMARY KEY UNIQUE,
+ time INTEGER
+ );
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.11.4.3')
+
+ def update_config_to_01144(self):
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript('DROP TABLE caps_cache;')
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE caps_cache (
+ hash_method TEXT,
+ hash TEXT,
+ data BLOB
+ );
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError, e:
+ pass
+ con.close()
+ gajim.config.set('version', '0.11.4.4')
+
+ def update_config_to_01201(self):
+ if 'uri_schemes' in self.old_values:
+ new_values = self.old_values['uri_schemes'].replace(' mailto', '').\
+ replace(' xmpp', '')
+ gajim.config.set('uri_schemes', new_values)
+ gajim.config.set('version', '0.12.0.1')
+
+ def update_config_to_01211(self):
+ if 'trayicon' in self.old_values:
+ if self.old_values['trayicon'] == 'False':
+ gajim.config.set('trayicon', 'never')
+ else:
+ gajim.config.set('trayicon', 'always')
+ gajim.config.set('version', '0.12.1.1')
+
+ def update_config_to_01212(self):
+ for opt in ('ignore_unknown_contacts', 'send_os_info',
+ 'log_encrypted_sessions'):
+ if opt in self.old_values:
+ val = self.old_values[opt]
+ for account in gajim.config.get_per('accounts'):
+ gajim.config.set_per('accounts', account, opt, val)
+ gajim.config.set('version', '0.12.1.2')
+
+ def update_config_to_01213(self):
+ msgs = gajim.config.statusmsg_default
+ for msg_name in gajim.config.get_per('statusmsg'):
+ if msg_name in msgs:
+ gajim.config.set_per('statusmsg', msg_name, 'activity',
+ msgs[msg_name][1])
+ gajim.config.set_per('statusmsg', msg_name, 'subactivity',
+ msgs[msg_name][2])
+ gajim.config.set_per('statusmsg', msg_name, 'activity_text',
+ msgs[msg_name][3])
+ gajim.config.set_per('statusmsg', msg_name, 'mood',
+ msgs[msg_name][4])
+ gajim.config.set_per('statusmsg', msg_name, 'mood_text',
+ msgs[msg_name][5])
+ gajim.config.set('version', '0.12.1.3')
+
+ def update_config_to_01214(self):
+ for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible',
+ 'offline']:
+ if 'last_status_msg_' + status in self.old_values:
+ gajim.config.add_per('statusmsg', '_last_' + status)
+ gajim.config.set_per('statusmsg', '_last_' + status, 'message',
+ self.old_values['last_status_msg_' + status])
+ gajim.config.set('version', '0.12.1.4')
+
+ def update_config_to_01215(self):
+ """
+ Remove hardcoded ../data/sounds from config
+ """
+ dirs = ('../data', gajim.gajimpaths.root, gajim.DATA_DIR)
+ for evt in gajim.config.get_per('soundevents'):
+ path = gajim.config.get_per('soundevents', evt ,'path')
+ # absolute and relative passes are necessary
+ path = helpers.strip_soundfile_path(path, dirs, abs=False)
+ path = helpers.strip_soundfile_path(path, dirs, abs=True)
+ gajim.config.set_per('soundevents', evt, 'path', path)
+ gajim.config.set('version', '0.12.1.5')
+
+ def update_config_to_01231(self):
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ CREATE TABLE IF NOT EXISTS roster_entry(
+ account_jid_id INTEGER,
+ jid_id INTEGER,
+ name TEXT,
+ subscription INTEGER,
+ ask BOOLEAN,
+ PRIMARY KEY (account_jid_id, jid_id)
+ );
+
+ CREATE TABLE IF NOT EXISTS roster_group(
+ account_jid_id INTEGER,
+ jid_id INTEGER,
+ group_name TEXT,
+ PRIMARY KEY (account_jid_id, jid_id, group_name)
+ );
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.12.3.1')
+
+ def update_config_from_0125(self):
+ # All those functions need to be called for 0.12.5 to 0.13 transition
+ self.update_config_to_01211()
+ self.update_config_to_01213()
+ self.update_config_to_01214()
+ self.update_config_to_01215()
+ self.update_config_to_01231()
+
+ def update_config_to_01251(self):
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ ALTER TABLE unread_messages
+ ADD shown BOOLEAN default 0;
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.12.5.1')
+
+ def update_config_to_01252(self):
+ if 'alwaysauth' in self.old_values:
+ val = self.old_values['alwaysauth']
+ for account in gajim.config.get_per('accounts'):
+ gajim.config.set_per('accounts', account, 'autoauth', val)
+ gajim.config.set('version', '0.12.5.2')
+
+ def update_config_to_01253(self):
+ if 'enable_zeroconf' in self.old_values:
+ val = self.old_values['enable_zeroconf']
+ for account in gajim.config.get_per('accounts'):
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ gajim.config.set_per('accounts', account, 'active', val)
+ else:
+ gajim.config.set_per('accounts', account, 'active', True)
+ gajim.config.set('version', '0.12.5.3')
+
+ def update_config_to_01254(self):
+ vals = {'inmsgcolor': ['#a34526', '#a40000'],
+ 'outmsgcolor': ['#164e6f', '#3465a4'],
+ 'restored_messages_color': ['grey', '#555753'],
+ 'statusmsgcolor': ['#1eaa1e', '#73d216'],
+ 'urlmsgcolor': ['#0000ff', '#204a87'],
+ 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
+ for c in vals:
+ if c not in self.old_values:
+ continue
+ val = self.old_values[c]
+ if val == vals[c][0]:
+ # We didn't change default value, so update it with new default
+ gajim.config.set(c, vals[c][1])
+ gajim.config.set('version', '0.12.5.4')
+
+ def update_config_to_01255(self):
+ vals = {'statusmsgcolor': ['#73d216', '#4e9a06'],
+ 'outmsgtxtcolor': ['#a2a2a2', '#555753']}
+ for c in vals:
+ if c not in self.old_values:
+ continue
+ val = self.old_values[c]
+ if val == vals[c][0]:
+ # We didn't change default value, so update it with new default
+ gajim.config.set(c, vals[c][1])
+ gajim.config.set('version', '0.12.5.5')
+
+ def update_config_to_01256(self):
+ vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
+ for c in vals:
+ if c not in self.old_values:
+ continue
+ val = self.old_values[c]
+ if val == vals[c][0]:
+ # We didn't change default value, so update it with new default
+ gajim.config.set(c, vals[c][1])
+ gajim.config.set('version', '0.12.5.6')
+
+ def update_config_to_01257(self):
+ if 'iconset' in self.old_values:
+ if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip',
+ 'simplebulb', 'stellar'):
+ gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET)
+ gajim.config.set('version', '0.12.5.7')
+
+ def update_config_to_01258(self):
+ self.update_ft_proxies(to_remove=['proxy65.talkonaut.com',
+ 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de',
+ 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org',
+ 'proxy.jabber.ru', 'proxy.jabbim.cz'])
+ gajim.config.set('version', '0.12.5.8')
+
+ def update_config_to_013100(self):
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ ALTER TABLE caps_cache
+ ADD last_seen INTEGER default %d;
+ ''' % int(time())
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.13.10.0')
+
+ def update_config_to_013101(self):
+ back = os.getcwd()
+ os.chdir(logger.LOG_DB_FOLDER)
+ con = sqlite.connect(logger.LOG_DB_FILE)
+ os.chdir(back)
+ cur = con.cursor()
+ try:
+ cur.executescript(
+ '''
+ DROP INDEX IF EXISTS idx_logs_jid_id_kind;
+
+ CREATE INDEX IF NOT EXISTS
+ idx_logs_jid_id_time ON logs (jid_id, time DESC);
+ '''
+ )
+ con.commit()
+ except sqlite.OperationalError:
+ pass
+ con.close()
+ gajim.config.set('version', '0.13.10.1')
diff --git a/src/common/passwords.py b/src/common/passwords.py
index 9000085c4..15607976d 100644
--- a/src/common/passwords.py
+++ b/src/common/passwords.py
@@ -36,189 +36,187 @@ USER_HAS_KWALLETCLI = False
gnomekeyring = None
class PasswordStorage(object):
- def get_password(self, account_name):
- raise NotImplementedError
- def save_password(self, account_name, password):
- raise NotImplementedError
+ def get_password(self, account_name):
+ raise NotImplementedError
+ def save_password(self, account_name, password):
+ raise NotImplementedError
class SimplePasswordStorage(PasswordStorage):
- def get_password(self, account_name):
- passwd = gajim.config.get_per('accounts', account_name, 'password')
- if passwd and (passwd.startswith('gnomekeyring:') or \
- passwd == '<kwallet>'):
- # this is not a real password, it's either a gnome
- # keyring token or stored in the KDE wallet
- return None
- else:
- return passwd
-
- def save_password(self, account_name, password):
- gajim.config.set_per('accounts', account_name, 'password', password)
- if account_name in gajim.connections:
- gajim.connections[account_name].password = password
+ def get_password(self, account_name):
+ passwd = gajim.config.get_per('accounts', account_name, 'password')
+ if passwd and (passwd.startswith('gnomekeyring:') or \
+ passwd == '<kwallet>'):
+ # this is not a real password, it's either a gnome
+ # keyring token or stored in the KDE wallet
+ return None
+ else:
+ return passwd
+
+ def save_password(self, account_name, password):
+ gajim.config.set_per('accounts', account_name, 'password', password)
+ if account_name in gajim.connections:
+ gajim.connections[account_name].password = password
class GnomePasswordStorage(PasswordStorage):
- def __init__(self):
- self.keyring = gnomekeyring.get_default_keyring_sync()
- if self.keyring is None:
- self.keyring = 'default'
- try:
- gnomekeyring.create_sync(self.keyring, None)
- except gnomekeyring.AlreadyExistsError:
- pass
-
- def get_password(self, account_name):
- conf = gajim.config.get_per('accounts', account_name, 'password')
- if conf is None or conf == '<kwallet>':
- return None
- if not conf.startswith('gnomekeyring:'):
- password = conf
- ## migrate the password over to keyring
- try:
- self.save_password(account_name, password, update=False)
- except gnomekeyring.NoKeyringDaemonError:
- ## no keyring daemon: in the future, stop using it
- set_storage(SimplePasswordStorage())
- return password
- try:
- server = gajim.config.get_per('accounts', account_name, 'hostname')
- user = gajim.config.get_per('accounts', account_name, 'name')
- attributes1 = dict(server=str(server), user=str(user), protocol='xmpp')
- attributes2 = dict(account_name=str(account_name), gajim=1)
- try:
- items = gnomekeyring.find_items_sync(
- gnomekeyring.ITEM_NETWORK_PASSWORD, attributes1)
- except gnomekeyring.Error:
- try:
- items = gnomekeyring.find_items_sync(
- gnomekeyring.ITEM_GENERIC_SECRET, attributes2)
- if items:
- # We found an old item, move it to new way of storing
- password = items[0].secret
- self.save_password(account_name, password)
- gnomekeyring.item_delete_sync(items[0].keyring,
- int(items[0].item_id))
- except gnomekeyring.Error:
- items = []
- if len(items) > 1:
- warnings.warn("multiple gnome keyring items found for account %s;"
- " trying to use the first one..."
- % account_name)
- if items:
- return items[0].secret
- else:
- return None
- except gnomekeyring.DeniedError:
- return None
- except gnomekeyring.NoKeyringDaemonError:
- ## no keyring daemon: in the future, stop using it
- set_storage(SimplePasswordStorage())
- return None
-
- def save_password(self, account_name, password, update=True):
- server = gajim.config.get_per('accounts', account_name, 'hostname')
- user = gajim.config.get_per('accounts', account_name, 'name')
- display_name = _('XMPP account %s@%s') % (user, server)
- attributes1 = dict(server=str(server), user=str(user), protocol='xmpp')
- if password is None:
- password = str()
- try:
- auth_token = gnomekeyring.item_create_sync(
- self.keyring, gnomekeyring.ITEM_NETWORK_PASSWORD,
- display_name, attributes1, password, update)
- except gnomekeyring.DeniedError:
- set_storage(SimplePasswordStorage())
- storage.save_password(account_name, password)
- return
- gajim.config.set_per('accounts', account_name, 'password',
- 'gnomekeyring:')
- if account_name in gajim.connections:
- gajim.connections[account_name].password = password
+ def __init__(self):
+ self.keyring = gnomekeyring.get_default_keyring_sync()
+ if self.keyring is None:
+ self.keyring = 'default'
+ try:
+ gnomekeyring.create_sync(self.keyring, None)
+ except gnomekeyring.AlreadyExistsError:
+ pass
+
+ def get_password(self, account_name):
+ conf = gajim.config.get_per('accounts', account_name, 'password')
+ if conf is None or conf == '<kwallet>':
+ return None
+ if not conf.startswith('gnomekeyring:'):
+ password = conf
+ ## migrate the password over to keyring
+ try:
+ self.save_password(account_name, password, update=False)
+ except gnomekeyring.NoKeyringDaemonError:
+ ## no keyring daemon: in the future, stop using it
+ set_storage(SimplePasswordStorage())
+ return password
+ try:
+ server = gajim.config.get_per('accounts', account_name, 'hostname')
+ user = gajim.config.get_per('accounts', account_name, 'name')
+ attributes1 = dict(server=str(server), user=str(user), protocol='xmpp')
+ attributes2 = dict(account_name=str(account_name), gajim=1)
+ try:
+ items = gnomekeyring.find_items_sync(
+ gnomekeyring.ITEM_NETWORK_PASSWORD, attributes1)
+ except gnomekeyring.Error:
+ try:
+ items = gnomekeyring.find_items_sync(
+ gnomekeyring.ITEM_GENERIC_SECRET, attributes2)
+ if items:
+ # We found an old item, move it to new way of storing
+ password = items[0].secret
+ self.save_password(account_name, password)
+ gnomekeyring.item_delete_sync(items[0].keyring,
+ int(items[0].item_id))
+ except gnomekeyring.Error:
+ items = []
+ if len(items) > 1:
+ warnings.warn("multiple gnome keyring items found for account %s;"
+ " trying to use the first one..."
+ % account_name)
+ if items:
+ return items[0].secret
+ else:
+ return None
+ except gnomekeyring.DeniedError:
+ return None
+ except gnomekeyring.NoKeyringDaemonError:
+ ## no keyring daemon: in the future, stop using it
+ set_storage(SimplePasswordStorage())
+ return None
+
+ def save_password(self, account_name, password, update=True):
+ server = gajim.config.get_per('accounts', account_name, 'hostname')
+ user = gajim.config.get_per('accounts', account_name, 'name')
+ display_name = _('XMPP account %s@%s') % (user, server)
+ attributes1 = dict(server=str(server), user=str(user), protocol='xmpp')
+ if password is None:
+ password = str()
+ try:
+ auth_token = gnomekeyring.item_create_sync(
+ self.keyring, gnomekeyring.ITEM_NETWORK_PASSWORD,
+ display_name, attributes1, password, update)
+ except gnomekeyring.DeniedError:
+ set_storage(SimplePasswordStorage())
+ storage.save_password(account_name, password)
+ return
+ gajim.config.set_per('accounts', account_name, 'password',
+ 'gnomekeyring:')
+ if account_name in gajim.connections:
+ gajim.connections[account_name].password = password
class KWalletPasswordStorage(PasswordStorage):
- def get_password(self, account_name):
- pw = gajim.config.get_per('accounts', account_name, 'password')
- if not pw or pw.startswith('gnomekeyring:'):
- # unset, empty or not ours
- return None
- if pw != '<kwallet>':
- # migrate the password
- if kwalletbinding.kwallet_put('gajim', account_name, pw):
- gajim.config.set_per('accounts', account_name, 'password',
- '<kwallet>')
- else:
- # stop using the KDE Wallet
- set_storage(SimplePasswordStorage())
- return pw
- pw = kwalletbinding.kwallet_get('gajim', account_name)
- if pw is None:
- # stop using the KDE Wallet
- set_storage(SimplePasswordStorage())
- if not pw:
- # False, None, or the empty string
- return None
- return pw
-
- def save_password(self, account_name, password):
- if not kwalletbinding.kwallet_put('gajim', account_name, password):
- # stop using the KDE Wallet
- set_storage(SimplePasswordStorage())
- storage.save_password(account_name, password)
- return
- pwtoken = '<kwallet>'
- if not password:
- # no sense in looking up the empty string in the KWallet
- pwtoken = ''
- gajim.config.set_per('accounts', account_name, 'password', pwtoken)
- if account_name in gajim.connections:
- gajim.connections[account_name].password = password
+ def get_password(self, account_name):
+ pw = gajim.config.get_per('accounts', account_name, 'password')
+ if not pw or pw.startswith('gnomekeyring:'):
+ # unset, empty or not ours
+ return None
+ if pw != '<kwallet>':
+ # migrate the password
+ if kwalletbinding.kwallet_put('gajim', account_name, pw):
+ gajim.config.set_per('accounts', account_name, 'password',
+ '<kwallet>')
+ else:
+ # stop using the KDE Wallet
+ set_storage(SimplePasswordStorage())
+ return pw
+ pw = kwalletbinding.kwallet_get('gajim', account_name)
+ if pw is None:
+ # stop using the KDE Wallet
+ set_storage(SimplePasswordStorage())
+ if not pw:
+ # False, None, or the empty string
+ return None
+ return pw
+
+ def save_password(self, account_name, password):
+ if not kwalletbinding.kwallet_put('gajim', account_name, password):
+ # stop using the KDE Wallet
+ set_storage(SimplePasswordStorage())
+ storage.save_password(account_name, password)
+ return
+ pwtoken = '<kwallet>'
+ if not password:
+ # no sense in looking up the empty string in the KWallet
+ pwtoken = ''
+ gajim.config.set_per('accounts', account_name, 'password', pwtoken)
+ if account_name in gajim.connections:
+ gajim.connections[account_name].password = password
storage = None
def get_storage():
- global storage
- if storage is None: # None is only in first time get_storage is called
- if gajim.config.get('use_gnomekeyring'):
- global gnomekeyring
- try:
- import gnomekeyring
- except ImportError:
- pass
- else:
- global USER_HAS_GNOMEKEYRING
- global USER_USES_GNOMEKEYRING
- USER_HAS_GNOMEKEYRING = True
- if gnomekeyring.is_available():
- USER_USES_GNOMEKEYRING = True
- else:
- USER_USES_GNOMEKEYRING = False
- if USER_USES_GNOMEKEYRING:
- try:
- storage = GnomePasswordStorage()
- except (gnomekeyring.NoKeyringDaemonError, gnomekeyring.DeniedError):
- storage = None
- if storage is None:
- if gajim.config.get('use_kwalletcli'):
- global USER_HAS_KWALLETCLI
- if kwalletbinding.kwallet_available():
- USER_HAS_KWALLETCLI = True
- if USER_HAS_KWALLETCLI:
- storage = KWalletPasswordStorage()
- if storage is None:
- storage = SimplePasswordStorage()
- return storage
+ global storage
+ if storage is None: # None is only in first time get_storage is called
+ if gajim.config.get('use_gnomekeyring'):
+ global gnomekeyring
+ try:
+ import gnomekeyring
+ except ImportError:
+ pass
+ else:
+ global USER_HAS_GNOMEKEYRING
+ global USER_USES_GNOMEKEYRING
+ USER_HAS_GNOMEKEYRING = True
+ if gnomekeyring.is_available():
+ USER_USES_GNOMEKEYRING = True
+ else:
+ USER_USES_GNOMEKEYRING = False
+ if USER_USES_GNOMEKEYRING:
+ try:
+ storage = GnomePasswordStorage()
+ except (gnomekeyring.NoKeyringDaemonError, gnomekeyring.DeniedError):
+ storage = None
+ if storage is None:
+ if gajim.config.get('use_kwalletcli'):
+ global USER_HAS_KWALLETCLI
+ if kwalletbinding.kwallet_available():
+ USER_HAS_KWALLETCLI = True
+ if USER_HAS_KWALLETCLI:
+ storage = KWalletPasswordStorage()
+ if storage is None:
+ storage = SimplePasswordStorage()
+ return storage
def set_storage(storage_):
- global storage
- storage = storage_
+ global storage
+ storage = storage_
def get_password(account_name):
- return get_storage().get_password(account_name)
+ return get_storage().get_password(account_name)
def save_password(account_name, password):
- return get_storage().save_password(account_name, password)
-
-# vim: se ts=3:
+ return get_storage().save_password(account_name, password)
diff --git a/src/common/pep.py b/src/common/pep.py
index 0457f6667..b417b77e0 100644
--- a/src/common/pep.py
+++ b/src/common/pep.py
@@ -24,177 +24,177 @@
##
MOODS = {
- 'afraid': _('Afraid'),
- 'amazed': _('Amazed'),
- 'amorous': _('Amorous'),
- 'angry': _('Angry'),
- 'annoyed': _('Annoyed'),
- 'anxious': _('Anxious'),
- 'aroused': _('Aroused'),
- 'ashamed': _('Ashamed'),
- 'bored': _('Bored'),
- 'brave': _('Brave'),
- 'calm': _('Calm'),
- 'cautious': _('Cautious'),
- 'cold': _('Cold'),
- 'confident': _('Confident'),
- 'confused': _('Confused'),
- 'contemplative': _('Contemplative'),
- 'contented': _('Contented'),
- 'cranky': _('Cranky'),
- 'crazy': _('Crazy'),
- 'creative': _('Creative'),
- 'curious': _('Curious'),
- 'dejected': _('Dejected'),
- 'depressed': _('Depressed'),
- 'disappointed': _('Disappointed'),
- 'disgusted': _('Disgusted'),
- 'dismayed': _('Dismayed'),
- 'distracted': _('Distracted'),
- 'embarrassed': _('Embarrassed'),
- 'envious': _('Envious'),
- 'excited': _('Excited'),
- 'flirtatious': _('Flirtatious'),
- 'frustrated': _('Frustrated'),
- 'grateful': _('Grateful'),
- 'grieving': _('Grieving'),
- 'grumpy': _('Grumpy'),
- 'guilty': _('Guilty'),
- 'happy': _('Happy'),
- 'hopeful': _('Hopeful'),
- 'hot': _('Hot'),
- 'humbled': _('Humbled'),
- 'humiliated': _('Humiliated'),
- 'hungry': _('Hungry'),
- 'hurt': _('Hurt'),
- 'impressed': _('Impressed'),
- 'in_awe': _('In Awe'),
- 'in_love': _('In Love'),
- 'indignant': _('Indignant'),
- 'interested': _('Interested'),
- 'intoxicated': _('Intoxicated'),
- 'invincible': _('Invincible'),
- 'jealous': _('Jealous'),
- 'lonely': _('Lonely'),
- 'lost': _('Lost'),
- 'lucky': _('Lucky'),
- 'mean': _('Mean'),
- 'moody': _('Moody'),
- 'nervous': _('Nervous'),
- 'neutral': _('Neutral'),
- 'offended': _('Offended'),
- 'outraged': _('Outraged'),
- 'playful': _('Playful'),
- 'proud': _('Proud'),
- 'relaxed': _('Relaxed'),
- 'relieved': _('Relieved'),
- 'remorseful': _('Remorseful'),
- 'restless': _('Restless'),
- 'sad': _('Sad'),
- 'sarcastic': _('Sarcastic'),
- 'satisfied': _('Satisfied'),
- 'serious': _('Serious'),
- 'shocked': _('Shocked'),
- 'shy': _('Shy'),
- 'sick': _('Sick'),
- 'sleepy': _('Sleepy'),
- 'spontaneous': _('Spontaneous'),
- 'stressed': _('Stressed'),
- 'strong': _('Strong'),
- 'surprised': _('Surprised'),
- 'thankful': _('Thankful'),
- 'thirsty': _('Thirsty'),
- 'tired': _('Tired'),
- 'undefined': _('Undefined'),
- 'weak': _('Weak'),
- 'worried': _('Worried')}
+ 'afraid': _('Afraid'),
+ 'amazed': _('Amazed'),
+ 'amorous': _('Amorous'),
+ 'angry': _('Angry'),
+ 'annoyed': _('Annoyed'),
+ 'anxious': _('Anxious'),
+ 'aroused': _('Aroused'),
+ 'ashamed': _('Ashamed'),
+ 'bored': _('Bored'),
+ 'brave': _('Brave'),
+ 'calm': _('Calm'),
+ 'cautious': _('Cautious'),
+ 'cold': _('Cold'),
+ 'confident': _('Confident'),
+ 'confused': _('Confused'),
+ 'contemplative': _('Contemplative'),
+ 'contented': _('Contented'),
+ 'cranky': _('Cranky'),
+ 'crazy': _('Crazy'),
+ 'creative': _('Creative'),
+ 'curious': _('Curious'),
+ 'dejected': _('Dejected'),
+ 'depressed': _('Depressed'),
+ 'disappointed': _('Disappointed'),
+ 'disgusted': _('Disgusted'),
+ 'dismayed': _('Dismayed'),
+ 'distracted': _('Distracted'),
+ 'embarrassed': _('Embarrassed'),
+ 'envious': _('Envious'),
+ 'excited': _('Excited'),
+ 'flirtatious': _('Flirtatious'),
+ 'frustrated': _('Frustrated'),
+ 'grateful': _('Grateful'),
+ 'grieving': _('Grieving'),
+ 'grumpy': _('Grumpy'),
+ 'guilty': _('Guilty'),
+ 'happy': _('Happy'),
+ 'hopeful': _('Hopeful'),
+ 'hot': _('Hot'),
+ 'humbled': _('Humbled'),
+ 'humiliated': _('Humiliated'),
+ 'hungry': _('Hungry'),
+ 'hurt': _('Hurt'),
+ 'impressed': _('Impressed'),
+ 'in_awe': _('In Awe'),
+ 'in_love': _('In Love'),
+ 'indignant': _('Indignant'),
+ 'interested': _('Interested'),
+ 'intoxicated': _('Intoxicated'),
+ 'invincible': _('Invincible'),
+ 'jealous': _('Jealous'),
+ 'lonely': _('Lonely'),
+ 'lost': _('Lost'),
+ 'lucky': _('Lucky'),
+ 'mean': _('Mean'),
+ 'moody': _('Moody'),
+ 'nervous': _('Nervous'),
+ 'neutral': _('Neutral'),
+ 'offended': _('Offended'),
+ 'outraged': _('Outraged'),
+ 'playful': _('Playful'),
+ 'proud': _('Proud'),
+ 'relaxed': _('Relaxed'),
+ 'relieved': _('Relieved'),
+ 'remorseful': _('Remorseful'),
+ 'restless': _('Restless'),
+ 'sad': _('Sad'),
+ 'sarcastic': _('Sarcastic'),
+ 'satisfied': _('Satisfied'),
+ 'serious': _('Serious'),
+ 'shocked': _('Shocked'),
+ 'shy': _('Shy'),
+ 'sick': _('Sick'),
+ 'sleepy': _('Sleepy'),
+ 'spontaneous': _('Spontaneous'),
+ 'stressed': _('Stressed'),
+ 'strong': _('Strong'),
+ 'surprised': _('Surprised'),
+ 'thankful': _('Thankful'),
+ 'thirsty': _('Thirsty'),
+ 'tired': _('Tired'),
+ 'undefined': _('Undefined'),
+ 'weak': _('Weak'),
+ 'worried': _('Worried')}
ACTIVITIES = {
- 'doing_chores': {'category': _('Doing Chores'),
- 'buying_groceries': _('Buying Groceries'),
- 'cleaning': _('Cleaning'),
- 'cooking': _('Cooking'),
- 'doing_maintenance': _('Doing Maintenance'),
- 'doing_the_dishes': _('Doing the Dishes'),
- 'doing_the_laundry': _('Doing the Laundry'),
- 'gardening': _('Gardening'),
- 'running_an_errand': _('Running an Errand'),
- 'walking_the_dog': _('Walking the Dog')},
- 'drinking': {'category': _('Drinking'),
- 'having_a_beer': _('Having a Beer'),
- 'having_coffee': _('Having Coffee'),
- 'having_tea': _('Having Tea')},
- 'eating': {'category': _('Eating'),
- 'having_a_snack': _('Having a Snack'),
- 'having_breakfast': _('Having Breakfast'),
- 'having_dinner': _('Having Dinner'),
- 'having_lunch': _('Having Lunch')},
- 'exercising': {'category': _('Exercising'),
- 'cycling': _('Cycling'),
- 'dancing': _('Dancing'),
- 'hiking': _('Hiking'),
- 'jogging': _('Jogging'),
- 'playing_sports': _('Playing Sports'),
- 'running': _('Running'),
- 'skiing': _('Skiing'),
- 'swimming': _('Swimming'),
- 'working_out': _('Working out')},
- 'grooming': {'category': _('Grooming'),
- 'at_the_spa': _('At the Spa'),
- 'brushing_teeth': _('Brushing Teeth'),
- 'getting_a_haircut': _('Getting a Haircut'),
- 'shaving': _('Shaving'),
- 'taking_a_bath': _('Taking a Bath'),
- 'taking_a_shower': _('Taking a Shower')},
- 'having_appointment': {'category': _('Having an Appointment')},
- 'inactive': {'category': _('Inactive'),
- 'day_off': _('Day Off'),
- 'hanging_out': _('Hanging out'),
- 'hiding': _('Hiding'),
- 'on_vacation': _('On Vacation'),
- 'praying': _('Praying'),
- 'scheduled_holiday': _('Scheduled Holiday'),
- 'sleeping': _('Sleeping'),
- 'thinking': _('Thinking')},
- 'relaxing': {'category': _('Relaxing'),
- 'fishing': _('Fishing'),
- 'gaming': _('Gaming'),
- 'going_out': _('Going out'),
- 'partying': _('Partying'),
- 'reading': _('Reading'),
- 'rehearsing': _('Rehearsing'),
- 'shopping': _('Shopping'),
- 'smoking': _('Smoking'),
- 'socializing': _('Socializing'),
- 'sunbathing': _('Sunbathing'),
- 'watching_tv': _('Watching TV'),
- 'watching_a_movie': _('Watching a Movie')},
- 'talking': {'category': _('Talking'),
- 'in_real_life': _('In Real Life'),
- 'on_the_phone': _('On the Phone'),
- 'on_video_phone': _('On Video Phone')},
- 'traveling': {'category': _('Traveling'),
- 'commuting': _('Commuting'),
- 'cycling': _('Cycling'),
- 'driving': _('Driving'),
- 'in_a_car': _('In a Car'),
- 'on_a_bus': _('On a Bus'),
- 'on_a_plane': _('On a Plane'),
- 'on_a_train': _('On a Train'),
- 'on_a_trip': _('On a Trip'),
- 'walking': _('Walking')},
- 'working': {'category': _('Working'),
- 'coding': _('Coding'),
- 'in_a_meeting': _('In a Meeting'),
- 'studying': _('Studying'),
- 'writing': _('Writing')}}
+ 'doing_chores': {'category': _('Doing Chores'),
+ 'buying_groceries': _('Buying Groceries'),
+ 'cleaning': _('Cleaning'),
+ 'cooking': _('Cooking'),
+ 'doing_maintenance': _('Doing Maintenance'),
+ 'doing_the_dishes': _('Doing the Dishes'),
+ 'doing_the_laundry': _('Doing the Laundry'),
+ 'gardening': _('Gardening'),
+ 'running_an_errand': _('Running an Errand'),
+ 'walking_the_dog': _('Walking the Dog')},
+ 'drinking': {'category': _('Drinking'),
+ 'having_a_beer': _('Having a Beer'),
+ 'having_coffee': _('Having Coffee'),
+ 'having_tea': _('Having Tea')},
+ 'eating': {'category': _('Eating'),
+ 'having_a_snack': _('Having a Snack'),
+ 'having_breakfast': _('Having Breakfast'),
+ 'having_dinner': _('Having Dinner'),
+ 'having_lunch': _('Having Lunch')},
+ 'exercising': {'category': _('Exercising'),
+ 'cycling': _('Cycling'),
+ 'dancing': _('Dancing'),
+ 'hiking': _('Hiking'),
+ 'jogging': _('Jogging'),
+ 'playing_sports': _('Playing Sports'),
+ 'running': _('Running'),
+ 'skiing': _('Skiing'),
+ 'swimming': _('Swimming'),
+ 'working_out': _('Working out')},
+ 'grooming': {'category': _('Grooming'),
+ 'at_the_spa': _('At the Spa'),
+ 'brushing_teeth': _('Brushing Teeth'),
+ 'getting_a_haircut': _('Getting a Haircut'),
+ 'shaving': _('Shaving'),
+ 'taking_a_bath': _('Taking a Bath'),
+ 'taking_a_shower': _('Taking a Shower')},
+ 'having_appointment': {'category': _('Having an Appointment')},
+ 'inactive': {'category': _('Inactive'),
+ 'day_off': _('Day Off'),
+ 'hanging_out': _('Hanging out'),
+ 'hiding': _('Hiding'),
+ 'on_vacation': _('On Vacation'),
+ 'praying': _('Praying'),
+ 'scheduled_holiday': _('Scheduled Holiday'),
+ 'sleeping': _('Sleeping'),
+ 'thinking': _('Thinking')},
+ 'relaxing': {'category': _('Relaxing'),
+ 'fishing': _('Fishing'),
+ 'gaming': _('Gaming'),
+ 'going_out': _('Going out'),
+ 'partying': _('Partying'),
+ 'reading': _('Reading'),
+ 'rehearsing': _('Rehearsing'),
+ 'shopping': _('Shopping'),
+ 'smoking': _('Smoking'),
+ 'socializing': _('Socializing'),
+ 'sunbathing': _('Sunbathing'),
+ 'watching_tv': _('Watching TV'),
+ 'watching_a_movie': _('Watching a Movie')},
+ 'talking': {'category': _('Talking'),
+ 'in_real_life': _('In Real Life'),
+ 'on_the_phone': _('On the Phone'),
+ 'on_video_phone': _('On Video Phone')},
+ 'traveling': {'category': _('Traveling'),
+ 'commuting': _('Commuting'),
+ 'cycling': _('Cycling'),
+ 'driving': _('Driving'),
+ 'in_a_car': _('In a Car'),
+ 'on_a_bus': _('On a Bus'),
+ 'on_a_plane': _('On a Plane'),
+ 'on_a_train': _('On a Train'),
+ 'on_a_trip': _('On a Trip'),
+ 'walking': _('Walking')},
+ 'working': {'category': _('Working'),
+ 'coding': _('Coding'),
+ 'in_a_meeting': _('In a Meeting'),
+ 'studying': _('Studying'),
+ 'writing': _('Writing')}}
TUNE_DATA = ['artist', 'title', 'source', 'track', 'length']
LOCATION_DATA = ['accuracy', 'alt', 'area', 'bearing', 'building', 'country',
- 'countrycode', 'datum', 'description', 'error', 'floor', 'lat',
- 'locality', 'lon', 'postalcode', 'region', 'room', 'speed', 'street',
- 'text', 'timestamp', 'uri']
+ 'countrycode', 'datum', 'description', 'error', 'floor', 'lat',
+ 'locality', 'lon', 'postalcode', 'region', 'room', 'speed', 'street',
+ 'text', 'timestamp', 'uri']
import gobject
import gtk
@@ -212,470 +212,468 @@ import gtkgui_helpers
class AbstractPEP(object):
- type = ''
- namespace = ''
-
- @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 ''
+ type = ''
+ namespace = ''
+
+ @classmethod
+ def get_tag_as_PEP(cls, jid, account, event_tag):
+ items = event_tag.getTag('items', {'node': cls.namespace})
+ if items:
+ log.debug("Received PEP 'user %s' from %s" % (cls.type, jid))
+ return cls(jid, account, items)
+ else:
+ return None
+
+ def __init__(self, jid, account, items):
+ self._pep_specific_data, self._retracted = self._extract_info(items)
+
+ self._update_contacts(jid, account)
+ if jid == gajim.get_jid_from_account(account):
+ self._update_account(account)
+
+ def _extract_info(self, items):
+ '''To be implemented by subclasses'''
+ raise NotImplementedError
+
+ def _update_contacts(self, jid, account):
+ for contact in gajim.contacts.get_contacts(account, jid):
+ if self._retracted:
+ if self.type in contact.pep:
+ del contact.pep[self.type]
+ else:
+ contact.pep[self.type] = self
+
+ def _update_account(self, account):
+ acc = gajim.connections[account]
+ if self._retracted:
+ if self.type in acc.pep:
+ del acc.pep[self.type]
+ else:
+ acc.pep[self.type] = self
+
+ def asPixbufIcon(self):
+ '''SHOULD be implemented by subclasses'''
+ return None
+
+ def asMarkupText(self):
+ '''SHOULD be implemented by subclasses'''
+ return ''
class UserMoodPEP(AbstractPEP):
- '''XEP-0107: User Mood'''
-
- type = 'mood'
- namespace = xmpp.NS_MOOD
-
- def _extract_info(self, items):
- mood_dict = {}
-
- for item in items.getTags('item'):
- mood_tag = item.getTag('mood')
- if mood_tag:
- for child in mood_tag.getChildren():
- name = child.getName().strip()
- if name == 'text':
- mood_dict['text'] = child.getData()
- else:
- mood_dict['mood'] = name
-
- retracted = items.getTag('retract') or not 'mood' in mood_dict
- return (mood_dict, retracted)
-
- def asPixbufIcon(self):
- assert not self._retracted
- received_mood = self._pep_specific_data['mood']
- mood = received_mood if received_mood in MOODS else 'unknown'
- pixbuf = gtkgui_helpers.load_mood_icon(mood).get_pixbuf()
- return pixbuf
-
- def asMarkupText(self):
- assert not self._retracted
- untranslated_mood = self._pep_specific_data['mood']
- mood = self._translate_mood(untranslated_mood)
- markuptext = '<b>%s</b>' % gobject.markup_escape_text(mood)
- if 'text' in self._pep_specific_data:
- text = self._pep_specific_data['text']
- markuptext += ' (%s)' % gobject.markup_escape_text(text)
- return markuptext
-
- def _translate_mood(self, mood):
- if mood in MOODS:
- return MOODS[mood]
- else:
- return mood
+ '''XEP-0107: User Mood'''
+
+ type = 'mood'
+ namespace = xmpp.NS_MOOD
+
+ def _extract_info(self, items):
+ mood_dict = {}
+
+ for item in items.getTags('item'):
+ mood_tag = item.getTag('mood')
+ if mood_tag:
+ for child in mood_tag.getChildren():
+ name = child.getName().strip()
+ if name == 'text':
+ mood_dict['text'] = child.getData()
+ else:
+ mood_dict['mood'] = name
+
+ retracted = items.getTag('retract') or not 'mood' in mood_dict
+ return (mood_dict, retracted)
+
+ def asPixbufIcon(self):
+ assert not self._retracted
+ received_mood = self._pep_specific_data['mood']
+ mood = received_mood if received_mood in MOODS else 'unknown'
+ pixbuf = gtkgui_helpers.load_mood_icon(mood).get_pixbuf()
+ return pixbuf
+
+ def asMarkupText(self):
+ assert not self._retracted
+ untranslated_mood = self._pep_specific_data['mood']
+ mood = self._translate_mood(untranslated_mood)
+ markuptext = '<b>%s</b>' % gobject.markup_escape_text(mood)
+ if 'text' in self._pep_specific_data:
+ text = self._pep_specific_data['text']
+ markuptext += ' (%s)' % gobject.markup_escape_text(text)
+ return markuptext
+
+ def _translate_mood(self, mood):
+ if mood in MOODS:
+ return MOODS[mood]
+ else:
+ return mood
class UserTunePEP(AbstractPEP):
- '''XEP-0118: User Tune'''
+ '''XEP-0118: User Tune'''
- type = 'tune'
- namespace = xmpp.NS_TUNE
+ type = 'tune'
+ namespace = xmpp.NS_TUNE
- def _extract_info(self, items):
- tune_dict = {}
+ def _extract_info(self, items):
+ tune_dict = {}
- for item in items.getTags('item'):
- tune_tag = item.getTag('tune')
- if tune_tag:
- for child in tune_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if child.getName() in TUNE_DATA:
- tune_dict[name] = data
+ for item in items.getTags('item'):
+ tune_tag = item.getTag('tune')
+ if tune_tag:
+ for child in tune_tag.getChildren():
+ name = child.getName().strip()
+ data = child.getData().strip()
+ if child.getName() in TUNE_DATA:
+ tune_dict[name] = data
- retracted = items.getTag('retract') or not ('artist' in tune_dict or
- 'title' in tune_dict)
- return (tune_dict, retracted)
+ retracted = items.getTag('retract') or not ('artist' in tune_dict or
+ 'title' in tune_dict)
+ return (tune_dict, retracted)
- def asPixbufIcon(self):
- import os
- path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
- return gtk.gdk.pixbuf_new_from_file(path)
+ def asPixbufIcon(self):
+ import os
+ path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
+ return gtk.gdk.pixbuf_new_from_file(path)
- def asMarkupText(self):
- assert not self._retracted
- tune = self._pep_specific_data
+ def asMarkupText(self):
+ assert not self._retracted
+ tune = self._pep_specific_data
- artist = tune.get('artist', _('Unknown Artist'))
- artist = gobject.markup_escape_text(artist)
+ artist = tune.get('artist', _('Unknown Artist'))
+ artist = gobject.markup_escape_text(artist)
- title = tune.get('title', _('Unknown Title'))
- title = gobject.markup_escape_text(title)
+ title = tune.get('title', _('Unknown Title'))
+ title = gobject.markup_escape_text(title)
- source = tune.get('source', _('Unknown Source'))
- source = gobject.markup_escape_text(source)
+ source = tune.get('source', _('Unknown Source'))
+ source = gobject.markup_escape_text(source)
- tune_string = _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
- 'from <i>%(source)s</i>') % {'title': title,
- 'artist': artist, 'source': source}
- return tune_string
+ tune_string = _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
+ 'from <i>%(source)s</i>') % {'title': title,
+ 'artist': artist, 'source': source}
+ return tune_string
class UserActivityPEP(AbstractPEP):
- '''XEP-0108: User Activity'''
-
- type = 'activity'
- namespace = xmpp.NS_ACTIVITY
-
- def _extract_info(self, items):
- activity_dict = {}
-
- for item in items.getTags('item'):
- activity_tag = item.getTag('activity')
- if activity_tag:
- for child in activity_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if name == 'text':
- activity_dict['text'] = data
- else:
- activity_dict['activity'] = name
- for subactivity in child.getChildren():
- subactivity_name = subactivity.getName().strip()
- activity_dict['subactivity'] = subactivity_name
-
- retracted = items.getTag('retract') or not 'activity' in activity_dict
- return (activity_dict, retracted)
-
- def asPixbufIcon(self):
- assert not self._retracted
- pep = self._pep_specific_data
- activity = pep['activity']
-
- has_known_activity = activity in ACTIVITIES
- has_known_subactivity = (has_known_activity and ('subactivity' in pep)
- and (pep['subactivity'] in ACTIVITIES[activity]))
-
- if has_known_activity:
- if has_known_subactivity:
- subactivity = pep['subactivity']
- return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf()
- else:
- return gtkgui_helpers.load_activity_icon(activity).get_pixbuf()
- else:
- return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf()
-
- def asMarkupText(self):
- assert not self._retracted
- pep = self._pep_specific_data
- activity = pep['activity']
- subactivity = pep['subactivity'] if 'subactivity' in pep else None
- text = pep['text'] if 'text' in pep else None
-
- if activity in ACTIVITIES:
- # Translate standard activities
- if subactivity in ACTIVITIES[activity]:
- subactivity = ACTIVITIES[activity][subactivity]
- activity = ACTIVITIES[activity]['category']
-
- markuptext = '<b>' + gobject.markup_escape_text(activity)
- if subactivity:
- markuptext += ': ' + gobject.markup_escape_text(subactivity)
- markuptext += '</b>'
- if text:
- markuptext += ' (%s)' % gobject.markup_escape_text(text)
- return markuptext
+ '''XEP-0108: User Activity'''
+
+ type = 'activity'
+ namespace = xmpp.NS_ACTIVITY
+
+ def _extract_info(self, items):
+ activity_dict = {}
+
+ for item in items.getTags('item'):
+ activity_tag = item.getTag('activity')
+ if activity_tag:
+ for child in activity_tag.getChildren():
+ name = child.getName().strip()
+ data = child.getData().strip()
+ if name == 'text':
+ activity_dict['text'] = data
+ else:
+ activity_dict['activity'] = name
+ for subactivity in child.getChildren():
+ subactivity_name = subactivity.getName().strip()
+ activity_dict['subactivity'] = subactivity_name
+
+ retracted = items.getTag('retract') or not 'activity' in activity_dict
+ return (activity_dict, retracted)
+
+ def asPixbufIcon(self):
+ assert not self._retracted
+ pep = self._pep_specific_data
+ activity = pep['activity']
+
+ has_known_activity = activity in ACTIVITIES
+ has_known_subactivity = (has_known_activity and ('subactivity' in pep)
+ and (pep['subactivity'] in ACTIVITIES[activity]))
+
+ if has_known_activity:
+ if has_known_subactivity:
+ subactivity = pep['subactivity']
+ return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf()
+ else:
+ return gtkgui_helpers.load_activity_icon(activity).get_pixbuf()
+ else:
+ return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf()
+
+ def asMarkupText(self):
+ assert not self._retracted
+ pep = self._pep_specific_data
+ activity = pep['activity']
+ subactivity = pep['subactivity'] if 'subactivity' in pep else None
+ text = pep['text'] if 'text' in pep else None
+
+ if activity in ACTIVITIES:
+ # Translate standard activities
+ if subactivity in ACTIVITIES[activity]:
+ subactivity = ACTIVITIES[activity][subactivity]
+ activity = ACTIVITIES[activity]['category']
+
+ markuptext = '<b>' + gobject.markup_escape_text(activity)
+ if subactivity:
+ markuptext += ': ' + gobject.markup_escape_text(subactivity)
+ markuptext += '</b>'
+ if text:
+ markuptext += ' (%s)' % gobject.markup_escape_text(text)
+ return markuptext
class UserNicknamePEP(AbstractPEP):
- '''XEP-0172: User Nickname'''
+ '''XEP-0172: User Nickname'''
- type = 'nickname'
- namespace = xmpp.NS_NICK
+ type = 'nickname'
+ namespace = xmpp.NS_NICK
- def _extract_info(self, items):
- nick = ''
- for item in items.getTags('item'):
- child = item.getTag('nick')
- if child:
- nick = child.getData()
- break
+ def _extract_info(self, items):
+ nick = ''
+ for item in items.getTags('item'):
+ child = item.getTag('nick')
+ if child:
+ nick = child.getData()
+ break
- retracted = items.getTag('retract') or not nick
- return (nick, retracted)
+ retracted = items.getTag('retract') or not nick
+ return (nick, retracted)
- def _update_contacts(self, jid, account):
- nick = '' if self._retracted else self._pep_specific_data
- for contact in gajim.contacts.get_contacts(account, jid):
- contact.contact_name = nick
+ def _update_contacts(self, jid, account):
+ nick = '' if self._retracted else self._pep_specific_data
+ for contact in gajim.contacts.get_contacts(account, jid):
+ contact.contact_name = nick
- def _update_account(self, account):
- if self._retracted:
- gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name')
- else:
- gajim.nicks[account] = self._pep_specific_data
+ def _update_account(self, account):
+ if self._retracted:
+ gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name')
+ else:
+ gajim.nicks[account] = self._pep_specific_data
class UserLocationPEP(AbstractPEP):
- '''XEP-0080: User Location'''
+ '''XEP-0080: User Location'''
- type = 'location'
- namespace = xmpp.NS_LOCATION
+ type = 'location'
+ namespace = xmpp.NS_LOCATION
- def _extract_info(self, items):
- location_dict = {}
+ def _extract_info(self, items):
+ location_dict = {}
- for item in items.getTags('item'):
- location_tag = item.getTag('geoloc')
- if location_tag:
- for child in location_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if child.getName() in LOCATION_DATA:
- location_dict[name] = data
+ for item in items.getTags('item'):
+ location_tag = item.getTag('geoloc')
+ if location_tag:
+ for child in location_tag.getChildren():
+ name = child.getName().strip()
+ data = child.getData().strip()
+ if child.getName() in LOCATION_DATA:
+ location_dict[name] = data
- retracted = items.getTag('retract') or not location_dict
- return (location_dict, retracted)
+ retracted = items.getTag('retract') or not location_dict
+ return (location_dict, retracted)
- def _update_account(self, account):
- AbstractPEP._update_account(self, account)
- con = gajim.connections[account].location_info = \
- self._pep_specific_data
+ def _update_account(self, account):
+ AbstractPEP._update_account(self, account)
+ con = gajim.connections[account].location_info = \
+ self._pep_specific_data
- def asPixbufIcon(self):
- path = gtkgui_helpers.get_icon_path('gajim-earth')
- return gtk.gdk.pixbuf_new_from_file(path)
+ def asPixbufIcon(self):
+ path = gtkgui_helpers.get_icon_path('gajim-earth')
+ return gtk.gdk.pixbuf_new_from_file(path)
- def asMarkupText(self):
- assert not self._retracted
- location = self._pep_specific_data
- location_string = ''
+ def asMarkupText(self):
+ assert not self._retracted
+ location = self._pep_specific_data
+ location_string = ''
- for entry in location.keys():
- text = location[entry]
- text = gobject.markup_escape_text(text)
- location_string += '\n<b>%(tag)s</b>: %(text)s' % \
- {'tag': entry.capitalize(), 'text': text}
+ for entry in location.keys():
+ text = location[entry]
+ text = gobject.markup_escape_text(text)
+ location_string += '\n<b>%(tag)s</b>: %(text)s' % \
+ {'tag': entry.capitalize(), 'text': text}
- return location_string.strip()
+ return location_string.strip()
SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP,
- UserNicknamePEP, UserLocationPEP]
+ UserNicknamePEP, UserLocationPEP]
class ConnectionPEP(object):
- def __init__(self, account, dispatcher, pubsub_connection):
- self._account = account
- self._dispatcher = dispatcher
- self._pubsub_connection = pubsub_connection
- self.reset_awaiting_pep()
-
- def reset_awaiting_pep(self):
- self.to_be_sent_activity = None
- self.to_be_sent_mood = None
- self.to_be_sent_tune = None
- self.to_be_sent_nick = None
- self.to_be_sent_location = None
-
- def send_awaiting_pep(self):
- """
- Send pep info that were waiting for connection
- """
- if self.to_be_sent_activity:
- self.send_activity(*self.to_be_sent_activity)
- if self.to_be_sent_mood:
- self.send_mood(*self.to_be_sent_mood)
- if self.to_be_sent_tune:
- self.send_tune(*self.to_be_sent_tune)
- if self.to_be_sent_nick:
- self.send_nick(self.to_be_sent_nick)
- if self.to_be_sent_location:
- self.send_location(self.to_be_sent_location)
- self.reset_awaiting_pep()
-
- def _pubsubEventCB(self, xmpp_dispatcher, msg):
- ''' Called when we receive <message /> with pubsub event. '''
- if not msg.getTag('event'):
- return
- if msg.getTag('error'):
- log.debug('PubsubEventCB received error stanza. Ignoring')
- raise xmpp.NodeProcessed
-
- jid = helpers.get_full_jid_from_iq(msg)
- event_tag = msg.getTag('event')
-
- for pep_class in SUPPORTED_PERSONAL_USER_EVENTS:
- pep = pep_class.get_tag_as_PEP(jid, self._account, event_tag)
- if pep:
- self._dispatcher.dispatch('PEP_RECEIVED', (jid, pep.type))
-
- items = event_tag.getTag('items')
- if items:
- for item in items.getTags('item'):
- entry = item.getTag('entry')
- if entry:
- # for each entry in feed (there shouldn't be more than one,
- # but to be sure...
- self._dispatcher.dispatch('ATOM_ENTRY',
- (atom.OldEntry(node=entry),))
-
- raise xmpp.NodeProcessed
-
- def send_activity(self, activity, subactivity=None, message=None):
- if self.connected == 1:
- # We are connecting, keep activity in mem and send it when we'll be
- # connected
- self.to_be_sent_activity = (activity, subactivity, message)
- return
- if not self.pep_supported:
- return
- item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY})
- if activity:
- i = item.addChild(activity)
- if subactivity:
- i.addChild(subactivity)
- if message:
- i = item.addChild('text')
- i.addData(message)
- self._pubsub_connection.send_pb_publish('', xmpp.NS_ACTIVITY, item, '0')
-
- def retract_activity(self):
- if not self.pep_supported:
- return
- self.send_activity(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', xmpp.NS_ACTIVITY, '0')
-
- def send_mood(self, mood, message=None):
- if self.connected == 1:
- # We are connecting, keep mood in mem and send it when we'll be
- # connected
- self.to_be_sent_mood = (mood, message)
- return
- if not self.pep_supported:
- return
- item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD})
- if mood:
- item.addChild(mood)
- if message:
- i = item.addChild('text')
- i.addData(message)
- self._pubsub_connection.send_pb_publish('', xmpp.NS_MOOD, item, '0')
-
- def retract_mood(self):
- if not self.pep_supported:
- return
- self.send_mood(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', xmpp.NS_MOOD, '0')
-
- def send_tune(self, artist='', title='', source='', track=0, length=0,
- items=None):
- if self.connected == 1:
- # We are connecting, keep tune in mem and send it when we'll be
- # connected
- self.to_be_sent_tune = (artist, title, source, track, length, items)
- return
- if not self.pep_supported:
- return
- item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE})
- if artist:
- i = item.addChild('artist')
- i.addData(artist)
- if title:
- i = item.addChild('title')
- i.addData(title)
- if source:
- i = item.addChild('source')
- i.addData(source)
- if track:
- i = item.addChild('track')
- i.addData(track)
- if length:
- i = item.addChild('length')
- i.addData(length)
- if items:
- item.addChild(payload=items)
- self._pubsub_connection.send_pb_publish('', xmpp.NS_TUNE, item, '0')
-
- def retract_tune(self):
- if not self.pep_supported:
- return
- self.send_tune(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', xmpp.NS_TUNE, '0')
-
- def send_nickname(self, nick):
- if self.connected == 1:
- # We are connecting, keep nick in mem and send it when we'll be
- # connected
- self.to_be_sent_nick = nick
- return
- if not self.pep_supported:
- return
- item = xmpp.Node('nick', {'xmlns': xmpp.NS_NICK})
- item.addData(nick)
- self._pubsub_connection.send_pb_publish('', xmpp.NS_NICK, item, '0')
-
- def retract_nickname(self):
- if not self.pep_supported:
- return
- self.send_nickname(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', xmpp.NS_NICK, '0')
-
- def send_location(self, info):
- if self.connected == 1:
- # We are connecting, keep location in mem and send it when we'll be
- # connected
- self.to_be_sent_location = info
- return
- if not self.pep_supported:
- return
- item = xmpp.Node('geoloc', {'xmlns': xmpp.NS_LOCATION})
- for field in LOCATION_DATA:
- if info.get(field, None):
- i = item.addChild(field)
- i.addData(info[field])
- self._pubsub_connection.send_pb_publish('', xmpp.NS_LOCATION, item, '0')
-
- def retract_location(self):
- if not self.pep_supported:
- return
- self.send_location({})
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', xmpp.NS_LOCATION, '0')
-
-# vim: se ts=3:
+ def __init__(self, account, dispatcher, pubsub_connection):
+ self._account = account
+ self._dispatcher = dispatcher
+ self._pubsub_connection = pubsub_connection
+ self.reset_awaiting_pep()
+
+ def reset_awaiting_pep(self):
+ self.to_be_sent_activity = None
+ self.to_be_sent_mood = None
+ self.to_be_sent_tune = None
+ self.to_be_sent_nick = None
+ self.to_be_sent_location = None
+
+ def send_awaiting_pep(self):
+ """
+ Send pep info that were waiting for connection
+ """
+ if self.to_be_sent_activity:
+ self.send_activity(*self.to_be_sent_activity)
+ if self.to_be_sent_mood:
+ self.send_mood(*self.to_be_sent_mood)
+ if self.to_be_sent_tune:
+ self.send_tune(*self.to_be_sent_tune)
+ if self.to_be_sent_nick:
+ self.send_nick(self.to_be_sent_nick)
+ if self.to_be_sent_location:
+ self.send_location(self.to_be_sent_location)
+ self.reset_awaiting_pep()
+
+ def _pubsubEventCB(self, xmpp_dispatcher, msg):
+ ''' Called when we receive <message /> with pubsub event. '''
+ if not msg.getTag('event'):
+ return
+ if msg.getTag('error'):
+ log.debug('PubsubEventCB received error stanza. Ignoring')
+ raise xmpp.NodeProcessed
+
+ jid = helpers.get_full_jid_from_iq(msg)
+ event_tag = msg.getTag('event')
+
+ for pep_class in SUPPORTED_PERSONAL_USER_EVENTS:
+ pep = pep_class.get_tag_as_PEP(jid, self._account, event_tag)
+ if pep:
+ self._dispatcher.dispatch('PEP_RECEIVED', (jid, pep.type))
+
+ items = event_tag.getTag('items')
+ if items:
+ for item in items.getTags('item'):
+ entry = item.getTag('entry')
+ if entry:
+ # for each entry in feed (there shouldn't be more than one,
+ # but to be sure...
+ self._dispatcher.dispatch('ATOM_ENTRY',
+ (atom.OldEntry(node=entry),))
+
+ raise xmpp.NodeProcessed
+
+ def send_activity(self, activity, subactivity=None, message=None):
+ if self.connected == 1:
+ # We are connecting, keep activity in mem and send it when we'll be
+ # connected
+ self.to_be_sent_activity = (activity, subactivity, message)
+ return
+ if not self.pep_supported:
+ return
+ item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY})
+ if activity:
+ i = item.addChild(activity)
+ if subactivity:
+ i.addChild(subactivity)
+ if message:
+ i = item.addChild('text')
+ i.addData(message)
+ self._pubsub_connection.send_pb_publish('', xmpp.NS_ACTIVITY, item, '0')
+
+ def retract_activity(self):
+ if not self.pep_supported:
+ return
+ self.send_activity(None)
+ # not all client support new XEP, so we still retract
+ self._pubsub_connection.send_pb_retract('', xmpp.NS_ACTIVITY, '0')
+
+ def send_mood(self, mood, message=None):
+ if self.connected == 1:
+ # We are connecting, keep mood in mem and send it when we'll be
+ # connected
+ self.to_be_sent_mood = (mood, message)
+ return
+ if not self.pep_supported:
+ return
+ item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD})
+ if mood:
+ item.addChild(mood)
+ if message:
+ i = item.addChild('text')
+ i.addData(message)
+ self._pubsub_connection.send_pb_publish('', xmpp.NS_MOOD, item, '0')
+
+ def retract_mood(self):
+ if not self.pep_supported:
+ return
+ self.send_mood(None)
+ # not all client support new XEP, so we still retract
+ self._pubsub_connection.send_pb_retract('', xmpp.NS_MOOD, '0')
+
+ def send_tune(self, artist='', title='', source='', track=0, length=0,
+ items=None):
+ if self.connected == 1:
+ # We are connecting, keep tune in mem and send it when we'll be
+ # connected
+ self.to_be_sent_tune = (artist, title, source, track, length, items)
+ return
+ if not self.pep_supported:
+ return
+ item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE})
+ if artist:
+ i = item.addChild('artist')
+ i.addData(artist)
+ if title:
+ i = item.addChild('title')
+ i.addData(title)
+ if source:
+ i = item.addChild('source')
+ i.addData(source)
+ if track:
+ i = item.addChild('track')
+ i.addData(track)
+ if length:
+ i = item.addChild('length')
+ i.addData(length)
+ if items:
+ item.addChild(payload=items)
+ self._pubsub_connection.send_pb_publish('', xmpp.NS_TUNE, item, '0')
+
+ def retract_tune(self):
+ if not self.pep_supported:
+ return
+ self.send_tune(None)
+ # not all client support new XEP, so we still retract
+ self._pubsub_connection.send_pb_retract('', xmpp.NS_TUNE, '0')
+
+ def send_nickname(self, nick):
+ if self.connected == 1:
+ # We are connecting, keep nick in mem and send it when we'll be
+ # connected
+ self.to_be_sent_nick = nick
+ return
+ if not self.pep_supported:
+ return
+ item = xmpp.Node('nick', {'xmlns': xmpp.NS_NICK})
+ item.addData(nick)
+ self._pubsub_connection.send_pb_publish('', xmpp.NS_NICK, item, '0')
+
+ def retract_nickname(self):
+ if not self.pep_supported:
+ return
+ self.send_nickname(None)
+ # not all client support new XEP, so we still retract
+ self._pubsub_connection.send_pb_retract('', xmpp.NS_NICK, '0')
+
+ def send_location(self, info):
+ if self.connected == 1:
+ # We are connecting, keep location in mem and send it when we'll be
+ # connected
+ self.to_be_sent_location = info
+ return
+ if not self.pep_supported:
+ return
+ item = xmpp.Node('geoloc', {'xmlns': xmpp.NS_LOCATION})
+ for field in LOCATION_DATA:
+ if info.get(field, None):
+ i = item.addChild(field)
+ i.addData(info[field])
+ self._pubsub_connection.send_pb_publish('', xmpp.NS_LOCATION, item, '0')
+
+ def retract_location(self):
+ if not self.pep_supported:
+ return
+ self.send_location({})
+ # not all client support new XEP, so we still retract
+ self._pubsub_connection.send_pb_retract('', xmpp.NS_LOCATION, '0')
diff --git a/src/common/protocol/__init__.py b/src/common/protocol/__init__.py
index f50b2cd01..2d167f344 100644
--- a/src/common/protocol/__init__.py
+++ b/src/common/protocol/__init__.py
@@ -1,3 +1,3 @@
"""
Implementations of specific XMPP protocols and XEPs
-""" \ No newline at end of file
+"""
diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py
index ce0086b19..f70b50aa5 100644
--- a/src/common/protocol/bytestream.py
+++ b/src/common/protocol/bytestream.py
@@ -39,612 +39,610 @@ from common.socks5 import Socks5Receiver
def is_transfer_paused(file_props):
- if 'stopped' in file_props and file_props['stopped']:
- return False
- if 'completed' in file_props and file_props['completed']:
- return False
- if 'disconnect_cb' not in file_props:
- return False
- return file_props['paused']
+ if 'stopped' in file_props and file_props['stopped']:
+ return False
+ if 'completed' in file_props and file_props['completed']:
+ return False
+ if 'disconnect_cb' not in file_props:
+ return False
+ return file_props['paused']
def is_transfer_active(file_props):
- if 'stopped' in file_props and file_props['stopped']:
- return False
- if 'completed' in file_props and file_props['completed']:
- return False
- if 'started' not in file_props or not file_props['started']:
- return False
- if 'paused' not in file_props:
- return True
- return not file_props['paused']
+ if 'stopped' in file_props and file_props['stopped']:
+ return False
+ if 'completed' in file_props and file_props['completed']:
+ return False
+ if 'started' not in file_props or not file_props['started']:
+ return False
+ if 'paused' not in file_props:
+ return True
+ return not file_props['paused']
def is_transfer_stopped(file_props):
- if 'error' in file_props and file_props['error'] != 0:
- return True
- if 'completed' in file_props and file_props['completed']:
- return True
- if 'connected' in file_props and file_props['connected'] == False:
- return True
- if 'stopped' not in file_props or not file_props['stopped']:
- return False
- return True
+ if 'error' in file_props and file_props['error'] != 0:
+ return True
+ if 'completed' in file_props and file_props['completed']:
+ return True
+ if 'connected' in file_props and file_props['connected'] == False:
+ return True
+ if 'stopped' not in file_props or not file_props['stopped']:
+ return False
+ return True
class ConnectionBytestream:
- def __init__(self):
- self.files_props = {}
-
- def send_success_connect_reply(self, streamhost):
- """
- Send reply to the initiator of FT that we made a connection
- """
- if not self.connection or self.connected < 2:
- return
- if streamhost is None:
- return None
- iq = xmpp.Iq(to=streamhost['initiator'], typ='result',
- frm=streamhost['target'])
- iq.setAttr('id', streamhost['id'])
- query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM)
- stream_tag = query.setTag('streamhost-used')
- stream_tag.setAttr('jid', streamhost['jid'])
- self.connection.send(iq)
-
- def stop_all_active_file_transfers(self, contact):
- """
- Stop all active transfer to or from the given contact
- """
- for file_props in self.files_props.values():
- if is_transfer_stopped(file_props):
- continue
- receiver_jid = unicode(file_props['receiver'])
- if contact.get_full_jid() == receiver_jid:
- file_props['error'] = -5
- self.remove_transfer(file_props)
- self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
- sender_jid = unicode(file_props['sender'])
- if contact.get_full_jid() == sender_jid:
- file_props['error'] = -3
- self.remove_transfer(file_props)
-
- def remove_all_transfers(self):
- """
- Stop and remove all active connections from the socks5 pool
- """
- for file_props in self.files_props.values():
- self.remove_transfer(file_props, remove_from_list=False)
- self.files_props = {}
-
- def remove_transfer(self, file_props, remove_from_list=True):
- if file_props is None:
- return
- self.disconnect_transfer(file_props)
- sid = file_props['sid']
- gajim.socks5queue.remove_file_props(self.name, sid)
-
- if remove_from_list:
- if 'sid' in self.files_props:
- del(self.files_props['sid'])
-
- def disconnect_transfer(self, file_props):
- if file_props is None:
- return
- if 'hash' in file_props:
- gajim.socks5queue.remove_sender(file_props['hash'])
-
- if 'streamhosts' in file_props:
- for host in file_props['streamhosts']:
- if 'idx' in host and host['idx'] > 0:
- gajim.socks5queue.remove_receiver(host['idx'])
- gajim.socks5queue.remove_sender(host['idx'])
-
- def _send_socks5_info(self, file_props):
- """
- Send iq for the present streamhosts and proxies
- """
- if not self.connection or self.connected < 2:
- return
- receiver = file_props['receiver']
- sender = file_props['sender']
-
- sha_str = helpers.get_auth_sha(file_props['sid'], sender, receiver)
- file_props['sha_str'] = sha_str
-
- port = gajim.config.get('file_transfers_port')
- listener = gajim.socks5queue.start_listener(port, sha_str,
- self._result_socks5_sid, file_props['sid'])
- if not listener:
- file_props['error'] = -5
- self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props, ''))
- self._connect_error(unicode(receiver), file_props['sid'],
- file_props['sid'], code=406)
- else:
- iq = xmpp.Iq(to=unicode(receiver), typ='set')
- file_props['request-id'] = 'id_' + file_props['sid']
- iq.setID(file_props['request-id'])
- query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM)
- query.setAttr('mode', 'plain')
- query.setAttr('sid', file_props['sid'])
-
- self._add_addiditional_streamhosts_to_query(query, file_props)
- self._add_local_ips_as_streamhosts_to_query(query, file_props)
- self._add_proxy_streamhosts_to_query(query, file_props)
-
- self.connection.send(iq)
-
- def _add_streamhosts_to_query(self, query, sender, port, hosts):
- for host in hosts:
- streamhost = xmpp.Node(tag='streamhost')
- query.addChild(node=streamhost)
- streamhost.setAttr('port', unicode(port))
- streamhost.setAttr('host', host)
- streamhost.setAttr('jid', sender)
-
- def _add_local_ips_as_streamhosts_to_query(self, query, file_props):
- try:
- my_ips = [self.peerhost[0]] # The ip we're connected to server with
- # all IPs from local DNS
- for addr in socket.getaddrinfo(socket.gethostname(), None):
- if not addr[4][0] in my_ips and not addr[4][0].startswith('127'):
- my_ips.append(addr[4][0])
-
- sender = file_props['sender']
- port = gajim.config.get('file_transfers_port')
- self._add_streamhosts_to_query(query, sender, port, my_ips)
- except socket.gaierror:
- self.dispatch('ERROR', (_('Wrong host'),
- _('Invalid local address? :-O')))
-
- def _add_addiditional_streamhosts_to_query(self, query, file_props):
- sender = file_props['sender']
- port = gajim.config.get('file_transfers_port')
- ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send')
- additional_hosts = []
- if ft_add_hosts_to_send:
- additional_hosts = [e.strip() for e in ft_add_hosts_to_send.split(',')]
- else:
- additional_hosts = []
- self._add_streamhosts_to_query(query, sender, port, additional_hosts)
-
- def _add_proxy_streamhosts_to_query(self, query, file_props):
- proxyhosts = self._get_file_transfer_proxies_from_config(file_props)
- if proxyhosts:
- file_props['proxy_receiver'] = unicode(file_props['receiver'])
- file_props['proxy_sender'] = unicode(file_props['sender'])
- file_props['proxyhosts'] = proxyhosts
-
- for proxyhost in proxyhosts:
- self._add_streamhosts_to_query(query, proxyhost['jid'],
- proxyhost['port'], [proxyhost['host']])
-
- def _get_file_transfer_proxies_from_config(self, file_props):
- configured_proxies = gajim.config.get_per('accounts', self.name,
- 'file_transfer_proxies')
- shall_use_proxies = gajim.config.get_per('accounts', self.name,
- 'use_ft_proxies')
- if shall_use_proxies and configured_proxies:
- proxyhost_dicts = []
- proxies = [item.strip() for item in configured_proxies.split(',')]
- default_proxy = gajim.proxy65_manager.get_default_for_name(self.name)
- if default_proxy:
- # add/move default proxy at top of the others
- if default_proxy in proxies:
- proxies.remove(default_proxy)
- proxies.insert(0, default_proxy)
-
- for proxy in proxies:
- (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name)
- if not host:
- continue
- host_dict = {
- 'state': 0,
- 'target': unicode(file_props['receiver']),
- 'id': file_props['sid'],
- 'sid': file_props['sid'],
- 'initiator': proxy,
- 'host': host,
- 'port': unicode(_port),
- 'jid': jid
- }
- proxyhost_dicts.append(host_dict)
- return proxyhost_dicts
- else:
- return []
-
- def send_file_rejection(self, file_props, code='403', typ=None):
- """
- Inform sender that we refuse to download the file
-
- typ is used when code = '400', in this case typ can be 'strean' for
- invalid stream or 'profile' for invalid profile
- """
- # user response to ConfirmationDialog may come after we've disconneted
- if not self.connection or self.connected < 2:
- return
- iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error')
- iq.setAttr('id', file_props['request-id'])
- if code == '400' and typ in ('stream', 'profile'):
- name = 'bad-request'
- text = ''
- else:
- name = 'forbidden'
- text = 'Offer Declined'
- err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text)
- if code == '400' and typ in ('stream', 'profile'):
- if typ == 'stream':
- err.setTag('no-valid-streams', namespace=xmpp.NS_SI)
- else:
- err.setTag('bad-profile', namespace=xmpp.NS_SI)
- iq.addChild(node=err)
- self.connection.send(iq)
-
- def send_file_approval(self, file_props):
- """
- Send iq, confirming that we want to download the file
- """
- # user response to ConfirmationDialog may come after we've disconneted
- if not self.connection or self.connected < 2:
- return
- iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result')
- iq.setAttr('id', file_props['request-id'])
- si = iq.setTag('si', namespace=xmpp.NS_SI)
- if 'offset' in file_props and file_props['offset']:
- file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
- range_tag = file_tag.setTag('range')
- range_tag.setAttr('offset', file_props['offset'])
- feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
- _feature = xmpp.DataForm(typ='submit')
- feature.addChild(node=_feature)
- field = _feature.setField('stream-method')
- field.delAttr('type')
- field.setValue(xmpp.NS_BYTESTREAM)
- self.connection.send(iq)
-
- def _ft_get_our_jid(self):
- our_jid = gajim.get_jid_from_account(self.name)
- resource = self.server_resource
- return our_jid + '/' + resource
-
- def _ft_get_receiver_jid(self, file_props):
- return file_props['receiver'].jid + '/' + file_props['receiver'].resource
-
- def send_file_request(self, file_props):
- """
- Send iq for new FT request
- """
- if not self.connection or self.connected < 2:
- return
- file_props['sender'] = self._ft_get_our_jid()
- fjid = self._ft_get_receiver_jid(file_props)
- iq = xmpp.Iq(to=fjid, typ='set')
- iq.setID(file_props['sid'])
- self.files_props[file_props['sid']] = file_props
- si = iq.setTag('si', namespace=xmpp.NS_SI)
- si.setAttr('profile', xmpp.NS_FILE)
- si.setAttr('id', file_props['sid'])
- file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
- file_tag.setAttr('name', file_props['name'])
- file_tag.setAttr('size', file_props['size'])
- desc = file_tag.setTag('desc')
- if 'desc' in file_props:
- desc.setData(file_props['desc'])
- file_tag.setTag('range')
- feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
- _feature = xmpp.DataForm(typ='form')
- feature.addChild(node=_feature)
- field = _feature.setField('stream-method')
- field.setAttr('type', 'list-single')
- field.addOption(xmpp.NS_BYTESTREAM)
- self.connection.send(iq)
-
- def _result_socks5_sid(self, sid, hash_id):
- """
- Store the result of SHA message from auth
- """
- if sid not in self.files_props:
- return
- file_props = self.files_props[sid]
- file_props['hash'] = hash_id
- return
-
- def _connect_error(self, to, _id, sid, code=404):
- """
- Called when there is an error establishing BS connection, or when
- connection is rejected
- """
- if not self.connection or self.connected < 2:
- return
- msg_dict = {
- 404: 'Could not connect to given hosts',
- 405: 'Cancel',
- 406: 'Not acceptable',
- }
- msg = msg_dict[code]
- iq = xmpp.Iq(to=to, typ='error')
- iq.setAttr('id', _id)
- err = iq.setTag('error')
- err.setAttr('code', unicode(code))
- err.setData(msg)
- self.connection.send(iq)
- if code == 404:
- file_props = gajim.socks5queue.get_file_props(self.name, sid)
- if file_props is not None:
- self.disconnect_transfer(file_props)
- file_props['error'] = -3
- self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
-
- def _proxy_auth_ok(self, proxy):
- """
- Called after authentication to proxy server
- """
- if not self.connection or self.connected < 2:
- return
- file_props = self.files_props[proxy['sid']]
- iq = xmpp.Iq(to=proxy['initiator'], typ='set')
- auth_id = "au_" + proxy['sid']
- iq.setID(auth_id)
- query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM)
- query.setAttr('sid', proxy['sid'])
- activate = query.setTag('activate')
- activate.setData(file_props['proxy_receiver'])
- iq.setID(auth_id)
- self.connection.send(iq)
-
- # register xmpppy handlers for bytestream and FT stanzas
- def _bytestreamErrorCB(self, con, iq_obj):
- id_ = unicode(iq_obj.getAttr('id'))
- frm = helpers.get_full_jid_from_iq(iq_obj)
- query = iq_obj.getTag('query')
- gajim.proxy65_manager.error_cb(frm, query)
- jid = helpers.get_jid_from_iq(iq_obj)
- id_ = id_[3:]
- if id_ not in self.files_props:
- return
- file_props = self.files_props[id_]
- file_props['error'] = -4
- self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
- raise xmpp.NodeProcessed
-
- def _ft_get_from(self, iq_obj):
- return helpers.get_full_jid_from_iq(iq_obj)
-
- def _bytestreamSetCB(self, con, iq_obj):
- target = unicode(iq_obj.getAttr('to'))
- id_ = unicode(iq_obj.getAttr('id'))
- query = iq_obj.getTag('query')
- sid = unicode(query.getAttr('sid'))
- file_props = gajim.socks5queue.get_file_props(self.name, sid)
- streamhosts = []
- for item in query.getChildren():
- if item.getName() == 'streamhost':
- host_dict = {
- 'state': 0,
- 'target': target,
- 'id': id_,
- 'sid': sid,
- 'initiator': self._ft_get_from(iq_obj)
- }
- for attr in item.getAttrs():
- host_dict[attr] = item.getAttr(attr)
- streamhosts.append(host_dict)
- if file_props is None:
- if sid in self.files_props:
- file_props = self.files_props[sid]
- file_props['fast'] = streamhosts
- if file_props['type'] == 's': # FIXME: remove fast xmlns
- # only psi do this
- if 'streamhosts' in file_props:
- file_props['streamhosts'].extend(streamhosts)
- else:
- file_props['streamhosts'] = streamhosts
- if not gajim.socks5queue.get_file_props(self.name, sid):
- gajim.socks5queue.add_file_props(self.name, file_props)
- gajim.socks5queue.connect_to_hosts(self.name, sid,
- self.send_success_connect_reply, None)
- raise xmpp.NodeProcessed
-
- file_props['streamhosts'] = streamhosts
- if file_props['type'] == 'r':
- gajim.socks5queue.connect_to_hosts(self.name, sid,
- self.send_success_connect_reply, self._connect_error)
- raise xmpp.NodeProcessed
-
- def _ResultCB(self, con, iq_obj):
- # if we want to respect xep-0065 we have to check for proxy
- # activation result in any result iq
- real_id = unicode(iq_obj.getAttr('id'))
- if not real_id.startswith('au_'):
- return
- frm = self._ft_get_from(iq_obj)
- id_ = real_id[3:]
- if id_ in self.files_props:
- file_props = self.files_props[id_]
- if file_props['streamhost-used']:
- for host in file_props['proxyhosts']:
- if host['initiator'] == frm and 'idx' in host:
- gajim.socks5queue.activate_proxy(host['idx'])
- raise xmpp.NodeProcessed
-
- def _ft_get_streamhost_jid_attr(self, streamhost):
- return helpers.parse_jid(streamhost.getAttr('jid'))
-
- def _bytestreamResultCB(self, con, iq_obj):
- frm = self._ft_get_from(iq_obj)
- real_id = unicode(iq_obj.getAttr('id'))
- query = iq_obj.getTag('query')
- gajim.proxy65_manager.resolve_result(frm, query)
-
- try:
- streamhost = query.getTag('streamhost-used')
- except Exception: # this bytestream result is not what we need
- pass
- id_ = real_id[3:]
- if id_ in self.files_props:
- file_props = self.files_props[id_]
- else:
- raise xmpp.NodeProcessed
- if streamhost is None:
- # proxy approves the activate query
- if real_id.startswith('au_'):
- if 'streamhost-used' not in file_props or \
- file_props['streamhost-used'] is False:
- raise xmpp.NodeProcessed
- if 'proxyhosts' not in file_props:
- raise xmpp.NodeProcessed
- for host in file_props['proxyhosts']:
- if host['initiator'] == frm and \
- unicode(query.getAttr('sid')) == file_props['sid']:
- gajim.socks5queue.activate_proxy(host['idx'])
- break
- raise xmpp.NodeProcessed
- jid = self._ft_get_streamhost_jid_attr(streamhost)
- if 'streamhost-used' in file_props and \
- file_props['streamhost-used'] is True:
- raise xmpp.NodeProcessed
-
- if real_id.startswith('au_'):
- if 'stopped' in file and file_props['stopped']:
- self.remove_transfer(file_props)
- else:
- gajim.socks5queue.send_file(file_props, self.name)
- raise xmpp.NodeProcessed
-
- proxy = None
- if 'proxyhosts' in file_props:
- for proxyhost in file_props['proxyhosts']:
- if proxyhost['jid'] == jid:
- proxy = proxyhost
-
- if proxy is not None:
- file_props['streamhost-used'] = True
- if 'streamhosts' not in file_props:
- file_props['streamhosts'] = []
- file_props['streamhosts'].append(proxy)
- file_props['is_a_proxy'] = True
- receiver = Socks5Receiver(gajim.idlequeue, proxy,
- file_props['sid'], file_props)
- gajim.socks5queue.add_receiver(self.name, receiver)
- proxy['idx'] = receiver.queue_idx
- gajim.socks5queue.on_success = self._proxy_auth_ok
- raise xmpp.NodeProcessed
-
- else:
- if 'stopped' in file_props and file_props['stopped']:
- self.remove_transfer(file_props)
- else:
- gajim.socks5queue.send_file(file_props, self.name)
- if 'fast' in file_props:
- fasts = file_props['fast']
- if len(fasts) > 0:
- self._connect_error(frm, fasts[0]['id'], file_props['sid'],
- code=406)
-
- raise xmpp.NodeProcessed
-
- def _siResultCB(self, con, iq_obj):
- file_props = self.files_props.get(iq_obj.getAttr('id'))
- if not file_props:
- return
- if 'request-id' in file_props:
- # we have already sent streamhosts info
- return
- file_props['receiver'] = self._ft_get_from(iq_obj)
- si = iq_obj.getTag('si')
- file_tag = si.getTag('file')
- range_tag = None
- if file_tag:
- range_tag = file_tag.getTag('range')
- if range_tag:
- offset = range_tag.getAttr('offset')
- if offset:
- file_props['offset'] = int(offset)
- length = range_tag.getAttr('length')
- if length:
- file_props['length'] = int(length)
- feature = si.setTag('feature')
- if feature.getNamespace() != xmpp.NS_FEATURE:
- return
- form_tag = feature.getTag('x')
- form = xmpp.DataForm(node=form_tag)
- field = form.getField('stream-method')
- if field.getValue() != xmpp.NS_BYTESTREAM:
- return
- self._send_socks5_info(file_props)
- raise xmpp.NodeProcessed
-
- def _siSetCB(self, con, iq_obj):
- jid = self._ft_get_from(iq_obj)
- file_props = {'type': 'r'}
- file_props['sender'] = jid
- file_props['request-id'] = unicode(iq_obj.getAttr('id'))
- si = iq_obj.getTag('si')
- profile = si.getAttr('profile')
- mime_type = si.getAttr('mime-type')
- if profile != xmpp.NS_FILE:
- self.send_file_rejection(file_props, code='400', typ='profile')
- raise xmpp.NodeProcessed
- feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE)
- if not feature_tag:
- return
- form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA)
- if not form_tag:
- return
- form = dataforms.ExtendForm(node=form_tag)
- for f in form.iter_fields():
- if f.var == 'stream-method' and f.type == 'list-single':
- values = [o[1] for o in f.options]
- if xmpp.NS_BYTESTREAM in values:
- break
- else:
- self.send_file_rejection(file_props, code='400', typ='stream')
- raise xmpp.NodeProcessed
- file_tag = si.getTag('file')
- for attribute in file_tag.getAttrs():
- if attribute in ('name', 'size', 'hash', 'date'):
- val = file_tag.getAttr(attribute)
- if val is None:
- continue
- file_props[attribute] = val
- file_desc_tag = file_tag.getTag('desc')
- if file_desc_tag is not None:
- file_props['desc'] = file_desc_tag.getData()
-
- if mime_type is not None:
- file_props['mime-type'] = mime_type
- file_props['receiver'] = self._ft_get_our_jid()
- file_props['sid'] = unicode(si.getAttr('id'))
- file_props['transfered_size'] = []
- gajim.socks5queue.add_file_props(self.name, file_props)
- self.dispatch('FILE_REQUEST', (jid, file_props))
- raise xmpp.NodeProcessed
-
- def _siErrorCB(self, con, iq_obj):
- si = iq_obj.getTag('si')
- profile = si.getAttr('profile')
- if profile != xmpp.NS_FILE:
- return
- file_props = self.files_props.get(iq_obj.getAttr('id'))
- if not file_props:
- return
- jid = self._ft_get_from(iq_obj)
- file_props['error'] = -3
- self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
- raise xmpp.NodeProcessed
+ def __init__(self):
+ self.files_props = {}
+
+ def send_success_connect_reply(self, streamhost):
+ """
+ Send reply to the initiator of FT that we made a connection
+ """
+ if not self.connection or self.connected < 2:
+ return
+ if streamhost is None:
+ return None
+ iq = xmpp.Iq(to=streamhost['initiator'], typ='result',
+ frm=streamhost['target'])
+ iq.setAttr('id', streamhost['id'])
+ query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM)
+ stream_tag = query.setTag('streamhost-used')
+ stream_tag.setAttr('jid', streamhost['jid'])
+ self.connection.send(iq)
+
+ def stop_all_active_file_transfers(self, contact):
+ """
+ Stop all active transfer to or from the given contact
+ """
+ for file_props in self.files_props.values():
+ if is_transfer_stopped(file_props):
+ continue
+ receiver_jid = unicode(file_props['receiver'])
+ if contact.get_full_jid() == receiver_jid:
+ file_props['error'] = -5
+ self.remove_transfer(file_props)
+ self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
+ sender_jid = unicode(file_props['sender'])
+ if contact.get_full_jid() == sender_jid:
+ file_props['error'] = -3
+ self.remove_transfer(file_props)
+
+ def remove_all_transfers(self):
+ """
+ Stop and remove all active connections from the socks5 pool
+ """
+ for file_props in self.files_props.values():
+ self.remove_transfer(file_props, remove_from_list=False)
+ self.files_props = {}
+
+ def remove_transfer(self, file_props, remove_from_list=True):
+ if file_props is None:
+ return
+ self.disconnect_transfer(file_props)
+ sid = file_props['sid']
+ gajim.socks5queue.remove_file_props(self.name, sid)
+
+ if remove_from_list:
+ if 'sid' in self.files_props:
+ del(self.files_props['sid'])
+
+ def disconnect_transfer(self, file_props):
+ if file_props is None:
+ return
+ if 'hash' in file_props:
+ gajim.socks5queue.remove_sender(file_props['hash'])
+
+ if 'streamhosts' in file_props:
+ for host in file_props['streamhosts']:
+ if 'idx' in host and host['idx'] > 0:
+ gajim.socks5queue.remove_receiver(host['idx'])
+ gajim.socks5queue.remove_sender(host['idx'])
+
+ def _send_socks5_info(self, file_props):
+ """
+ Send iq for the present streamhosts and proxies
+ """
+ if not self.connection or self.connected < 2:
+ return
+ receiver = file_props['receiver']
+ sender = file_props['sender']
+
+ sha_str = helpers.get_auth_sha(file_props['sid'], sender, receiver)
+ file_props['sha_str'] = sha_str
+
+ port = gajim.config.get('file_transfers_port')
+ listener = gajim.socks5queue.start_listener(port, sha_str,
+ self._result_socks5_sid, file_props['sid'])
+ if not listener:
+ file_props['error'] = -5
+ self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props, ''))
+ self._connect_error(unicode(receiver), file_props['sid'],
+ file_props['sid'], code=406)
+ else:
+ iq = xmpp.Iq(to=unicode(receiver), typ='set')
+ file_props['request-id'] = 'id_' + file_props['sid']
+ iq.setID(file_props['request-id'])
+ query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM)
+ query.setAttr('mode', 'plain')
+ query.setAttr('sid', file_props['sid'])
+
+ self._add_addiditional_streamhosts_to_query(query, file_props)
+ self._add_local_ips_as_streamhosts_to_query(query, file_props)
+ self._add_proxy_streamhosts_to_query(query, file_props)
+
+ self.connection.send(iq)
+
+ def _add_streamhosts_to_query(self, query, sender, port, hosts):
+ for host in hosts:
+ streamhost = xmpp.Node(tag='streamhost')
+ query.addChild(node=streamhost)
+ streamhost.setAttr('port', unicode(port))
+ streamhost.setAttr('host', host)
+ streamhost.setAttr('jid', sender)
+
+ def _add_local_ips_as_streamhosts_to_query(self, query, file_props):
+ try:
+ my_ips = [self.peerhost[0]] # The ip we're connected to server with
+ # all IPs from local DNS
+ for addr in socket.getaddrinfo(socket.gethostname(), None):
+ if not addr[4][0] in my_ips and not addr[4][0].startswith('127'):
+ my_ips.append(addr[4][0])
+
+ sender = file_props['sender']
+ port = gajim.config.get('file_transfers_port')
+ self._add_streamhosts_to_query(query, sender, port, my_ips)
+ except socket.gaierror:
+ self.dispatch('ERROR', (_('Wrong host'),
+ _('Invalid local address? :-O')))
+
+ def _add_addiditional_streamhosts_to_query(self, query, file_props):
+ sender = file_props['sender']
+ port = gajim.config.get('file_transfers_port')
+ ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send')
+ additional_hosts = []
+ if ft_add_hosts_to_send:
+ additional_hosts = [e.strip() for e in ft_add_hosts_to_send.split(',')]
+ else:
+ additional_hosts = []
+ self._add_streamhosts_to_query(query, sender, port, additional_hosts)
+
+ def _add_proxy_streamhosts_to_query(self, query, file_props):
+ proxyhosts = self._get_file_transfer_proxies_from_config(file_props)
+ if proxyhosts:
+ file_props['proxy_receiver'] = unicode(file_props['receiver'])
+ file_props['proxy_sender'] = unicode(file_props['sender'])
+ file_props['proxyhosts'] = proxyhosts
+
+ for proxyhost in proxyhosts:
+ self._add_streamhosts_to_query(query, proxyhost['jid'],
+ proxyhost['port'], [proxyhost['host']])
+
+ def _get_file_transfer_proxies_from_config(self, file_props):
+ configured_proxies = gajim.config.get_per('accounts', self.name,
+ 'file_transfer_proxies')
+ shall_use_proxies = gajim.config.get_per('accounts', self.name,
+ 'use_ft_proxies')
+ if shall_use_proxies and configured_proxies:
+ proxyhost_dicts = []
+ proxies = [item.strip() for item in configured_proxies.split(',')]
+ default_proxy = gajim.proxy65_manager.get_default_for_name(self.name)
+ if default_proxy:
+ # add/move default proxy at top of the others
+ if default_proxy in proxies:
+ proxies.remove(default_proxy)
+ proxies.insert(0, default_proxy)
+
+ for proxy in proxies:
+ (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name)
+ if not host:
+ continue
+ host_dict = {
+ 'state': 0,
+ 'target': unicode(file_props['receiver']),
+ 'id': file_props['sid'],
+ 'sid': file_props['sid'],
+ 'initiator': proxy,
+ 'host': host,
+ 'port': unicode(_port),
+ 'jid': jid
+ }
+ proxyhost_dicts.append(host_dict)
+ return proxyhost_dicts
+ else:
+ return []
+
+ def send_file_rejection(self, file_props, code='403', typ=None):
+ """
+ Inform sender that we refuse to download the file
+
+ typ is used when code = '400', in this case typ can be 'strean' for
+ invalid stream or 'profile' for invalid profile
+ """
+ # user response to ConfirmationDialog may come after we've disconneted
+ if not self.connection or self.connected < 2:
+ return
+ iq = xmpp.Iq(to=unicode(file_props['sender']), typ='error')
+ iq.setAttr('id', file_props['request-id'])
+ if code == '400' and typ in ('stream', 'profile'):
+ name = 'bad-request'
+ text = ''
+ else:
+ name = 'forbidden'
+ text = 'Offer Declined'
+ err = xmpp.ErrorNode(code=code, typ='cancel', name=name, text=text)
+ if code == '400' and typ in ('stream', 'profile'):
+ if typ == 'stream':
+ err.setTag('no-valid-streams', namespace=xmpp.NS_SI)
+ else:
+ err.setTag('bad-profile', namespace=xmpp.NS_SI)
+ iq.addChild(node=err)
+ self.connection.send(iq)
+
+ def send_file_approval(self, file_props):
+ """
+ Send iq, confirming that we want to download the file
+ """
+ # user response to ConfirmationDialog may come after we've disconneted
+ if not self.connection or self.connected < 2:
+ return
+ iq = xmpp.Iq(to=unicode(file_props['sender']), typ='result')
+ iq.setAttr('id', file_props['request-id'])
+ si = iq.setTag('si', namespace=xmpp.NS_SI)
+ if 'offset' in file_props and file_props['offset']:
+ file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
+ range_tag = file_tag.setTag('range')
+ range_tag.setAttr('offset', file_props['offset'])
+ feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
+ _feature = xmpp.DataForm(typ='submit')
+ feature.addChild(node=_feature)
+ field = _feature.setField('stream-method')
+ field.delAttr('type')
+ field.setValue(xmpp.NS_BYTESTREAM)
+ self.connection.send(iq)
+
+ def _ft_get_our_jid(self):
+ our_jid = gajim.get_jid_from_account(self.name)
+ resource = self.server_resource
+ return our_jid + '/' + resource
+
+ def _ft_get_receiver_jid(self, file_props):
+ return file_props['receiver'].jid + '/' + file_props['receiver'].resource
+
+ def send_file_request(self, file_props):
+ """
+ Send iq for new FT request
+ """
+ if not self.connection or self.connected < 2:
+ return
+ file_props['sender'] = self._ft_get_our_jid()
+ fjid = self._ft_get_receiver_jid(file_props)
+ iq = xmpp.Iq(to=fjid, typ='set')
+ iq.setID(file_props['sid'])
+ self.files_props[file_props['sid']] = file_props
+ si = iq.setTag('si', namespace=xmpp.NS_SI)
+ si.setAttr('profile', xmpp.NS_FILE)
+ si.setAttr('id', file_props['sid'])
+ file_tag = si.setTag('file', namespace=xmpp.NS_FILE)
+ file_tag.setAttr('name', file_props['name'])
+ file_tag.setAttr('size', file_props['size'])
+ desc = file_tag.setTag('desc')
+ if 'desc' in file_props:
+ desc.setData(file_props['desc'])
+ file_tag.setTag('range')
+ feature = si.setTag('feature', namespace=xmpp.NS_FEATURE)
+ _feature = xmpp.DataForm(typ='form')
+ feature.addChild(node=_feature)
+ field = _feature.setField('stream-method')
+ field.setAttr('type', 'list-single')
+ field.addOption(xmpp.NS_BYTESTREAM)
+ self.connection.send(iq)
+
+ def _result_socks5_sid(self, sid, hash_id):
+ """
+ Store the result of SHA message from auth
+ """
+ if sid not in self.files_props:
+ return
+ file_props = self.files_props[sid]
+ file_props['hash'] = hash_id
+ return
+
+ def _connect_error(self, to, _id, sid, code=404):
+ """
+ Called when there is an error establishing BS connection, or when
+ connection is rejected
+ """
+ if not self.connection or self.connected < 2:
+ return
+ msg_dict = {
+ 404: 'Could not connect to given hosts',
+ 405: 'Cancel',
+ 406: 'Not acceptable',
+ }
+ msg = msg_dict[code]
+ iq = xmpp.Iq(to=to, typ='error')
+ iq.setAttr('id', _id)
+ err = iq.setTag('error')
+ err.setAttr('code', unicode(code))
+ err.setData(msg)
+ self.connection.send(iq)
+ if code == 404:
+ file_props = gajim.socks5queue.get_file_props(self.name, sid)
+ if file_props is not None:
+ self.disconnect_transfer(file_props)
+ file_props['error'] = -3
+ self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
+
+ def _proxy_auth_ok(self, proxy):
+ """
+ Called after authentication to proxy server
+ """
+ if not self.connection or self.connected < 2:
+ return
+ file_props = self.files_props[proxy['sid']]
+ iq = xmpp.Iq(to=proxy['initiator'], typ='set')
+ auth_id = "au_" + proxy['sid']
+ iq.setID(auth_id)
+ query = iq.setTag('query', namespace=xmpp.NS_BYTESTREAM)
+ query.setAttr('sid', proxy['sid'])
+ activate = query.setTag('activate')
+ activate.setData(file_props['proxy_receiver'])
+ iq.setID(auth_id)
+ self.connection.send(iq)
+
+ # register xmpppy handlers for bytestream and FT stanzas
+ def _bytestreamErrorCB(self, con, iq_obj):
+ id_ = unicode(iq_obj.getAttr('id'))
+ frm = helpers.get_full_jid_from_iq(iq_obj)
+ query = iq_obj.getTag('query')
+ gajim.proxy65_manager.error_cb(frm, query)
+ jid = helpers.get_jid_from_iq(iq_obj)
+ id_ = id_[3:]
+ if id_ not in self.files_props:
+ return
+ file_props = self.files_props[id_]
+ file_props['error'] = -4
+ self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
+ raise xmpp.NodeProcessed
+
+ def _ft_get_from(self, iq_obj):
+ return helpers.get_full_jid_from_iq(iq_obj)
+
+ def _bytestreamSetCB(self, con, iq_obj):
+ target = unicode(iq_obj.getAttr('to'))
+ id_ = unicode(iq_obj.getAttr('id'))
+ query = iq_obj.getTag('query')
+ sid = unicode(query.getAttr('sid'))
+ file_props = gajim.socks5queue.get_file_props(self.name, sid)
+ streamhosts = []
+ for item in query.getChildren():
+ if item.getName() == 'streamhost':
+ host_dict = {
+ 'state': 0,
+ 'target': target,
+ 'id': id_,
+ 'sid': sid,
+ 'initiator': self._ft_get_from(iq_obj)
+ }
+ for attr in item.getAttrs():
+ host_dict[attr] = item.getAttr(attr)
+ streamhosts.append(host_dict)
+ if file_props is None:
+ if sid in self.files_props:
+ file_props = self.files_props[sid]
+ file_props['fast'] = streamhosts
+ if file_props['type'] == 's': # FIXME: remove fast xmlns
+ # only psi do this
+ if 'streamhosts' in file_props:
+ file_props['streamhosts'].extend(streamhosts)
+ else:
+ file_props['streamhosts'] = streamhosts
+ if not gajim.socks5queue.get_file_props(self.name, sid):
+ gajim.socks5queue.add_file_props(self.name, file_props)
+ gajim.socks5queue.connect_to_hosts(self.name, sid,
+ self.send_success_connect_reply, None)
+ raise xmpp.NodeProcessed
+
+ file_props['streamhosts'] = streamhosts
+ if file_props['type'] == 'r':
+ gajim.socks5queue.connect_to_hosts(self.name, sid,
+ self.send_success_connect_reply, self._connect_error)
+ raise xmpp.NodeProcessed
+
+ def _ResultCB(self, con, iq_obj):
+ # if we want to respect xep-0065 we have to check for proxy
+ # activation result in any result iq
+ real_id = unicode(iq_obj.getAttr('id'))
+ if not real_id.startswith('au_'):
+ return
+ frm = self._ft_get_from(iq_obj)
+ id_ = real_id[3:]
+ if id_ in self.files_props:
+ file_props = self.files_props[id_]
+ if file_props['streamhost-used']:
+ for host in file_props['proxyhosts']:
+ if host['initiator'] == frm and 'idx' in host:
+ gajim.socks5queue.activate_proxy(host['idx'])
+ raise xmpp.NodeProcessed
+
+ def _ft_get_streamhost_jid_attr(self, streamhost):
+ return helpers.parse_jid(streamhost.getAttr('jid'))
+
+ def _bytestreamResultCB(self, con, iq_obj):
+ frm = self._ft_get_from(iq_obj)
+ real_id = unicode(iq_obj.getAttr('id'))
+ query = iq_obj.getTag('query')
+ gajim.proxy65_manager.resolve_result(frm, query)
+
+ try:
+ streamhost = query.getTag('streamhost-used')
+ except Exception: # this bytestream result is not what we need
+ pass
+ id_ = real_id[3:]
+ if id_ in self.files_props:
+ file_props = self.files_props[id_]
+ else:
+ raise xmpp.NodeProcessed
+ if streamhost is None:
+ # proxy approves the activate query
+ if real_id.startswith('au_'):
+ if 'streamhost-used' not in file_props or \
+ file_props['streamhost-used'] is False:
+ raise xmpp.NodeProcessed
+ if 'proxyhosts' not in file_props:
+ raise xmpp.NodeProcessed
+ for host in file_props['proxyhosts']:
+ if host['initiator'] == frm and \
+ unicode(query.getAttr('sid')) == file_props['sid']:
+ gajim.socks5queue.activate_proxy(host['idx'])
+ break
+ raise xmpp.NodeProcessed
+ jid = self._ft_get_streamhost_jid_attr(streamhost)
+ if 'streamhost-used' in file_props and \
+ file_props['streamhost-used'] is True:
+ raise xmpp.NodeProcessed
+
+ if real_id.startswith('au_'):
+ if 'stopped' in file and file_props['stopped']:
+ self.remove_transfer(file_props)
+ else:
+ gajim.socks5queue.send_file(file_props, self.name)
+ raise xmpp.NodeProcessed
+
+ proxy = None
+ if 'proxyhosts' in file_props:
+ for proxyhost in file_props['proxyhosts']:
+ if proxyhost['jid'] == jid:
+ proxy = proxyhost
+
+ if proxy is not None:
+ file_props['streamhost-used'] = True
+ if 'streamhosts' not in file_props:
+ file_props['streamhosts'] = []
+ file_props['streamhosts'].append(proxy)
+ file_props['is_a_proxy'] = True
+ receiver = Socks5Receiver(gajim.idlequeue, proxy,
+ file_props['sid'], file_props)
+ gajim.socks5queue.add_receiver(self.name, receiver)
+ proxy['idx'] = receiver.queue_idx
+ gajim.socks5queue.on_success = self._proxy_auth_ok
+ raise xmpp.NodeProcessed
+
+ else:
+ if 'stopped' in file_props and file_props['stopped']:
+ self.remove_transfer(file_props)
+ else:
+ gajim.socks5queue.send_file(file_props, self.name)
+ if 'fast' in file_props:
+ fasts = file_props['fast']
+ if len(fasts) > 0:
+ self._connect_error(frm, fasts[0]['id'], file_props['sid'],
+ code=406)
+
+ raise xmpp.NodeProcessed
+
+ def _siResultCB(self, con, iq_obj):
+ file_props = self.files_props.get(iq_obj.getAttr('id'))
+ if not file_props:
+ return
+ if 'request-id' in file_props:
+ # we have already sent streamhosts info
+ return
+ file_props['receiver'] = self._ft_get_from(iq_obj)
+ si = iq_obj.getTag('si')
+ file_tag = si.getTag('file')
+ range_tag = None
+ if file_tag:
+ range_tag = file_tag.getTag('range')
+ if range_tag:
+ offset = range_tag.getAttr('offset')
+ if offset:
+ file_props['offset'] = int(offset)
+ length = range_tag.getAttr('length')
+ if length:
+ file_props['length'] = int(length)
+ feature = si.setTag('feature')
+ if feature.getNamespace() != xmpp.NS_FEATURE:
+ return
+ form_tag = feature.getTag('x')
+ form = xmpp.DataForm(node=form_tag)
+ field = form.getField('stream-method')
+ if field.getValue() != xmpp.NS_BYTESTREAM:
+ return
+ self._send_socks5_info(file_props)
+ raise xmpp.NodeProcessed
+
+ def _siSetCB(self, con, iq_obj):
+ jid = self._ft_get_from(iq_obj)
+ file_props = {'type': 'r'}
+ file_props['sender'] = jid
+ file_props['request-id'] = unicode(iq_obj.getAttr('id'))
+ si = iq_obj.getTag('si')
+ profile = si.getAttr('profile')
+ mime_type = si.getAttr('mime-type')
+ if profile != xmpp.NS_FILE:
+ self.send_file_rejection(file_props, code='400', typ='profile')
+ raise xmpp.NodeProcessed
+ feature_tag = si.getTag('feature', namespace=xmpp.NS_FEATURE)
+ if not feature_tag:
+ return
+ form_tag = feature_tag.getTag('x', namespace=xmpp.NS_DATA)
+ if not form_tag:
+ return
+ form = dataforms.ExtendForm(node=form_tag)
+ for f in form.iter_fields():
+ if f.var == 'stream-method' and f.type == 'list-single':
+ values = [o[1] for o in f.options]
+ if xmpp.NS_BYTESTREAM in values:
+ break
+ else:
+ self.send_file_rejection(file_props, code='400', typ='stream')
+ raise xmpp.NodeProcessed
+ file_tag = si.getTag('file')
+ for attribute in file_tag.getAttrs():
+ if attribute in ('name', 'size', 'hash', 'date'):
+ val = file_tag.getAttr(attribute)
+ if val is None:
+ continue
+ file_props[attribute] = val
+ file_desc_tag = file_tag.getTag('desc')
+ if file_desc_tag is not None:
+ file_props['desc'] = file_desc_tag.getData()
+
+ if mime_type is not None:
+ file_props['mime-type'] = mime_type
+ file_props['receiver'] = self._ft_get_our_jid()
+ file_props['sid'] = unicode(si.getAttr('id'))
+ file_props['transfered_size'] = []
+ gajim.socks5queue.add_file_props(self.name, file_props)
+ self.dispatch('FILE_REQUEST', (jid, file_props))
+ raise xmpp.NodeProcessed
+
+ def _siErrorCB(self, con, iq_obj):
+ si = iq_obj.getTag('si')
+ profile = si.getAttr('profile')
+ if profile != xmpp.NS_FILE:
+ return
+ file_props = self.files_props.get(iq_obj.getAttr('id'))
+ if not file_props:
+ return
+ jid = self._ft_get_from(iq_obj)
+ file_props['error'] = -3
+ self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
+ raise xmpp.NodeProcessed
class ConnectionBytestreamZeroconf(ConnectionBytestream):
- def _ft_get_from(self, iq_obj):
- return unicode(iq_obj.getFrom())
+ def _ft_get_from(self, iq_obj):
+ return unicode(iq_obj.getFrom())
- def _ft_get_our_jid(self):
- return gajim.get_jid_from_account(self.name)
+ def _ft_get_our_jid(self):
+ return gajim.get_jid_from_account(self.name)
- def _ft_get_receiver_jid(self, file_props):
- return file_props['receiver'].jid
+ def _ft_get_receiver_jid(self, file_props):
+ return file_props['receiver'].jid
- def _ft_get_streamhost_jid_attr(self, streamhost):
- return streamhost.getAttr('jid')
-
-# vim: se ts=3:
+ def _ft_get_streamhost_jid_attr(self, streamhost):
+ return streamhost.getAttr('jid')
diff --git a/src/common/protocol/caps.py b/src/common/protocol/caps.py
index 3fd6c1386..a80ca527a 100644
--- a/src/common/protocol/caps.py
+++ b/src/common/protocol/caps.py
@@ -32,80 +32,78 @@ from common import helpers
class ConnectionCaps(object):
- def __init__(self, account, dispatch_event, capscache, client_caps_factory):
- self._account = account
- self._dispatch_event = dispatch_event
- self._capscache = capscache
- self._create_suitable_client_caps = client_caps_factory
-
- def _capsPresenceCB(self, con, presence):
- """
- XMMPPY callback method to handle retrieved caps info
- """
- try:
- jid = helpers.get_full_jid_from_iq(presence)
- except:
- log.info("Ignoring invalid JID in caps presenceCB")
- return
-
- client_caps = self._extract_client_caps_from_presence(presence)
- self._capscache.query_client_of_jid_if_unknown(self, jid, client_caps)
- self._update_client_caps_of_contact(jid, client_caps)
-
- self._dispatch_event('CAPS_RECEIVED', (jid,))
-
- def _extract_client_caps_from_presence(self, presence):
- caps_tag = presence.getTag('c', namespace=NS_CAPS)
- if caps_tag:
- hash_method, node, caps_hash = caps_tag['hash'], caps_tag['node'], caps_tag['ver']
- else:
- hash_method = node = caps_hash = None
- return self._create_suitable_client_caps(node, caps_hash, hash_method)
-
- def _update_client_caps_of_contact(self, jid, client_caps):
- contact = self._get_contact_or_gc_contact_for_jid(jid)
- if contact:
- contact.client_caps = client_caps
- else:
- log.info("Received Caps from unknown contact %s" % jid)
-
- def _get_contact_or_gc_contact_for_jid(self, jid):
- contact = gajim.contacts.get_contact_from_full_jid(self._account, jid)
- if contact is None:
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
- contact = gajim.contacts.get_gc_contact(self._account, room_jid, nick)
- return contact
-
- def _capsDiscoCB(self, jid, node, identities, features, dataforms):
- """
- XMMPPY callback to update our caps cache with queried information after
- we have retrieved an unknown caps hash and issued a disco
- """
- contact = self._get_contact_or_gc_contact_for_jid(jid)
- if not contact:
- log.info("Received Disco from unknown contact %s" % jid)
- return
-
- lookup = contact.client_caps.get_cache_lookup_strategy()
- cache_item = lookup(self._capscache)
-
- if cache_item.is_valid():
- # we already know that the hash is fine and have already cached
- # the identities and features
- return
- else:
- validate = contact.client_caps.get_hash_validation_strategy()
- hash_is_valid = validate(identities, features, dataforms)
-
- if hash_is_valid:
- cache_item.set_and_store(identities, features)
- else:
- node = caps_hash = hash_method = None
- contact.client_caps = self._create_suitable_client_caps(node,
- caps_hash, hash_method)
- log.warn("Computed and retrieved caps hash differ." +
- "Ignoring caps of contact %s" % contact.get_full_jid())
-
- self._dispatch_event('CAPS_RECEIVED', (jid,))
-
-# vim: se ts=3:
+ def __init__(self, account, dispatch_event, capscache, client_caps_factory):
+ self._account = account
+ self._dispatch_event = dispatch_event
+ self._capscache = capscache
+ self._create_suitable_client_caps = client_caps_factory
+
+ def _capsPresenceCB(self, con, presence):
+ """
+ XMMPPY callback method to handle retrieved caps info
+ """
+ try:
+ jid = helpers.get_full_jid_from_iq(presence)
+ except:
+ log.info("Ignoring invalid JID in caps presenceCB")
+ return
+
+ client_caps = self._extract_client_caps_from_presence(presence)
+ self._capscache.query_client_of_jid_if_unknown(self, jid, client_caps)
+ self._update_client_caps_of_contact(jid, client_caps)
+
+ self._dispatch_event('CAPS_RECEIVED', (jid,))
+
+ def _extract_client_caps_from_presence(self, presence):
+ caps_tag = presence.getTag('c', namespace=NS_CAPS)
+ if caps_tag:
+ hash_method, node, caps_hash = caps_tag['hash'], caps_tag['node'], caps_tag['ver']
+ else:
+ hash_method = node = caps_hash = None
+ return self._create_suitable_client_caps(node, caps_hash, hash_method)
+
+ def _update_client_caps_of_contact(self, jid, client_caps):
+ contact = self._get_contact_or_gc_contact_for_jid(jid)
+ if contact:
+ contact.client_caps = client_caps
+ else:
+ log.info("Received Caps from unknown contact %s" % jid)
+
+ def _get_contact_or_gc_contact_for_jid(self, jid):
+ contact = gajim.contacts.get_contact_from_full_jid(self._account, jid)
+ if contact is None:
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+ contact = gajim.contacts.get_gc_contact(self._account, room_jid, nick)
+ return contact
+
+ def _capsDiscoCB(self, jid, node, identities, features, dataforms):
+ """
+ XMMPPY callback to update our caps cache with queried information after
+ we have retrieved an unknown caps hash and issued a disco
+ """
+ contact = self._get_contact_or_gc_contact_for_jid(jid)
+ if not contact:
+ log.info("Received Disco from unknown contact %s" % jid)
+ return
+
+ lookup = contact.client_caps.get_cache_lookup_strategy()
+ cache_item = lookup(self._capscache)
+
+ if cache_item.is_valid():
+ # we already know that the hash is fine and have already cached
+ # the identities and features
+ return
+ else:
+ validate = contact.client_caps.get_hash_validation_strategy()
+ hash_is_valid = validate(identities, features, dataforms)
+
+ if hash_is_valid:
+ cache_item.set_and_store(identities, features)
+ else:
+ node = caps_hash = hash_method = None
+ contact.client_caps = self._create_suitable_client_caps(node,
+ caps_hash, hash_method)
+ log.warn("Computed and retrieved caps hash differ." +
+ "Ignoring caps of contact %s" % contact.get_full_jid())
+
+ self._dispatch_event('CAPS_RECEIVED', (jid,))
diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py
index 5c4420839..e30af586d 100644
--- a/src/common/proxy65_manager.py
+++ b/src/common/proxy65_manager.py
@@ -41,429 +41,427 @@ S_FINISHED = 4
CONNECT_TIMEOUT = 20
class Proxy65Manager:
- """
- Keep records for file transfer proxies. Each time account establishes a
- connection to its server call proxy65manger.resolve(proxy) for every proxy
- that is convigured within the account. The class takes care to resolve and
- test each proxy only once
- """
-
- def __init__(self, idlequeue):
- # dict {proxy: proxy properties}
- self.idlequeue = idlequeue
- self.proxies = {}
- # dict {account: proxy} default proxy for account
- self.default_proxies = {}
-
- def resolve(self, proxy, connection, sender_jid, default=None):
- """
- Start
- """
- if proxy in self.proxies:
- resolver = self.proxies[proxy]
- else:
- # proxy is being ressolved for the first time
- resolver = ProxyResolver(proxy, sender_jid)
- self.proxies[proxy] = resolver
- resolver.add_connection(connection)
- if default:
- # add this proxy as default for account
- self.default_proxies[default] = proxy
-
- def disconnect(self, connection):
- for resolver in self.proxies.values():
- resolver.disconnect(connection)
-
- def resolve_result(self, proxy, query):
- if proxy not in self.proxies:
- return
- jid = None
- for item in query.getChildren():
- if item.getName() == 'streamhost':
- host = item.getAttr('host')
- port = item.getAttr('port')
- jid = item.getAttr('jid')
- self.proxies[proxy].resolve_result(host, port, jid)
- # we can have only one streamhost
- raise common.xmpp.NodeProcessed
-
- def error_cb(self, proxy, query):
- sid = query.getAttr('sid')
- for resolver in self.proxies.values():
- if resolver.sid == sid:
- resolver.keep_conf()
- break
-
- def get_default_for_name(self, account):
- if account in self.default_proxies:
- return self.default_proxies[account]
-
- def get_proxy(self, proxy, account):
- if proxy in self.proxies:
- resolver = self.proxies[proxy]
- if resolver.state == S_FINISHED:
- return (resolver.host, resolver.port, resolver.jid)
- return (None, 0, None)
+ """
+ Keep records for file transfer proxies. Each time account establishes a
+ connection to its server call proxy65manger.resolve(proxy) for every proxy
+ that is convigured within the account. The class takes care to resolve and
+ test each proxy only once
+ """
+
+ def __init__(self, idlequeue):
+ # dict {proxy: proxy properties}
+ self.idlequeue = idlequeue
+ self.proxies = {}
+ # dict {account: proxy} default proxy for account
+ self.default_proxies = {}
+
+ def resolve(self, proxy, connection, sender_jid, default=None):
+ """
+ Start
+ """
+ if proxy in self.proxies:
+ resolver = self.proxies[proxy]
+ else:
+ # proxy is being ressolved for the first time
+ resolver = ProxyResolver(proxy, sender_jid)
+ self.proxies[proxy] = resolver
+ resolver.add_connection(connection)
+ if default:
+ # add this proxy as default for account
+ self.default_proxies[default] = proxy
+
+ def disconnect(self, connection):
+ for resolver in self.proxies.values():
+ resolver.disconnect(connection)
+
+ def resolve_result(self, proxy, query):
+ if proxy not in self.proxies:
+ return
+ jid = None
+ for item in query.getChildren():
+ if item.getName() == 'streamhost':
+ host = item.getAttr('host')
+ port = item.getAttr('port')
+ jid = item.getAttr('jid')
+ self.proxies[proxy].resolve_result(host, port, jid)
+ # we can have only one streamhost
+ raise common.xmpp.NodeProcessed
+
+ def error_cb(self, proxy, query):
+ sid = query.getAttr('sid')
+ for resolver in self.proxies.values():
+ if resolver.sid == sid:
+ resolver.keep_conf()
+ break
+
+ def get_default_for_name(self, account):
+ if account in self.default_proxies:
+ return self.default_proxies[account]
+
+ def get_proxy(self, proxy, account):
+ if proxy in self.proxies:
+ resolver = self.proxies[proxy]
+ if resolver.state == S_FINISHED:
+ return (resolver.host, resolver.port, resolver.jid)
+ return (None, 0, None)
class ProxyResolver:
- def resolve_result(self, host, port, jid):
- """
- Test if host has a real proxy65 listening on port
- """
- self.host = str(host)
- self.port = int(port)
- self.jid = unicode(jid)
- self.state = S_RESOLVED
- #FIXME: re-enable proxy testing
- log.info('start resolving %s:%s' % (self.host, self.port))
- self.receiver_tester = ReceiverTester(self.host, self.port, self.jid,
- self.sid, self.sender_jid, self._on_receiver_success,
- self._on_connect_failure)
- self.receiver_tester.connect()
-
- def _on_receiver_success(self):
- log.debug('Receiver successfully connected %s:%s' % (self.host,
- self.port))
- self.host_tester = HostTester(self.host, self.port, self.jid,
- self.sid, self.sender_jid, self._on_connect_success,
- self._on_connect_failure)
- self.host_tester.connect()
-
- def _on_connect_success(self):
- log.debug('Host successfully connected %s:%s' % (self.host, self.port))
- iq = common.xmpp.Protocol(name='iq', to=self.jid, typ='set')
- query = iq.setTag('query')
- query.setNamespace(common.xmpp.NS_BYTESTREAM)
- query.setAttr('sid', self.sid)
-
- activate = query.setTag('activate')
- activate.setData('test@gajim.org/test2')
-
- if self.active_connection:
- log.debug('Activating bytestream on %s:%s' % (self.host, self.port))
- self.active_connection.SendAndCallForResponse(iq,
- self._result_received)
- self.state = S_ACTIVATED
- else:
- self.state = S_INITIAL
-
- def _result_received(self, data):
- self.disconnect(self.active_connection)
- if data.getType() == 'result':
- self.keep_conf()
- else:
- self._on_connect_failure()
-
- def keep_conf(self):
- log.debug('Bytestream activated %s:%s' % (self.host, self.port))
- self.state = S_FINISHED
-
- def _on_connect_failure(self):
- self.state = S_FINISHED
- self.host = None
- self.port = 0
- self.jid = None
-
- def disconnect(self, connection):
- if self.host_tester:
- self.host_tester.disconnect()
- self.host_tester = None
- if self.receiver_tester:
- self.receiver_tester.disconnect()
- self.receiver_tester = None
- try:
- self.connections.remove(connection)
- except ValueError:
- pass
- if connection == self.active_connection:
- self.active_connection = None
- if self.state != S_FINISHED:
- self.state = S_INITIAL
- self.try_next_connection()
-
- def try_next_connection(self):
- """
- Try to resolve proxy with the next possible connection
- """
- if self.connections:
- connection = self.connections.pop(0)
- self.start_resolve(connection)
-
- def add_connection(self, connection):
- """
- Add a new connection in case the first fails
- """
- self.connections.append(connection)
- if self.state == S_INITIAL:
- self.start_resolve(connection)
-
- def start_resolve(self, connection):
- """
- Request network address from proxy
- """
- self.state = S_STARTED
- self.active_connection = connection
- iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get')
- query = iq.setTag('query')
- query.setNamespace(common.xmpp.NS_BYTESTREAM)
- connection.send(iq)
-
- def __init__(self, proxy, sender_jid):
- self.proxy = proxy
- self.state = S_INITIAL
- self.active_connection = None
- self.connections = []
- self.host_tester = None
- self.receiver_tester = None
- self.jid = None
- self.host = None
- self.port = None
- self.sid = helpers.get_random_string_16()
- self.sender_jid = sender_jid
+ def resolve_result(self, host, port, jid):
+ """
+ Test if host has a real proxy65 listening on port
+ """
+ self.host = str(host)
+ self.port = int(port)
+ self.jid = unicode(jid)
+ self.state = S_RESOLVED
+ #FIXME: re-enable proxy testing
+ log.info('start resolving %s:%s' % (self.host, self.port))
+ self.receiver_tester = ReceiverTester(self.host, self.port, self.jid,
+ self.sid, self.sender_jid, self._on_receiver_success,
+ self._on_connect_failure)
+ self.receiver_tester.connect()
+
+ def _on_receiver_success(self):
+ log.debug('Receiver successfully connected %s:%s' % (self.host,
+ self.port))
+ self.host_tester = HostTester(self.host, self.port, self.jid,
+ self.sid, self.sender_jid, self._on_connect_success,
+ self._on_connect_failure)
+ self.host_tester.connect()
+
+ def _on_connect_success(self):
+ log.debug('Host successfully connected %s:%s' % (self.host, self.port))
+ iq = common.xmpp.Protocol(name='iq', to=self.jid, typ='set')
+ query = iq.setTag('query')
+ query.setNamespace(common.xmpp.NS_BYTESTREAM)
+ query.setAttr('sid', self.sid)
+
+ activate = query.setTag('activate')
+ activate.setData('test@gajim.org/test2')
+
+ if self.active_connection:
+ log.debug('Activating bytestream on %s:%s' % (self.host, self.port))
+ self.active_connection.SendAndCallForResponse(iq,
+ self._result_received)
+ self.state = S_ACTIVATED
+ else:
+ self.state = S_INITIAL
+
+ def _result_received(self, data):
+ self.disconnect(self.active_connection)
+ if data.getType() == 'result':
+ self.keep_conf()
+ else:
+ self._on_connect_failure()
+
+ def keep_conf(self):
+ log.debug('Bytestream activated %s:%s' % (self.host, self.port))
+ self.state = S_FINISHED
+
+ def _on_connect_failure(self):
+ self.state = S_FINISHED
+ self.host = None
+ self.port = 0
+ self.jid = None
+
+ def disconnect(self, connection):
+ if self.host_tester:
+ self.host_tester.disconnect()
+ self.host_tester = None
+ if self.receiver_tester:
+ self.receiver_tester.disconnect()
+ self.receiver_tester = None
+ try:
+ self.connections.remove(connection)
+ except ValueError:
+ pass
+ if connection == self.active_connection:
+ self.active_connection = None
+ if self.state != S_FINISHED:
+ self.state = S_INITIAL
+ self.try_next_connection()
+
+ def try_next_connection(self):
+ """
+ Try to resolve proxy with the next possible connection
+ """
+ if self.connections:
+ connection = self.connections.pop(0)
+ self.start_resolve(connection)
+
+ def add_connection(self, connection):
+ """
+ Add a new connection in case the first fails
+ """
+ self.connections.append(connection)
+ if self.state == S_INITIAL:
+ self.start_resolve(connection)
+
+ def start_resolve(self, connection):
+ """
+ Request network address from proxy
+ """
+ self.state = S_STARTED
+ self.active_connection = connection
+ iq = common.xmpp.Protocol(name='iq', to=self.proxy, typ='get')
+ query = iq.setTag('query')
+ query.setNamespace(common.xmpp.NS_BYTESTREAM)
+ connection.send(iq)
+
+ def __init__(self, proxy, sender_jid):
+ self.proxy = proxy
+ self.state = S_INITIAL
+ self.active_connection = None
+ self.connections = []
+ self.host_tester = None
+ self.receiver_tester = None
+ self.jid = None
+ self.host = None
+ self.port = None
+ self.sid = helpers.get_random_string_16()
+ self.sender_jid = sender_jid
class HostTester(Socks5, IdleObject):
- """
- Fake proxy tester
- """
-
- def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
- """
- Try to establish and auth to proxy at (host, port)
-
- Calls on_success, or on_failure according to the result.
- """
- self.host = host
- self.port = port
- self.jid = jid
- self.on_success = on_success
- self.on_failure = on_failure
- self._sock = None
- self.file_props = {'is_a_proxy': True,
- 'proxy_sender': sender_jid,
- 'proxy_receiver': 'test@gajim.org/test2'}
- Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
- self.sid = sid
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.host is None:
- self.on_failure()
- return None
- self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- gajim.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- self.pollend()
-
- def pollend(self):
- self.disconnect()
- self.on_failure()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- data = self._get_auth_buff()
- self.send_raw(data)
- else:
- return
- self.state += 1
- # unplug and plug for reading
- gajim.idlequeue.plug_idle(self, False, True)
- gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 2:
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- # begin negotiation. on success 'address' != 0
- buff = self.receive()
- if buff == '':
- # end connection
- self.pollend()
- return
- # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.pollend()
- return
- data = self._get_request_buff(self._get_sha1_auth())
- self.send_raw(data)
- self.state += 1
- log.debug('Host authenticating to %s:%s' % (self.host, self.port))
- elif self.state == 3:
- log.debug('Host authenticated to %s:%s' % (self.host, self.port))
- self.on_success()
- self.disconnect()
- self.state += 1
- else:
- assert False, 'unexpected state: %d' % self.state
-
- def do_connect(self):
- try:
- self._sock.connect((self.host, self.port))
- self._sock.setblocking(False)
- log.debug('Host Connecting to %s:%s' % (self.host, self.port))
- self._send = self._sock.send
- self._recv = self._sock.recv
- except Exception, ee:
- errnum = ee[0]
- # 56 is for freebsd
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- # still trying to connect
- return
- # win32 needs this
- if errnum not in (0, 10056, errno.EISCONN):
- # connection failed
- self.on_failure()
- return
- # socket is already connected
- self._sock.setblocking(False)
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.buff = ''
- self.state = 1 # connected
- log.debug('Host connected to %s:%s' % (self.host, self.port))
- self.idlequeue.plug_idle(self, True, False)
- return
+ """
+ Fake proxy tester
+ """
+
+ def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
+ """
+ Try to establish and auth to proxy at (host, port)
+
+ Calls on_success, or on_failure according to the result.
+ """
+ self.host = host
+ self.port = port
+ self.jid = jid
+ self.on_success = on_success
+ self.on_failure = on_failure
+ self._sock = None
+ self.file_props = {'is_a_proxy': True,
+ 'proxy_sender': sender_jid,
+ 'proxy_receiver': 'test@gajim.org/test2'}
+ Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
+ self.sid = sid
+
+ def connect(self):
+ """
+ Create the socket and plug it to the idlequeue
+ """
+ if self.host is None:
+ self.on_failure()
+ return None
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._sock.setblocking(False)
+ self.fd = self._sock.fileno()
+ self.state = 0 # about to be connected
+ gajim.idlequeue.plug_idle(self, True, False)
+ self.do_connect()
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+ return None
+
+ def read_timeout(self):
+ self.idlequeue.remove_timeout(self.fd)
+ self.pollend()
+
+ def pollend(self):
+ self.disconnect()
+ self.on_failure()
+
+ def pollout(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state == 0:
+ self.do_connect()
+ return
+ elif self.state == 1: # send initially: version and auth types
+ data = self._get_auth_buff()
+ self.send_raw(data)
+ else:
+ return
+ self.state += 1
+ # unplug and plug for reading
+ gajim.idlequeue.plug_idle(self, False, True)
+ gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+
+ def pollin(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state == 2:
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+ # begin negotiation. on success 'address' != 0
+ buff = self.receive()
+ if buff == '':
+ # end connection
+ self.pollend()
+ return
+ # read auth response
+ if buff is None or len(buff) != 2:
+ return None
+ version, method = struct.unpack('!BB', buff[:2])
+ if version != 0x05 or method == 0xff:
+ self.pollend()
+ return
+ data = self._get_request_buff(self._get_sha1_auth())
+ self.send_raw(data)
+ self.state += 1
+ log.debug('Host authenticating to %s:%s' % (self.host, self.port))
+ elif self.state == 3:
+ log.debug('Host authenticated to %s:%s' % (self.host, self.port))
+ self.on_success()
+ self.disconnect()
+ self.state += 1
+ else:
+ assert False, 'unexpected state: %d' % self.state
+
+ def do_connect(self):
+ try:
+ self._sock.connect((self.host, self.port))
+ self._sock.setblocking(False)
+ log.debug('Host Connecting to %s:%s' % (self.host, self.port))
+ self._send = self._sock.send
+ self._recv = self._sock.recv
+ except Exception, ee:
+ errnum = ee[0]
+ # 56 is for freebsd
+ if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
+ # still trying to connect
+ return
+ # win32 needs this
+ if errnum not in (0, 10056, errno.EISCONN):
+ # connection failed
+ self.on_failure()
+ return
+ # socket is already connected
+ self._sock.setblocking(False)
+ self._send = self._sock.send
+ self._recv = self._sock.recv
+ self.buff = ''
+ self.state = 1 # connected
+ log.debug('Host connected to %s:%s' % (self.host, self.port))
+ self.idlequeue.plug_idle(self, True, False)
+ return
class ReceiverTester(Socks5, IdleObject):
- """
- Fake proxy tester
- """
-
- def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
- """
- Try to establish and auth to proxy at (host, port)
-
- Call on_success, or on_failure according to the result.
- """
- self.host = host
- self.port = port
- self.jid = jid
- self.on_success = on_success
- self.on_failure = on_failure
- self._sock = None
- self.file_props = {'is_a_proxy': True,
- 'proxy_sender': sender_jid,
- 'proxy_receiver': 'test@gajim.org/test2'}
- Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
- self.sid = sid
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.host is None:
- self.on_failure()
- return None
- self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- gajim.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- self.pollend()
-
- def pollend(self):
- self.disconnect()
- self.on_failure()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- data = self._get_auth_buff()
- self.send_raw(data)
- else:
- return
- self.state += 1
- # unplug and plug for reading
- gajim.idlequeue.plug_idle(self, False, True)
- gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state in (2, 3):
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- # begin negotiation. on success 'address' != 0
- buff = self.receive()
- if buff == '':
- # end connection
- self.pollend()
- return
- if self.state == 2:
- # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.pollend()
- return
- log.debug('Receiver authenticating to %s:%s' % (self.host, self.port))
- data = self._get_request_buff(self._get_sha1_auth())
- self.send_raw(data)
- self.state += 1
- elif self.state == 3:
- # read connect response
- if buff is None or len(buff) < 2:
- return None
- version, reply = struct.unpack('!BB', buff[:2])
- if version != 0x05 or reply != 0x00:
- self.pollend()
- return
- log.debug('Receiver authenticated to %s:%s' % (self.host, self.port))
- self.on_success()
- self.disconnect()
- self.state += 1
- else:
- assert False, 'unexpected state: %d' % self.state
-
- def do_connect(self):
- try:
- self._sock.setblocking(False)
- self._sock.connect((self.host, self.port))
- log.debug('Receiver Connecting to %s:%s' % (self.host, self.port))
- self._send = self._sock.send
- self._recv = self._sock.recv
- except Exception, ee:
- errnum = ee[0]
- # 56 is for freebsd
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- # still trying to connect
- return
- # win32 needs this
- if errnum not in (0, 10056, errno.EISCONN):
- # connection failed
- self.on_failure()
- return
- # socket is already connected
- self._sock.setblocking(False)
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.buff = ''
- self.state = 1 # connected
- log.debug('Receiver connected to %s:%s' % (self.host, self.port))
- self.idlequeue.plug_idle(self, True, False)
-
-# vim: se ts=3:
+ """
+ Fake proxy tester
+ """
+
+ def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
+ """
+ Try to establish and auth to proxy at (host, port)
+
+ Call on_success, or on_failure according to the result.
+ """
+ self.host = host
+ self.port = port
+ self.jid = jid
+ self.on_success = on_success
+ self.on_failure = on_failure
+ self._sock = None
+ self.file_props = {'is_a_proxy': True,
+ 'proxy_sender': sender_jid,
+ 'proxy_receiver': 'test@gajim.org/test2'}
+ Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
+ self.sid = sid
+
+ def connect(self):
+ """
+ Create the socket and plug it to the idlequeue
+ """
+ if self.host is None:
+ self.on_failure()
+ return None
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._sock.setblocking(False)
+ self.fd = self._sock.fileno()
+ self.state = 0 # about to be connected
+ gajim.idlequeue.plug_idle(self, True, False)
+ self.do_connect()
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+ return None
+
+ def read_timeout(self):
+ self.idlequeue.remove_timeout(self.fd)
+ self.pollend()
+
+ def pollend(self):
+ self.disconnect()
+ self.on_failure()
+
+ def pollout(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state == 0:
+ self.do_connect()
+ return
+ elif self.state == 1: # send initially: version and auth types
+ data = self._get_auth_buff()
+ self.send_raw(data)
+ else:
+ return
+ self.state += 1
+ # unplug and plug for reading
+ gajim.idlequeue.plug_idle(self, False, True)
+ gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+
+ def pollin(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state in (2, 3):
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+ # begin negotiation. on success 'address' != 0
+ buff = self.receive()
+ if buff == '':
+ # end connection
+ self.pollend()
+ return
+ if self.state == 2:
+ # read auth response
+ if buff is None or len(buff) != 2:
+ return None
+ version, method = struct.unpack('!BB', buff[:2])
+ if version != 0x05 or method == 0xff:
+ self.pollend()
+ return
+ log.debug('Receiver authenticating to %s:%s' % (self.host, self.port))
+ data = self._get_request_buff(self._get_sha1_auth())
+ self.send_raw(data)
+ self.state += 1
+ elif self.state == 3:
+ # read connect response
+ if buff is None or len(buff) < 2:
+ return None
+ version, reply = struct.unpack('!BB', buff[:2])
+ if version != 0x05 or reply != 0x00:
+ self.pollend()
+ return
+ log.debug('Receiver authenticated to %s:%s' % (self.host, self.port))
+ self.on_success()
+ self.disconnect()
+ self.state += 1
+ else:
+ assert False, 'unexpected state: %d' % self.state
+
+ def do_connect(self):
+ try:
+ self._sock.setblocking(False)
+ self._sock.connect((self.host, self.port))
+ log.debug('Receiver Connecting to %s:%s' % (self.host, self.port))
+ self._send = self._sock.send
+ self._recv = self._sock.recv
+ except Exception, ee:
+ errnum = ee[0]
+ # 56 is for freebsd
+ if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
+ # still trying to connect
+ return
+ # win32 needs this
+ if errnum not in (0, 10056, errno.EISCONN):
+ # connection failed
+ self.on_failure()
+ return
+ # socket is already connected
+ self._sock.setblocking(False)
+ self._send = self._sock.send
+ self._recv = self._sock.recv
+ self.buff = ''
+ self.state = 1 # connected
+ log.debug('Receiver connected to %s:%s' % (self.host, self.port))
+ self.idlequeue.plug_idle(self, True, False)
diff --git a/src/common/pubsub.py b/src/common/pubsub.py
index d2dbf88e1..2a75d34a9 100644
--- a/src/common/pubsub.py
+++ b/src/common/pubsub.py
@@ -28,177 +28,175 @@ import logging
log = logging.getLogger('gajim.c.pubsub')
class ConnectionPubSub:
- def __init__(self):
- self.__callbacks={}
-
- def send_pb_subscription_query(self, jid, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('get', to=jid)
- pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- pb.addChild('subscriptions')
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_]=(cb, args, kwargs)
-
- def send_pb_subscribe(self, jid, node, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- query = xmpp.Iq('set', to=jid)
- pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- pb.addChild('subscribe', {'node': node, 'jid': our_jid})
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_]=(cb, args, kwargs)
-
- def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- query = xmpp.Iq('set', to=jid)
- pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- pb.addChild('unsubscribe', {'node': node, 'jid': our_jid})
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_]=(cb, args, kwargs)
-
- def send_pb_publish(self, jid, node, item, id_, options=None):
- """
- Publish item to a node
- """
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('set', to=jid)
- e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- p = e.addChild('publish', {'node': node})
- p.addChild('item', {'id': id_}, [item])
- if options:
- p = e.addChild('publish-options')
- p.addChild(node=options)
-
- self.connection.send(query)
-
- def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs):
- """
- Get items from a node
- """
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('get', to=jid)
- r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- r = r.addChild('items', {'node': node})
- id_ = self.connection.send(query)
-
- if cb:
- self.__callbacks[id_]=(cb, args, kwargs)
-
- def send_pb_retract(self, jid, node, id_):
- """
- Delete item from a node
- """
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('set', to=jid)
- r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- r = r.addChild('retract', {'node': node, 'notify': '1'})
- r = r.addChild('item', {'id': id_})
-
- self.connection.send(query)
-
- def send_pb_delete(self, jid, node):
- """
- Delete node
- """
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('set', to=jid)
- d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- d = d.addChild('delete', {'node': node})
-
- def response(con, resp, jid, node):
- if resp.getType() == 'result':
- self.dispatch('PUBSUB_NODE_REMOVED', (jid, node))
- else:
- msg = resp.getErrorMsg()
- self.dispatch('PUBSUB_NODE_NOT_REMOVED', (jid, node, msg))
-
- self.connection.SendAndCallForResponse(query, response, {'jid': jid,
- 'node': node})
-
- def send_pb_create(self, jid, node, configure = False, configure_form = None):
- """
- Create a new node
- """
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('set', to=jid)
- c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
- c = c.addChild('create', {'node': node})
- if configure:
- conf = c.addChild('configure')
- if configure_form is not None:
- conf.addChild(node=configure_form)
-
- self.connection.send(query)
-
- def send_pb_configure(self, jid, node, form):
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('set', to=jid)
- c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER)
- c = c.addChild('configure', {'node': node})
- c.addChild(node=form)
-
- self.connection.send(query)
-
- def _PubSubCB(self, conn, stanza):
- log.debug('_PubsubCB')
- try:
- cb, args, kwargs = self.__callbacks.pop(stanza.getID())
- cb(conn, stanza, *args, **kwargs)
- except Exception:
- pass
-
- pubsub = stanza.getTag('pubsub')
- if not pubsub:
- return
- items = pubsub.getTag('items')
- if not items:
- return
- item = items.getTag('item')
- if not item:
- return
- storage = item.getTag('storage')
- if storage:
- ns = storage.getNamespace()
- if ns == 'storage:bookmarks':
- self._parse_bookmarks(storage, 'pubsub')
-
- def _PubSubErrorCB(self, conn, stanza):
- log.debug('_PubsubErrorCB')
- pubsub = stanza.getTag('pubsub')
- if not pubsub:
- return
- items = pubsub.getTag('items')
- if not items:
- return
- if items.getAttr('node') == 'storage:bookmarks':
- # Receiving bookmarks from pubsub failed, so take them from xml
- self.get_bookmarks(storage_type='xml')
-
- def request_pb_configuration(self, jid, node):
- if not self.connection or self.connected < 2:
- return
- query = xmpp.Iq('get', to=jid)
- e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER)
- e = e.addChild('configure', {'node': node})
- id_ = self.connection.getAnID()
- query.setID(id_)
- self.awaiting_answers[id_] = (connection_handlers.PEP_CONFIG,)
- self.connection.send(query)
-
-# vim: se ts=3:
+ def __init__(self):
+ self.__callbacks={}
+
+ def send_pb_subscription_query(self, jid, cb, *args, **kwargs):
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('get', to=jid)
+ pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ pb.addChild('subscriptions')
+
+ id_ = self.connection.send(query)
+
+ self.__callbacks[id_]=(cb, args, kwargs)
+
+ def send_pb_subscribe(self, jid, node, cb, *args, **kwargs):
+ if not self.connection or self.connected < 2:
+ return
+ our_jid = gajim.get_jid_from_account(self.name)
+ query = xmpp.Iq('set', to=jid)
+ pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ pb.addChild('subscribe', {'node': node, 'jid': our_jid})
+
+ id_ = self.connection.send(query)
+
+ self.__callbacks[id_]=(cb, args, kwargs)
+
+ def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs):
+ if not self.connection or self.connected < 2:
+ return
+ our_jid = gajim.get_jid_from_account(self.name)
+ query = xmpp.Iq('set', to=jid)
+ pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ pb.addChild('unsubscribe', {'node': node, 'jid': our_jid})
+
+ id_ = self.connection.send(query)
+
+ self.__callbacks[id_]=(cb, args, kwargs)
+
+ def send_pb_publish(self, jid, node, item, id_, options=None):
+ """
+ Publish item to a node
+ """
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('set', to=jid)
+ e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ p = e.addChild('publish', {'node': node})
+ p.addChild('item', {'id': id_}, [item])
+ if options:
+ p = e.addChild('publish-options')
+ p.addChild(node=options)
+
+ self.connection.send(query)
+
+ def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs):
+ """
+ Get items from a node
+ """
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('get', to=jid)
+ r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ r = r.addChild('items', {'node': node})
+ id_ = self.connection.send(query)
+
+ if cb:
+ self.__callbacks[id_]=(cb, args, kwargs)
+
+ def send_pb_retract(self, jid, node, id_):
+ """
+ Delete item from a node
+ """
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('set', to=jid)
+ r = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ r = r.addChild('retract', {'node': node, 'notify': '1'})
+ r = r.addChild('item', {'id': id_})
+
+ self.connection.send(query)
+
+ def send_pb_delete(self, jid, node):
+ """
+ Delete node
+ """
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('set', to=jid)
+ d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ d = d.addChild('delete', {'node': node})
+
+ def response(con, resp, jid, node):
+ if resp.getType() == 'result':
+ self.dispatch('PUBSUB_NODE_REMOVED', (jid, node))
+ else:
+ msg = resp.getErrorMsg()
+ self.dispatch('PUBSUB_NODE_NOT_REMOVED', (jid, node, msg))
+
+ self.connection.SendAndCallForResponse(query, response, {'jid': jid,
+ 'node': node})
+
+ def send_pb_create(self, jid, node, configure = False, configure_form = None):
+ """
+ Create a new node
+ """
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('set', to=jid)
+ c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
+ c = c.addChild('create', {'node': node})
+ if configure:
+ conf = c.addChild('configure')
+ if configure_form is not None:
+ conf.addChild(node=configure_form)
+
+ self.connection.send(query)
+
+ def send_pb_configure(self, jid, node, form):
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('set', to=jid)
+ c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER)
+ c = c.addChild('configure', {'node': node})
+ c.addChild(node=form)
+
+ self.connection.send(query)
+
+ def _PubSubCB(self, conn, stanza):
+ log.debug('_PubsubCB')
+ try:
+ cb, args, kwargs = self.__callbacks.pop(stanza.getID())
+ cb(conn, stanza, *args, **kwargs)
+ except Exception:
+ pass
+
+ pubsub = stanza.getTag('pubsub')
+ if not pubsub:
+ return
+ items = pubsub.getTag('items')
+ if not items:
+ return
+ item = items.getTag('item')
+ if not item:
+ return
+ storage = item.getTag('storage')
+ if storage:
+ ns = storage.getNamespace()
+ if ns == 'storage:bookmarks':
+ self._parse_bookmarks(storage, 'pubsub')
+
+ def _PubSubErrorCB(self, conn, stanza):
+ log.debug('_PubsubErrorCB')
+ pubsub = stanza.getTag('pubsub')
+ if not pubsub:
+ return
+ items = pubsub.getTag('items')
+ if not items:
+ return
+ if items.getAttr('node') == 'storage:bookmarks':
+ # Receiving bookmarks from pubsub failed, so take them from xml
+ self.get_bookmarks(storage_type='xml')
+
+ def request_pb_configuration(self, jid, node):
+ if not self.connection or self.connected < 2:
+ return
+ query = xmpp.Iq('get', to=jid)
+ e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER)
+ e = e.addChild('configure', {'node': node})
+ id_ = self.connection.getAnID()
+ query.setID(id_)
+ self.awaiting_answers[id_] = (connection_handlers.PEP_CONFIG,)
+ self.connection.send(query)
diff --git a/src/common/resolver.py b/src/common/resolver.py
index 8ed31dd13..461da3421 100644
--- a/src/common/resolver.py
+++ b/src/common/resolver.py
@@ -1,4 +1,4 @@
-## common/resolver.py
+## common/resolver.py
##
## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com>
##
@@ -24,10 +24,10 @@ import logging
log = logging.getLogger('gajim.c.resolver')
if __name__ == '__main__':
- sys.path.append('..')
- from common import i18n
- import common.configpaths
- common.configpaths.gajimpaths.init(None)
+ sys.path.append('..')
+ from common import i18n
+ import common.configpaths
+ common.configpaths.gajimpaths.init(None)
from common import helpers
from common.xmpp.idlequeue import IdleCommand
@@ -39,309 +39,307 @@ ns_type_pattern = re.compile('^[a-z]+$')
host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$')
try:
- #raise ImportError("Manually disabled libasync")
- import libasyncns
- USE_LIBASYNCNS = True
- log.info("libasyncns-python loaded")
+ #raise ImportError("Manually disabled libasync")
+ import libasyncns
+ USE_LIBASYNCNS = True
+ log.info("libasyncns-python loaded")
except ImportError:
- USE_LIBASYNCNS = False
- log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True)
+ USE_LIBASYNCNS = False
+ log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True)
def get_resolver(idlequeue):
- if USE_LIBASYNCNS:
- return LibAsyncNSResolver()
- else:
- return NSLookupResolver(idlequeue)
+ if USE_LIBASYNCNS:
+ return LibAsyncNSResolver()
+ else:
+ return NSLookupResolver(idlequeue)
class CommonResolver():
- def __init__(self):
- # dict {"host+type" : list of records}
- self.resolved_hosts = {}
- # dict {"host+type" : list of callbacks}
- self.handlers = {}
-
- def resolve(self, host, on_ready, type='srv'):
- log.debug('resolve %s type=%s' % (host, type))
- assert(type in ['srv', 'txt'])
- if not host:
- # empty host, return empty list of srv records
- on_ready([])
- return
- if self.resolved_hosts.has_key(host+type):
- # host is already resolved, return cached values
- log.debug('%s already resolved: %s')
- on_ready(host, self.resolved_hosts[host+type])
- return
- if self.handlers.has_key(host+type):
- # host is about to be resolved by another connection,
- # attach our callback
- log.debug('already resolving %s' % host)
- self.handlers[host+type].append(on_ready)
- else:
- # host has never been resolved, start now
- log.debug('Starting to resolve %s using %s' % (host, self))
- self.handlers[host+type] = [on_ready]
- self.start_resolve(host, type)
-
- def _on_ready(self, host, type, result_list):
- # practically it is impossible to be the opposite, but who knows :)
- log.debug('Resolving result for %s: %s' % (host, result_list))
- if not self.resolved_hosts.has_key(host+type):
- self.resolved_hosts[host+type] = result_list
- if self.handlers.has_key(host+type):
- for callback in self.handlers[host+type]:
- callback(host, result_list)
- del(self.handlers[host+type])
-
- def start_resolve(self, host, type):
- pass
+ def __init__(self):
+ # dict {"host+type" : list of records}
+ self.resolved_hosts = {}
+ # dict {"host+type" : list of callbacks}
+ self.handlers = {}
+
+ def resolve(self, host, on_ready, type='srv'):
+ log.debug('resolve %s type=%s' % (host, type))
+ assert(type in ['srv', 'txt'])
+ if not host:
+ # empty host, return empty list of srv records
+ on_ready([])
+ return
+ if self.resolved_hosts.has_key(host+type):
+ # host is already resolved, return cached values
+ log.debug('%s already resolved: %s')
+ on_ready(host, self.resolved_hosts[host+type])
+ return
+ if self.handlers.has_key(host+type):
+ # host is about to be resolved by another connection,
+ # attach our callback
+ log.debug('already resolving %s' % host)
+ self.handlers[host+type].append(on_ready)
+ else:
+ # host has never been resolved, start now
+ log.debug('Starting to resolve %s using %s' % (host, self))
+ self.handlers[host+type] = [on_ready]
+ self.start_resolve(host, type)
+
+ def _on_ready(self, host, type, result_list):
+ # practically it is impossible to be the opposite, but who knows :)
+ log.debug('Resolving result for %s: %s' % (host, result_list))
+ if not self.resolved_hosts.has_key(host+type):
+ self.resolved_hosts[host+type] = result_list
+ if self.handlers.has_key(host+type):
+ for callback in self.handlers[host+type]:
+ callback(host, result_list)
+ del(self.handlers[host+type])
+
+ def start_resolve(self, host, type):
+ pass
# FIXME: API usage is not consistent! This one requires that process is called
class LibAsyncNSResolver(CommonResolver):
- """
- Asynchronous resolver using libasyncns-python. process() method has to be
- called in order to proceed the pending requests. Based on patch submitted by
- Damien Thebault.
- """
-
- def __init__(self):
- self.asyncns = libasyncns.Asyncns()
- CommonResolver.__init__(self)
-
- def start_resolve(self, host, type):
- type = libasyncns.ns_t_srv
- if type == 'txt': type = libasyncns.ns_t_txt
- resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type)
- resq.userdata = {'host':host, 'type':type}
-
- # getaddrinfo to be done
- #def resolve_name(self, dname, callback):
- #resq = self.asyncns.getaddrinfo(dname)
- #resq.userdata = {'callback':callback, 'dname':dname}
-
- def _on_ready(self, host, type, result_list):
- if type == libasyncns.ns_t_srv: type = 'srv'
- elif type == libasyncns.ns_t_txt: type = 'txt'
-
- CommonResolver._on_ready(self, host, type, result_list)
-
- def process(self):
- try:
- self.asyncns.wait(False)
- resq = self.asyncns.get_next()
- except:
- return True
- if type(resq) == libasyncns.ResQuery:
- # TXT or SRV result
- while resq is not None:
- try:
- rl = resq.get_done()
- except Exception:
- rl = []
- hosts = []
- requested_type = resq.userdata['type']
- requested_host = resq.userdata['host']
- if rl:
- for r in rl:
- if r['type'] != requested_type:
- # Answer doesn't contain valid SRV data
- continue
- r['prio'] = r['pref']
- hosts.append(r)
- self._on_ready(host=requested_host, type=requested_type,
- result_list=hosts)
- try:
- resq = self.asyncns.get_next()
- except Exception:
- resq = None
- elif type(resq) == libasyncns.AddrInfoQuery:
- # getaddrinfo result (A or AAAA)
- rl = resq.get_done()
- resq.userdata['callback'](resq.userdata['dname'], rl)
- return True
+ """
+ Asynchronous resolver using libasyncns-python. process() method has to be
+ called in order to proceed the pending requests. Based on patch submitted by
+ Damien Thebault.
+ """
+
+ def __init__(self):
+ self.asyncns = libasyncns.Asyncns()
+ CommonResolver.__init__(self)
+
+ def start_resolve(self, host, type):
+ type = libasyncns.ns_t_srv
+ if type == 'txt': type = libasyncns.ns_t_txt
+ resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type)
+ resq.userdata = {'host':host, 'type':type}
+
+ # getaddrinfo to be done
+ #def resolve_name(self, dname, callback):
+ #resq = self.asyncns.getaddrinfo(dname)
+ #resq.userdata = {'callback':callback, 'dname':dname}
+
+ def _on_ready(self, host, type, result_list):
+ if type == libasyncns.ns_t_srv: type = 'srv'
+ elif type == libasyncns.ns_t_txt: type = 'txt'
+
+ CommonResolver._on_ready(self, host, type, result_list)
+
+ def process(self):
+ try:
+ self.asyncns.wait(False)
+ resq = self.asyncns.get_next()
+ except:
+ return True
+ if type(resq) == libasyncns.ResQuery:
+ # TXT or SRV result
+ while resq is not None:
+ try:
+ rl = resq.get_done()
+ except Exception:
+ rl = []
+ hosts = []
+ requested_type = resq.userdata['type']
+ requested_host = resq.userdata['host']
+ if rl:
+ for r in rl:
+ if r['type'] != requested_type:
+ # Answer doesn't contain valid SRV data
+ continue
+ r['prio'] = r['pref']
+ hosts.append(r)
+ self._on_ready(host=requested_host, type=requested_type,
+ result_list=hosts)
+ try:
+ resq = self.asyncns.get_next()
+ except Exception:
+ resq = None
+ elif type(resq) == libasyncns.AddrInfoQuery:
+ # getaddrinfo result (A or AAAA)
+ rl = resq.get_done()
+ resq.userdata['callback'](resq.userdata['dname'], rl)
+ return True
class NSLookupResolver(CommonResolver):
- """
- Asynchronous DNS resolver calling nslookup. Processing of pending requests
- is invoked from idlequeue which is watching file descriptor of pipe of
- stdout of nslookup process.
- """
-
- def __init__(self, idlequeue):
- self.idlequeue = idlequeue
- self.process = False
- CommonResolver.__init__(self)
-
- def parse_srv_result(self, fqdn, result):
- """
- Parse the output of nslookup command and return list of properties:
- 'host', 'port','weight', 'priority' corresponding to the found srv hosts
- """
- if os.name == 'nt':
- return self._parse_srv_result_nt(fqdn, result)
- elif os.name == 'posix':
- return self._parse_srv_result_posix(fqdn, result)
-
- def _parse_srv_result_nt(self, fqdn, result):
- # output from win32 nslookup command
- if not result:
- return []
- hosts = []
- lines = result.replace('\r','').split('\n')
- current_host = None
- for line in lines:
- line = line.lstrip()
- if line == '':
- continue
- if line.startswith(fqdn):
- rest = line[len(fqdn):]
- if rest.find('service') > -1:
- current_host = {}
- elif isinstance(current_host, dict):
- res = line.strip().split('=')
- if len(res) != 2:
- if len(current_host) == 4:
- hosts.append(current_host)
- current_host = None
- continue
- prop_type = res[0].strip()
- prop_value = res[1].strip()
- if prop_type.find('prio') > -1:
- try:
- current_host['prio'] = int(prop_value)
- except ValueError:
- continue
- elif prop_type.find('weight') > -1:
- try:
- current_host['weight'] = int(prop_value)
- except ValueError:
- continue
- elif prop_type.find('port') > -1:
- try:
- current_host['port'] = int(prop_value)
- except ValueError:
- continue
- elif prop_type.find('host') > -1:
- # strip '.' at the end of hostname
- if prop_value[-1] == '.':
- prop_value = prop_value[:-1]
- current_host['host'] = prop_value
- if len(current_host) == 4:
- hosts.append(current_host)
- current_host = None
- return hosts
-
- def _parse_srv_result_posix(self, fqdn, result):
- # typical output of bind-tools nslookup command:
- # _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org.
- if not result:
- return []
- ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name
- hosts = []
- lines = result.split('\n')
- for line in lines:
- if line == '':
- continue
- domain = None
- if line.startswith(fqdn):
- domain = fqdn # For nslookup 9.5
- elif helpers.decode_string(line).startswith(ufqdn):
- line = helpers.decode_string(line)
- domain = ufqdn # For nslookup 9.6
- if domain:
- rest = line[len(domain):].split('=')
- if len(rest) != 2:
- continue
- answer_type, props_str = rest
- if answer_type.strip() != 'service':
- continue
- props = props_str.strip().split(' ')
- if len(props) < 4:
- continue
- prio, weight, port, host = props[-4:]
- if host[-1] == '.':
- host = host[:-1]
- try:
- prio = int(prio)
- weight = int(weight)
- port = int(port)
- except ValueError:
- continue
- hosts.append({'host': host, 'port': port, 'weight': weight,
- 'prio': prio})
- return hosts
-
- def _on_ready(self, host, type, result):
- # nslookup finished, parse the result and call the handlers
- result_list = self.parse_srv_result(host, result)
- CommonResolver._on_ready(self, host, type, result_list)
-
- def start_resolve(self, host, type):
- """
- Spawn new nslookup process and start waiting for results
- """
- ns = NsLookup(self._on_ready, host, type)
- ns.set_idlequeue(self.idlequeue)
- ns.commandtimeout = 20
- ns.start()
+ """
+ Asynchronous DNS resolver calling nslookup. Processing of pending requests
+ is invoked from idlequeue which is watching file descriptor of pipe of
+ stdout of nslookup process.
+ """
+
+ def __init__(self, idlequeue):
+ self.idlequeue = idlequeue
+ self.process = False
+ CommonResolver.__init__(self)
+
+ def parse_srv_result(self, fqdn, result):
+ """
+ Parse the output of nslookup command and return list of properties:
+ 'host', 'port','weight', 'priority' corresponding to the found srv hosts
+ """
+ if os.name == 'nt':
+ return self._parse_srv_result_nt(fqdn, result)
+ elif os.name == 'posix':
+ return self._parse_srv_result_posix(fqdn, result)
+
+ def _parse_srv_result_nt(self, fqdn, result):
+ # output from win32 nslookup command
+ if not result:
+ return []
+ hosts = []
+ lines = result.replace('\r','').split('\n')
+ current_host = None
+ for line in lines:
+ line = line.lstrip()
+ if line == '':
+ continue
+ if line.startswith(fqdn):
+ rest = line[len(fqdn):]
+ if rest.find('service') > -1:
+ current_host = {}
+ elif isinstance(current_host, dict):
+ res = line.strip().split('=')
+ if len(res) != 2:
+ if len(current_host) == 4:
+ hosts.append(current_host)
+ current_host = None
+ continue
+ prop_type = res[0].strip()
+ prop_value = res[1].strip()
+ if prop_type.find('prio') > -1:
+ try:
+ current_host['prio'] = int(prop_value)
+ except ValueError:
+ continue
+ elif prop_type.find('weight') > -1:
+ try:
+ current_host['weight'] = int(prop_value)
+ except ValueError:
+ continue
+ elif prop_type.find('port') > -1:
+ try:
+ current_host['port'] = int(prop_value)
+ except ValueError:
+ continue
+ elif prop_type.find('host') > -1:
+ # strip '.' at the end of hostname
+ if prop_value[-1] == '.':
+ prop_value = prop_value[:-1]
+ current_host['host'] = prop_value
+ if len(current_host) == 4:
+ hosts.append(current_host)
+ current_host = None
+ return hosts
+
+ def _parse_srv_result_posix(self, fqdn, result):
+ # typical output of bind-tools nslookup command:
+ # _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org.
+ if not result:
+ return []
+ ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name
+ hosts = []
+ lines = result.split('\n')
+ for line in lines:
+ if line == '':
+ continue
+ domain = None
+ if line.startswith(fqdn):
+ domain = fqdn # For nslookup 9.5
+ elif helpers.decode_string(line).startswith(ufqdn):
+ line = helpers.decode_string(line)
+ domain = ufqdn # For nslookup 9.6
+ if domain:
+ rest = line[len(domain):].split('=')
+ if len(rest) != 2:
+ continue
+ answer_type, props_str = rest
+ if answer_type.strip() != 'service':
+ continue
+ props = props_str.strip().split(' ')
+ if len(props) < 4:
+ continue
+ prio, weight, port, host = props[-4:]
+ if host[-1] == '.':
+ host = host[:-1]
+ try:
+ prio = int(prio)
+ weight = int(weight)
+ port = int(port)
+ except ValueError:
+ continue
+ hosts.append({'host': host, 'port': port, 'weight': weight,
+ 'prio': prio})
+ return hosts
+
+ def _on_ready(self, host, type, result):
+ # nslookup finished, parse the result and call the handlers
+ result_list = self.parse_srv_result(host, result)
+ CommonResolver._on_ready(self, host, type, result_list)
+
+ def start_resolve(self, host, type):
+ """
+ Spawn new nslookup process and start waiting for results
+ """
+ ns = NsLookup(self._on_ready, host, type)
+ ns.set_idlequeue(self.idlequeue)
+ ns.commandtimeout = 20
+ ns.start()
class NsLookup(IdleCommand):
- def __init__(self, on_result, host='_xmpp-client', type='srv'):
- IdleCommand.__init__(self, on_result)
- self.commandtimeout = 10
- self.host = host.lower()
- self.type = type.lower()
- if not host_pattern.match(self.host):
- # invalid host name
- log.error('Invalid host: %s' % self.host)
- self.canexecute = False
- return
- if not ns_type_pattern.match(self.type):
- log.error('Invalid querytype: %s' % self.type)
- self.canexecute = False
- return
-
- def _compose_command_args(self):
- return ['nslookup', '-type=' + self.type , self.host]
-
- def _return_result(self):
- if self.result_handler:
- self.result_handler(self.host, self.type, self.result)
- self.result_handler = None
+ def __init__(self, on_result, host='_xmpp-client', type='srv'):
+ IdleCommand.__init__(self, on_result)
+ self.commandtimeout = 10
+ self.host = host.lower()
+ self.type = type.lower()
+ if not host_pattern.match(self.host):
+ # invalid host name
+ log.error('Invalid host: %s' % self.host)
+ self.canexecute = False
+ return
+ if not ns_type_pattern.match(self.type):
+ log.error('Invalid querytype: %s' % self.type)
+ self.canexecute = False
+ return
+
+ def _compose_command_args(self):
+ return ['nslookup', '-type=' + self.type , self.host]
+
+ def _return_result(self):
+ if self.result_handler:
+ self.result_handler(self.host, self.type, self.result)
+ self.result_handler = None
# below lines is on how to use API and assist in testing
if __name__ == '__main__':
- import gobject
- import gtk
- from xmpp import idlequeue
-
- idlequeue = idlequeue.get_idlequeue()
- resolver = get_resolver(idlequeue)
-
- def clicked(widget):
- global resolver
- host = text_view.get_text()
- def on_result(host, result_array):
- print 'Result:\n' + repr(result_array)
- resolver.resolve(host, on_result)
- win = gtk.Window()
- win.set_border_width(6)
- text_view = gtk.Entry()
- text_view.set_text('_xmpp-client._tcp.jabber.org')
- hbox = gtk.HBox()
- hbox.set_spacing(3)
- but = gtk.Button(' Lookup SRV ')
- hbox.pack_start(text_view, 5)
- hbox.pack_start(but, 0)
- but.connect('clicked', clicked)
- win.add(hbox)
- win.show_all()
- gobject.timeout_add(200, idlequeue.process)
- if USE_LIBASYNCNS:
- gobject.timeout_add(200, resolver.process)
- gtk.main()
-
-# vim: se ts=3:
+ import gobject
+ import gtk
+ from xmpp import idlequeue
+
+ idlequeue = idlequeue.get_idlequeue()
+ resolver = get_resolver(idlequeue)
+
+ def clicked(widget):
+ global resolver
+ host = text_view.get_text()
+ def on_result(host, result_array):
+ print 'Result:\n' + repr(result_array)
+ resolver.resolve(host, on_result)
+ win = gtk.Window()
+ win.set_border_width(6)
+ text_view = gtk.Entry()
+ text_view.set_text('_xmpp-client._tcp.jabber.org')
+ hbox = gtk.HBox()
+ hbox.set_spacing(3)
+ but = gtk.Button(' Lookup SRV ')
+ hbox.pack_start(text_view, 5)
+ hbox.pack_start(but, 0)
+ but.connect('clicked', clicked)
+ win.add(hbox)
+ win.show_all()
+ gobject.timeout_add(200, idlequeue.process)
+ if USE_LIBASYNCNS:
+ gobject.timeout_add(200, resolver.process)
+ gtk.main()
diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py
index 7211e8a1d..d912ce279 100644
--- a/src/common/rst_xhtml_generator.py
+++ b/src/common/rst_xhtml_generator.py
@@ -22,132 +22,132 @@
##
try:
- from docutils import io
- from docutils.core import Publisher
- from docutils.parsers.rst import roles
- from docutils import nodes,utils
- from docutils.parsers.rst.roles import set_classes
+ from docutils import io
+ from docutils.core import Publisher
+ from docutils.parsers.rst import roles
+ from docutils import nodes,utils
+ from docutils.parsers.rst.roles import set_classes
except ImportError:
- print "Requires docutils 0.4 for set_classes to be available"
- def create_xhtml(text):
- return None
+ print "Requires docutils 0.4 for set_classes to be available"
+ def create_xhtml(text):
+ return None
else:
- def pos_int_validator(text):
- """
- Validates that text can be evaluated as a positive integer
- """
- result = int(text)
- if result < 0:
- raise ValueError("Error: value '%(text)s' "
- "must be a positive integer")
- return result
-
- def generate_uri_role( role_name, aliases, anchor_text, base_url,
- interpret_url, validator):
- """
- Create and register a uri based "interpreted role"
-
- Those are similar to the RFC, and PEP ones, and take
- role_name:
- name that will be registered
- aliases:
- list of alternate names
- anchor_text:
- text that will be used, together with the role
- base_url:
- base url for the link
- interpret_url:
- this, modulo the validated text, will be added to it
- validator:
- should return the validated text, or raise ValueError
- """
- def uri_reference_role(role, rawtext, text, lineno, inliner,
- options={}, content=[]):
- try:
- valid_text = validator(text)
- except ValueError, e:
- msg = inliner.reporter.error( e.message % dict(text=text), line=lineno)
- prb = inliner.problematic(rawtext, rawtext, msg)
- return [prb], [msg]
- ref = base_url + interpret_url % valid_text
- set_classes(options)
- node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref,
- **options)
- return [node], []
-
- uri_reference_role.__doc__ = """Role to make handy references to URIs.
-
- Use as :%(role_name)s:`71` (or any of %(aliases)s).
- It will use %(base_url)s+%(interpret_url)s
- validator should throw a ValueError, containing optionally
- a %%(text)s format, if the interpreted text is not valid.
- """ % locals()
- roles.register_canonical_role(role_name, uri_reference_role)
- from docutils.parsers.rst.languages import en
- en.roles[role_name] = role_name
- for alias in aliases:
- en.roles[alias] = role_name
-
- generate_uri_role('xep-reference', ('jep', 'xep'),
- 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html',
- pos_int_validator)
- generate_uri_role('gajim-ticket-reference', ('ticket','gtrack'),
- 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d',
- pos_int_validator)
-
- class HTMLGenerator:
- """
- Really simple HTMLGenerator starting from publish_parts
-
- It reuses the docutils.core.Publisher class, which means it is *not*
- threadsafe.
- """
- def __init__(self, settings_spec=None,
- settings_overrides=dict(report_level=5, halt_level=5),
- config_section='general'):
- self.pub = Publisher(reader=None, parser=None, writer=None,
- settings=None,
- source_class=io.StringInput,
- destination_class=io.StringOutput)
- self.pub.set_components(reader_name='standalone',
- parser_name='restructuredtext',
- writer_name='html')
- # hack: JEP-0071 does not allow HTML char entities, so we hack our way
- # out of it.
- # &mdash; == u"\u2014"
- # a setting to only emit charater entities in the writer would be nice
- # FIXME: several &nbsp; are emitted, and they are explicitly forbidden
- # in the JEP
- # &nbsp; == u"\u00a0"
- self.pub.writer.translator_class.attribution_formats['dash'] = (
- u'\u2014', '')
- self.pub.process_programmatic_settings(settings_spec,
- settings_overrides,
- config_section)
-
-
- def create_xhtml(self, text, destination=None, destination_path=None,
- enable_exit_status=None):
- """
- Create xhtml for a fragment of IM dialog. We can use the source_name
- to store info about the message
- """
- self.pub.set_source(text, None)
- self.pub.set_destination(destination, destination_path)
- output = self.pub.publish(enable_exit_status=enable_exit_status)
- # kludge until we can get docutils to stop generating (rare) &nbsp;
- # entities
- return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(
- '&nbsp;'))
-
- Generator = HTMLGenerator()
-
- def create_xhtml(text):
- return Generator.create_xhtml(text)
+ def pos_int_validator(text):
+ """
+ Validates that text can be evaluated as a positive integer
+ """
+ result = int(text)
+ if result < 0:
+ raise ValueError("Error: value '%(text)s' "
+ "must be a positive integer")
+ return result
+
+ def generate_uri_role( role_name, aliases, anchor_text, base_url,
+ interpret_url, validator):
+ """
+ Create and register a uri based "interpreted role"
+
+ Those are similar to the RFC, and PEP ones, and take
+ role_name:
+ name that will be registered
+ aliases:
+ list of alternate names
+ anchor_text:
+ text that will be used, together with the role
+ base_url:
+ base url for the link
+ interpret_url:
+ this, modulo the validated text, will be added to it
+ validator:
+ should return the validated text, or raise ValueError
+ """
+ def uri_reference_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ try:
+ valid_text = validator(text)
+ except ValueError, e:
+ msg = inliner.reporter.error( e.message % dict(text=text), line=lineno)
+ prb = inliner.problematic(rawtext, rawtext, msg)
+ return [prb], [msg]
+ ref = base_url + interpret_url % valid_text
+ set_classes(options)
+ node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref,
+ **options)
+ return [node], []
+
+ uri_reference_role.__doc__ = """Role to make handy references to URIs.
+
+ Use as :%(role_name)s:`71` (or any of %(aliases)s).
+ It will use %(base_url)s+%(interpret_url)s
+ validator should throw a ValueError, containing optionally
+ a %%(text)s format, if the interpreted text is not valid.
+ """ % locals()
+ roles.register_canonical_role(role_name, uri_reference_role)
+ from docutils.parsers.rst.languages import en
+ en.roles[role_name] = role_name
+ for alias in aliases:
+ en.roles[alias] = role_name
+
+ generate_uri_role('xep-reference', ('jep', 'xep'),
+ 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html',
+ pos_int_validator)
+ generate_uri_role('gajim-ticket-reference', ('ticket','gtrack'),
+ 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d',
+ pos_int_validator)
+
+ class HTMLGenerator:
+ """
+ Really simple HTMLGenerator starting from publish_parts
+
+ It reuses the docutils.core.Publisher class, which means it is *not*
+ threadsafe.
+ """
+ def __init__(self, settings_spec=None,
+ settings_overrides=dict(report_level=5, halt_level=5),
+ config_section='general'):
+ self.pub = Publisher(reader=None, parser=None, writer=None,
+ settings=None,
+ source_class=io.StringInput,
+ destination_class=io.StringOutput)
+ self.pub.set_components(reader_name='standalone',
+ parser_name='restructuredtext',
+ writer_name='html')
+ # hack: JEP-0071 does not allow HTML char entities, so we hack our way
+ # out of it.
+ # &mdash; == u"\u2014"
+ # a setting to only emit charater entities in the writer would be nice
+ # FIXME: several &nbsp; are emitted, and they are explicitly forbidden
+ # in the JEP
+ # &nbsp; == u"\u00a0"
+ self.pub.writer.translator_class.attribution_formats['dash'] = (
+ u'\u2014', '')
+ self.pub.process_programmatic_settings(settings_spec,
+ settings_overrides,
+ config_section)
+
+
+ def create_xhtml(self, text, destination=None, destination_path=None,
+ enable_exit_status=None):
+ """
+ Create xhtml for a fragment of IM dialog. We can use the source_name
+ to store info about the message
+ """
+ self.pub.set_source(text, None)
+ self.pub.set_destination(destination, destination_path)
+ output = self.pub.publish(enable_exit_status=enable_exit_status)
+ # kludge until we can get docutils to stop generating (rare) &nbsp;
+ # entities
+ return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(
+ '&nbsp;'))
+
+ Generator = HTMLGenerator()
+
+ def create_xhtml(text):
+ return Generator.create_xhtml(text)
if __name__ == '__main__':
- print "test 1\n", Generator.create_xhtml("""
+ print "test 1\n", Generator.create_xhtml("""
test::
>>> print 1
@@ -158,11 +158,9 @@ test::
this `` should trigger`` should trigger the &nbsp; problem.
""")
- print "test 2\n", Generator.create_xhtml("""
+ print "test 2\n", Generator.create_xhtml("""
*test1
test2_
""")
- print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""")
-
-# vim: se ts=3:
+ print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""")
diff --git a/src/common/sleepy.py b/src/common/sleepy.py
index 0116faefb..7f115da21 100644
--- a/src/common/sleepy.py
+++ b/src/common/sleepy.py
@@ -28,119 +28,117 @@ import os, sys
STATE_UNKNOWN = 'OS probably not supported'
STATE_XA = 'extended away'
STATE_AWAY = 'away'
-STATE_AWAKE = 'awake'
+STATE_AWAKE = 'awake'
SUPPORTED = True
try:
- if os.name == 'nt':
- import ctypes
+ if os.name == 'nt':
+ import ctypes
- GetTickCount = ctypes.windll.kernel32.GetTickCount
- GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
+ GetTickCount = ctypes.windll.kernel32.GetTickCount
+ GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
- class LASTINPUTINFO(ctypes.Structure):
- _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)]
+ class LASTINPUTINFO(ctypes.Structure):
+ _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)]
- lastInputInfo = LASTINPUTINFO()
- lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)
+ lastInputInfo = LASTINPUTINFO()
+ lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)
- # one or more of these may not be supported before XP.
- OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop
- CloseDesktop = ctypes.windll.user32.CloseDesktop
- SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW
- else: # unix
- from common import idle
+ # one or more of these may not be supported before XP.
+ OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop
+ CloseDesktop = ctypes.windll.user32.CloseDesktop
+ SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW
+ else: # unix
+ from common import idle
except Exception:
- gajim.log.debug('Unable to load idle module')
- SUPPORTED = False
+ gajim.log.debug('Unable to load idle module')
+ SUPPORTED = False
class SleepyWindows:
- def __init__(self, away_interval = 60, xa_interval = 120):
- self.away_interval = away_interval
- self.xa_interval = xa_interval
- self.state = STATE_AWAKE # assume we are awake
-
- def getIdleSec(self):
- GetLastInputInfo(ctypes.byref(lastInputInfo))
- idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000
- return idleDelta
-
- def poll(self):
- """
- Check to see if we should change state
- """
- if not SUPPORTED:
- return False
-
- # screen saver, in windows >= XP
- saver_runing = ctypes.c_int(0)
- # 0x72 is SPI_GETSCREENSAVERRUNNING
- if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \
- saver_runing.value:
- self.state = STATE_XA
- return True
-
- desk = OpenInputDesktop(0, False, 0)
- if not desk:
- # Screen locked
- self.state = STATE_XA
- return True
- CloseDesktop(desk)
-
- idleTime = self.getIdleSec()
-
- # xa is stronger than away so check for xa first
- if idleTime > self.xa_interval:
- self.state = STATE_XA
- elif idleTime > self.away_interval:
- self.state = STATE_AWAY
- else:
- self.state = STATE_AWAKE
- return True
-
- def getState(self):
- return self.state
-
- def setState(self, val):
- self.state = val
+ def __init__(self, away_interval = 60, xa_interval = 120):
+ self.away_interval = away_interval
+ self.xa_interval = xa_interval
+ self.state = STATE_AWAKE # assume we are awake
+
+ def getIdleSec(self):
+ GetLastInputInfo(ctypes.byref(lastInputInfo))
+ idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000
+ return idleDelta
+
+ def poll(self):
+ """
+ Check to see if we should change state
+ """
+ if not SUPPORTED:
+ return False
+
+ # screen saver, in windows >= XP
+ saver_runing = ctypes.c_int(0)
+ # 0x72 is SPI_GETSCREENSAVERRUNNING
+ if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \
+ saver_runing.value:
+ self.state = STATE_XA
+ return True
+
+ desk = OpenInputDesktop(0, False, 0)
+ if not desk:
+ # Screen locked
+ self.state = STATE_XA
+ return True
+ CloseDesktop(desk)
+
+ idleTime = self.getIdleSec()
+
+ # xa is stronger than away so check for xa first
+ if idleTime > self.xa_interval:
+ self.state = STATE_XA
+ elif idleTime > self.away_interval:
+ self.state = STATE_AWAY
+ else:
+ self.state = STATE_AWAKE
+ return True
+
+ def getState(self):
+ return self.state
+
+ def setState(self, val):
+ self.state = val
class SleepyUnix:
- def __init__(self, away_interval = 60, xa_interval = 120):
- global SUPPORTED
- self.away_interval = away_interval
- self.xa_interval = xa_interval
- self.state = STATE_AWAKE # assume we are awake
-
- def getIdleSec(self):
- return idle.getIdleSec()
-
- def poll(self):
- """
- Check to see if we should change state
- """
- if not SUPPORTED:
- return False
-
- idleTime = self.getIdleSec()
-
- # xa is stronger than away so check for xa first
- if idleTime > self.xa_interval:
- self.state = STATE_XA
- elif idleTime > self.away_interval:
- self.state = STATE_AWAY
- else:
- self.state = STATE_AWAKE
- return True
-
- def getState(self):
- return self.state
-
- def setState(self, val):
- self.state = val
+ def __init__(self, away_interval = 60, xa_interval = 120):
+ global SUPPORTED
+ self.away_interval = away_interval
+ self.xa_interval = xa_interval
+ self.state = STATE_AWAKE # assume we are awake
+
+ def getIdleSec(self):
+ return idle.getIdleSec()
+
+ def poll(self):
+ """
+ Check to see if we should change state
+ """
+ if not SUPPORTED:
+ return False
+
+ idleTime = self.getIdleSec()
+
+ # xa is stronger than away so check for xa first
+ if idleTime > self.xa_interval:
+ self.state = STATE_XA
+ elif idleTime > self.away_interval:
+ self.state = STATE_AWAY
+ else:
+ self.state = STATE_AWAKE
+ return True
+
+ def getState(self):
+ return self.state
+
+ def setState(self, val):
+ self.state = val
if os.name == 'nt':
- Sleepy = SleepyWindows
+ Sleepy = SleepyWindows
else:
- Sleepy = SleepyUnix
-
-# vim: se ts=3:
+ Sleepy = SleepyUnix
diff --git a/src/common/socks5.py b/src/common/socks5.py
index 59d7e4b5a..9d1fca1ce 100644
--- a/src/common/socks5.py
+++ b/src/common/socks5.py
@@ -53,1114 +53,1112 @@ READ_TIMEOUT = 180
SEND_TIMEOUT = 180
class SocksQueue:
- """
- Queue for all file requests objects
- """
-
- def __init__(self, idlequeue, complete_transfer_cb=None,
- progress_transfer_cb=None, error_cb=None):
- self.connected = 0
- self.readers = {}
- self.files_props = {}
- self.senders = {}
- self.idx = 1
- self.listener = None
- self.sha_handlers = {}
- # handle all io events in the global idle queue, instead of processing
- # each foo seconds
- self.idlequeue = idlequeue
- self.complete_transfer_cb = complete_transfer_cb
- self.progress_transfer_cb = progress_transfer_cb
- self.error_cb = error_cb
- self.on_success = None
- self.on_failure = None
-
- def start_listener(self, port, sha_str, sha_handler, sid):
- """
- Start waiting for incomming connections on (host, port) and do a socks5
- authentication using sid for generated SHA
- """
- self.sha_handlers[sha_str] = (sha_handler, sid)
- if self.listener is None:
- self.listener = Socks5Listener(self.idlequeue, port)
- self.listener.queue = self
- self.listener.bind()
- if self.listener.started is False:
- self.listener = None
- # We cannot bind port, call error callback and fail
- self.error_cb(_('Unable to bind to port %s.') % port,
- _('Maybe you have another running instance of Gajim. File '
- 'Transfer will be cancelled.'))
- return None
- self.connected += 1
- return self.listener
-
- def send_success_reply(self, file_props, streamhost):
- if 'streamhost-used' in file_props and \
- file_props['streamhost-used'] is True:
- if 'proxyhosts' in file_props:
- for proxy in file_props['proxyhosts']:
- if proxy == streamhost:
- self.on_success(streamhost)
- return 2
- return 0
- if 'streamhosts' in file_props:
- for host in file_props['streamhosts']:
- if streamhost['state'] == 1:
- return 0
- streamhost['state'] = 1
- self.on_success(streamhost)
- return 1
- return 0
-
- def connect_to_hosts(self, account, sid, on_success=None, on_failure=None):
- self.on_success = on_success
- self.on_failure = on_failure
- file_props = self.files_props[account][sid]
- file_props['failure_cb'] = on_failure
-
- # add streamhosts to the queue
- for streamhost in file_props['streamhosts']:
- receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props)
- self.add_receiver(account, receiver)
- streamhost['idx'] = receiver.queue_idx
-
- def _socket_connected(self, streamhost, file_props):
- """
- Called when there is a host connected to one of the senders's
- streamhosts. Stop othere attempts for connections
- """
- for host in file_props['streamhosts']:
- if host != streamhost and 'idx' in host:
- if host['state'] == 1:
- # remove current
- self.remove_receiver(streamhost['idx'])
- return
- # set state -2, meaning that this streamhost is stopped,
- # but it may be connectected later
- if host['state'] >= 0:
- self.remove_receiver(host['idx'])
- host['idx'] = -1
- host['state'] = -2
-
- def reconnect_receiver(self, receiver, streamhost):
- """
- Check the state of all streamhosts and if all has failed, then emit
- connection failure cb. If there are some which are still not connected
- try to establish connection to one of them
- """
- self.idlequeue.remove_timeout(receiver.fd)
- self.idlequeue.unplug_idle(receiver.fd)
- file_props = receiver.file_props
- streamhost['state'] = -1
- # boolean, indicates that there are hosts, which are not tested yet
- unused_hosts = False
- for host in file_props['streamhosts']:
- if 'idx' in host:
- if host['state'] >= 0:
- return
- elif host['state'] == -2:
- unused_hosts = True
- if unused_hosts:
- for host in file_props['streamhosts']:
- if host['state'] == -2:
- host['state'] = 0
- receiver = Socks5Receiver(self.idlequeue, host, host['sid'],
- file_props)
- self.add_receiver(receiver.account, receiver)
- host['idx'] = receiver.queue_idx
- # we still have chances to connect
- return
- if 'received-len' not in file_props or file_props['received-len'] == 0:
- # there are no other streamhosts and transfer hasn't started
- self._connection_refused(streamhost, file_props, receiver.queue_idx)
- else:
- # transfer stopped, it is most likely stopped from sender
- receiver.disconnect()
- file_props['error'] = -1
- self.process_result(-1, receiver)
-
- def _connection_refused(self, streamhost, file_props, idx):
- """
- Called when we loose connection during transfer
- """
- if file_props is None:
- return
- streamhost['state'] = -1
- self.remove_receiver(idx, False)
- if 'streamhosts' in file_props:
- for host in file_props['streamhosts']:
- if host['state'] != -1:
- return
- # failure_cb exists - this means that it has never been called
- if 'failure_cb' in file_props and file_props['failure_cb']:
- file_props['failure_cb'](streamhost['initiator'], streamhost['id'],
- file_props['sid'], code = 404)
- del(file_props['failure_cb'])
-
- def add_receiver(self, account, sock5_receiver):
- """
- Add new file request
- """
- self.readers[self.idx] = sock5_receiver
- sock5_receiver.queue_idx = self.idx
- sock5_receiver.queue = self
- sock5_receiver.account = account
- self.idx += 1
- result = sock5_receiver.connect()
- self.connected += 1
- if result is not None:
- result = sock5_receiver.main()
- self.process_result(result, sock5_receiver)
- return 1
- return None
-
- def get_file_from_sender(self, file_props, account):
- if file_props is None:
- return
- if 'hash' in file_props and file_props['hash'] in self.senders:
- sender = self.senders[file_props['hash']]
- sender.account = account
- result = self.get_file_contents(0)
- self.process_result(result, sender)
-
- def result_sha(self, sha_str, idx):
- if sha_str in self.sha_handlers:
- props = self.sha_handlers[sha_str]
- props[0](props[1], idx)
-
- def activate_proxy(self, idx):
- if idx not in self.readers:
- return
- reader = self.readers[idx]
- if reader.file_props['type'] != 's':
- return
- if reader.state != 5:
- return
- reader.state = 6
- if reader.connected:
- reader.file_props['error'] = 0
- reader.file_props['disconnect_cb'] = reader.disconnect
- reader.file_props['started'] = True
- reader.file_props['completed'] = False
- reader.file_props['paused'] = False
- reader.file_props['stalled'] = False
- reader.file_props['elapsed-time'] = 0
- reader.file_props['last-time'] = self.idlequeue.current_time()
- reader.file_props['received-len'] = 0
- reader.pauses = 0
- # start sending file to proxy
- self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT)
- self.idlequeue.plug_idle(reader, True, False)
- result = reader.write_next()
- self.process_result(result, reader)
-
- def send_file(self, file_props, account):
- if 'hash' in file_props and file_props['hash'] in self.senders:
- sender = self.senders[file_props['hash']]
- file_props['streamhost-used'] = True
- sender.account = account
- if file_props['type'] == 's':
- sender.file_props = file_props
- result = sender.send_file()
- self.process_result(result, sender)
- else:
- file_props['elapsed-time'] = 0
- file_props['last-time'] = self.idlequeue.current_time()
- file_props['received-len'] = 0
- sender.file_props = file_props
-
- def add_file_props(self, account, file_props):
- """
- File_prop to the dict of current file_props. It is identified by account
- name and sid
- """
- if file_props is None or ('sid' in file_props) is False:
- return
- _id = file_props['sid']
- if account not in self.files_props:
- self.files_props[account] = {}
- self.files_props[account][_id] = file_props
-
- def remove_file_props(self, account, sid):
- if account in self.files_props:
- fl_props = self.files_props[account]
- if sid in fl_props:
- del(fl_props[sid])
-
- if len(self.files_props) == 0:
- self.connected = 0
-
- def get_file_props(self, account, sid):
- """
- Get fil_prop by account name and session id
- """
- if account in self.files_props:
- fl_props = self.files_props[account]
- if sid in fl_props:
- return fl_props[sid]
- return None
-
- def on_connection_accepted(self, sock):
- sock_hash = sock.__hash__()
- if sock_hash not in self.senders:
- self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self,
- sock[0], sock[1][0], sock[1][1])
- self.connected += 1
-
- def process_result(self, result, actor):
- """
- Take appropriate actions upon the result:
- [ 0, - 1 ] complete/end transfer
- [ > 0 ] send progress message
- [ None ] do nothing
- """
- if result is None:
- return
- if result in (0, -1) and self.complete_transfer_cb is not None:
- account = actor.account
- if account is None and 'tt_account' in actor.file_props:
- account = actor.file_props['tt_account']
- self.complete_transfer_cb(account, actor.file_props)
- elif self.progress_transfer_cb is not None:
- self.progress_transfer_cb(actor.account, actor.file_props)
-
- def remove_receiver(self, idx, do_disconnect=True):
- """
- Remove reciver from the list and decrease the number of active
- connections with 1
- """
- if idx != -1:
- if idx in self.readers:
- reader = self.readers[idx]
- self.idlequeue.unplug_idle(reader.fd)
- self.idlequeue.remove_timeout(reader.fd)
- if do_disconnect:
- reader.disconnect()
- else:
- if reader.streamhost is not None:
- reader.streamhost['state'] = -1
- del(self.readers[idx])
-
- def remove_sender(self, idx, do_disconnect=True):
- """
- Remove sender from the list of senders and decrease the number of active
- connections with 1
- """
- if idx != -1:
- if idx in self.senders:
- if do_disconnect:
- self.senders[idx].disconnect()
- return
- else:
- del(self.senders[idx])
- if self.connected > 0:
- self.connected -= 1
- if len(self.senders) == 0 and self.listener is not None:
- self.listener.disconnect()
- self.listener = None
- self.connected -= 1
+ """
+ Queue for all file requests objects
+ """
+
+ def __init__(self, idlequeue, complete_transfer_cb=None,
+ progress_transfer_cb=None, error_cb=None):
+ self.connected = 0
+ self.readers = {}
+ self.files_props = {}
+ self.senders = {}
+ self.idx = 1
+ self.listener = None
+ self.sha_handlers = {}
+ # handle all io events in the global idle queue, instead of processing
+ # each foo seconds
+ self.idlequeue = idlequeue
+ self.complete_transfer_cb = complete_transfer_cb
+ self.progress_transfer_cb = progress_transfer_cb
+ self.error_cb = error_cb
+ self.on_success = None
+ self.on_failure = None
+
+ def start_listener(self, port, sha_str, sha_handler, sid):
+ """
+ Start waiting for incomming connections on (host, port) and do a socks5
+ authentication using sid for generated SHA
+ """
+ self.sha_handlers[sha_str] = (sha_handler, sid)
+ if self.listener is None:
+ self.listener = Socks5Listener(self.idlequeue, port)
+ self.listener.queue = self
+ self.listener.bind()
+ if self.listener.started is False:
+ self.listener = None
+ # We cannot bind port, call error callback and fail
+ self.error_cb(_('Unable to bind to port %s.') % port,
+ _('Maybe you have another running instance of Gajim. File '
+ 'Transfer will be cancelled.'))
+ return None
+ self.connected += 1
+ return self.listener
+
+ def send_success_reply(self, file_props, streamhost):
+ if 'streamhost-used' in file_props and \
+ file_props['streamhost-used'] is True:
+ if 'proxyhosts' in file_props:
+ for proxy in file_props['proxyhosts']:
+ if proxy == streamhost:
+ self.on_success(streamhost)
+ return 2
+ return 0
+ if 'streamhosts' in file_props:
+ for host in file_props['streamhosts']:
+ if streamhost['state'] == 1:
+ return 0
+ streamhost['state'] = 1
+ self.on_success(streamhost)
+ return 1
+ return 0
+
+ def connect_to_hosts(self, account, sid, on_success=None, on_failure=None):
+ self.on_success = on_success
+ self.on_failure = on_failure
+ file_props = self.files_props[account][sid]
+ file_props['failure_cb'] = on_failure
+
+ # add streamhosts to the queue
+ for streamhost in file_props['streamhosts']:
+ receiver = Socks5Receiver(self.idlequeue, streamhost, sid, file_props)
+ self.add_receiver(account, receiver)
+ streamhost['idx'] = receiver.queue_idx
+
+ def _socket_connected(self, streamhost, file_props):
+ """
+ Called when there is a host connected to one of the senders's
+ streamhosts. Stop othere attempts for connections
+ """
+ for host in file_props['streamhosts']:
+ if host != streamhost and 'idx' in host:
+ if host['state'] == 1:
+ # remove current
+ self.remove_receiver(streamhost['idx'])
+ return
+ # set state -2, meaning that this streamhost is stopped,
+ # but it may be connectected later
+ if host['state'] >= 0:
+ self.remove_receiver(host['idx'])
+ host['idx'] = -1
+ host['state'] = -2
+
+ def reconnect_receiver(self, receiver, streamhost):
+ """
+ Check the state of all streamhosts and if all has failed, then emit
+ connection failure cb. If there are some which are still not connected
+ try to establish connection to one of them
+ """
+ self.idlequeue.remove_timeout(receiver.fd)
+ self.idlequeue.unplug_idle(receiver.fd)
+ file_props = receiver.file_props
+ streamhost['state'] = -1
+ # boolean, indicates that there are hosts, which are not tested yet
+ unused_hosts = False
+ for host in file_props['streamhosts']:
+ if 'idx' in host:
+ if host['state'] >= 0:
+ return
+ elif host['state'] == -2:
+ unused_hosts = True
+ if unused_hosts:
+ for host in file_props['streamhosts']:
+ if host['state'] == -2:
+ host['state'] = 0
+ receiver = Socks5Receiver(self.idlequeue, host, host['sid'],
+ file_props)
+ self.add_receiver(receiver.account, receiver)
+ host['idx'] = receiver.queue_idx
+ # we still have chances to connect
+ return
+ if 'received-len' not in file_props or file_props['received-len'] == 0:
+ # there are no other streamhosts and transfer hasn't started
+ self._connection_refused(streamhost, file_props, receiver.queue_idx)
+ else:
+ # transfer stopped, it is most likely stopped from sender
+ receiver.disconnect()
+ file_props['error'] = -1
+ self.process_result(-1, receiver)
+
+ def _connection_refused(self, streamhost, file_props, idx):
+ """
+ Called when we loose connection during transfer
+ """
+ if file_props is None:
+ return
+ streamhost['state'] = -1
+ self.remove_receiver(idx, False)
+ if 'streamhosts' in file_props:
+ for host in file_props['streamhosts']:
+ if host['state'] != -1:
+ return
+ # failure_cb exists - this means that it has never been called
+ if 'failure_cb' in file_props and file_props['failure_cb']:
+ file_props['failure_cb'](streamhost['initiator'], streamhost['id'],
+ file_props['sid'], code = 404)
+ del(file_props['failure_cb'])
+
+ def add_receiver(self, account, sock5_receiver):
+ """
+ Add new file request
+ """
+ self.readers[self.idx] = sock5_receiver
+ sock5_receiver.queue_idx = self.idx
+ sock5_receiver.queue = self
+ sock5_receiver.account = account
+ self.idx += 1
+ result = sock5_receiver.connect()
+ self.connected += 1
+ if result is not None:
+ result = sock5_receiver.main()
+ self.process_result(result, sock5_receiver)
+ return 1
+ return None
+
+ def get_file_from_sender(self, file_props, account):
+ if file_props is None:
+ return
+ if 'hash' in file_props and file_props['hash'] in self.senders:
+ sender = self.senders[file_props['hash']]
+ sender.account = account
+ result = self.get_file_contents(0)
+ self.process_result(result, sender)
+
+ def result_sha(self, sha_str, idx):
+ if sha_str in self.sha_handlers:
+ props = self.sha_handlers[sha_str]
+ props[0](props[1], idx)
+
+ def activate_proxy(self, idx):
+ if idx not in self.readers:
+ return
+ reader = self.readers[idx]
+ if reader.file_props['type'] != 's':
+ return
+ if reader.state != 5:
+ return
+ reader.state = 6
+ if reader.connected:
+ reader.file_props['error'] = 0
+ reader.file_props['disconnect_cb'] = reader.disconnect
+ reader.file_props['started'] = True
+ reader.file_props['completed'] = False
+ reader.file_props['paused'] = False
+ reader.file_props['stalled'] = False
+ reader.file_props['elapsed-time'] = 0
+ reader.file_props['last-time'] = self.idlequeue.current_time()
+ reader.file_props['received-len'] = 0
+ reader.pauses = 0
+ # start sending file to proxy
+ self.idlequeue.set_read_timeout(reader.fd, STALLED_TIMEOUT)
+ self.idlequeue.plug_idle(reader, True, False)
+ result = reader.write_next()
+ self.process_result(result, reader)
+
+ def send_file(self, file_props, account):
+ if 'hash' in file_props and file_props['hash'] in self.senders:
+ sender = self.senders[file_props['hash']]
+ file_props['streamhost-used'] = True
+ sender.account = account
+ if file_props['type'] == 's':
+ sender.file_props = file_props
+ result = sender.send_file()
+ self.process_result(result, sender)
+ else:
+ file_props['elapsed-time'] = 0
+ file_props['last-time'] = self.idlequeue.current_time()
+ file_props['received-len'] = 0
+ sender.file_props = file_props
+
+ def add_file_props(self, account, file_props):
+ """
+ File_prop to the dict of current file_props. It is identified by account
+ name and sid
+ """
+ if file_props is None or ('sid' in file_props) is False:
+ return
+ _id = file_props['sid']
+ if account not in self.files_props:
+ self.files_props[account] = {}
+ self.files_props[account][_id] = file_props
+
+ def remove_file_props(self, account, sid):
+ if account in self.files_props:
+ fl_props = self.files_props[account]
+ if sid in fl_props:
+ del(fl_props[sid])
+
+ if len(self.files_props) == 0:
+ self.connected = 0
+
+ def get_file_props(self, account, sid):
+ """
+ Get fil_prop by account name and session id
+ """
+ if account in self.files_props:
+ fl_props = self.files_props[account]
+ if sid in fl_props:
+ return fl_props[sid]
+ return None
+
+ def on_connection_accepted(self, sock):
+ sock_hash = sock.__hash__()
+ if sock_hash not in self.senders:
+ self.senders[sock_hash] = Socks5Sender(self.idlequeue, sock_hash, self,
+ sock[0], sock[1][0], sock[1][1])
+ self.connected += 1
+
+ def process_result(self, result, actor):
+ """
+ Take appropriate actions upon the result:
+ [ 0, - 1 ] complete/end transfer
+ [ > 0 ] send progress message
+ [ None ] do nothing
+ """
+ if result is None:
+ return
+ if result in (0, -1) and self.complete_transfer_cb is not None:
+ account = actor.account
+ if account is None and 'tt_account' in actor.file_props:
+ account = actor.file_props['tt_account']
+ self.complete_transfer_cb(account, actor.file_props)
+ elif self.progress_transfer_cb is not None:
+ self.progress_transfer_cb(actor.account, actor.file_props)
+
+ def remove_receiver(self, idx, do_disconnect=True):
+ """
+ Remove reciver from the list and decrease the number of active
+ connections with 1
+ """
+ if idx != -1:
+ if idx in self.readers:
+ reader = self.readers[idx]
+ self.idlequeue.unplug_idle(reader.fd)
+ self.idlequeue.remove_timeout(reader.fd)
+ if do_disconnect:
+ reader.disconnect()
+ else:
+ if reader.streamhost is not None:
+ reader.streamhost['state'] = -1
+ del(self.readers[idx])
+
+ def remove_sender(self, idx, do_disconnect=True):
+ """
+ Remove sender from the list of senders and decrease the number of active
+ connections with 1
+ """
+ if idx != -1:
+ if idx in self.senders:
+ if do_disconnect:
+ self.senders[idx].disconnect()
+ return
+ else:
+ del(self.senders[idx])
+ if self.connected > 0:
+ self.connected -= 1
+ if len(self.senders) == 0 and self.listener is not None:
+ self.listener.disconnect()
+ self.listener = None
+ self.connected -= 1
class Socks5:
- def __init__(self, idlequeue, host, port, initiator, target, sid):
- if host is not None:
- try:
- self.host = host
- self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- except socket.gaierror:
- self.ais = None
- self.idlequeue = idlequeue
- self.fd = -1
- self.port = port
- self.initiator = initiator
- self.target = target
- self.sid = sid
- self._sock = None
- self.account = None
- self.state = 0 # not connected
- self.pauses = 0
- self.size = 0
- self.remaining_buff = ''
- self.file = None
-
- def open_file_for_reading(self):
- if self.file is None:
- try:
- self.file = open(self.file_props['file-name'],'rb')
- if 'offset' in self.file_props and self.file_props['offset']:
- self.size = self.file_props['offset']
- self.file.seek(self.size)
- self.file_props['received-len'] = self.size
- except IOError, e:
- self.close_file()
- raise IOError, e
-
- def close_file(self):
- if self.file:
- if not self.file.closed:
- try:
- self.file.close()
- except Exception:
- pass
- self.file = None
-
- def get_fd(self):
- """
- Test if file is already open and return its fd, or just open the file and
- return the fd
- """
- if 'fd' in self.file_props:
- fd = self.file_props['fd']
- else:
- offset = 0
- opt = 'wb'
- if 'offset' in self.file_props and self.file_props['offset']:
- offset = self.file_props['offset']
- opt = 'ab'
- fd = open(self.file_props['file-name'], opt)
- self.file_props['fd'] = fd
- self.file_props['elapsed-time'] = 0
- self.file_props['last-time'] = self.idlequeue.current_time()
- self.file_props['received-len'] = offset
- return fd
-
- def rem_fd(self, fd):
- if 'fd' in self.file_props:
- del(self.file_props['fd'])
- try:
- fd.close()
- except Exception:
- pass
-
- def receive(self):
- """
- Read small chunks of data. Call owner's disconnected() method if
- appropriate
- """
- received = ''
- try:
- add = self._recv(64)
- except Exception:
- add = ''
- received += add
- if len(add) == 0:
- self.disconnect()
- return add
-
- def send_raw(self,raw_data):
- """
- Write raw outgoing data
- """
- try:
- self._send(raw_data)
- except Exception:
- self.disconnect()
- return len(raw_data)
-
- def write_next(self):
- if self.remaining_buff != '':
- buff = self.remaining_buff
- self.remaining_buff = ''
- else:
- try:
- self.open_file_for_reading()
- except IOError, e:
- self.state = 8 # end connection
- self.disconnect()
- self.file_props['error'] = -7 # unable to read from file
- return -1
- buff = self.file.read(MAX_BUFF_LEN)
- if len(buff) > 0:
- lenn = 0
- try:
- lenn = self._send(buff)
- except Exception, e:
- if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK):
- # peer stopped reading
- self.state = 8 # end connection
- self.disconnect()
- self.file_props['error'] = -1
- return -1
- self.size += lenn
- current_time = self.idlequeue.current_time()
- self.file_props['elapsed-time'] += current_time - \
- self.file_props['last-time']
- self.file_props['last-time'] = current_time
- self.file_props['received-len'] = self.size
- if self.size >= int(self.file_props['size']):
- self.state = 8 # end connection
- self.file_props['error'] = 0
- self.disconnect()
- return -1
- if lenn != len(buff):
- self.remaining_buff = buff[lenn:]
- else:
- self.remaining_buff = ''
- self.state = 7 # continue to write in the socket
- if lenn == 0:
- return None
- self.file_props['stalled'] = False
- return lenn
- else:
- self.state = 8 # end connection
- self.disconnect()
- return -1
-
- def get_file_contents(self, timeout):
- """
- Read file contents from socket and write them to file
- """
- if self.file_props is None or ('file-name' in self.file_props) is False:
- self.file_props['error'] = -2
- return None
- fd = None
- if self.remaining_buff != '':
- try:
- fd = self.get_fd()
- except IOError, e:
- self.disconnect(False)
- self.file_props['error'] = -6 # file system error
- return 0
- fd.write(self.remaining_buff)
- lenn = len(self.remaining_buff)
- current_time = self.idlequeue.current_time()
- self.file_props['elapsed-time'] += current_time - \
- self.file_props['last-time']
- self.file_props['last-time'] = current_time
- self.file_props['received-len'] += lenn
- self.remaining_buff = ''
- if self.file_props['received-len'] == int(self.file_props['size']):
- self.rem_fd(fd)
- self.disconnect()
- self.file_props['error'] = 0
- self.file_props['completed'] = True
- return 0
- else:
- try:
- fd = self.get_fd()
- except IOError, e:
- self.disconnect(False)
- self.file_props['error'] = -6 # file system error
- return 0
- try:
- buff = self._recv(MAX_BUFF_LEN)
- except Exception:
- buff = ''
- current_time = self.idlequeue.current_time()
- self.file_props['elapsed-time'] += current_time - \
- self.file_props['last-time']
- self.file_props['last-time'] = current_time
- self.file_props['received-len'] += len(buff)
- if len(buff) == 0:
- # Transfer stopped somehow:
- # reset, paused or network error
- self.rem_fd(fd)
- self.disconnect(False)
- self.file_props['error'] = -1
- return 0
- try:
- fd.write(buff)
- except IOError, e:
- self.rem_fd(fd)
- self.disconnect(False)
- self.file_props['error'] = -6 # file system error
- return 0
- if self.file_props['received-len'] >= int(self.file_props['size']):
- # transfer completed
- self.rem_fd(fd)
- self.disconnect()
- self.file_props['error'] = 0
- self.file_props['completed'] = True
- return 0
- # return number of read bytes. It can be used in progressbar
- if fd is not None:
- self.file_props['stalled'] = False
- if fd is None and self.file_props['stalled'] is False:
- return None
- if 'received-len' in self.file_props:
- if self.file_props['received-len'] != 0:
- return self.file_props['received-len']
- return None
-
- def disconnect(self):
- """
- Close open descriptors and remover socket descr. from idleque
- """
- # be sure that we don't leave open file
- self.close_file()
- self.idlequeue.remove_timeout(self.fd)
- self.idlequeue.unplug_idle(self.fd)
- try:
- self._sock.shutdown(socket.SHUT_RDWR)
- self._sock.close()
- except Exception:
- # socket is already closed
- pass
- self.connected = False
- self.fd = -1
- self.state = -1
-
- def _get_auth_buff(self):
- """
- Message, that we support 1 one auth mechanism: the 'no auth' mechanism
- """
- return struct.pack('!BBB', 0x05, 0x01, 0x00)
-
- def _parse_auth_buff(self, buff):
- """
- Parse the initial message and create a list of auth mechanisms
- """
- auth_mechanisms = []
- try:
- num_auth = struct.unpack('!xB', buff[:2])[0]
- for i in xrange(num_auth):
- mechanism, = struct.unpack('!B', buff[1 + i])
- auth_mechanisms.append(mechanism)
- except Exception:
- return None
- return auth_mechanisms
-
- def _get_auth_response(self):
- """
- Socks version(5), number of extra auth methods (we send 0x00 - no auth)
- """
- return struct.pack('!BB', 0x05, 0x00)
-
- def _get_connect_buff(self):
- ''' Connect request by domain name '''
- buff = struct.pack('!BBBBB%dsBB' % len(self.host),
- 0x05, 0x01, 0x00, 0x03, len(self.host), self.host,
- self.port >> 8, self.port & 0xff)
- return buff
-
- def _get_request_buff(self, msg, command = 0x01):
- """
- Connect request by domain name, sid sha, instead of domain name (jep
- 0096)
- """
- buff = struct.pack('!BBBBB%dsBB' % len(msg),
- 0x05, command, 0x00, 0x03, len(msg), msg, 0, 0)
- return buff
-
- def _parse_request_buff(self, buff):
- try: # don't trust on what comes from the outside
- req_type, host_type, = struct.unpack('!xBxB', buff[:4])
- if host_type == 0x01:
- host_arr = struct.unpack('!iiii', buff[4:8])
- host, = '.'.join(str(s) for s in host_arr)
- host_len = len(host)
- elif host_type == 0x03:
- host_len, = struct.unpack('!B' , buff[4])
- host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len])
- portlen = len(buff[host_len + 5:])
- if portlen == 1:
- port, = struct.unpack('!B', buff[host_len + 5])
- elif portlen == 2:
- port, = struct.unpack('!H', buff[host_len + 5:])
- # file data, comes with auth message (Gaim bug)
- else:
- port, = struct.unpack('!H', buff[host_len + 5: host_len + 7])
- self.remaining_buff = buff[host_len + 7:]
- except Exception:
- return (None, None, None)
- return (req_type, host, port)
-
- def read_connect(self):
- """
- Connect response: version, auth method
- """
- buff = self._recv()
- try:
- version, method = struct.unpack('!BB', buff)
- except Exception:
- version, method = None, None
- if version != 0x05 or method == 0xff:
- self.disconnect()
-
- def continue_paused_transfer(self):
- if self.state < 5:
- return
- if self.file_props['type'] == 'r':
- self.idlequeue.plug_idle(self, False, True)
- else:
- self.idlequeue.plug_idle(self, True, False)
-
- def _get_sha1_auth(self):
- """
- Get sha of sid + Initiator jid + Target jid
- """
- if 'is_a_proxy' in self.file_props:
- del(self.file_props['is_a_proxy'])
- return hashlib.sha1('%s%s%s' % (self.sid,
- self.file_props['proxy_sender'],
- self.file_props['proxy_receiver'])).hexdigest()
- return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\
- hexdigest()
+ def __init__(self, idlequeue, host, port, initiator, target, sid):
+ if host is not None:
+ try:
+ self.host = host
+ self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM)
+ except socket.gaierror:
+ self.ais = None
+ self.idlequeue = idlequeue
+ self.fd = -1
+ self.port = port
+ self.initiator = initiator
+ self.target = target
+ self.sid = sid
+ self._sock = None
+ self.account = None
+ self.state = 0 # not connected
+ self.pauses = 0
+ self.size = 0
+ self.remaining_buff = ''
+ self.file = None
+
+ def open_file_for_reading(self):
+ if self.file is None:
+ try:
+ self.file = open(self.file_props['file-name'],'rb')
+ if 'offset' in self.file_props and self.file_props['offset']:
+ self.size = self.file_props['offset']
+ self.file.seek(self.size)
+ self.file_props['received-len'] = self.size
+ except IOError, e:
+ self.close_file()
+ raise IOError, e
+
+ def close_file(self):
+ if self.file:
+ if not self.file.closed:
+ try:
+ self.file.close()
+ except Exception:
+ pass
+ self.file = None
+
+ def get_fd(self):
+ """
+ Test if file is already open and return its fd, or just open the file and
+ return the fd
+ """
+ if 'fd' in self.file_props:
+ fd = self.file_props['fd']
+ else:
+ offset = 0
+ opt = 'wb'
+ if 'offset' in self.file_props and self.file_props['offset']:
+ offset = self.file_props['offset']
+ opt = 'ab'
+ fd = open(self.file_props['file-name'], opt)
+ self.file_props['fd'] = fd
+ self.file_props['elapsed-time'] = 0
+ self.file_props['last-time'] = self.idlequeue.current_time()
+ self.file_props['received-len'] = offset
+ return fd
+
+ def rem_fd(self, fd):
+ if 'fd' in self.file_props:
+ del(self.file_props['fd'])
+ try:
+ fd.close()
+ except Exception:
+ pass
+
+ def receive(self):
+ """
+ Read small chunks of data. Call owner's disconnected() method if
+ appropriate
+ """
+ received = ''
+ try:
+ add = self._recv(64)
+ except Exception:
+ add = ''
+ received += add
+ if len(add) == 0:
+ self.disconnect()
+ return add
+
+ def send_raw(self,raw_data):
+ """
+ Write raw outgoing data
+ """
+ try:
+ self._send(raw_data)
+ except Exception:
+ self.disconnect()
+ return len(raw_data)
+
+ def write_next(self):
+ if self.remaining_buff != '':
+ buff = self.remaining_buff
+ self.remaining_buff = ''
+ else:
+ try:
+ self.open_file_for_reading()
+ except IOError, e:
+ self.state = 8 # end connection
+ self.disconnect()
+ self.file_props['error'] = -7 # unable to read from file
+ return -1
+ buff = self.file.read(MAX_BUFF_LEN)
+ if len(buff) > 0:
+ lenn = 0
+ try:
+ lenn = self._send(buff)
+ except Exception, e:
+ if e.args[0] not in (EINTR, ENOBUFS, EWOULDBLOCK):
+ # peer stopped reading
+ self.state = 8 # end connection
+ self.disconnect()
+ self.file_props['error'] = -1
+ return -1
+ self.size += lenn
+ current_time = self.idlequeue.current_time()
+ self.file_props['elapsed-time'] += current_time - \
+ self.file_props['last-time']
+ self.file_props['last-time'] = current_time
+ self.file_props['received-len'] = self.size
+ if self.size >= int(self.file_props['size']):
+ self.state = 8 # end connection
+ self.file_props['error'] = 0
+ self.disconnect()
+ return -1
+ if lenn != len(buff):
+ self.remaining_buff = buff[lenn:]
+ else:
+ self.remaining_buff = ''
+ self.state = 7 # continue to write in the socket
+ if lenn == 0:
+ return None
+ self.file_props['stalled'] = False
+ return lenn
+ else:
+ self.state = 8 # end connection
+ self.disconnect()
+ return -1
+
+ def get_file_contents(self, timeout):
+ """
+ Read file contents from socket and write them to file
+ """
+ if self.file_props is None or ('file-name' in self.file_props) is False:
+ self.file_props['error'] = -2
+ return None
+ fd = None
+ if self.remaining_buff != '':
+ try:
+ fd = self.get_fd()
+ except IOError, e:
+ self.disconnect(False)
+ self.file_props['error'] = -6 # file system error
+ return 0
+ fd.write(self.remaining_buff)
+ lenn = len(self.remaining_buff)
+ current_time = self.idlequeue.current_time()
+ self.file_props['elapsed-time'] += current_time - \
+ self.file_props['last-time']
+ self.file_props['last-time'] = current_time
+ self.file_props['received-len'] += lenn
+ self.remaining_buff = ''
+ if self.file_props['received-len'] == int(self.file_props['size']):
+ self.rem_fd(fd)
+ self.disconnect()
+ self.file_props['error'] = 0
+ self.file_props['completed'] = True
+ return 0
+ else:
+ try:
+ fd = self.get_fd()
+ except IOError, e:
+ self.disconnect(False)
+ self.file_props['error'] = -6 # file system error
+ return 0
+ try:
+ buff = self._recv(MAX_BUFF_LEN)
+ except Exception:
+ buff = ''
+ current_time = self.idlequeue.current_time()
+ self.file_props['elapsed-time'] += current_time - \
+ self.file_props['last-time']
+ self.file_props['last-time'] = current_time
+ self.file_props['received-len'] += len(buff)
+ if len(buff) == 0:
+ # Transfer stopped somehow:
+ # reset, paused or network error
+ self.rem_fd(fd)
+ self.disconnect(False)
+ self.file_props['error'] = -1
+ return 0
+ try:
+ fd.write(buff)
+ except IOError, e:
+ self.rem_fd(fd)
+ self.disconnect(False)
+ self.file_props['error'] = -6 # file system error
+ return 0
+ if self.file_props['received-len'] >= int(self.file_props['size']):
+ # transfer completed
+ self.rem_fd(fd)
+ self.disconnect()
+ self.file_props['error'] = 0
+ self.file_props['completed'] = True
+ return 0
+ # return number of read bytes. It can be used in progressbar
+ if fd is not None:
+ self.file_props['stalled'] = False
+ if fd is None and self.file_props['stalled'] is False:
+ return None
+ if 'received-len' in self.file_props:
+ if self.file_props['received-len'] != 0:
+ return self.file_props['received-len']
+ return None
+
+ def disconnect(self):
+ """
+ Close open descriptors and remover socket descr. from idleque
+ """
+ # be sure that we don't leave open file
+ self.close_file()
+ self.idlequeue.remove_timeout(self.fd)
+ self.idlequeue.unplug_idle(self.fd)
+ try:
+ self._sock.shutdown(socket.SHUT_RDWR)
+ self._sock.close()
+ except Exception:
+ # socket is already closed
+ pass
+ self.connected = False
+ self.fd = -1
+ self.state = -1
+
+ def _get_auth_buff(self):
+ """
+ Message, that we support 1 one auth mechanism: the 'no auth' mechanism
+ """
+ return struct.pack('!BBB', 0x05, 0x01, 0x00)
+
+ def _parse_auth_buff(self, buff):
+ """
+ Parse the initial message and create a list of auth mechanisms
+ """
+ auth_mechanisms = []
+ try:
+ num_auth = struct.unpack('!xB', buff[:2])[0]
+ for i in xrange(num_auth):
+ mechanism, = struct.unpack('!B', buff[1 + i])
+ auth_mechanisms.append(mechanism)
+ except Exception:
+ return None
+ return auth_mechanisms
+
+ def _get_auth_response(self):
+ """
+ Socks version(5), number of extra auth methods (we send 0x00 - no auth)
+ """
+ return struct.pack('!BB', 0x05, 0x00)
+
+ def _get_connect_buff(self):
+ ''' Connect request by domain name '''
+ buff = struct.pack('!BBBBB%dsBB' % len(self.host),
+ 0x05, 0x01, 0x00, 0x03, len(self.host), self.host,
+ self.port >> 8, self.port & 0xff)
+ return buff
+
+ def _get_request_buff(self, msg, command = 0x01):
+ """
+ Connect request by domain name, sid sha, instead of domain name (jep
+ 0096)
+ """
+ buff = struct.pack('!BBBBB%dsBB' % len(msg),
+ 0x05, command, 0x00, 0x03, len(msg), msg, 0, 0)
+ return buff
+
+ def _parse_request_buff(self, buff):
+ try: # don't trust on what comes from the outside
+ req_type, host_type, = struct.unpack('!xBxB', buff[:4])
+ if host_type == 0x01:
+ host_arr = struct.unpack('!iiii', buff[4:8])
+ host, = '.'.join(str(s) for s in host_arr)
+ host_len = len(host)
+ elif host_type == 0x03:
+ host_len, = struct.unpack('!B' , buff[4])
+ host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len])
+ portlen = len(buff[host_len + 5:])
+ if portlen == 1:
+ port, = struct.unpack('!B', buff[host_len + 5])
+ elif portlen == 2:
+ port, = struct.unpack('!H', buff[host_len + 5:])
+ # file data, comes with auth message (Gaim bug)
+ else:
+ port, = struct.unpack('!H', buff[host_len + 5: host_len + 7])
+ self.remaining_buff = buff[host_len + 7:]
+ except Exception:
+ return (None, None, None)
+ return (req_type, host, port)
+
+ def read_connect(self):
+ """
+ Connect response: version, auth method
+ """
+ buff = self._recv()
+ try:
+ version, method = struct.unpack('!BB', buff)
+ except Exception:
+ version, method = None, None
+ if version != 0x05 or method == 0xff:
+ self.disconnect()
+
+ def continue_paused_transfer(self):
+ if self.state < 5:
+ return
+ if self.file_props['type'] == 'r':
+ self.idlequeue.plug_idle(self, False, True)
+ else:
+ self.idlequeue.plug_idle(self, True, False)
+
+ def _get_sha1_auth(self):
+ """
+ Get sha of sid + Initiator jid + Target jid
+ """
+ if 'is_a_proxy' in self.file_props:
+ del(self.file_props['is_a_proxy'])
+ return hashlib.sha1('%s%s%s' % (self.sid,
+ self.file_props['proxy_sender'],
+ self.file_props['proxy_receiver'])).hexdigest()
+ return hashlib.sha1('%s%s%s' % (self.sid, self.initiator, self.target)).\
+ hexdigest()
class Socks5Sender(Socks5, IdleObject):
- """
- Class for sending file to socket over socks5
- """
-
- def __init__(self, idlequeue, sock_hash, parent, _sock, host=None,
- port=None):
- self.queue_idx = sock_hash
- self.queue = parent
- Socks5.__init__(self, idlequeue, host, port, None, None, None)
- self._sock = _sock
- self._sock.setblocking(False)
- self.fd = _sock.fileno()
- self._recv = _sock.recv
- self._send = _sock.send
- self.connected = True
- self.state = 1 # waiting for first bytes
- self.file_props = None
- # start waiting for data
- self.idlequeue.plug_idle(self, False, True)
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state > 5:
- # no activity for foo seconds
- if self.file_props['stalled'] == False:
- self.file_props['stalled'] = True
- self.queue.process_result(-1, self)
- if SEND_TIMEOUT > 0:
- self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT)
- else:
- # stop transfer, there is no error code for this
- self.pollend()
-
- def pollout(self):
- if not self.connected:
- self.disconnect()
- return
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 2: # send reply with desired auth type
- self.send_raw(self._get_auth_response())
- elif self.state == 4: # send positive response to the 'connect'
- self.send_raw(self._get_request_buff(self.sha_msg, 0x00))
- elif self.state == 7:
- if self.file_props['paused']:
- self.file_props['continue_cb'] = self.continue_paused_transfer
- self.idlequeue.plug_idle(self, False, False)
- return
- result = self.write_next()
- self.queue.process_result(result, self)
- if result is None or result <= 0:
- self.disconnect()
- return
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- elif self.state == 8:
- self.disconnect()
- return
- else:
- self.disconnect()
- if self.state < 5:
- self.state += 1
- # unplug and plug this time for reading
- self.idlequeue.plug_idle(self, False, True)
-
- def pollend(self):
- self.state = 8 # end connection
- self.disconnect()
- self.file_props['error'] = -1
- self.queue.process_result(-1, self)
-
- def pollin(self):
- if self.connected:
- if self.state < 5:
- result = self.main()
- if self.state == 4:
- self.queue.result_sha(self.sha_msg, self.queue_idx)
- if result == -1:
- self.disconnect()
-
- elif self.state == 5:
- if self.file_props is not None and self.file_props['type'] == 'r':
- result = self.get_file_contents(0)
- self.queue.process_result(result, self)
- else:
- self.disconnect()
-
- def send_file(self):
- """
- Start sending the file over verified connection
- """
- if self.file_props['started']:
- return
- self.file_props['error'] = 0
- self.file_props['disconnect_cb'] = self.disconnect
- self.file_props['started'] = True
- self.file_props['completed'] = False
- self.file_props['paused'] = False
- self.file_props['continue_cb'] = self.continue_paused_transfer
- self.file_props['stalled'] = False
- self.file_props['connected'] = True
- self.file_props['elapsed-time'] = 0
- self.file_props['last-time'] = self.idlequeue.current_time()
- self.file_props['received-len'] = 0
- self.pauses = 0
- self.state = 7
- # plug for writing
- self.idlequeue.plug_idle(self, True, False)
- return self.write_next() # initial for nl byte
-
- def main(self):
- """
- Initial requests for verifying the connection
- """
- if self.state == 1: # initial read
- buff = self.receive()
- if not self.connected:
- return -1
- mechs = self._parse_auth_buff(buff)
- if mechs is None:
- return -1 # invalid auth methods received
- elif self.state == 3: # get next request
- buff = self.receive()
- req_type, self.sha_msg = self._parse_request_buff(buff)[:2]
- if req_type != 0x01:
- return -1 # request is not of type 'connect'
- self.state += 1 # go to the next step
- # unplug & plug for writing
- self.idlequeue.plug_idle(self, True, False)
- return None
-
- def disconnect(self, cb=True):
- """
- Close the socket
- """
- # close connection and remove us from the queue
- Socks5.disconnect(self)
- if self.file_props is not None:
- self.file_props['connected'] = False
- self.file_props['disconnect_cb'] = None
- if self.queue is not None:
- self.queue.remove_sender(self.queue_idx, False)
+ """
+ Class for sending file to socket over socks5
+ """
+
+ def __init__(self, idlequeue, sock_hash, parent, _sock, host=None,
+ port=None):
+ self.queue_idx = sock_hash
+ self.queue = parent
+ Socks5.__init__(self, idlequeue, host, port, None, None, None)
+ self._sock = _sock
+ self._sock.setblocking(False)
+ self.fd = _sock.fileno()
+ self._recv = _sock.recv
+ self._send = _sock.send
+ self.connected = True
+ self.state = 1 # waiting for first bytes
+ self.file_props = None
+ # start waiting for data
+ self.idlequeue.plug_idle(self, False, True)
+
+ def read_timeout(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state > 5:
+ # no activity for foo seconds
+ if self.file_props['stalled'] == False:
+ self.file_props['stalled'] = True
+ self.queue.process_result(-1, self)
+ if SEND_TIMEOUT > 0:
+ self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT)
+ else:
+ # stop transfer, there is no error code for this
+ self.pollend()
+
+ def pollout(self):
+ if not self.connected:
+ self.disconnect()
+ return
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state == 2: # send reply with desired auth type
+ self.send_raw(self._get_auth_response())
+ elif self.state == 4: # send positive response to the 'connect'
+ self.send_raw(self._get_request_buff(self.sha_msg, 0x00))
+ elif self.state == 7:
+ if self.file_props['paused']:
+ self.file_props['continue_cb'] = self.continue_paused_transfer
+ self.idlequeue.plug_idle(self, False, False)
+ return
+ result = self.write_next()
+ self.queue.process_result(result, self)
+ if result is None or result <= 0:
+ self.disconnect()
+ return
+ self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
+ elif self.state == 8:
+ self.disconnect()
+ return
+ else:
+ self.disconnect()
+ if self.state < 5:
+ self.state += 1
+ # unplug and plug this time for reading
+ self.idlequeue.plug_idle(self, False, True)
+
+ def pollend(self):
+ self.state = 8 # end connection
+ self.disconnect()
+ self.file_props['error'] = -1
+ self.queue.process_result(-1, self)
+
+ def pollin(self):
+ if self.connected:
+ if self.state < 5:
+ result = self.main()
+ if self.state == 4:
+ self.queue.result_sha(self.sha_msg, self.queue_idx)
+ if result == -1:
+ self.disconnect()
+
+ elif self.state == 5:
+ if self.file_props is not None and self.file_props['type'] == 'r':
+ result = self.get_file_contents(0)
+ self.queue.process_result(result, self)
+ else:
+ self.disconnect()
+
+ def send_file(self):
+ """
+ Start sending the file over verified connection
+ """
+ if self.file_props['started']:
+ return
+ self.file_props['error'] = 0
+ self.file_props['disconnect_cb'] = self.disconnect
+ self.file_props['started'] = True
+ self.file_props['completed'] = False
+ self.file_props['paused'] = False
+ self.file_props['continue_cb'] = self.continue_paused_transfer
+ self.file_props['stalled'] = False
+ self.file_props['connected'] = True
+ self.file_props['elapsed-time'] = 0
+ self.file_props['last-time'] = self.idlequeue.current_time()
+ self.file_props['received-len'] = 0
+ self.pauses = 0
+ self.state = 7
+ # plug for writing
+ self.idlequeue.plug_idle(self, True, False)
+ return self.write_next() # initial for nl byte
+
+ def main(self):
+ """
+ Initial requests for verifying the connection
+ """
+ if self.state == 1: # initial read
+ buff = self.receive()
+ if not self.connected:
+ return -1
+ mechs = self._parse_auth_buff(buff)
+ if mechs is None:
+ return -1 # invalid auth methods received
+ elif self.state == 3: # get next request
+ buff = self.receive()
+ req_type, self.sha_msg = self._parse_request_buff(buff)[:2]
+ if req_type != 0x01:
+ return -1 # request is not of type 'connect'
+ self.state += 1 # go to the next step
+ # unplug & plug for writing
+ self.idlequeue.plug_idle(self, True, False)
+ return None
+
+ def disconnect(self, cb=True):
+ """
+ Close the socket
+ """
+ # close connection and remove us from the queue
+ Socks5.disconnect(self)
+ if self.file_props is not None:
+ self.file_props['connected'] = False
+ self.file_props['disconnect_cb'] = None
+ if self.queue is not None:
+ self.queue.remove_sender(self.queue_idx, False)
class Socks5Listener(IdleObject):
- def __init__(self, idlequeue, port):
- """
- Handle all incomming connections on (0.0.0.0, port)
-
- This class implements IdleObject, but we will expect
- only pollin events though
- """
- self.port = port
- self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE)
- self.ais.sort(reverse=True) # Try IPv6 first
- self.queue_idx = -1
- self.idlequeue = idlequeue
- self.queue = None
- self.started = False
- self._sock = None
- self.fd = -1
-
- def bind(self):
- for ai in self.ais:
- # try the different possibilities (ipv6, ipv4, etc.)
- try:
- self._serv = socket.socket(*ai[:3])
- except socket.error, e:
- if e.args[0] == EAFNOSUPPORT:
- self.ai = None
- continue
- raise
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- # Under windows Vista, we need that to listen on ipv6 AND ipv4
- # Doesn't work under windows XP
- if os.name == 'nt':
- ver = os.sys.getwindowsversion()
- if (ver[3], ver[0], ver[1]) == (2, 6, 0):
- # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
- self._serv.setsockopt(socket.IPPROTO_IPV6, 27, 1)
- # will fail when port as busy, or we don't have rights to bind
- try:
- self._serv.bind(ai[4])
- self.ai = ai
- break
- except Exception:
- self.ai = None
- continue
- if not self.ai:
- # unable to bind, show error dialog
- return None
- self._serv.listen(socket.SOMAXCONN)
- self._serv.setblocking(False)
- self.fd = self._serv.fileno()
- self.idlequeue.plug_idle(self, False, True)
- self.started = True
-
- def pollend(self):
- """
- Called when we stop listening on (host, port)
- """
- self.disconnect()
-
- def pollin(self):
- """
- Accept a new incomming connection and notify queue
- """
- sock = self.accept_conn()
- self.queue.on_connection_accepted(sock)
-
- def disconnect(self):
- """
- Free all resources, we are not listening anymore
- """
- self.idlequeue.remove_timeout(self.fd)
- self.idlequeue.unplug_idle(self.fd)
- self.fd = -1
- self.state = -1
- self.started = False
- try:
- self._serv.close()
- except Exception:
- pass
-
- def accept_conn(self):
- """
- Accept a new incomming connection
- """
- _sock = self._serv.accept()
- _sock[0].setblocking(False)
- return _sock
+ def __init__(self, idlequeue, port):
+ """
+ Handle all incomming connections on (0.0.0.0, port)
+
+ This class implements IdleObject, but we will expect
+ only pollin events though
+ """
+ self.port = port
+ self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE)
+ self.ais.sort(reverse=True) # Try IPv6 first
+ self.queue_idx = -1
+ self.idlequeue = idlequeue
+ self.queue = None
+ self.started = False
+ self._sock = None
+ self.fd = -1
+
+ def bind(self):
+ for ai in self.ais:
+ # try the different possibilities (ipv6, ipv4, etc.)
+ try:
+ self._serv = socket.socket(*ai[:3])
+ except socket.error, e:
+ if e.args[0] == EAFNOSUPPORT:
+ self.ai = None
+ continue
+ raise
+ self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ # Under windows Vista, we need that to listen on ipv6 AND ipv4
+ # Doesn't work under windows XP
+ if os.name == 'nt':
+ ver = os.sys.getwindowsversion()
+ if (ver[3], ver[0], ver[1]) == (2, 6, 0):
+ # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
+ self._serv.setsockopt(socket.IPPROTO_IPV6, 27, 1)
+ # will fail when port as busy, or we don't have rights to bind
+ try:
+ self._serv.bind(ai[4])
+ self.ai = ai
+ break
+ except Exception:
+ self.ai = None
+ continue
+ if not self.ai:
+ # unable to bind, show error dialog
+ return None
+ self._serv.listen(socket.SOMAXCONN)
+ self._serv.setblocking(False)
+ self.fd = self._serv.fileno()
+ self.idlequeue.plug_idle(self, False, True)
+ self.started = True
+
+ def pollend(self):
+ """
+ Called when we stop listening on (host, port)
+ """
+ self.disconnect()
+
+ def pollin(self):
+ """
+ Accept a new incomming connection and notify queue
+ """
+ sock = self.accept_conn()
+ self.queue.on_connection_accepted(sock)
+
+ def disconnect(self):
+ """
+ Free all resources, we are not listening anymore
+ """
+ self.idlequeue.remove_timeout(self.fd)
+ self.idlequeue.unplug_idle(self.fd)
+ self.fd = -1
+ self.state = -1
+ self.started = False
+ try:
+ self._serv.close()
+ except Exception:
+ pass
+
+ def accept_conn(self):
+ """
+ Accept a new incomming connection
+ """
+ _sock = self._serv.accept()
+ _sock[0].setblocking(False)
+ return _sock
class Socks5Receiver(Socks5, IdleObject):
- def __init__(self, idlequeue, streamhost, sid, file_props = None):
- self.queue_idx = -1
- self.streamhost = streamhost
- self.queue = None
- self.file_props = file_props
- self.connect_timeout = 0
- self.connected = False
- self.pauses = 0
- if not self.file_props:
- self.file_props = {}
- self.file_props['disconnect_cb'] = self.disconnect
- self.file_props['error'] = 0
- self.file_props['started'] = True
- self.file_props['completed'] = False
- self.file_props['paused'] = False
- self.file_props['continue_cb'] = self.continue_paused_transfer
- self.file_props['stalled'] = False
- Socks5.__init__(self, idlequeue, streamhost['host'],
- int(streamhost['port']), streamhost['initiator'], streamhost['target'],
- sid)
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state > 5:
- # no activity for foo seconds
- if self.file_props['stalled'] == False:
- self.file_props['stalled'] = True
- if 'received-len' not in self.file_props:
- self.file_props['received-len'] = 0
- self.queue.process_result(-1, self)
- if READ_TIMEOUT > 0:
- self.idlequeue.set_read_timeout(self.fd, READ_TIMEOUT)
- else:
- # stop transfer, there is no error code for this
- self.pollend()
- else:
- self.queue.reconnect_receiver(self, self.streamhost)
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.ais is None:
- return None
-
- for ai in self.ais:
- try:
- self._sock = socket.socket(*ai[:3])
- # this will not block the GUI
- self._sock.setblocking(False)
- self._server = ai[4]
- break
- except socket.error, e:
- if not isinstance(e, basestring) and e[0] == EINPROGRESS:
- break
- # for all other errors, we try other addresses
- continue
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- self.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def _is_connected(self):
- if self.state < 5:
- return False
- return True
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- self.send_raw(self._get_auth_buff())
- elif self.state == 3: # send 'connect' request
- self.send_raw(self._get_request_buff(self._get_sha1_auth()))
- elif self.file_props['type'] != 'r':
- if self.file_props['paused']:
- self.idlequeue.plug_idle(self, False, False)
- return
- result = self.write_next()
- self.queue.process_result(result, self)
- return
- self.state += 1
- # unplug and plug for reading
- self.idlequeue.plug_idle(self, False, True)
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollend(self):
- if self.state >= 5:
- # error during transfer
- self.disconnect()
- self.file_props['error'] = -1
- self.queue.process_result(-1, self)
- else:
- self.queue.reconnect_receiver(self, self.streamhost)
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.connected:
- if self.file_props['paused']:
- self.idlequeue.plug_idle(self, False, False)
- return
- if self.state < 5:
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- result = self.main(0)
- self.queue.process_result(result, self)
- elif self.state == 5: # wait for proxy reply
- pass
- elif self.file_props['type'] == 'r':
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- result = self.get_file_contents(0)
- self.queue.process_result(result, self)
- else:
- self.disconnect()
-
- def do_connect(self):
- try:
- self._sock.connect(self._server)
- self._sock.setblocking(False)
- self._send=self._sock.send
- self._recv=self._sock.recv
- except Exception, ee:
- errnum = ee[0]
- self.connect_timeout += 1
- if errnum == 111 or self.connect_timeout > 1000:
- self.queue._connection_refused(self.streamhost,
- self.file_props, self.queue_idx)
- return None
- # win32 needs this
- elif errnum not in (10056, EISCONN) or self.state != 0:
- return None
- else: # socket is already connected
- self._sock.setblocking(False)
- self._send=self._sock.send
- self._recv=self._sock.recv
- self.buff = ''
- self.connected = True
- self.file_props['connected'] = True
- self.file_props['disconnect_cb'] = self.disconnect
- self.state = 1 # connected
-
- # stop all others connections to sender's streamhosts
- self.queue._socket_connected(self.streamhost, self.file_props)
- self.idlequeue.plug_idle(self, True, False)
- return 1 # we are connected
-
- def main(self, timeout=0):
- """
- Begin negotiation. on success 'address' != 0
- """
- result = 1
- buff = self.receive()
- if buff == '':
- # end connection
- self.pollend()
- return
-
- if self.state == 2: # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.disconnect()
- elif self.state == 4: # get approve of our request
- if buff is None:
- return None
- sub_buff = buff[:4]
- if len(sub_buff) < 4:
- return None
- version, address_type = struct.unpack('!BxxB', buff[:4])
- addrlen = 0
- if address_type == 0x03:
- addrlen = ord(buff[4])
- address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5])
- portlen = len(buff[addrlen + 5:])
- if portlen == 1:
- port, = struct.unpack('!B', buff[addrlen + 5])
- elif portlen == 2:
- port, = struct.unpack('!H', buff[addrlen + 5:])
- else: # Gaim bug :)
- port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7])
- self.remaining_buff = buff[addrlen + 7:]
- self.state = 5 # for senders: init file_props and send '\n'
- if self.queue.on_success:
- result = self.queue.send_success_reply(self.file_props,
- self.streamhost)
- if result == 0:
- self.state = 8
- self.disconnect()
-
- # for senders: init file_props
- if result == 1 and self.state == 5:
- if self.file_props['type'] == 's':
- self.file_props['error'] = 0
- self.file_props['disconnect_cb'] = self.disconnect
- self.file_props['started'] = True
- self.file_props['completed'] = False
- self.file_props['paused'] = False
- self.file_props['stalled'] = False
- self.file_props['elapsed-time'] = 0
- self.file_props['last-time'] = self.idlequeue.current_time()
- self.file_props['received-len'] = 0
- self.pauses = 0
- # start sending file contents to socket
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- self.idlequeue.plug_idle(self, True, False)
- else:
- # receiving file contents from socket
- self.idlequeue.plug_idle(self, False, True)
- self.file_props['continue_cb'] = self.continue_paused_transfer
- # we have set up the connection, next - retrieve file
- self.state = 6
- if self.state < 5:
- self.idlequeue.plug_idle(self, True, False)
- self.state += 1
- return None
-
- def disconnect(self, cb=True):
- """
- Close the socket. Remove self from queue if cb is True
- """
- # close connection
- Socks5.disconnect(self)
- if cb is True:
- self.file_props['disconnect_cb'] = None
- if self.queue is not None:
- self.queue.remove_receiver(self.queue_idx, False)
-
-# vim: se ts=3:
+ def __init__(self, idlequeue, streamhost, sid, file_props = None):
+ self.queue_idx = -1
+ self.streamhost = streamhost
+ self.queue = None
+ self.file_props = file_props
+ self.connect_timeout = 0
+ self.connected = False
+ self.pauses = 0
+ if not self.file_props:
+ self.file_props = {}
+ self.file_props['disconnect_cb'] = self.disconnect
+ self.file_props['error'] = 0
+ self.file_props['started'] = True
+ self.file_props['completed'] = False
+ self.file_props['paused'] = False
+ self.file_props['continue_cb'] = self.continue_paused_transfer
+ self.file_props['stalled'] = False
+ Socks5.__init__(self, idlequeue, streamhost['host'],
+ int(streamhost['port']), streamhost['initiator'], streamhost['target'],
+ sid)
+
+ def read_timeout(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state > 5:
+ # no activity for foo seconds
+ if self.file_props['stalled'] == False:
+ self.file_props['stalled'] = True
+ if 'received-len' not in self.file_props:
+ self.file_props['received-len'] = 0
+ self.queue.process_result(-1, self)
+ if READ_TIMEOUT > 0:
+ self.idlequeue.set_read_timeout(self.fd, READ_TIMEOUT)
+ else:
+ # stop transfer, there is no error code for this
+ self.pollend()
+ else:
+ self.queue.reconnect_receiver(self, self.streamhost)
+
+ def connect(self):
+ """
+ Create the socket and plug it to the idlequeue
+ """
+ if self.ais is None:
+ return None
+
+ for ai in self.ais:
+ try:
+ self._sock = socket.socket(*ai[:3])
+ # this will not block the GUI
+ self._sock.setblocking(False)
+ self._server = ai[4]
+ break
+ except socket.error, e:
+ if not isinstance(e, basestring) and e[0] == EINPROGRESS:
+ break
+ # for all other errors, we try other addresses
+ continue
+ self.fd = self._sock.fileno()
+ self.state = 0 # about to be connected
+ self.idlequeue.plug_idle(self, True, False)
+ self.do_connect()
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+ return None
+
+ def _is_connected(self):
+ if self.state < 5:
+ return False
+ return True
+
+ def pollout(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.state == 0:
+ self.do_connect()
+ return
+ elif self.state == 1: # send initially: version and auth types
+ self.send_raw(self._get_auth_buff())
+ elif self.state == 3: # send 'connect' request
+ self.send_raw(self._get_request_buff(self._get_sha1_auth()))
+ elif self.file_props['type'] != 'r':
+ if self.file_props['paused']:
+ self.idlequeue.plug_idle(self, False, False)
+ return
+ result = self.write_next()
+ self.queue.process_result(result, self)
+ return
+ self.state += 1
+ # unplug and plug for reading
+ self.idlequeue.plug_idle(self, False, True)
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+
+ def pollend(self):
+ if self.state >= 5:
+ # error during transfer
+ self.disconnect()
+ self.file_props['error'] = -1
+ self.queue.process_result(-1, self)
+ else:
+ self.queue.reconnect_receiver(self, self.streamhost)
+
+ def pollin(self):
+ self.idlequeue.remove_timeout(self.fd)
+ if self.connected:
+ if self.file_props['paused']:
+ self.idlequeue.plug_idle(self, False, False)
+ return
+ if self.state < 5:
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
+ result = self.main(0)
+ self.queue.process_result(result, self)
+ elif self.state == 5: # wait for proxy reply
+ pass
+ elif self.file_props['type'] == 'r':
+ self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
+ result = self.get_file_contents(0)
+ self.queue.process_result(result, self)
+ else:
+ self.disconnect()
+
+ def do_connect(self):
+ try:
+ self._sock.connect(self._server)
+ self._sock.setblocking(False)
+ self._send=self._sock.send
+ self._recv=self._sock.recv
+ except Exception, ee:
+ errnum = ee[0]
+ self.connect_timeout += 1
+ if errnum == 111 or self.connect_timeout > 1000:
+ self.queue._connection_refused(self.streamhost,
+ self.file_props, self.queue_idx)
+ return None
+ # win32 needs this
+ elif errnum not in (10056, EISCONN) or self.state != 0:
+ return None
+ else: # socket is already connected
+ self._sock.setblocking(False)
+ self._send=self._sock.send
+ self._recv=self._sock.recv
+ self.buff = ''
+ self.connected = True
+ self.file_props['connected'] = True
+ self.file_props['disconnect_cb'] = self.disconnect
+ self.state = 1 # connected
+
+ # stop all others connections to sender's streamhosts
+ self.queue._socket_connected(self.streamhost, self.file_props)
+ self.idlequeue.plug_idle(self, True, False)
+ return 1 # we are connected
+
+ def main(self, timeout=0):
+ """
+ Begin negotiation. on success 'address' != 0
+ """
+ result = 1
+ buff = self.receive()
+ if buff == '':
+ # end connection
+ self.pollend()
+ return
+
+ if self.state == 2: # read auth response
+ if buff is None or len(buff) != 2:
+ return None
+ version, method = struct.unpack('!BB', buff[:2])
+ if version != 0x05 or method == 0xff:
+ self.disconnect()
+ elif self.state == 4: # get approve of our request
+ if buff is None:
+ return None
+ sub_buff = buff[:4]
+ if len(sub_buff) < 4:
+ return None
+ version, address_type = struct.unpack('!BxxB', buff[:4])
+ addrlen = 0
+ if address_type == 0x03:
+ addrlen = ord(buff[4])
+ address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5])
+ portlen = len(buff[addrlen + 5:])
+ if portlen == 1:
+ port, = struct.unpack('!B', buff[addrlen + 5])
+ elif portlen == 2:
+ port, = struct.unpack('!H', buff[addrlen + 5:])
+ else: # Gaim bug :)
+ port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7])
+ self.remaining_buff = buff[addrlen + 7:]
+ self.state = 5 # for senders: init file_props and send '\n'
+ if self.queue.on_success:
+ result = self.queue.send_success_reply(self.file_props,
+ self.streamhost)
+ if result == 0:
+ self.state = 8
+ self.disconnect()
+
+ # for senders: init file_props
+ if result == 1 and self.state == 5:
+ if self.file_props['type'] == 's':
+ self.file_props['error'] = 0
+ self.file_props['disconnect_cb'] = self.disconnect
+ self.file_props['started'] = True
+ self.file_props['completed'] = False
+ self.file_props['paused'] = False
+ self.file_props['stalled'] = False
+ self.file_props['elapsed-time'] = 0
+ self.file_props['last-time'] = self.idlequeue.current_time()
+ self.file_props['received-len'] = 0
+ self.pauses = 0
+ # start sending file contents to socket
+ self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
+ self.idlequeue.plug_idle(self, True, False)
+ else:
+ # receiving file contents from socket
+ self.idlequeue.plug_idle(self, False, True)
+ self.file_props['continue_cb'] = self.continue_paused_transfer
+ # we have set up the connection, next - retrieve file
+ self.state = 6
+ if self.state < 5:
+ self.idlequeue.plug_idle(self, True, False)
+ self.state += 1
+ return None
+
+ def disconnect(self, cb=True):
+ """
+ Close the socket. Remove self from queue if cb is True
+ """
+ # close connection
+ Socks5.disconnect(self)
+ if cb is True:
+ self.file_props['disconnect_cb'] = None
+ if self.queue is not None:
+ self.queue.remove_receiver(self.queue_idx, False)
diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py
index a85155efd..3d65c572b 100644
--- a/src/common/stanza_session.py
+++ b/src/common/stanza_session.py
@@ -38,1030 +38,1028 @@ from hmac import HMAC
from common import crypto
if gajim.HAVE_PYCRYPTO:
- from Crypto.Cipher import AES
- from Crypto.PublicKey import RSA
+ from Crypto.Cipher import AES
+ from Crypto.PublicKey import RSA
- from common import dh
- import secrets
+ from common import dh
+ import secrets
XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
class StanzaSession(object):
- '''
- '''
- def __init__(self, conn, jid, thread_id, type_):
- '''
- '''
- self.conn = conn
- self.jid = jid
- self.type = type_
- self.resource = None
-
- if thread_id:
- self.received_thread_id = True
- self.thread_id = thread_id
- else:
- self.received_thread_id = False
- if type_ == 'normal':
- self.thread_id = None
- else:
- self.thread_id = self.generate_thread_id()
-
- self.loggable = True
-
- self.last_send = 0
- self.last_receive = 0
- self.status = None
- self.negotiated = {}
-
- def is_loggable(self):
- return self.loggable and gajim.config.should_log(self.conn.name, self.jid)
-
- def get_to(self):
- to = str(self.jid)
- if self.resource and not to.endswith(self.resource):
- to += '/' + self.resource
- return to
-
- def remove_events(self, types):
- """
- Remove events associated with this session from the queue
-
- Returns True if any events were removed (unlike events.py remove_events)
- """
- any_removed = False
-
- for j in (self.jid, self.jid.getStripped()):
- for event in gajim.events.get_events(self.conn.name, j, types=types):
- # the event wasn't in this session
- if (event.type_ == 'chat' and event.parameters[8] != self) or \
- (event.type_ == 'printed_chat' and event.parameters[0].session != \
- self):
- continue
-
- # events.remove_events returns True when there were no events
- # for some reason
- r = gajim.events.remove_events(self.conn.name, j, event)
-
- if not r:
- any_removed = True
-
- return any_removed
-
- def generate_thread_id(self):
- return ''.join([f(string.ascii_letters) for f in itertools.repeat(
- random.choice, 32)])
-
- def send(self, msg):
- if self.thread_id:
- msg.NT.thread = self.thread_id
-
- msg.setAttr('to', self.get_to())
- self.conn.send_stanza(msg)
-
- if isinstance(msg, xmpp.Message):
- self.last_send = time.time()
-
- def reject_negotiation(self, body=None):
- msg = xmpp.Message()
- feature = msg.NT.feature
- feature.setNamespace(xmpp.NS_FEATURE)
-
- x = xmpp.DataForm(typ='submit')
- x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
- x.addChild(node=xmpp.DataField(name='accept', value='0'))
-
- feature.addChild(node=x)
-
- if body:
- msg.setBody(body)
-
- self.send(msg)
-
- self.cancelled_negotiation()
-
- def cancelled_negotiation(self):
- """
- A negotiation has been cancelled, so reset this session to its default
- state
- """
- if self.control:
- self.control.on_cancel_session_negotiation()
-
- self.status = None
- self.negotiated = {}
+ '''
+ '''
+ def __init__(self, conn, jid, thread_id, type_):
+ '''
+ '''
+ self.conn = conn
+ self.jid = jid
+ self.type = type_
+ self.resource = None
+
+ if thread_id:
+ self.received_thread_id = True
+ self.thread_id = thread_id
+ else:
+ self.received_thread_id = False
+ if type_ == 'normal':
+ self.thread_id = None
+ else:
+ self.thread_id = self.generate_thread_id()
+
+ self.loggable = True
+
+ self.last_send = 0
+ self.last_receive = 0
+ self.status = None
+ self.negotiated = {}
+
+ def is_loggable(self):
+ return self.loggable and gajim.config.should_log(self.conn.name, self.jid)
+
+ def get_to(self):
+ to = str(self.jid)
+ if self.resource and not to.endswith(self.resource):
+ to += '/' + self.resource
+ return to
+
+ def remove_events(self, types):
+ """
+ Remove events associated with this session from the queue
+
+ Returns True if any events were removed (unlike events.py remove_events)
+ """
+ any_removed = False
+
+ for j in (self.jid, self.jid.getStripped()):
+ for event in gajim.events.get_events(self.conn.name, j, types=types):
+ # the event wasn't in this session
+ if (event.type_ == 'chat' and event.parameters[8] != self) or \
+ (event.type_ == 'printed_chat' and event.parameters[0].session != \
+ self):
+ continue
+
+ # events.remove_events returns True when there were no events
+ # for some reason
+ r = gajim.events.remove_events(self.conn.name, j, event)
+
+ if not r:
+ any_removed = True
+
+ return any_removed
+
+ def generate_thread_id(self):
+ return ''.join([f(string.ascii_letters) for f in itertools.repeat(
+ random.choice, 32)])
+
+ def send(self, msg):
+ if self.thread_id:
+ msg.NT.thread = self.thread_id
+
+ msg.setAttr('to', self.get_to())
+ self.conn.send_stanza(msg)
+
+ if isinstance(msg, xmpp.Message):
+ self.last_send = time.time()
+
+ def reject_negotiation(self, body=None):
+ msg = xmpp.Message()
+ feature = msg.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
+
+ x = xmpp.DataForm(typ='submit')
+ x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
+ x.addChild(node=xmpp.DataField(name='accept', value='0'))
+
+ feature.addChild(node=x)
+
+ if body:
+ msg.setBody(body)
+
+ self.send(msg)
+
+ self.cancelled_negotiation()
+
+ def cancelled_negotiation(self):
+ """
+ A negotiation has been cancelled, so reset this session to its default
+ state
+ """
+ if self.control:
+ self.control.on_cancel_session_negotiation()
+
+ self.status = None
+ self.negotiated = {}
- def terminate(self, send_termination = True):
- # only send termination message if we've sent a message and think they
- # have XEP-0201 support
- if send_termination and self.last_send > 0 and \
- (self.received_thread_id or self.last_receive == 0):
- msg = xmpp.Message()
- feature = msg.NT.feature
- feature.setNamespace(xmpp.NS_FEATURE)
+ def terminate(self, send_termination = True):
+ # only send termination message if we've sent a message and think they
+ # have XEP-0201 support
+ if send_termination and self.last_send > 0 and \
+ (self.received_thread_id or self.last_receive == 0):
+ msg = xmpp.Message()
+ feature = msg.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
- x = xmpp.DataForm(typ='submit')
- x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
- x.addChild(node=xmpp.DataField(name='terminate', value='1'))
+ x = xmpp.DataForm(typ='submit')
+ x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
+ x.addChild(node=xmpp.DataField(name='terminate', value='1'))
- feature.addChild(node=x)
+ feature.addChild(node=x)
- self.send(msg)
+ self.send(msg)
- self.status = None
+ self.status = None
- def acknowledge_termination(self):
- # we could send an acknowledgement message to the remote client here
- self.status = None
+ def acknowledge_termination(self):
+ # we could send an acknowledgement message to the remote client here
+ self.status = None
class EncryptedStanzaSession(StanzaSession):
- """
- An encrypted stanza negotiation has several states. They arerepresented as
- the following values in the 'status' attribute of the session object:
-
- 1. None:
- default state
- 2. 'requested-e2e':
- this client has initiated an esession negotiation and is waiting
- for a response
- 3. 'responded-e2e':
- this client has responded to an esession negotiation request and
- is waiting for the initiator to identify itself and complete the
- negotiation
- 4. 'identified-alice':
- this client identified itself and is waiting for the responder to
- identify itself and complete the negotiation
- 5. 'active':
- an encrypted session has been successfully negotiated. messages
- of any of the types listed in 'encryptable_stanzas' should be
- encrypted before they're sent.
-
- The transition between these states is handled in gajim.py's
- handle_session_negotiation method.
- """
-
- def __init__(self, conn, jid, thread_id, type_='chat'):
- StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
-
- self.xes = {}
- self.es = {}
- self.n = 128
- self.enable_encryption = False
-
- # _s denotes 'self' (ie. this client)
- self._kc_s = None
- # _o denotes 'other' (ie. the client at the other end of the session)
- self._kc_o = None
-
- # has the remote contact's identity ever been verified?
- self.verified_identity = False
-
- def _get_contact(self):
- c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource)
- if not c:
- c = gajim.contacts.get_contact(self.conn.name, self.jid)
- return c
-
- def _is_buggy_gajim(self):
- c = self._get_contact()
- if c and c.supports(xmpp.NS_ROSTERX):
- return False
- return True
-
- def set_kc_s(self, value):
- """
- Keep the encrypter updated with my latest cipher key
- """
- self._kc_s = value
- self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR,
- counter=self.encryptcounter)
-
- def get_kc_s(self):
- return self._kc_s
-
- def set_kc_o(self, value):
- """
- Keep the decrypter updated with the other party's latest cipher key
- """
- self._kc_o = value
- self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
- counter=self.decryptcounter)
-
- def get_kc_o(self):
- return self._kc_o
-
- kc_s = property(get_kc_s, set_kc_s)
- kc_o = property(get_kc_o, set_kc_o)
-
- def encryptcounter(self):
- self.c_s = (self.c_s + 1) % (2 ** self.n)
- return crypto.encode_mpi_with_padding(self.c_s)
-
- def decryptcounter(self):
- self.c_o = (self.c_o + 1) % (2 ** self.n)
- return crypto.encode_mpi_with_padding(self.c_o)
-
- def sign(self, string):
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- hash_ = crypto.sha256(string)
- return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0])
-
- def encrypt_stanza(self, stanza):
- encryptable = [x for x in stanza.getChildren() if x.getName() not in
- ('error', 'amp', 'thread')]
-
- # FIXME can also encrypt contents of <error/> elements in stanzas @type =
- # 'error'
- # (except for <defined-condition
- # xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
-
- old_en_counter = self.c_s
-
- for element in encryptable:
- stanza.delChild(element)
-
- plaintext = ''.join(map(str, encryptable))
-
- m_compressed = self.compress(plaintext)
- m_final = self.encrypt(m_compressed)
-
- c = stanza.NT.c
- c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
- c.NT.data = base64.b64encode(m_final)
-
- # FIXME check for rekey request, handle <key/> elements
-
- m_content = ''.join(map(str, c.getChildren()))
- c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
- crypto.encode_mpi(old_en_counter)))
+ """
+ An encrypted stanza negotiation has several states. They arerepresented as
+ the following values in the 'status' attribute of the session object:
+
+ 1. None:
+ default state
+ 2. 'requested-e2e':
+ this client has initiated an esession negotiation and is waiting
+ for a response
+ 3. 'responded-e2e':
+ this client has responded to an esession negotiation request and
+ is waiting for the initiator to identify itself and complete the
+ negotiation
+ 4. 'identified-alice':
+ this client identified itself and is waiting for the responder to
+ identify itself and complete the negotiation
+ 5. 'active':
+ an encrypted session has been successfully negotiated. messages
+ of any of the types listed in 'encryptable_stanzas' should be
+ encrypted before they're sent.
+
+ The transition between these states is handled in gajim.py's
+ handle_session_negotiation method.
+ """
+
+ def __init__(self, conn, jid, thread_id, type_='chat'):
+ StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
+
+ self.xes = {}
+ self.es = {}
+ self.n = 128
+ self.enable_encryption = False
+
+ # _s denotes 'self' (ie. this client)
+ self._kc_s = None
+ # _o denotes 'other' (ie. the client at the other end of the session)
+ self._kc_o = None
+
+ # has the remote contact's identity ever been verified?
+ self.verified_identity = False
+
+ def _get_contact(self):
+ c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource)
+ if not c:
+ c = gajim.contacts.get_contact(self.conn.name, self.jid)
+ return c
+
+ def _is_buggy_gajim(self):
+ c = self._get_contact()
+ if c and c.supports(xmpp.NS_ROSTERX):
+ return False
+ return True
+
+ def set_kc_s(self, value):
+ """
+ Keep the encrypter updated with my latest cipher key
+ """
+ self._kc_s = value
+ self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR,
+ counter=self.encryptcounter)
+
+ def get_kc_s(self):
+ return self._kc_s
+
+ def set_kc_o(self, value):
+ """
+ Keep the decrypter updated with the other party's latest cipher key
+ """
+ self._kc_o = value
+ self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
+ counter=self.decryptcounter)
+
+ def get_kc_o(self):
+ return self._kc_o
+
+ kc_s = property(get_kc_s, set_kc_s)
+ kc_o = property(get_kc_o, set_kc_o)
+
+ def encryptcounter(self):
+ self.c_s = (self.c_s + 1) % (2 ** self.n)
+ return crypto.encode_mpi_with_padding(self.c_s)
+
+ def decryptcounter(self):
+ self.c_o = (self.c_o + 1) % (2 ** self.n)
+ return crypto.encode_mpi_with_padding(self.c_o)
+
+ def sign(self, string):
+ if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
+ hash_ = crypto.sha256(string)
+ return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0])
+
+ def encrypt_stanza(self, stanza):
+ encryptable = [x for x in stanza.getChildren() if x.getName() not in
+ ('error', 'amp', 'thread')]
+
+ # FIXME can also encrypt contents of <error/> elements in stanzas @type =
+ # 'error'
+ # (except for <defined-condition
+ # xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
+
+ old_en_counter = self.c_s
+
+ for element in encryptable:
+ stanza.delChild(element)
+
+ plaintext = ''.join(map(str, encryptable))
+
+ m_compressed = self.compress(plaintext)
+ m_final = self.encrypt(m_compressed)
+
+ c = stanza.NT.c
+ c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
+ c.NT.data = base64.b64encode(m_final)
+
+ # FIXME check for rekey request, handle <key/> elements
+
+ m_content = ''.join(map(str, c.getChildren()))
+ c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
+ crypto.encode_mpi(old_en_counter)))
- msgtxt = '[This is part of an encrypted session. ' \
- 'If you see this message, something went wrong.]'
- lang = os.getenv('LANG')
- if lang is not None and lang != 'en': # we're not english
- msgtxt = _('[This is part of an encrypted session. '
- 'If you see this message, something went wrong.]') + ' (' + \
- msgtxt + ')'
- stanza.setBody(msgtxt)
+ msgtxt = '[This is part of an encrypted session. ' \
+ 'If you see this message, something went wrong.]'
+ lang = os.getenv('LANG')
+ if lang is not None and lang != 'en': # we're not english
+ msgtxt = _('[This is part of an encrypted session. '
+ 'If you see this message, something went wrong.]') + ' (' + \
+ msgtxt + ')'
+ stanza.setBody(msgtxt)
- return stanza
+ return stanza
- def is_xep_200_encrypted(self, msg):
- msg.getTag('c', namespace=xmpp.NS_STANZA_CRYPTO)
+ def is_xep_200_encrypted(self, msg):
+ msg.getTag('c', namespace=xmpp.NS_STANZA_CRYPTO)
- def hmac(self, key, content):
- return HMAC(key, content, self.hash_alg).digest()
+ def hmac(self, key, content):
+ return HMAC(key, content, self.hash_alg).digest()
- def generate_initiator_keys(self, k):
- return (self.hmac(k, 'Initiator Cipher Key'),
- self.hmac(k, 'Initiator MAC Key'),
- self.hmac(k, 'Initiator SIGMA Key'))
-
- def generate_responder_keys(self, k):
- return (self.hmac(k, 'Responder Cipher Key'),
- self.hmac(k, 'Responder MAC Key'),
- self.hmac(k, 'Responder SIGMA Key'))
+ def generate_initiator_keys(self, k):
+ return (self.hmac(k, 'Initiator Cipher Key'),
+ self.hmac(k, 'Initiator MAC Key'),
+ self.hmac(k, 'Initiator SIGMA Key'))
+
+ def generate_responder_keys(self, k):
+ return (self.hmac(k, 'Responder Cipher Key'),
+ self.hmac(k, 'Responder MAC Key'),
+ self.hmac(k, 'Responder SIGMA Key'))
- def compress(self, plaintext):
- if self.compression is None:
- return plaintext
+ def compress(self, plaintext):
+ if self.compression is None:
+ return plaintext
- def decompress(self, compressed):
- if self.compression is None:
- return compressed
+ def decompress(self, compressed):
+ if self.compression is None:
+ return compressed
- def encrypt(self, encryptable):
- padded = crypto.pad_to_multiple(encryptable, 16, ' ', False)
+ def encrypt(self, encryptable):
+ padded = crypto.pad_to_multiple(encryptable, 16, ' ', False)
- return self.encrypter.encrypt(padded)
+ return self.encrypter.encrypt(padded)
- def decrypt_stanza(self, stanza):
- """
- Delete the unencrypted explanation body, if it exists
- """
- orig_body = stanza.getTag('body')
- if orig_body:
- stanza.delChild(orig_body)
+ def decrypt_stanza(self, stanza):
+ """
+ Delete the unencrypted explanation body, if it exists
+ """
+ orig_body = stanza.getTag('body')
+ if orig_body:
+ stanza.delChild(orig_body)
- c = stanza.getTag(name='c',
- namespace='http://www.xmpp.org/extensions/xep-0200.html#ns')
+ c = stanza.getTag(name='c',
+ namespace='http://www.xmpp.org/extensions/xep-0200.html#ns')
- stanza.delChild(c)
+ stanza.delChild(c)
- # contents of <c>, minus <mac>, minus whitespace
- macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
+ # contents of <c>, minus <mac>, minus whitespace
+ macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
- received_mac = base64.b64decode(c.getTagData('mac'))
- calculated_mac = self.hmac(self.km_o, macable + \
- crypto.encode_mpi_with_padding(self.c_o))
+ received_mac = base64.b64decode(c.getTagData('mac'))
+ calculated_mac = self.hmac(self.km_o, macable + \
+ crypto.encode_mpi_with_padding(self.c_o))
- if not calculated_mac == received_mac:
- raise DecryptionError('bad signature')
+ if not calculated_mac == received_mac:
+ raise DecryptionError('bad signature')
- m_final = base64.b64decode(c.getTagData('data'))
- m_compressed = self.decrypt(m_final)
- plaintext = self.decompress(m_compressed)
+ m_final = base64.b64decode(c.getTagData('data'))
+ m_compressed = self.decrypt(m_final)
+ plaintext = self.decompress(m_compressed)
- try:
- parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
- except Exception:
- raise DecryptionError('decrypted <data/> not parseable as XML')
+ try:
+ parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
+ except Exception:
+ raise DecryptionError('decrypted <data/> not parseable as XML')
- for child in parsed.getChildren():
- stanza.addChild(node=child)
+ for child in parsed.getChildren():
+ stanza.addChild(node=child)
- return stanza
+ return stanza
- def decrypt(self, ciphertext):
- return self.decrypter.decrypt(ciphertext)
+ def decrypt(self, ciphertext):
+ return self.decrypter.decrypt(ciphertext)
- def logging_preference(self):
- if gajim.config.get_per('accounts', self.conn.name,
- 'log_encrypted_sessions'):
- return ['may', 'mustnot']
- else:
- return ['mustnot', 'may']
+ def logging_preference(self):
+ if gajim.config.get_per('accounts', self.conn.name,
+ 'log_encrypted_sessions'):
+ return ['may', 'mustnot']
+ else:
+ return ['mustnot', 'may']
- def get_shared_secret(self, e, y, p):
- if (not 1 < e < (p - 1)):
- raise NegotiationError('invalid DH value')
+ def get_shared_secret(self, e, y, p):
+ if (not 1 < e < (p - 1)):
+ raise NegotiationError('invalid DH value')
- return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p)))
+ return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p)))
- def c7lize_mac_id(self, form):
- kids = form.getChildren()
- macable = [x for x in kids if x.getVar() not in ('mac', 'identity')]
- return ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
- macable)
+ def c7lize_mac_id(self, form):
+ kids = form.getChildren()
+ macable = [x for x in kids if x.getVar() not in ('mac', 'identity')]
+ return ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
+ macable)
- def verify_identity(self, form, dh_i, sigmai, i_o):
- m_o = base64.b64decode(form['mac'])
- id_o = base64.b64decode(form['identity'])
+ def verify_identity(self, form, dh_i, sigmai, i_o):
+ m_o = base64.b64decode(form['mac'])
+ id_o = base64.b64decode(form['identity'])
- m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o)
+ m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o)
- if m_o_calculated != m_o:
- raise NegotiationError('calculated m_%s differs from received m_%s' %
- (i_o, i_o))
+ if m_o_calculated != m_o:
+ raise NegotiationError('calculated m_%s differs from received m_%s' %
+ (i_o, i_o))
- if i_o == 'a' and self.sas_algs == 'sas28x5':
- # we don't need to calculate this if there's a verified retained secret
- # (but we do anyways)
- self.sas = crypto.sas_28x5(m_o, self.form_s)
+ if i_o == 'a' and self.sas_algs == 'sas28x5':
+ # we don't need to calculate this if there's a verified retained secret
+ # (but we do anyways)
+ self.sas = crypto.sas_28x5(m_o, self.form_s)
- if self.negotiated['recv_pubkey']:
- plaintext = self.decrypt(id_o)
- parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
+ if self.negotiated['recv_pubkey']:
+ plaintext = self.decrypt(id_o)
+ parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
- if self.negotiated['recv_pubkey'] == 'hash':
- # fingerprint = parsed.getTagData('fingerprint')
- # FIXME find stored pubkey or terminate session
- raise NotImplementedError()
- else:
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
+ if self.negotiated['recv_pubkey'] == 'hash':
+ # fingerprint = parsed.getTagData('fingerprint')
+ # FIXME find stored pubkey or terminate session
+ raise NotImplementedError()
+ else:
+ if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
+ keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
- n, e = (crypto.decode_mpi(base64.b64decode(
- keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent'))
- eir_pubkey = RSA.construct((n,long(e)))
+ n, e = (crypto.decode_mpi(base64.b64decode(
+ keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent'))
+ eir_pubkey = RSA.construct((n,long(e)))
- pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim())
- else:
- # FIXME DSA, etc.
- raise NotImplementedError()
+ pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim())
+ else:
+ # FIXME DSA, etc.
+ raise NotImplementedError()
- enc_sig = parsed.getTag(name='SignatureValue',
- namespace=XmlDsig).getData()
- signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), )
- else:
- mac_o = self.decrypt(id_o)
- pubkey_o = ''
+ enc_sig = parsed.getTag(name='SignatureValue',
+ namespace=XmlDsig).getData()
+ signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), )
+ else:
+ mac_o = self.decrypt(id_o)
+ pubkey_o = ''
- c7l_form = self.c7lize_mac_id(form)
+ c7l_form = self.c7lize_mac_id(form)
- content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o
+ content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o
- if sigmai:
- self.form_o = c7l_form
- content += self.form_o
- else:
- form_o2 = c7l_form
- content += self.form_o + form_o2
+ if sigmai:
+ self.form_o = c7l_form
+ content += self.form_o
+ else:
+ form_o2 = c7l_form
+ content += self.form_o + form_o2
- mac_o_calculated = self.hmac(self.ks_o, content)
+ mac_o_calculated = self.hmac(self.ks_o, content)
- if self.negotiated['recv_pubkey']:
- hash_ = crypto.sha256(mac_o_calculated)
+ if self.negotiated['recv_pubkey']:
+ hash_ = crypto.sha256(mac_o_calculated)
- if not eir_pubkey.verify(hash_, signature):
- raise NegotiationError('public key signature verification failed!')
+ if not eir_pubkey.verify(hash_, signature):
+ raise NegotiationError('public key signature verification failed!')
- elif mac_o_calculated != mac_o:
- raise NegotiationError('calculated mac_%s differs from received mac_%s'
- % (i_o, i_o))
+ elif mac_o_calculated != mac_o:
+ raise NegotiationError('calculated mac_%s differs from received mac_%s'
+ % (i_o, i_o))
- def make_identity(self, form, dh_i):
- if self.negotiated['send_pubkey']:
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- pubkey = secrets.secrets().my_pubkey(self.conn.name)
- fields = (pubkey.n, pubkey.e)
+ def make_identity(self, form, dh_i):
+ if self.negotiated['send_pubkey']:
+ if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
+ pubkey = secrets.secrets().my_pubkey(self.conn.name)
+ fields = (pubkey.n, pubkey.e)
- cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in
- fields]
+ cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in
+ fields]
- pubkey_s = '<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"'
- '><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % \
- tuple(cb_fields)
- else:
- pubkey_s = ''
+ pubkey_s = '<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"'
+ '><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % \
+ tuple(cb_fields)
+ else:
+ pubkey_s = ''
- form_s2 = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
- form.getChildren())
+ form_s2 = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
+ form.getChildren())
- old_c_s = self.c_s
- content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \
- self.form_s + form_s2
+ old_c_s = self.c_s
+ content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \
+ self.form_s + form_s2
- mac_s = self.hmac(self.ks_s, content)
+ mac_s = self.hmac(self.ks_s, content)
- if self.negotiated['send_pubkey']:
- signature = self.sign(mac_s)
+ if self.negotiated['send_pubkey']:
+ signature = self.sign(mac_s)
- sign_s = '<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">'
- '%s</SignatureValue>' % base64.b64encode(signature)
+ sign_s = '<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">'
+ '%s</SignatureValue>' % base64.b64encode(signature)
- if self.negotiated['send_pubkey'] == 'hash':
- b64ed = base64.b64encode(self.hash(pubkey_s))
- pubkey_s = '<fingerprint>%s</fingerprint>' % b64ed
+ if self.negotiated['send_pubkey'] == 'hash':
+ b64ed = base64.b64encode(self.hash(pubkey_s))
+ pubkey_s = '<fingerprint>%s</fingerprint>' % b64ed
- id_s = self.encrypt(pubkey_s + sign_s)
- else:
- id_s = self.encrypt(mac_s)
+ id_s = self.encrypt(pubkey_s + sign_s)
+ else:
+ id_s = self.encrypt(mac_s)
- m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s)
+ m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s)
- if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5':
- # we're alice; check for a retained secret
- # if none exists, prompt the user with the SAS
- self.sas = crypto.sas_28x5(m_s, self.form_o)
+ if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5':
+ # we're alice; check for a retained secret
+ # if none exists, prompt the user with the SAS
+ self.sas = crypto.sas_28x5(m_s, self.form_o)
- if self.sigmai:
- # FIXME save retained secret?
- self.check_identity(tuple)
+ if self.sigmai:
+ # FIXME save retained secret?
+ self.check_identity(tuple)
- return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)),
- xmpp.DataField(name='mac', value=base64.b64encode(m_s)))
+ return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)),
+ xmpp.DataField(name='mac', value=base64.b64encode(m_s)))
- def negotiate_e2e(self, sigmai):
- self.negotiated = {}
+ def negotiate_e2e(self, sigmai):
+ self.negotiated = {}
- request = xmpp.Message()
- feature = request.NT.feature
- feature.setNamespace(xmpp.NS_FEATURE)
+ request = xmpp.Message()
+ feature = request.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
- x = xmpp.DataForm(typ='form')
+ x = xmpp.DataForm(typ='form')
- x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn',
- typ='hidden'))
- x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean',
- required=True))
+ x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn',
+ typ='hidden'))
+ x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean',
+ required=True))
- # this field is incorrectly called 'otr' in XEPs 0116 and 0217
- x.addChild(node=xmpp.DataField(name='logging', typ='list-single',
- options=self.logging_preference(), required=True))
+ # this field is incorrectly called 'otr' in XEPs 0116 and 0217
+ x.addChild(node=xmpp.DataField(name='logging', typ='list-single',
+ options=self.logging_preference(), required=True))
- # unsupported options: 'disabled', 'enabled'
- x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single',
- options=['never'], required=True))
- x.addChild(node=xmpp.DataField(name='security', typ='list-single',
- options=['e2e'], required=True))
- x.addChild(node=xmpp.DataField(name='crypt_algs', value='aes128-ctr',
- typ='hidden'))
- x.addChild(node=xmpp.DataField(name='hash_algs', value='sha256',
- typ='hidden'))
- x.addChild(node=xmpp.DataField(name='compress', value='none',
- typ='hidden'))
-
- # unsupported options: 'iq', 'presence'
- x.addChild(node=xmpp.DataField(name='stanzas', typ='list-multi',
- options=['message']))
-
- x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key',
- 'hash'], typ='list-single'))
-
- # FIXME store key, use hash
- x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none',
- 'key'], typ='list-single'))
-
- x.addChild(node=xmpp.DataField(name='ver', value='1.0', typ='hidden'))
-
- x.addChild(node=xmpp.DataField(name='rekey_freq', value='4294967295',
- typ='hidden'))
-
- x.addChild(node=xmpp.DataField(name='sas_algs', value='sas28x5',
- typ='hidden'))
- x.addChild(node=xmpp.DataField(name='sign_algs',
- value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden'))
-
- self.n_s = crypto.generate_nonce()
-
- x.addChild(node=xmpp.DataField(name='my_nonce',
- value=base64.b64encode(self.n_s), typ='hidden'))
-
- modp_options = [ int(g) for g in gajim.config.get('esession_modp').split(
- ',') ]
-
- x.addChild(node=xmpp.DataField(name='modp', typ='list-single',
- options=[[None, y] for y in modp_options]))
-
- x.addChild(node=self.make_dhfield(modp_options, sigmai))
- self.sigmai = sigmai
-
- self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
- in x.getChildren())
-
- feature.addChild(node=x)
-
- self.status = 'requested-e2e'
-
- self.send(request)
-
- def verify_options_bob(self, form):
- """
- 4.3 esession response (bob)
- """
- negotiated = {'recv_pubkey': None, 'send_pubkey': None}
- not_acceptable = []
- ask_user = {}
-
- fixed = { 'disclosure': 'never', 'security': 'e2e',
- 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none',
- 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none',
- 'ver': '1.0', 'sas_algs': 'sas28x5' }
-
- self.encryptable_stanzas = ['message']
-
- self.sas_algs = 'sas28x5'
- self.cipher = AES
- self.hash_alg = sha256
- self.compression = None
-
- for name in form.asDict():
- field = form.getField(name)
- options = [x[1] for x in field.getOptions()]
- values = field.getValues()
-
- if not field.getType() in ('list-single', 'list-multi'):
- options = values
-
- if name in fixed:
- if fixed[name] in options:
- negotiated[name] = fixed[name]
- else:
- not_acceptable.append(name)
- elif name == 'rekey_freq':
- preferred = int(options[0])
- negotiated['rekey_freq'] = preferred
- self.rekey_freq = preferred
- elif name == 'logging':
- my_prefs = self.logging_preference()
-
- if my_prefs[0] in options: # our first choice is offered, select it
- pref = my_prefs[0]
- negotiated['logging'] = pref
- else: # see if other acceptable choices are offered
- for pref in my_prefs:
- if pref in options:
- ask_user['logging'] = pref
- break
-
- if not 'logging' in ask_user:
- not_acceptable.append(name)
- elif name == 'init_pubkey':
- for x in ('key'):
- if x in options:
- negotiated['recv_pubkey'] = x
- break
- elif name == 'resp_pubkey':
- for x in ('hash', 'key'):
- if x in options:
- negotiated['send_pubkey'] = x
- break
- elif name == 'sign_algs':
- if (XmlDsig + 'rsa-sha256') in options:
- negotiated['sign_algs'] = XmlDsig + 'rsa-sha256'
- else:
- # FIXME some things are handled elsewhere, some things are
- # not-implemented
- pass
-
- return (negotiated, not_acceptable, ask_user)
-
- def respond_e2e_bob(self, form, negotiated, not_acceptable):
- """
- 4.3 esession response (bob)
- """
- response = xmpp.Message()
- feature = response.NT.feature
- feature.setNamespace(xmpp.NS_FEATURE)
-
- x = xmpp.DataForm(typ='submit')
-
- x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
- x.addChild(node=xmpp.DataField(name='accept', value='true'))
-
- for name in negotiated:
- # some fields are internal and should not be sent
- if not name in ('send_pubkey', 'recv_pubkey'):
- x.addChild(node=xmpp.DataField(name=name, value=negotiated[name]))
-
- self.negotiated = negotiated
-
- # the offset of the group we chose (need it to match up with the dhhash)
- group_order = 0
- self.modp = int(form.getField('modp').getOptions()[group_order][1])
- x.addChild(node=xmpp.DataField(name='modp', value=self.modp))
-
- g = dh.generators[self.modp]
- p = dh.primes[self.modp]
-
- self.n_o = base64.b64decode(form['my_nonce'])
-
- dhhashes = form.getField('dhhashes').getValues()
- self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode(
- 'utf8'))
-
- bytes = int(self.n / 8)
-
- self.n_s = crypto.generate_nonce()
-
- # n-bit random number
- self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes))
- self.c_s = self.c_o ^ (2 ** (self.n - 1))
-
- self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1)
- self.d = crypto.powmod(g, self.y, p)
-
- to_add = {'my_nonce': self.n_s,
- 'dhkeys': crypto.encode_mpi(self.d),
- 'counter': crypto.encode_mpi(self.c_o),
- 'nonce': self.n_o}
-
- for name in to_add:
- b64ed = base64.b64encode(to_add[name])
- x.addChild(node=xmpp.DataField(name=name, value=b64ed))
-
- self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
- in form.getChildren())
- self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
- in x.getChildren())
-
- self.status = 'responded-e2e'
-
- feature.addChild(node=x)
-
- if not_acceptable:
- response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE)
-
- feature = xmpp.Node(xmpp.NS_FEATURE + ' feature')
-
- for f in not_acceptable:
- n = xmpp.Node('field')
- n['var'] = f
- feature.addChild(node=n)
-
- response.T.error.addChild(node=feature)
-
- self.send(response)
-
- def verify_options_alice(self, form):
- """
- 'Alice Accepts'
- """
- negotiated = {}
- ask_user = {}
- not_acceptable = []
-
- if not form['logging'] in self.logging_preference():
- not_acceptable.append(form['logging'])
- elif form['logging'] != self.logging_preference()[0]:
- ask_user['logging'] = form['logging']
- else:
- negotiated['logging'] = self.logging_preference()[0]
+ # unsupported options: 'disabled', 'enabled'
+ x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single',
+ options=['never'], required=True))
+ x.addChild(node=xmpp.DataField(name='security', typ='list-single',
+ options=['e2e'], required=True))
+ x.addChild(node=xmpp.DataField(name='crypt_algs', value='aes128-ctr',
+ typ='hidden'))
+ x.addChild(node=xmpp.DataField(name='hash_algs', value='sha256',
+ typ='hidden'))
+ x.addChild(node=xmpp.DataField(name='compress', value='none',
+ typ='hidden'))
+
+ # unsupported options: 'iq', 'presence'
+ x.addChild(node=xmpp.DataField(name='stanzas', typ='list-multi',
+ options=['message']))
+
+ x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key',
+ 'hash'], typ='list-single'))
+
+ # FIXME store key, use hash
+ x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none',
+ 'key'], typ='list-single'))
+
+ x.addChild(node=xmpp.DataField(name='ver', value='1.0', typ='hidden'))
+
+ x.addChild(node=xmpp.DataField(name='rekey_freq', value='4294967295',
+ typ='hidden'))
+
+ x.addChild(node=xmpp.DataField(name='sas_algs', value='sas28x5',
+ typ='hidden'))
+ x.addChild(node=xmpp.DataField(name='sign_algs',
+ value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden'))
+
+ self.n_s = crypto.generate_nonce()
+
+ x.addChild(node=xmpp.DataField(name='my_nonce',
+ value=base64.b64encode(self.n_s), typ='hidden'))
+
+ modp_options = [ int(g) for g in gajim.config.get('esession_modp').split(
+ ',') ]
+
+ x.addChild(node=xmpp.DataField(name='modp', typ='list-single',
+ options=[[None, y] for y in modp_options]))
+
+ x.addChild(node=self.make_dhfield(modp_options, sigmai))
+ self.sigmai = sigmai
+
+ self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
+ in x.getChildren())
+
+ feature.addChild(node=x)
+
+ self.status = 'requested-e2e'
+
+ self.send(request)
+
+ def verify_options_bob(self, form):
+ """
+ 4.3 esession response (bob)
+ """
+ negotiated = {'recv_pubkey': None, 'send_pubkey': None}
+ not_acceptable = []
+ ask_user = {}
+
+ fixed = { 'disclosure': 'never', 'security': 'e2e',
+ 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none',
+ 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none',
+ 'ver': '1.0', 'sas_algs': 'sas28x5' }
+
+ self.encryptable_stanzas = ['message']
+
+ self.sas_algs = 'sas28x5'
+ self.cipher = AES
+ self.hash_alg = sha256
+ self.compression = None
+
+ for name in form.asDict():
+ field = form.getField(name)
+ options = [x[1] for x in field.getOptions()]
+ values = field.getValues()
+
+ if not field.getType() in ('list-single', 'list-multi'):
+ options = values
+
+ if name in fixed:
+ if fixed[name] in options:
+ negotiated[name] = fixed[name]
+ else:
+ not_acceptable.append(name)
+ elif name == 'rekey_freq':
+ preferred = int(options[0])
+ negotiated['rekey_freq'] = preferred
+ self.rekey_freq = preferred
+ elif name == 'logging':
+ my_prefs = self.logging_preference()
+
+ if my_prefs[0] in options: # our first choice is offered, select it
+ pref = my_prefs[0]
+ negotiated['logging'] = pref
+ else: # see if other acceptable choices are offered
+ for pref in my_prefs:
+ if pref in options:
+ ask_user['logging'] = pref
+ break
+
+ if not 'logging' in ask_user:
+ not_acceptable.append(name)
+ elif name == 'init_pubkey':
+ for x in ('key'):
+ if x in options:
+ negotiated['recv_pubkey'] = x
+ break
+ elif name == 'resp_pubkey':
+ for x in ('hash', 'key'):
+ if x in options:
+ negotiated['send_pubkey'] = x
+ break
+ elif name == 'sign_algs':
+ if (XmlDsig + 'rsa-sha256') in options:
+ negotiated['sign_algs'] = XmlDsig + 'rsa-sha256'
+ else:
+ # FIXME some things are handled elsewhere, some things are
+ # not-implemented
+ pass
+
+ return (negotiated, not_acceptable, ask_user)
+
+ def respond_e2e_bob(self, form, negotiated, not_acceptable):
+ """
+ 4.3 esession response (bob)
+ """
+ response = xmpp.Message()
+ feature = response.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
+
+ x = xmpp.DataForm(typ='submit')
+
+ x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
+ x.addChild(node=xmpp.DataField(name='accept', value='true'))
+
+ for name in negotiated:
+ # some fields are internal and should not be sent
+ if not name in ('send_pubkey', 'recv_pubkey'):
+ x.addChild(node=xmpp.DataField(name=name, value=negotiated[name]))
+
+ self.negotiated = negotiated
+
+ # the offset of the group we chose (need it to match up with the dhhash)
+ group_order = 0
+ self.modp = int(form.getField('modp').getOptions()[group_order][1])
+ x.addChild(node=xmpp.DataField(name='modp', value=self.modp))
+
+ g = dh.generators[self.modp]
+ p = dh.primes[self.modp]
+
+ self.n_o = base64.b64decode(form['my_nonce'])
+
+ dhhashes = form.getField('dhhashes').getValues()
+ self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode(
+ 'utf8'))
+
+ bytes = int(self.n / 8)
+
+ self.n_s = crypto.generate_nonce()
+
+ # n-bit random number
+ self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes))
+ self.c_s = self.c_o ^ (2 ** (self.n - 1))
+
+ self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1)
+ self.d = crypto.powmod(g, self.y, p)
+
+ to_add = {'my_nonce': self.n_s,
+ 'dhkeys': crypto.encode_mpi(self.d),
+ 'counter': crypto.encode_mpi(self.c_o),
+ 'nonce': self.n_o}
+
+ for name in to_add:
+ b64ed = base64.b64encode(to_add[name])
+ x.addChild(node=xmpp.DataField(name=name, value=b64ed))
+
+ self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
+ in form.getChildren())
+ self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
+ in x.getChildren())
+
+ self.status = 'responded-e2e'
+
+ feature.addChild(node=x)
+
+ if not_acceptable:
+ response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE)
+
+ feature = xmpp.Node(xmpp.NS_FEATURE + ' feature')
+
+ for f in not_acceptable:
+ n = xmpp.Node('field')
+ n['var'] = f
+ feature.addChild(node=n)
+
+ response.T.error.addChild(node=feature)
+
+ self.send(response)
+
+ def verify_options_alice(self, form):
+ """
+ 'Alice Accepts'
+ """
+ negotiated = {}
+ ask_user = {}
+ not_acceptable = []
+
+ if not form['logging'] in self.logging_preference():
+ not_acceptable.append(form['logging'])
+ elif form['logging'] != self.logging_preference()[0]:
+ ask_user['logging'] = form['logging']
+ else:
+ negotiated['logging'] = self.logging_preference()[0]
- for r,a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey',
- 'init_pubkey')):
- negotiated[r] = None
+ for r,a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey',
+ 'init_pubkey')):
+ negotiated[r] = None
- if a in form.asDict() and form[a] in ('key', 'hash'):
- negotiated[r] = form[a]
+ if a in form.asDict() and form[a] in ('key', 'hash'):
+ negotiated[r] = form[a]
- if 'sign_algs' in form.asDict():
- if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ):
- negotiated['sign_algs'] = form['sign_algs']
- else:
- not_acceptable.append(form['sign_algs'])
+ if 'sign_algs' in form.asDict():
+ if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ):
+ negotiated['sign_algs'] = form['sign_algs']
+ else:
+ not_acceptable.append(form['sign_algs'])
- return (negotiated, not_acceptable, ask_user)
+ return (negotiated, not_acceptable, ask_user)
- def accept_e2e_alice(self, form, negotiated):
- """
- 'Alice Accepts', continued
- """
- self.encryptable_stanzas = ['message']
- self.sas_algs = 'sas28x5'
- self.cipher = AES
- self.hash_alg = sha256
- self.compression = None
+ def accept_e2e_alice(self, form, negotiated):
+ """
+ 'Alice Accepts', continued
+ """
+ self.encryptable_stanzas = ['message']
+ self.sas_algs = 'sas28x5'
+ self.cipher = AES
+ self.hash_alg = sha256
+ self.compression = None
- self.negotiated = negotiated
+ self.negotiated = negotiated
- accept = xmpp.Message()
- feature = accept.NT.feature
- feature.setNamespace(xmpp.NS_FEATURE)
+ accept = xmpp.Message()
+ feature = accept.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
- result = xmpp.DataForm(typ='result')
+ result = xmpp.DataForm(typ='result')
- self.c_s = crypto.decode_mpi(base64.b64decode(form['counter']))
- self.c_o = self.c_s ^ (2 ** (self.n - 1))
- self.n_o = base64.b64decode(form['my_nonce'])
+ self.c_s = crypto.decode_mpi(base64.b64decode(form['counter']))
+ self.c_o = self.c_s ^ (2 ** (self.n - 1))
+ self.n_o = base64.b64decode(form['my_nonce'])
- mod_p = int(form['modp'])
- p = dh.primes[mod_p]
- x = self.xes[mod_p]
- e = self.es[mod_p]
+ mod_p = int(form['modp'])
+ p = dh.primes[mod_p]
+ x = self.xes[mod_p]
+ e = self.es[mod_p]
- self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
- self.k = self.get_shared_secret(self.d, x, p)
+ self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
+ self.k = self.get_shared_secret(self.d, x, p)
- result.addChild(node=xmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- result.addChild(node=xmpp.DataField(name='accept', value='1'))
- result.addChild(node=xmpp.DataField(name='nonce',
- value=base64.b64encode(self.n_o)))
-
- self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
+ result.addChild(node=xmpp.DataField(name='FORM_TYPE',
+ value='urn:xmpp:ssn'))
+ result.addChild(node=xmpp.DataField(name='accept', value='1'))
+ result.addChild(node=xmpp.DataField(name='nonce',
+ value=base64.b64encode(self.n_o)))
+
+ self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
- if self.sigmai:
- self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
- self.verify_identity(form, self.d, True, 'b')
- else:
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
- rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses]
+ if self.sigmai:
+ self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
+ self.verify_identity(form, self.d, True, 'b')
+ else:
+ srses = secrets.secrets().retained_secrets(self.conn.name,
+ self.jid.getStripped())
+ rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses]
- if not rshashes:
- # we've never spoken before, but we'll pretend we have
- rshash_size = self.hash_alg().digest_size
- rshashes.append(crypto.random_bytes(rshash_size))
+ if not rshashes:
+ # we've never spoken before, but we'll pretend we have
+ rshash_size = self.hash_alg().digest_size
+ rshashes.append(crypto.random_bytes(rshash_size))
- rshashes = [base64.b64encode(rshash) for rshash in rshashes]
- result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes))
- result.addChild(node=xmpp.DataField(name='dhkeys',
- value=base64.b64encode(crypto.encode_mpi(e))))
+ rshashes = [base64.b64encode(rshash) for rshash in rshashes]
+ result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes))
+ result.addChild(node=xmpp.DataField(name='dhkeys',
+ value=base64.b64encode(crypto.encode_mpi(e))))
- self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
- el in form.getChildren())
+ self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
+ el in form.getChildren())
- # MUST securely destroy K unless it will be used later to generate the
- # final shared secret
+ # MUST securely destroy K unless it will be used later to generate the
+ # final shared secret
- for datafield in self.make_identity(result, e):
- result.addChild(node=datafield)
+ for datafield in self.make_identity(result, e):
+ result.addChild(node=datafield)
- feature.addChild(node=result)
- self.send(accept)
+ feature.addChild(node=result)
+ self.send(accept)
- if self.sigmai:
- self.status = 'active'
- self.enable_encryption = True
- else:
- self.status = 'identified-alice'
-
- def accept_e2e_bob(self, form):
- """
- 4.5 esession accept (bob)
- """
- response = xmpp.Message()
+ if self.sigmai:
+ self.status = 'active'
+ self.enable_encryption = True
+ else:
+ self.status = 'identified-alice'
+
+ def accept_e2e_bob(self, form):
+ """
+ 4.5 esession accept (bob)
+ """
+ response = xmpp.Message()
- init = response.NT.init
- init.setNamespace(xmpp.NS_ESESSION_INIT)
+ init = response.NT.init
+ init.setNamespace(xmpp.NS_ESESSION_INIT)
- x = xmpp.DataForm(typ='result')
+ x = xmpp.DataForm(typ='result')
- for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'):
- # FIXME: will do nothing in real world...
- assert field in form.asDict(), "alice's form didn't have a %s field" \
- % field
+ for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'):
+ # FIXME: will do nothing in real world...
+ assert field in form.asDict(), "alice's form didn't have a %s field" \
+ % field
- # 4.5.1 generating provisory session keys
- e = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
- p = dh.primes[self.modp]
+ # 4.5.1 generating provisory session keys
+ e = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
+ p = dh.primes[self.modp]
- if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']:
- raise NegotiationError('SHA256(e) != He')
+ if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']:
+ raise NegotiationError('SHA256(e) != He')
- k = self.get_shared_secret(e, self.y, p)
- self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
+ k = self.get_shared_secret(e, self.y, p)
+ self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
- # 4.5.2 verifying alice's identity
- self.verify_identity(form, e, False, 'a')
+ # 4.5.2 verifying alice's identity
+ self.verify_identity(form, e, False, 'a')
- # 4.5.4 generating bob's final session keys
- srs = ''
+ # 4.5.4 generating bob's final session keys
+ srs = ''
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
- rshashes = [base64.b64decode(rshash) for rshash in form.getField(
- 'rshashes').getValues()]
+ srses = secrets.secrets().retained_secrets(self.conn.name,
+ self.jid.getStripped())
+ rshashes = [base64.b64decode(rshash) for rshash in form.getField(
+ 'rshashes').getValues()]
- for s in srses:
- secret = s[0]
- if self.hmac(self.n_o, secret) in rshashes:
- srs = secret
- break
+ for s in srses:
+ secret = s[0]
+ if self.hmac(self.n_o, secret) in rshashes:
+ srs = secret
+ break
- # other shared secret
- # (we're not using one)
- oss = ''
-
- k = crypto.sha256(k + srs + oss)
-
- self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k)
- self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
-
- # 4.5.5
- if srs:
- srshash = self.hmac(srs, 'Shared Retained Secret')
- else:
- srshash = crypto.random_bytes(32)
-
- x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
- x.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode(
- self.n_o)))
- x.addChild(node=xmpp.DataField(name='srshash', value=base64.b64encode(
- srshash)))
-
- for datafield in self.make_identity(x, self.d):
- x.addChild(node=datafield)
+ # other shared secret
+ # (we're not using one)
+ oss = ''
+
+ k = crypto.sha256(k + srs + oss)
+
+ self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k)
+ self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
+
+ # 4.5.5
+ if srs:
+ srshash = self.hmac(srs, 'Shared Retained Secret')
+ else:
+ srshash = crypto.random_bytes(32)
+
+ x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
+ x.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode(
+ self.n_o)))
+ x.addChild(node=xmpp.DataField(name='srshash', value=base64.b64encode(
+ srshash)))
+
+ for datafield in self.make_identity(x, self.d):
+ x.addChild(node=datafield)
- init.addChild(node=x)
+ init.addChild(node=x)
- self.send(response)
+ self.send(response)
- self.do_retained_secret(k, srs)
+ self.do_retained_secret(k, srs)
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
+ if self.negotiated['logging'] == 'mustnot':
+ self.loggable = False
- self.status = 'active'
- self.enable_encryption = True
+ self.status = 'active'
+ self.enable_encryption = True
- if self.control:
- self.control.print_esession_details()
+ if self.control:
+ self.control.print_esession_details()
- def final_steps_alice(self, form):
- srs = ''
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
+ def final_steps_alice(self, form):
+ srs = ''
+ srses = secrets.secrets().retained_secrets(self.conn.name,
+ self.jid.getStripped())
- try:
- srshash = base64.b64decode(form['srshash'])
- except IndexError:
- return
+ try:
+ srshash = base64.b64decode(form['srshash'])
+ except IndexError:
+ return
- for s in srses:
- secret = s[0]
- if self.hmac(secret, 'Shared Retained Secret') == srshash:
- srs = secret
- break
+ for s in srses:
+ secret = s[0]
+ if self.hmac(secret, 'Shared Retained Secret') == srshash:
+ srs = secret
+ break
- oss = ''
- k = crypto.sha256(self.k + srs + oss)
- del self.k
+ oss = ''
+ k = crypto.sha256(self.k + srs + oss)
+ del self.k
- self.do_retained_secret(k, srs)
+ self.do_retained_secret(k, srs)
- # ks_s doesn't need to be calculated here
- self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k)
- self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k)
+ # ks_s doesn't need to be calculated here
+ self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k)
+ self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k)
- # 4.6.2 Verifying Bob's Identity
- self.verify_identity(form, self.d, False, 'b')
- # Note: If Alice discovers an error then she SHOULD ignore any encrypted
- # content she received in the stanza.
+ # 4.6.2 Verifying Bob's Identity
+ self.verify_identity(form, self.d, False, 'b')
+ # Note: If Alice discovers an error then she SHOULD ignore any encrypted
+ # content she received in the stanza.
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
+ if self.negotiated['logging'] == 'mustnot':
+ self.loggable = False
- self.status = 'active'
- self.enable_encryption = True
+ self.status = 'active'
+ self.enable_encryption = True
- if self.control:
- self.control.print_esession_details()
+ if self.control:
+ self.control.print_esession_details()
- def do_retained_secret(self, k, old_srs):
- """
- Calculate the new retained secret. determine if the user needs to check
- the remote party's identity. Set up callbacks for when the identity has
- been verified
- """
- new_srs = self.hmac(k, 'New Retained Secret')
- self.srs = new_srs
+ def do_retained_secret(self, k, old_srs):
+ """
+ Calculate the new retained secret. determine if the user needs to check
+ the remote party's identity. Set up callbacks for when the identity has
+ been verified
+ """
+ new_srs = self.hmac(k, 'New Retained Secret')
+ self.srs = new_srs
- account = self.conn.name
- bjid = self.jid.getStripped()
+ account = self.conn.name
+ bjid = self.jid.getStripped()
- self.verified_identity = False
+ self.verified_identity = False
- if old_srs:
- if secrets.secrets().srs_verified(account, bjid, old_srs):
- # already had a stored secret verified by the user.
- secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True)
- # continue without warning.
- self.verified_identity = True
- else:
- # had a secret, but it wasn't verified.
- secrets.secrets().replace_srs(account, bjid, old_srs, new_srs,
- False)
- else:
- # we don't even have an SRS
- secrets.secrets().save_new_srs(account, bjid, new_srs, False)
+ if old_srs:
+ if secrets.secrets().srs_verified(account, bjid, old_srs):
+ # already had a stored secret verified by the user.
+ secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True)
+ # continue without warning.
+ self.verified_identity = True
+ else:
+ # had a secret, but it wasn't verified.
+ secrets.secrets().replace_srs(account, bjid, old_srs, new_srs,
+ False)
+ else:
+ # we don't even have an SRS
+ secrets.secrets().save_new_srs(account, bjid, new_srs, False)
- def _verified_srs_cb(self):
- secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
- self.srs, self.srs, True)
+ def _verified_srs_cb(self):
+ secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
+ self.srs, self.srs, True)
- def _unverified_srs_cb(self):
- secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
- self.srs, self.srs, False)
+ def _unverified_srs_cb(self):
+ secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
+ self.srs, self.srs, False)
- def make_dhfield(self, modp_options, sigmai):
- dhs = []
+ def make_dhfield(self, modp_options, sigmai):
+ dhs = []
- for modp in modp_options:
- p = dh.primes[modp]
- g = dh.generators[modp]
+ for modp in modp_options:
+ p = dh.primes[modp]
+ g = dh.generators[modp]
- x = crypto.srand(2 ** (2 * self.n - 1), p - 1)
+ x = crypto.srand(2 ** (2 * self.n - 1), p - 1)
- # FIXME this may be a source of performance issues
- e = crypto.powmod(g, x, p)
+ # FIXME this may be a source of performance issues
+ e = crypto.powmod(g, x, p)
- self.xes[modp] = x
- self.es[modp] = e
-
- if sigmai:
- dhs.append(base64.b64encode(crypto.encode_mpi(e)))
- name = 'dhkeys'
- else:
- He = crypto.sha256(crypto.encode_mpi(e))
- dhs.append(base64.b64encode(He))
- name = 'dhhashes'
-
- return xmpp.DataField(name=name, typ='hidden', value=dhs)
-
- def terminate_e2e(self):
- self.terminate()
- self.enable_encryption = False
-
- def acknowledge_termination(self):
- StanzaSession.acknowledge_termination(self)
- self.enable_encryption = False
-
- def fail_bad_negotiation(self, reason, fields=None):
- """
- Send an error and cancels everything
-
- If fields is None, the remote party has given us a bad cryptographic
- value of some kind. Otherwise, list the fields we haven't implemented.
- """
- err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
- err.T.error.T.text.setData(reason)
-
- if fields:
- feature = xmpp.Node(xmpp.NS_FEATURE + ' feature')
-
- for field in fields:
- fn = xmpp.Node('field')
- fn['var'] = field
- feature.addChild(node=feature)
-
- err.addChild(node=feature)
-
- self.send(err)
-
- self.status = None
- self.enable_encryption = False
-
- # this prevents the MAC check on decryption from succeeding,
- # preventing falsified messages from going through.
- self.km_o = ''
-
- def cancelled_negotiation(self):
- StanzaSession.cancelled_negotiation(self)
- self.enable_encryption = False
- self.km_o = ''
-
-# vim: se ts=3:
+ self.xes[modp] = x
+ self.es[modp] = e
+
+ if sigmai:
+ dhs.append(base64.b64encode(crypto.encode_mpi(e)))
+ name = 'dhkeys'
+ else:
+ He = crypto.sha256(crypto.encode_mpi(e))
+ dhs.append(base64.b64encode(He))
+ name = 'dhhashes'
+
+ return xmpp.DataField(name=name, typ='hidden', value=dhs)
+
+ def terminate_e2e(self):
+ self.terminate()
+ self.enable_encryption = False
+
+ def acknowledge_termination(self):
+ StanzaSession.acknowledge_termination(self)
+ self.enable_encryption = False
+
+ def fail_bad_negotiation(self, reason, fields=None):
+ """
+ Send an error and cancels everything
+
+ If fields is None, the remote party has given us a bad cryptographic
+ value of some kind. Otherwise, list the fields we haven't implemented.
+ """
+ err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
+ err.T.error.T.text.setData(reason)
+
+ if fields:
+ feature = xmpp.Node(xmpp.NS_FEATURE + ' feature')
+
+ for field in fields:
+ fn = xmpp.Node('field')
+ fn['var'] = field
+ feature.addChild(node=feature)
+
+ err.addChild(node=feature)
+
+ self.send(err)
+
+ self.status = None
+ self.enable_encryption = False
+
+ # this prevents the MAC check on decryption from succeeding,
+ # preventing falsified messages from going through.
+ self.km_o = ''
+
+ def cancelled_negotiation(self):
+ StanzaSession.cancelled_negotiation(self)
+ self.enable_encryption = False
+ self.km_o = ''
diff --git a/src/common/xmpp/__init__.py b/src/common/xmpp/__init__.py
index 8f0fb3d48..4ebc4cfa3 100644
--- a/src/common/xmpp/__init__.py
+++ b/src/common/xmpp/__init__.py
@@ -15,5 +15,3 @@ import simplexml, protocol, auth_nb, transports_nb, roster_nb
import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb, proxy_connectors
from client_nb import NonBlockingClient
from plugin import PlugIn
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py
index 9872f651b..d5077e3ac 100644
--- a/src/common/xmpp/auth_nb.py
+++ b/src/common/xmpp/auth_nb.py
@@ -38,10 +38,10 @@ def H(some): return hashlib.md5(some).digest()
def C(some): return ':'.join(some)
try:
- import kerberos
- have_kerberos = True
+ import kerberos
+ have_kerberos = True
except ImportError:
- have_kerberos = False
+ have_kerberos = False
GSS_STATE_STEP = 0
GSS_STATE_WRAP = 1
@@ -51,518 +51,516 @@ SASL_UNSUPPORTED = 'not-supported'
SASL_IN_PROCESS = 'in-process'
def challenge_splitter(data):
- """
- Helper function that creates a dict from challenge string
-
- Sample challenge string:
- username="example.org",realm="somerealm",\
- nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
- nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8
-
- Expected result for challan:
- dict['qop'] = ('auth','auth-int','auth-conf')
- dict['realm'] = 'somerealm'
- """
- X_KEYWORD, X_VALUE, X_END = 0, 1, 2
- quotes_open = False
- keyword, value = '', ''
- dict_ = {}
- arr = None
-
- expecting = X_KEYWORD
- for iter_ in range(len(data) + 1):
- end = False
- if iter_ == len(data):
- expecting = X_END
- end = True
- else:
- char = data[iter_]
- if expecting == X_KEYWORD:
- if char == '=':
- expecting = X_VALUE
- elif char in (',', ' ', '\t'):
- pass
- else:
- keyword = '%s%c' % (keyword, char)
- elif expecting == X_VALUE:
- if char == '"':
- if quotes_open:
- end = True
- else:
- quotes_open = True
- elif char in (',', ' ', '\t'):
- if quotes_open:
- if not arr:
- arr = [value]
- else:
- arr.append(value)
- value = ""
- else:
- end = True
- else:
- value = '%s%c' % (value, char)
- if end:
- if arr:
- arr.append(value)
- dict_[keyword] = arr
- arr = None
- else:
- dict_[keyword] = value
- value, keyword = '', ''
- expecting = X_KEYWORD
- quotes_open = False
- return dict_
+ """
+ Helper function that creates a dict from challenge string
+
+ Sample challenge string:
+ username="example.org",realm="somerealm",\
+ nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
+ nc=00000001,qop="auth,auth-int,auth-conf",charset=utf-8
+
+ Expected result for challan:
+ dict['qop'] = ('auth','auth-int','auth-conf')
+ dict['realm'] = 'somerealm'
+ """
+ X_KEYWORD, X_VALUE, X_END = 0, 1, 2
+ quotes_open = False
+ keyword, value = '', ''
+ dict_ = {}
+ arr = None
+
+ expecting = X_KEYWORD
+ for iter_ in range(len(data) + 1):
+ end = False
+ if iter_ == len(data):
+ expecting = X_END
+ end = True
+ else:
+ char = data[iter_]
+ if expecting == X_KEYWORD:
+ if char == '=':
+ expecting = X_VALUE
+ elif char in (',', ' ', '\t'):
+ pass
+ else:
+ keyword = '%s%c' % (keyword, char)
+ elif expecting == X_VALUE:
+ if char == '"':
+ if quotes_open:
+ end = True
+ else:
+ quotes_open = True
+ elif char in (',', ' ', '\t'):
+ if quotes_open:
+ if not arr:
+ arr = [value]
+ else:
+ arr.append(value)
+ value = ""
+ else:
+ end = True
+ else:
+ value = '%s%c' % (value, char)
+ if end:
+ if arr:
+ arr.append(value)
+ dict_[keyword] = arr
+ arr = None
+ else:
+ dict_[keyword] = value
+ value, keyword = '', ''
+ expecting = X_KEYWORD
+ quotes_open = False
+ return dict_
class SASL(PlugIn):
- """
- Implements SASL authentication. Can be plugged into NonBlockingClient
- to start authentication
- """
-
- def __init__(self, username, password, on_sasl):
- """
- :param user: XMPP username
- :param password: XMPP password
- :param on_sasl: Callback, will be called after each SASL auth-step.
- """
- PlugIn.__init__(self)
- self.username = username
- self.password = password
- self.on_sasl = on_sasl
- self.realm = None
-
- def plugin(self, owner):
- if 'version' not in self._owner.Dispatcher.Stream._document_attrs:
- self.startsasl = SASL_UNSUPPORTED
- elif self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher,
- self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self.startsasl = None
-
- def plugout(self):
- """
- Remove SASL handlers from owner's dispatcher. Used internally
- """
- if 'features' in self._owner.__dict__:
- self._owner.UnregisterHandler('features', self.FeaturesHandler,
- xmlns=NS_STREAMS)
- if 'challenge' in self._owner.__dict__:
- self._owner.UnregisterHandler('challenge', self.SASLHandler,
- xmlns=NS_SASL)
- if 'failure' in self._owner.__dict__:
- self._owner.UnregisterHandler('failure', self.SASLHandler,
- xmlns=NS_SASL)
- if 'success' in self._owner.__dict__:
- self._owner.UnregisterHandler('success', self.SASLHandler,
- xmlns=NS_SASL)
-
- def auth(self):
- """
- Start authentication. Result can be obtained via "SASL.startsasl"
- attribute and will be either SASL_SUCCESS or SASL_FAILURE
-
- Note that successfull auth will take at least two Dispatcher.Process()
- calls.
- """
- if self.startsasl:
- pass
- elif self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher,
- self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandler('features',
- self.FeaturesHandler, xmlns=NS_STREAMS)
-
- def FeaturesHandler(self, conn, feats):
- """
- Used to determine if server supports SASL auth. Used internally
- """
- if not feats.getTag('mechanisms', namespace=NS_SASL):
- self.startsasl='not-supported'
- log.error('SASL not supported by server')
- return
- self.mecs = []
- for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags(
- 'mechanism'):
- self.mecs.append(mec.getData())
-
- self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL)
- self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
- self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
- self.MechanismHandler()
-
- def MechanismHandler(self):
- if 'ANONYMOUS' in self.mecs and self.username is None:
- self.mecs.remove('ANONYMOUS')
- node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'ANONYMOUS'})
- self.mechanism = 'ANONYMOUS'
- self.startsasl = SASL_IN_PROCESS
- self._owner.send(str(node))
- raise NodeProcessed
- if "EXTERNAL" in self.mecs:
- self.mecs.remove('EXTERNAL')
- node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'EXTERNAL'},
- payload=[base64.encodestring('%s@%s' % (self.username,
- self._owner.Server)).replace('\n', '')])
- self.startsasl = SASL_IN_PROCESS
- self._owner.send(str(node))
- raise NodeProcessed
- if 'GSSAPI' in self.mecs and have_kerberos:
- self.mecs.remove('GSSAPI')
- try:
- self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \
- self._owner.xmpp_hostname)[1]
- kerberos.authGSSClientStep(self.gss_vc, '')
- response = kerberos.authGSSClientResponse(self.gss_vc)
- node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'GSSAPI'},
- payload=(response or ''))
- self.mechanism = 'GSSAPI'
- self.gss_step = GSS_STATE_STEP
- self.startsasl = SASL_IN_PROCESS
- self._owner.send(str(node))
- raise NodeProcessed
- except kerberos.GSSError, e:
- log.info('GSSAPI authentication failed: %s' % str(e))
- if 'DIGEST-MD5' in self.mecs:
- self.mecs.remove('DIGEST-MD5')
- node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
- self.mechanism = 'DIGEST-MD5'
- self.startsasl = SASL_IN_PROCESS
- self._owner.send(str(node))
- raise NodeProcessed
- if 'PLAIN' in self.mecs:
- self.mecs.remove('PLAIN')
- self.mechanism = 'PLAIN'
- self._owner._caller.get_password(self.set_password)
- self.startsasl = SASL_IN_PROCESS
- raise NodeProcessed
- self.startsasl = SASL_FAILURE
- log.error('I can only use EXTERNAL, DIGEST-MD5, GSSAPI and PLAIN '
- 'mecanisms.')
- if self.on_sasl:
- self.on_sasl()
- return
-
- def SASLHandler(self, conn, challenge):
- """
- Perform next SASL auth step. Used internally
- """
- if challenge.getNamespace() != NS_SASL:
- return
- ### Handle Auth result
- if challenge.getName() == 'failure':
- self.startsasl = SASL_FAILURE
- try:
- reason = challenge.getChildren()[0]
- except Exception:
- reason = challenge
- log.error('Failed SASL authentification: %s' % reason)
- if len(self.mecs) > 0:
- # There are other mechanisms to test
- self.MechanismHandler()
- raise NodeProcessed
- if self.on_sasl:
- self.on_sasl()
- raise NodeProcessed
- elif challenge.getName() == 'success':
- self.startsasl = SASL_SUCCESS
- log.info('Successfully authenticated with remote server.')
- handlers = self._owner.Dispatcher.dumpHandlers()
-
- # Bosh specific dispatcher replugging
- # save old features. They will be used in case we won't get response on
- # stream restart after SASL auth (happens with XMPP over BOSH with
- # Openfire)
- old_features = self._owner.Dispatcher.Stream.features
- self._owner.Dispatcher.PlugOut()
- dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner,
- after_SASL=True, old_features=old_features)
- self._owner.Dispatcher.restoreHandlers(handlers)
- self._owner.User = self.username
-
- if self.on_sasl:
- self.on_sasl()
- raise NodeProcessed
-
- ### Perform auth step
- incoming_data = challenge.getData()
- data=base64.decodestring(incoming_data)
- log.info('Got challenge:' + data)
-
- if self.mechanism == 'GSSAPI':
- if self.gss_step == GSS_STATE_STEP:
- rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data)
- if rc != kerberos.AUTH_GSS_CONTINUE:
- self.gss_step = GSS_STATE_WRAP
- elif self.gss_step == GSS_STATE_WRAP:
- rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data)
- response = kerberos.authGSSClientResponse(self.gss_vc)
- rc = kerberos.authGSSClientWrap(self.gss_vc, response,
- kerberos.authGSSClientUserName(self.gss_vc))
- response = kerberos.authGSSClientResponse(self.gss_vc)
- if not response:
- response = ''
- self._owner.send(Node('response', attrs={'xmlns':NS_SASL},
- payload=response).__str__())
- raise NodeProcessed
-
- # magic foo...
- chal = challenge_splitter(data)
- if not self.realm and 'realm' in chal:
- self.realm = chal['realm']
- if 'qop' in chal and ((isinstance(chal['qop'], str) and \
- chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \
- chal['qop'])):
- self.resp = {}
- self.resp['username'] = self.username
- if self.realm:
- self.resp['realm'] = self.realm
- else:
- self.resp['realm'] = self._owner.Server
- self.resp['nonce'] = chal['nonce']
- self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in
- itertools.repeat(random.randint, 7))
- self.resp['nc'] = ('00000001')
- self.resp['qop'] = 'auth'
- self.resp['digest-uri'] = 'xmpp/' + self._owner.Server
- self.resp['charset'] = 'utf-8'
- # Password is now required
- self._owner._caller.get_password(self.set_password)
- elif 'rspauth' in chal:
- self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL})))
- else:
- self.startsasl = SASL_FAILURE
- log.error('Failed SASL authentification: unknown challenge')
- if self.on_sasl:
- self.on_sasl()
- raise NodeProcessed
-
- def set_password(self, password):
- if password is None:
- self.password = ''
- else:
- self.password = password
- if self.mechanism == 'DIGEST-MD5':
- def convert_to_iso88591(string):
- try:
- string = string.decode('utf-8').encode('iso-8859-1')
- except UnicodeEncodeError:
- pass
- return string
- hash_username = convert_to_iso88591(self.resp['username'])
- hash_realm = convert_to_iso88591(self.resp['realm'])
- hash_password = convert_to_iso88591(self.password)
- A1 = C([H(C([hash_username, hash_realm, hash_password])),
- self.resp['nonce'], self.resp['cnonce']])
- A2 = C(['AUTHENTICATE', self.resp['digest-uri']])
- response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'],
- self.resp['cnonce'], self.resp['qop'], HH(A2)]))
- self.resp['response'] = response
- sasl_data = u''
- for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce',
- 'digest-uri', 'response', 'qop'):
- if key in ('nc','qop','response','charset'):
- sasl_data += u"%s=%s," % (key, self.resp[key])
- else:
- sasl_data += u'%s="%s",' % (key, self.resp[key])
- sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace(
- '\r', '').replace('\n', '')
- node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data])
- elif self.mechanism == 'PLAIN':
- sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \
- self._owner.Server, self.username, self.password)
- sasl_data = sasl_data.encode('utf-8').encode('base64').replace(
- '\n', '')
- node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'},
- payload=[sasl_data])
- self._owner.send(str(node))
+ """
+ Implements SASL authentication. Can be plugged into NonBlockingClient
+ to start authentication
+ """
+
+ def __init__(self, username, password, on_sasl):
+ """
+ :param user: XMPP username
+ :param password: XMPP password
+ :param on_sasl: Callback, will be called after each SASL auth-step.
+ """
+ PlugIn.__init__(self)
+ self.username = username
+ self.password = password
+ self.on_sasl = on_sasl
+ self.realm = None
+
+ def plugin(self, owner):
+ if 'version' not in self._owner.Dispatcher.Stream._document_attrs:
+ self.startsasl = SASL_UNSUPPORTED
+ elif self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher,
+ self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self.startsasl = None
+
+ def plugout(self):
+ """
+ Remove SASL handlers from owner's dispatcher. Used internally
+ """
+ if 'features' in self._owner.__dict__:
+ self._owner.UnregisterHandler('features', self.FeaturesHandler,
+ xmlns=NS_STREAMS)
+ if 'challenge' in self._owner.__dict__:
+ self._owner.UnregisterHandler('challenge', self.SASLHandler,
+ xmlns=NS_SASL)
+ if 'failure' in self._owner.__dict__:
+ self._owner.UnregisterHandler('failure', self.SASLHandler,
+ xmlns=NS_SASL)
+ if 'success' in self._owner.__dict__:
+ self._owner.UnregisterHandler('success', self.SASLHandler,
+ xmlns=NS_SASL)
+
+ def auth(self):
+ """
+ Start authentication. Result can be obtained via "SASL.startsasl"
+ attribute and will be either SASL_SUCCESS or SASL_FAILURE
+
+ Note that successfull auth will take at least two Dispatcher.Process()
+ calls.
+ """
+ if self.startsasl:
+ pass
+ elif self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher,
+ self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self._owner.RegisterHandler('features',
+ self.FeaturesHandler, xmlns=NS_STREAMS)
+
+ def FeaturesHandler(self, conn, feats):
+ """
+ Used to determine if server supports SASL auth. Used internally
+ """
+ if not feats.getTag('mechanisms', namespace=NS_SASL):
+ self.startsasl='not-supported'
+ log.error('SASL not supported by server')
+ return
+ self.mecs = []
+ for mec in feats.getTag('mechanisms', namespace=NS_SASL).getTags(
+ 'mechanism'):
+ self.mecs.append(mec.getData())
+
+ self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL)
+ self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
+ self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
+ self.MechanismHandler()
+
+ def MechanismHandler(self):
+ if 'ANONYMOUS' in self.mecs and self.username is None:
+ self.mecs.remove('ANONYMOUS')
+ node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'ANONYMOUS'})
+ self.mechanism = 'ANONYMOUS'
+ self.startsasl = SASL_IN_PROCESS
+ self._owner.send(str(node))
+ raise NodeProcessed
+ if "EXTERNAL" in self.mecs:
+ self.mecs.remove('EXTERNAL')
+ node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'EXTERNAL'},
+ payload=[base64.encodestring('%s@%s' % (self.username,
+ self._owner.Server)).replace('\n', '')])
+ self.startsasl = SASL_IN_PROCESS
+ self._owner.send(str(node))
+ raise NodeProcessed
+ if 'GSSAPI' in self.mecs and have_kerberos:
+ self.mecs.remove('GSSAPI')
+ try:
+ self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \
+ self._owner.xmpp_hostname)[1]
+ kerberos.authGSSClientStep(self.gss_vc, '')
+ response = kerberos.authGSSClientResponse(self.gss_vc)
+ node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'GSSAPI'},
+ payload=(response or ''))
+ self.mechanism = 'GSSAPI'
+ self.gss_step = GSS_STATE_STEP
+ self.startsasl = SASL_IN_PROCESS
+ self._owner.send(str(node))
+ raise NodeProcessed
+ except kerberos.GSSError, e:
+ log.info('GSSAPI authentication failed: %s' % str(e))
+ if 'DIGEST-MD5' in self.mecs:
+ self.mecs.remove('DIGEST-MD5')
+ node = Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
+ self.mechanism = 'DIGEST-MD5'
+ self.startsasl = SASL_IN_PROCESS
+ self._owner.send(str(node))
+ raise NodeProcessed
+ if 'PLAIN' in self.mecs:
+ self.mecs.remove('PLAIN')
+ self.mechanism = 'PLAIN'
+ self._owner._caller.get_password(self.set_password)
+ self.startsasl = SASL_IN_PROCESS
+ raise NodeProcessed
+ self.startsasl = SASL_FAILURE
+ log.error('I can only use EXTERNAL, DIGEST-MD5, GSSAPI and PLAIN '
+ 'mecanisms.')
+ if self.on_sasl:
+ self.on_sasl()
+ return
+
+ def SASLHandler(self, conn, challenge):
+ """
+ Perform next SASL auth step. Used internally
+ """
+ if challenge.getNamespace() != NS_SASL:
+ return
+ ### Handle Auth result
+ if challenge.getName() == 'failure':
+ self.startsasl = SASL_FAILURE
+ try:
+ reason = challenge.getChildren()[0]
+ except Exception:
+ reason = challenge
+ log.error('Failed SASL authentification: %s' % reason)
+ if len(self.mecs) > 0:
+ # There are other mechanisms to test
+ self.MechanismHandler()
+ raise NodeProcessed
+ if self.on_sasl:
+ self.on_sasl()
+ raise NodeProcessed
+ elif challenge.getName() == 'success':
+ self.startsasl = SASL_SUCCESS
+ log.info('Successfully authenticated with remote server.')
+ handlers = self._owner.Dispatcher.dumpHandlers()
+
+ # Bosh specific dispatcher replugging
+ # save old features. They will be used in case we won't get response on
+ # stream restart after SASL auth (happens with XMPP over BOSH with
+ # Openfire)
+ old_features = self._owner.Dispatcher.Stream.features
+ self._owner.Dispatcher.PlugOut()
+ dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner,
+ after_SASL=True, old_features=old_features)
+ self._owner.Dispatcher.restoreHandlers(handlers)
+ self._owner.User = self.username
+
+ if self.on_sasl:
+ self.on_sasl()
+ raise NodeProcessed
+
+ ### Perform auth step
+ incoming_data = challenge.getData()
+ data=base64.decodestring(incoming_data)
+ log.info('Got challenge:' + data)
+
+ if self.mechanism == 'GSSAPI':
+ if self.gss_step == GSS_STATE_STEP:
+ rc = kerberos.authGSSClientStep(self.gss_vc, incoming_data)
+ if rc != kerberos.AUTH_GSS_CONTINUE:
+ self.gss_step = GSS_STATE_WRAP
+ elif self.gss_step == GSS_STATE_WRAP:
+ rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data)
+ response = kerberos.authGSSClientResponse(self.gss_vc)
+ rc = kerberos.authGSSClientWrap(self.gss_vc, response,
+ kerberos.authGSSClientUserName(self.gss_vc))
+ response = kerberos.authGSSClientResponse(self.gss_vc)
+ if not response:
+ response = ''
+ self._owner.send(Node('response', attrs={'xmlns':NS_SASL},
+ payload=response).__str__())
+ raise NodeProcessed
+
+ # magic foo...
+ chal = challenge_splitter(data)
+ if not self.realm and 'realm' in chal:
+ self.realm = chal['realm']
+ if 'qop' in chal and ((isinstance(chal['qop'], str) and \
+ chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \
+ chal['qop'])):
+ self.resp = {}
+ self.resp['username'] = self.username
+ if self.realm:
+ self.resp['realm'] = self.realm
+ else:
+ self.resp['realm'] = self._owner.Server
+ self.resp['nonce'] = chal['nonce']
+ self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in
+ itertools.repeat(random.randint, 7))
+ self.resp['nc'] = ('00000001')
+ self.resp['qop'] = 'auth'
+ self.resp['digest-uri'] = 'xmpp/' + self._owner.Server
+ self.resp['charset'] = 'utf-8'
+ # Password is now required
+ self._owner._caller.get_password(self.set_password)
+ elif 'rspauth' in chal:
+ self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL})))
+ else:
+ self.startsasl = SASL_FAILURE
+ log.error('Failed SASL authentification: unknown challenge')
+ if self.on_sasl:
+ self.on_sasl()
+ raise NodeProcessed
+
+ def set_password(self, password):
+ if password is None:
+ self.password = ''
+ else:
+ self.password = password
+ if self.mechanism == 'DIGEST-MD5':
+ def convert_to_iso88591(string):
+ try:
+ string = string.decode('utf-8').encode('iso-8859-1')
+ except UnicodeEncodeError:
+ pass
+ return string
+ hash_username = convert_to_iso88591(self.resp['username'])
+ hash_realm = convert_to_iso88591(self.resp['realm'])
+ hash_password = convert_to_iso88591(self.password)
+ A1 = C([H(C([hash_username, hash_realm, hash_password])),
+ self.resp['nonce'], self.resp['cnonce']])
+ A2 = C(['AUTHENTICATE', self.resp['digest-uri']])
+ response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'],
+ self.resp['cnonce'], self.resp['qop'], HH(A2)]))
+ self.resp['response'] = response
+ sasl_data = u''
+ for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce',
+ 'digest-uri', 'response', 'qop'):
+ if key in ('nc','qop','response','charset'):
+ sasl_data += u"%s=%s," % (key, self.resp[key])
+ else:
+ sasl_data += u'%s="%s",' % (key, self.resp[key])
+ sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace(
+ '\r', '').replace('\n', '')
+ node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data])
+ elif self.mechanism == 'PLAIN':
+ sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \
+ self._owner.Server, self.username, self.password)
+ sasl_data = sasl_data.encode('utf-8').encode('base64').replace(
+ '\n', '')
+ node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'},
+ payload=[sasl_data])
+ self._owner.send(str(node))
class NonBlockingNonSASL(PlugIn):
- """
- Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
- transport authentication
- """
-
- def __init__(self, user, password, resource, on_auth):
- """
- Caches username, password and resource for auth
- """
- PlugIn.__init__(self)
- self.user = user
- if password is None:
- self.password = ''
- else:
- self.password = password
- self.resource = resource
- self.on_auth = on_auth
-
- def plugin(self, owner):
- """
- Determine the best auth method (digest/0k/plain) and use it for auth.
- Returns used method name on success. Used internally
- """
- log.info('Querying server about possible auth methods')
- self.owner = owner
-
- owner.Dispatcher.SendAndWaitForResponse(
- Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
- func=self._on_username)
-
- def _on_username(self, resp):
- if not isResultNode(resp):
- log.error('No result node arrived! Aborting...')
- return self.on_auth(None)
-
- iq=Iq(typ='set',node=resp)
- query = iq.getTag('query')
- query.setTagData('username',self.user)
- query.setTagData('resource',self.resource)
-
- if query.getTag('digest'):
- log.info("Performing digest authentication")
- query.setTagData('digest',
- hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id']
- + self.password).hexdigest())
- if query.getTag('password'):
- query.delChild('password')
- self._method = 'digest'
- elif query.getTag('token'):
- token = query.getTagData('token')
- seq = query.getTagData('sequence')
- log.info("Performing zero-k authentication")
-
- def hasher(s):
- return hashlib.sha1(s).hexdigest()
-
- def hash_n_times(s, count):
- return count and hasher(hash_n_times(s, count-1)) or s
-
- hash_ = hash_n_times(hasher(hasher(self.password) + token), int(seq))
- query.setTagData('hash', hash_)
- self._method='0k'
- else:
- log.warn("Sequre methods unsupported, performing plain text \
- authentication")
- query.setTagData('password', self.password)
- self._method = 'plain'
- resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,func=self._on_auth)
-
- def _on_auth(self, resp):
- if isResultNode(resp):
- log.info('Sucessfully authenticated with remote host.')
- self.owner.User = self.user
- self.owner.Resource = self.resource
- self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\
- '/'+self.owner.Resource
- return self.on_auth(self._method)
- log.error('Authentication failed!')
- return self.on_auth(None)
+ """
+ Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and
+ transport authentication
+ """
+
+ def __init__(self, user, password, resource, on_auth):
+ """
+ Caches username, password and resource for auth
+ """
+ PlugIn.__init__(self)
+ self.user = user
+ if password is None:
+ self.password = ''
+ else:
+ self.password = password
+ self.resource = resource
+ self.on_auth = on_auth
+
+ def plugin(self, owner):
+ """
+ Determine the best auth method (digest/0k/plain) and use it for auth.
+ Returns used method name on success. Used internally
+ """
+ log.info('Querying server about possible auth methods')
+ self.owner = owner
+
+ owner.Dispatcher.SendAndWaitForResponse(
+ Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
+ func=self._on_username)
+
+ def _on_username(self, resp):
+ if not isResultNode(resp):
+ log.error('No result node arrived! Aborting...')
+ return self.on_auth(None)
+
+ iq=Iq(typ='set',node=resp)
+ query = iq.getTag('query')
+ query.setTagData('username',self.user)
+ query.setTagData('resource',self.resource)
+
+ if query.getTag('digest'):
+ log.info("Performing digest authentication")
+ query.setTagData('digest',
+ hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id']
+ + self.password).hexdigest())
+ if query.getTag('password'):
+ query.delChild('password')
+ self._method = 'digest'
+ elif query.getTag('token'):
+ token = query.getTagData('token')
+ seq = query.getTagData('sequence')
+ log.info("Performing zero-k authentication")
+
+ def hasher(s):
+ return hashlib.sha1(s).hexdigest()
+
+ def hash_n_times(s, count):
+ return count and hasher(hash_n_times(s, count-1)) or s
+
+ hash_ = hash_n_times(hasher(hasher(self.password) + token), int(seq))
+ query.setTagData('hash', hash_)
+ self._method='0k'
+ else:
+ log.warn("Sequre methods unsupported, performing plain text \
+ authentication")
+ query.setTagData('password', self.password)
+ self._method = 'plain'
+ resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,func=self._on_auth)
+
+ def _on_auth(self, resp):
+ if isResultNode(resp):
+ log.info('Sucessfully authenticated with remote host.')
+ self.owner.User = self.user
+ self.owner.Resource = self.resource
+ self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\
+ '/'+self.owner.Resource
+ return self.on_auth(self._method)
+ log.error('Authentication failed!')
+ return self.on_auth(None)
class NonBlockingBind(PlugIn):
- """
- Bind some JID to the current connection to allow router know of our
- location. Must be plugged after successful SASL auth
- """
-
- def __init__(self):
- PlugIn.__init__(self)
- self.bound = None
-
- def plugin(self, owner):
- ''' Start resource binding, if allowed at this time. Used internally. '''
- if self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher,
- self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandler('features', self.FeaturesHandler,
- xmlns=NS_STREAMS)
-
- def FeaturesHandler(self, conn, feats):
- """
- Determine if server supports resource binding and set some internal
- attributes accordingly
- """
- if not feats.getTag('bind', namespace=NS_BIND):
- log.error('Server does not requested binding.')
- # we try to bind resource anyway
- #self.bound='failure'
- self.bound = []
- return
- if feats.getTag('session', namespace=NS_SESSION):
- self.session = 1
- else:
- self.session = -1
- self.bound = []
-
- def plugout(self):
- """
- Remove Bind handler from owner's dispatcher. Used internally
- """
- self._owner.UnregisterHandler('features', self.FeaturesHandler,
- xmlns=NS_STREAMS)
-
- def NonBlockingBind(self, resource=None, on_bound=None):
- """
- Perform binding. Use provided resource name or random (if not provided).
- """
- self.on_bound = on_bound
- self._resource = resource
- if self._resource:
- self._resource = [Node('resource', payload=[self._resource])]
- else:
- self._resource = []
-
- self._owner.onreceive(None)
- self._owner.Dispatcher.SendAndWaitForResponse(
- Protocol('iq',typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND},
- payload=self._resource)]), func=self._on_bound)
-
- def _on_bound(self, resp):
- if isResultNode(resp):
- if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'):
- self.bound.append(resp.getTag('bind').getTagData('jid'))
- log.info('Successfully bound %s.' % self.bound[-1])
- jid = JID(resp.getTag('bind').getTagData('jid'))
- self._owner.User = jid.getNode()
- self._owner.Resource = jid.getResource()
- if hasattr(self, 'session') and self.session == -1:
- # Server don't want us to initialize a session
- log.info('No session required.')
- self.on_bound('ok')
- else:
- self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
- payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
- func=self._on_session)
- return
- if resp:
- log.error('Binding failed: %s.' % resp.getTag('error'))
- self.on_bound(None)
- else:
- log.error('Binding failed: timeout expired.')
- self.on_bound(None)
-
- def _on_session(self, resp):
- self._owner.onreceive(None)
- if isResultNode(resp):
- log.info('Successfully opened session.')
- self.session = 1
- self.on_bound('ok')
- else:
- log.error('Session open failed.')
- self.session = 0
- self.on_bound(None)
-
-# vim: se ts=3:
+ """
+ Bind some JID to the current connection to allow router know of our
+ location. Must be plugged after successful SASL auth
+ """
+
+ def __init__(self):
+ PlugIn.__init__(self)
+ self.bound = None
+
+ def plugin(self, owner):
+ ''' Start resource binding, if allowed at this time. Used internally. '''
+ if self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher,
+ self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self._owner.RegisterHandler('features', self.FeaturesHandler,
+ xmlns=NS_STREAMS)
+
+ def FeaturesHandler(self, conn, feats):
+ """
+ Determine if server supports resource binding and set some internal
+ attributes accordingly
+ """
+ if not feats.getTag('bind', namespace=NS_BIND):
+ log.error('Server does not requested binding.')
+ # we try to bind resource anyway
+ #self.bound='failure'
+ self.bound = []
+ return
+ if feats.getTag('session', namespace=NS_SESSION):
+ self.session = 1
+ else:
+ self.session = -1
+ self.bound = []
+
+ def plugout(self):
+ """
+ Remove Bind handler from owner's dispatcher. Used internally
+ """
+ self._owner.UnregisterHandler('features', self.FeaturesHandler,
+ xmlns=NS_STREAMS)
+
+ def NonBlockingBind(self, resource=None, on_bound=None):
+ """
+ Perform binding. Use provided resource name or random (if not provided).
+ """
+ self.on_bound = on_bound
+ self._resource = resource
+ if self._resource:
+ self._resource = [Node('resource', payload=[self._resource])]
+ else:
+ self._resource = []
+
+ self._owner.onreceive(None)
+ self._owner.Dispatcher.SendAndWaitForResponse(
+ Protocol('iq',typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND},
+ payload=self._resource)]), func=self._on_bound)
+
+ def _on_bound(self, resp):
+ if isResultNode(resp):
+ if resp.getTag('bind') and resp.getTag('bind').getTagData('jid'):
+ self.bound.append(resp.getTag('bind').getTagData('jid'))
+ log.info('Successfully bound %s.' % self.bound[-1])
+ jid = JID(resp.getTag('bind').getTagData('jid'))
+ self._owner.User = jid.getNode()
+ self._owner.Resource = jid.getResource()
+ if hasattr(self, 'session') and self.session == -1:
+ # Server don't want us to initialize a session
+ log.info('No session required.')
+ self.on_bound('ok')
+ else:
+ self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
+ payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
+ func=self._on_session)
+ return
+ if resp:
+ log.error('Binding failed: %s.' % resp.getTag('error'))
+ self.on_bound(None)
+ else:
+ log.error('Binding failed: timeout expired.')
+ self.on_bound(None)
+
+ def _on_session(self, resp):
+ self._owner.onreceive(None)
+ if isResultNode(resp):
+ log.info('Successfully opened session.')
+ self.session = 1
+ self.on_bound('ok')
+ else:
+ log.error('Session open failed.')
+ self.session = 0
+ self.on_bound(None)
diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py
index 2658668ba..8c1178b5a 100644
--- a/src/common/xmpp/bosh.py
+++ b/src/common/xmpp/bosh.py
@@ -21,8 +21,8 @@
import locale, random
from hashlib import sha1
from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\
- CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\
- urisplit, DISCONNECT_TIMEOUT_SECONDS
+ CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\
+ urisplit, DISCONNECT_TIMEOUT_SECONDS
from protocol import BOSHBody
from simplexml import Node
@@ -37,540 +37,540 @@ FAKE_DESCRIPTOR = -1337
class NonBlockingBOSH(NonBlockingTransport):
- def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
- xmpp_server, domain, bosh_dict, proxy_creds):
- NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
- estabilish_tls, certs)
-
- self.bosh_sid = None
- if locale.getdefaultlocale()[0]:
- self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0]
- else:
- self.bosh_xml_lang = 'en'
-
- self.http_version = 'HTTP/1.1'
- self.http_persistent = True
- self.http_pipelining = bosh_dict['bosh_http_pipelining']
- self.bosh_to = domain
-
- self.route_host, self.route_port = xmpp_server
-
- self.bosh_wait = bosh_dict['bosh_wait']
- if not self.http_pipelining:
- self.bosh_hold = 1
- else:
- self.bosh_hold = bosh_dict['bosh_hold']
- self.bosh_requests = self.bosh_hold
- self.bosh_uri = bosh_dict['bosh_uri']
- self.bosh_content = bosh_dict['bosh_content']
- self.over_proxy = bosh_dict['bosh_useproxy']
- if estabilish_tls:
- self.bosh_secure = 'true'
- else:
- self.bosh_secure = 'false'
- self.use_proxy_auth = bosh_dict['useauth']
- self.proxy_creds = proxy_creds
- self.wait_cb_time = None
- self.http_socks = []
- self.stanza_buffer = []
- self.prio_bosh_stanzas = []
- self.current_recv_handler = None
- self.current_recv_socket = None
- self.key_stack = None
- self.ack_checker = None
- self.after_init = False
- self.proxy_dict = {}
- if self.over_proxy and self.estabilish_tls:
- self.proxy_dict['type'] = 'http'
- # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to
- # BOSH Connection Manager
- host, port = urisplit(self.bosh_uri)[1:3]
- self.proxy_dict['xmpp_server'] = (host, port)
- self.proxy_dict['credentials'] = self.proxy_creds
-
-
- def connect(self, conn_5tuple, on_connect, on_connect_failure):
- NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
-
- global FAKE_DESCRIPTOR
- FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
- self.fd = FAKE_DESCRIPTOR
-
- self.stanza_buffer = []
- self.prio_bosh_stanzas = []
-
- self.key_stack = KeyStack(KEY_COUNT)
- self.ack_checker = AckChecker()
- self.after_init = True
-
- self.http_socks.append(self.get_new_http_socket())
- self._tcp_connecting_started()
-
- self.http_socks[0].connect(
- conn_5tuple = conn_5tuple,
- on_connect = self._on_connect,
- on_connect_failure = self._on_connect_failure)
-
- def _on_connect(self):
- self.peerhost = self.http_socks[0].peerhost
- self.ssl_lib = self.http_socks[0].ssl_lib
- NonBlockingTransport._on_connect(self)
-
-
-
- def set_timeout(self, timeout):
- if self.get_state() != DISCONNECTED and self.fd != -1:
- NonBlockingTransport.set_timeout(self, timeout)
- else:
- log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd))
-
- def on_http_request_possible(self):
- """
- Called when HTTP request it's possible to send a HTTP request. It can be when
- socket is connected or when HTTP response arrived
-
- There should be always one pending request to BOSH CM.
- """
- log.debug('on_http_req possible, state:\n%s' % self.get_current_state())
- if self.get_state()==DISCONNECTED: return
-
- #Hack for making the non-secure warning dialog work
- if self._owner.got_features:
- if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')):
- self.send_BOSH(None)
- else:
- # If we already got features and no auth module was plugged yet, we are
- # probably waiting for confirmation of the "not-secure-connection" dialog.
- # We don't send HTTP request in that case.
- # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html
- return
- else:
- self.send_BOSH(None)
-
-
-
- def get_socket_in(self, state):
- """
- Get sockets in desired state
- """
- for s in self.http_socks:
- if s.get_state()==state: return s
- return None
-
-
- def get_free_socket(self):
- """
- Select and returns socket eligible for sending a data to
- """
- if self.http_pipelining:
- return self.get_socket_in(CONNECTED)
- else:
- last_recv_time, tmpsock = 0, None
- for s in self.http_socks:
- # we're interested only in CONNECTED socket with no requests pending
- if s.get_state()==CONNECTED and s.pending_requests==0:
- # if there's more of them, we want the one with the least recent data receive
- # (lowest last_recv_time)
- if (last_recv_time==0) or (s.last_recv_time < last_recv_time):
- last_recv_time = s.last_recv_time
- tmpsock = s
- if tmpsock:
- return tmpsock
- else:
- return None
-
-
- def send_BOSH(self, payload):
- """
- Tries to send a stanza in payload by appeding it to a buffer and plugging a
- free socket for writing.
- """
- total_pending_reqs = sum([s.pending_requests for s in self.http_socks])
-
- # when called after HTTP response (Payload=None) and when there are already
- # some pending requests and no data to send, or when the socket is
- # disconnected, we do nothing
- if payload is None and \
- total_pending_reqs > 0 and \
- self.stanza_buffer == [] and \
- self.prio_bosh_stanzas == [] or \
- self.get_state()==DISCONNECTED:
- return
-
- # now the payload is put to buffer and will be sent at some point
- self.append_stanza(payload)
-
- # if we're about to make more requests than allowed, we don't send - stanzas will be
- # sent after HTTP response from CM, exception is when we're disconnecting - then we
- # send anyway
- if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING:
- log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' %
- self.get_current_state())
- return
-
- # when there's free CONNECTED socket, we plug it for write and the data will
- # be sent when write is possible
- if self.get_free_socket():
- self.plug_socket()
- return
-
- # if there is a connecting socket, we just wait for when it connects,
- # payload will be sent in a sec when the socket connects
- if self.get_socket_in(CONNECTING): return
-
- # being here means there are either DISCONNECTED sockets or all sockets are
- # CONNECTED with too many pending requests
- s = self.get_socket_in(DISCONNECTED)
-
- # if we have DISCONNECTED socket, lets connect it and plug for send
- if s:
- self.connect_and_flush(s)
- else:
- # otherwise create and connect a new one
- ss = self.get_new_http_socket()
- self.http_socks.append(ss)
- self.connect_and_flush(ss)
- return
-
- def plug_socket(self):
- stanza = None
- s = self.get_free_socket()
- if s:
- s._plug_idle(writable=True, readable=True)
- else:
- log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())')
-
- def build_stanza(self, socket):
- """
- Build a BOSH body tag from data in buffers and adds key, rid and ack
- attributes to it
-
- This method is called from _do_send() of underlying transport. This is to
- ensure rid and keys will be processed in correct order. If I generate
- them before plugging a socket for write (and did it for two sockets/HTTP
- connections) in parallel, they might be sent in wrong order, which
- results in violating the BOSH session and server-side disconnect.
- """
- if self.prio_bosh_stanzas:
- stanza, add_payload = self.prio_bosh_stanzas.pop(0)
- if add_payload:
- stanza.setPayload(self.stanza_buffer)
- self.stanza_buffer = []
- else:
- stanza = self.boshify_stanzas(self.stanza_buffer)
- self.stanza_buffer = []
-
- stanza = self.ack_checker.backup_stanza(stanza, socket)
-
- key, newkey = self.key_stack.get()
- if key:
- stanza.setAttr('key', key)
- if newkey:
- stanza.setAttr('newkey', newkey)
-
-
- log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket)))
- self.renew_bosh_wait_timeout(self.bosh_wait + 3)
- return stanza
-
-
- def on_bosh_wait_timeout(self):
- log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait)
- self.disconnect()
-
-
- def renew_bosh_wait_timeout(self, timeout):
- if self.wait_cb_time is not None:
- self.remove_bosh_wait_timeout()
- sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout)
- self.wait_cb_time = sched_time
-
- def remove_bosh_wait_timeout(self):
- self.idlequeue.remove_alarm(
- self.on_bosh_wait_timeout,
- self.wait_cb_time)
-
- def on_persistent_fallback(self, socket):
- """
- Called from underlying transport when server closes TCP connection
-
- :param socket: disconnected transport object
- """
- if socket.http_persistent:
- log.warn('Fallback to nonpersistent HTTP (no pipelining as well)')
- socket.http_persistent = False
- self.http_persistent = False
- self.http_pipelining = False
- socket.disconnect(do_callback=False)
- self.connect_and_flush(socket)
- else:
- socket.disconnect()
-
-
-
- def handle_body_attrs(self, stanza_attrs):
- """
- Called for each incoming body stanza from dispatcher. Checks body
- attributes.
- """
- self.remove_bosh_wait_timeout()
-
- if self.after_init:
- if stanza_attrs.has_key('sid'):
- # session ID should be only in init response
- self.bosh_sid = stanza_attrs['sid']
-
- if stanza_attrs.has_key('requests'):
- self.bosh_requests = int(stanza_attrs['requests'])
-
- if stanza_attrs.has_key('wait'):
- self.bosh_wait = int(stanza_attrs['wait'])
- self.after_init = False
-
- ack = None
- if stanza_attrs.has_key('ack'):
- ack = stanza_attrs['ack']
- self.ack_checker.process_incoming_ack(ack=ack,
- socket=self.current_recv_socket)
-
- if stanza_attrs.has_key('type'):
- if stanza_attrs['type'] in ['terminate', 'terminal']:
- condition = 'n/a'
- if stanza_attrs.has_key('condition'):
- condition = stanza_attrs['condition']
- if condition == 'n/a':
- log.info('Received sesion-ending terminating stanza')
- else:
- log.error('Received terminating stanza: %s - %s' % (condition,
- bosh_errors[condition]))
- self.disconnect()
- return
-
- if stanza_attrs['type'] == 'error':
- # recoverable error
- pass
- return
-
-
- def append_stanza(self, stanza):
- """
- Append stanza to a buffer to send
- """
- if stanza:
- if isinstance(stanza, tuple):
- # stanza is tuple of BOSH stanza and bool value for whether to add payload
- self.prio_bosh_stanzas.append(stanza)
- else:
- # stanza is XMPP stanza. Will be boshified before send.
- self.stanza_buffer.append(stanza)
-
-
- def send(self, stanza, now=False):
- self.send_BOSH(stanza)
-
-
-
- def get_current_state(self):
- t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
- for s in self.http_socks:
- t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.get_state(), s.pending_requests)
- t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \
- % (t, self.prio_bosh_stanzas, self.stanza_buffer,
- self.ack_checker.get_not_acked_rids())
- return t
-
-
- def connect_and_flush(self, socket):
- socket.connect(
- conn_5tuple = self.conn_5tuple,
- on_connect = self.on_http_request_possible,
- on_connect_failure = self.disconnect)
-
-
- def boshify_stanzas(self, stanzas=[], body_attrs=None):
- """
- Wraps zero to many stanzas by body tag with xmlns and sid
- """
- log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas))
- tag = BOSHBody(attrs={'sid': self.bosh_sid})
- tag.setPayload(stanzas)
- return tag
-
-
- def send_init(self, after_SASL=False):
- if after_SASL:
- t = BOSHBody(
- attrs={ 'to': self.bosh_to,
- 'sid': self.bosh_sid,
- 'xml:lang': self.bosh_xml_lang,
- 'xmpp:restart': 'true',
- 'secure': self.bosh_secure,
- 'xmlns:xmpp': 'urn:xmpp:xbosh'})
- else:
- t = BOSHBody(
- attrs={ 'content': self.bosh_content,
- 'hold': str(self.bosh_hold),
- 'route': '%s:%s' % (self.route_host, self.route_port),
- 'to': self.bosh_to,
- 'wait': str(self.bosh_wait),
- 'xml:lang': self.bosh_xml_lang,
- 'xmpp:version': '1.0',
- 'ver': '1.6',
- 'xmlns:xmpp': 'urn:xmpp:xbosh'})
- self.send_BOSH((t,True))
-
- def start_disconnect(self):
- NonBlockingTransport.start_disconnect(self)
- self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS)
- self.send_BOSH(
- (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True))
-
-
- def get_new_http_socket(self):
- http_dict = {'http_uri': self.bosh_uri,
- 'http_version': self.http_version,
- 'http_persistent': self.http_persistent,
- 'add_proxy_headers': self.over_proxy and not self.estabilish_tls}
- if self.use_proxy_auth:
- http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds
-
- s = NonBlockingHTTPBOSH(
- raise_event=self.raise_event,
- on_disconnect=self.disconnect,
- idlequeue = self.idlequeue,
- estabilish_tls = self.estabilish_tls,
- certs = self.certs,
- on_http_request_possible = self.on_http_request_possible,
- http_dict = http_dict,
- proxy_dict = self.proxy_dict,
- on_persistent_fallback = self.on_persistent_fallback)
-
- s.onreceive(self.on_received_http)
- s.set_stanza_build_cb(self.build_stanza)
- return s
-
-
- def onreceive(self, recv_handler):
- if recv_handler is None:
- recv_handler = self._owner.Dispatcher.ProcessNonBlocking
- self.current_recv_handler = recv_handler
-
-
- def on_received_http(self, data, socket):
- self.current_recv_socket = socket
- self.current_recv_handler(data)
-
-
- def disconnect(self, do_callback=True):
- self.remove_bosh_wait_timeout()
- if self.get_state() == DISCONNECTED: return
- self.fd = -1
- for s in self.http_socks:
- s.disconnect(do_callback=False)
- NonBlockingTransport.disconnect(self, do_callback)
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls, certs,
+ xmpp_server, domain, bosh_dict, proxy_creds):
+ NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
+ estabilish_tls, certs)
+
+ self.bosh_sid = None
+ if locale.getdefaultlocale()[0]:
+ self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0]
+ else:
+ self.bosh_xml_lang = 'en'
+
+ self.http_version = 'HTTP/1.1'
+ self.http_persistent = True
+ self.http_pipelining = bosh_dict['bosh_http_pipelining']
+ self.bosh_to = domain
+
+ self.route_host, self.route_port = xmpp_server
+
+ self.bosh_wait = bosh_dict['bosh_wait']
+ if not self.http_pipelining:
+ self.bosh_hold = 1
+ else:
+ self.bosh_hold = bosh_dict['bosh_hold']
+ self.bosh_requests = self.bosh_hold
+ self.bosh_uri = bosh_dict['bosh_uri']
+ self.bosh_content = bosh_dict['bosh_content']
+ self.over_proxy = bosh_dict['bosh_useproxy']
+ if estabilish_tls:
+ self.bosh_secure = 'true'
+ else:
+ self.bosh_secure = 'false'
+ self.use_proxy_auth = bosh_dict['useauth']
+ self.proxy_creds = proxy_creds
+ self.wait_cb_time = None
+ self.http_socks = []
+ self.stanza_buffer = []
+ self.prio_bosh_stanzas = []
+ self.current_recv_handler = None
+ self.current_recv_socket = None
+ self.key_stack = None
+ self.ack_checker = None
+ self.after_init = False
+ self.proxy_dict = {}
+ if self.over_proxy and self.estabilish_tls:
+ self.proxy_dict['type'] = 'http'
+ # with SSL over proxy, we do HTTP CONNECT to proxy to open a channel to
+ # BOSH Connection Manager
+ host, port = urisplit(self.bosh_uri)[1:3]
+ self.proxy_dict['xmpp_server'] = (host, port)
+ self.proxy_dict['credentials'] = self.proxy_creds
+
+
+ def connect(self, conn_5tuple, on_connect, on_connect_failure):
+ NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
+
+ global FAKE_DESCRIPTOR
+ FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
+ self.fd = FAKE_DESCRIPTOR
+
+ self.stanza_buffer = []
+ self.prio_bosh_stanzas = []
+
+ self.key_stack = KeyStack(KEY_COUNT)
+ self.ack_checker = AckChecker()
+ self.after_init = True
+
+ self.http_socks.append(self.get_new_http_socket())
+ self._tcp_connecting_started()
+
+ self.http_socks[0].connect(
+ conn_5tuple = conn_5tuple,
+ on_connect = self._on_connect,
+ on_connect_failure = self._on_connect_failure)
+
+ def _on_connect(self):
+ self.peerhost = self.http_socks[0].peerhost
+ self.ssl_lib = self.http_socks[0].ssl_lib
+ NonBlockingTransport._on_connect(self)
+
+
+
+ def set_timeout(self, timeout):
+ if self.get_state() != DISCONNECTED and self.fd != -1:
+ NonBlockingTransport.set_timeout(self, timeout)
+ else:
+ log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd))
+
+ def on_http_request_possible(self):
+ """
+ Called when HTTP request it's possible to send a HTTP request. It can be when
+ socket is connected or when HTTP response arrived
+
+ There should be always one pending request to BOSH CM.
+ """
+ log.debug('on_http_req possible, state:\n%s' % self.get_current_state())
+ if self.get_state()==DISCONNECTED: return
+
+ #Hack for making the non-secure warning dialog work
+ if self._owner.got_features:
+ if (hasattr(self._owner, 'NonBlockingNonSASL') or hasattr(self._owner, 'SASL')):
+ self.send_BOSH(None)
+ else:
+ # If we already got features and no auth module was plugged yet, we are
+ # probably waiting for confirmation of the "not-secure-connection" dialog.
+ # We don't send HTTP request in that case.
+ # see http://lists.jabber.ru/pipermail/ejabberd/2008-August/004027.html
+ return
+ else:
+ self.send_BOSH(None)
+
+
+
+ def get_socket_in(self, state):
+ """
+ Get sockets in desired state
+ """
+ for s in self.http_socks:
+ if s.get_state()==state: return s
+ return None
+
+
+ def get_free_socket(self):
+ """
+ Select and returns socket eligible for sending a data to
+ """
+ if self.http_pipelining:
+ return self.get_socket_in(CONNECTED)
+ else:
+ last_recv_time, tmpsock = 0, None
+ for s in self.http_socks:
+ # we're interested only in CONNECTED socket with no requests pending
+ if s.get_state()==CONNECTED and s.pending_requests==0:
+ # if there's more of them, we want the one with the least recent data receive
+ # (lowest last_recv_time)
+ if (last_recv_time==0) or (s.last_recv_time < last_recv_time):
+ last_recv_time = s.last_recv_time
+ tmpsock = s
+ if tmpsock:
+ return tmpsock
+ else:
+ return None
+
+
+ def send_BOSH(self, payload):
+ """
+ Tries to send a stanza in payload by appeding it to a buffer and plugging a
+ free socket for writing.
+ """
+ total_pending_reqs = sum([s.pending_requests for s in self.http_socks])
+
+ # when called after HTTP response (Payload=None) and when there are already
+ # some pending requests and no data to send, or when the socket is
+ # disconnected, we do nothing
+ if payload is None and \
+ total_pending_reqs > 0 and \
+ self.stanza_buffer == [] and \
+ self.prio_bosh_stanzas == [] or \
+ self.get_state()==DISCONNECTED:
+ return
+
+ # now the payload is put to buffer and will be sent at some point
+ self.append_stanza(payload)
+
+ # if we're about to make more requests than allowed, we don't send - stanzas will be
+ # sent after HTTP response from CM, exception is when we're disconnecting - then we
+ # send anyway
+ if total_pending_reqs >= self.bosh_requests and self.get_state()!=DISCONNECTING:
+ log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' %
+ self.get_current_state())
+ return
+
+ # when there's free CONNECTED socket, we plug it for write and the data will
+ # be sent when write is possible
+ if self.get_free_socket():
+ self.plug_socket()
+ return
+
+ # if there is a connecting socket, we just wait for when it connects,
+ # payload will be sent in a sec when the socket connects
+ if self.get_socket_in(CONNECTING): return
+
+ # being here means there are either DISCONNECTED sockets or all sockets are
+ # CONNECTED with too many pending requests
+ s = self.get_socket_in(DISCONNECTED)
+
+ # if we have DISCONNECTED socket, lets connect it and plug for send
+ if s:
+ self.connect_and_flush(s)
+ else:
+ # otherwise create and connect a new one
+ ss = self.get_new_http_socket()
+ self.http_socks.append(ss)
+ self.connect_and_flush(ss)
+ return
+
+ def plug_socket(self):
+ stanza = None
+ s = self.get_free_socket()
+ if s:
+ s._plug_idle(writable=True, readable=True)
+ else:
+ log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())')
+
+ def build_stanza(self, socket):
+ """
+ Build a BOSH body tag from data in buffers and adds key, rid and ack
+ attributes to it
+
+ This method is called from _do_send() of underlying transport. This is to
+ ensure rid and keys will be processed in correct order. If I generate
+ them before plugging a socket for write (and did it for two sockets/HTTP
+ connections) in parallel, they might be sent in wrong order, which
+ results in violating the BOSH session and server-side disconnect.
+ """
+ if self.prio_bosh_stanzas:
+ stanza, add_payload = self.prio_bosh_stanzas.pop(0)
+ if add_payload:
+ stanza.setPayload(self.stanza_buffer)
+ self.stanza_buffer = []
+ else:
+ stanza = self.boshify_stanzas(self.stanza_buffer)
+ self.stanza_buffer = []
+
+ stanza = self.ack_checker.backup_stanza(stanza, socket)
+
+ key, newkey = self.key_stack.get()
+ if key:
+ stanza.setAttr('key', key)
+ if newkey:
+ stanza.setAttr('newkey', newkey)
+
+
+ log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket)))
+ self.renew_bosh_wait_timeout(self.bosh_wait + 3)
+ return stanza
+
+
+ def on_bosh_wait_timeout(self):
+ log.error('Connection Manager didn\'t respond within %s + 3 seconds --> forcing disconnect' % self.bosh_wait)
+ self.disconnect()
+
+
+ def renew_bosh_wait_timeout(self, timeout):
+ if self.wait_cb_time is not None:
+ self.remove_bosh_wait_timeout()
+ sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, timeout)
+ self.wait_cb_time = sched_time
+
+ def remove_bosh_wait_timeout(self):
+ self.idlequeue.remove_alarm(
+ self.on_bosh_wait_timeout,
+ self.wait_cb_time)
+
+ def on_persistent_fallback(self, socket):
+ """
+ Called from underlying transport when server closes TCP connection
+
+ :param socket: disconnected transport object
+ """
+ if socket.http_persistent:
+ log.warn('Fallback to nonpersistent HTTP (no pipelining as well)')
+ socket.http_persistent = False
+ self.http_persistent = False
+ self.http_pipelining = False
+ socket.disconnect(do_callback=False)
+ self.connect_and_flush(socket)
+ else:
+ socket.disconnect()
+
+
+
+ def handle_body_attrs(self, stanza_attrs):
+ """
+ Called for each incoming body stanza from dispatcher. Checks body
+ attributes.
+ """
+ self.remove_bosh_wait_timeout()
+
+ if self.after_init:
+ if stanza_attrs.has_key('sid'):
+ # session ID should be only in init response
+ self.bosh_sid = stanza_attrs['sid']
+
+ if stanza_attrs.has_key('requests'):
+ self.bosh_requests = int(stanza_attrs['requests'])
+
+ if stanza_attrs.has_key('wait'):
+ self.bosh_wait = int(stanza_attrs['wait'])
+ self.after_init = False
+
+ ack = None
+ if stanza_attrs.has_key('ack'):
+ ack = stanza_attrs['ack']
+ self.ack_checker.process_incoming_ack(ack=ack,
+ socket=self.current_recv_socket)
+
+ if stanza_attrs.has_key('type'):
+ if stanza_attrs['type'] in ['terminate', 'terminal']:
+ condition = 'n/a'
+ if stanza_attrs.has_key('condition'):
+ condition = stanza_attrs['condition']
+ if condition == 'n/a':
+ log.info('Received sesion-ending terminating stanza')
+ else:
+ log.error('Received terminating stanza: %s - %s' % (condition,
+ bosh_errors[condition]))
+ self.disconnect()
+ return
+
+ if stanza_attrs['type'] == 'error':
+ # recoverable error
+ pass
+ return
+
+
+ def append_stanza(self, stanza):
+ """
+ Append stanza to a buffer to send
+ """
+ if stanza:
+ if isinstance(stanza, tuple):
+ # stanza is tuple of BOSH stanza and bool value for whether to add payload
+ self.prio_bosh_stanzas.append(stanza)
+ else:
+ # stanza is XMPP stanza. Will be boshified before send.
+ self.stanza_buffer.append(stanza)
+
+
+ def send(self, stanza, now=False):
+ self.send_BOSH(stanza)
+
+
+
+ def get_current_state(self):
+ t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
+ for s in self.http_socks:
+ t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.get_state(), s.pending_requests)
+ t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \
+ % (t, self.prio_bosh_stanzas, self.stanza_buffer,
+ self.ack_checker.get_not_acked_rids())
+ return t
+
+
+ def connect_and_flush(self, socket):
+ socket.connect(
+ conn_5tuple = self.conn_5tuple,
+ on_connect = self.on_http_request_possible,
+ on_connect_failure = self.disconnect)
+
+
+ def boshify_stanzas(self, stanzas=[], body_attrs=None):
+ """
+ Wraps zero to many stanzas by body tag with xmlns and sid
+ """
+ log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas))
+ tag = BOSHBody(attrs={'sid': self.bosh_sid})
+ tag.setPayload(stanzas)
+ return tag
+
+
+ def send_init(self, after_SASL=False):
+ if after_SASL:
+ t = BOSHBody(
+ attrs={ 'to': self.bosh_to,
+ 'sid': self.bosh_sid,
+ 'xml:lang': self.bosh_xml_lang,
+ 'xmpp:restart': 'true',
+ 'secure': self.bosh_secure,
+ 'xmlns:xmpp': 'urn:xmpp:xbosh'})
+ else:
+ t = BOSHBody(
+ attrs={ 'content': self.bosh_content,
+ 'hold': str(self.bosh_hold),
+ 'route': '%s:%s' % (self.route_host, self.route_port),
+ 'to': self.bosh_to,
+ 'wait': str(self.bosh_wait),
+ 'xml:lang': self.bosh_xml_lang,
+ 'xmpp:version': '1.0',
+ 'ver': '1.6',
+ 'xmlns:xmpp': 'urn:xmpp:xbosh'})
+ self.send_BOSH((t,True))
+
+ def start_disconnect(self):
+ NonBlockingTransport.start_disconnect(self)
+ self.renew_bosh_wait_timeout(DISCONNECT_TIMEOUT_SECONDS)
+ self.send_BOSH(
+ (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True))
+
+
+ def get_new_http_socket(self):
+ http_dict = {'http_uri': self.bosh_uri,
+ 'http_version': self.http_version,
+ 'http_persistent': self.http_persistent,
+ 'add_proxy_headers': self.over_proxy and not self.estabilish_tls}
+ if self.use_proxy_auth:
+ http_dict['proxy_user'], http_dict['proxy_pass'] = self.proxy_creds
+
+ s = NonBlockingHTTPBOSH(
+ raise_event=self.raise_event,
+ on_disconnect=self.disconnect,
+ idlequeue = self.idlequeue,
+ estabilish_tls = self.estabilish_tls,
+ certs = self.certs,
+ on_http_request_possible = self.on_http_request_possible,
+ http_dict = http_dict,
+ proxy_dict = self.proxy_dict,
+ on_persistent_fallback = self.on_persistent_fallback)
+
+ s.onreceive(self.on_received_http)
+ s.set_stanza_build_cb(self.build_stanza)
+ return s
+
+
+ def onreceive(self, recv_handler):
+ if recv_handler is None:
+ recv_handler = self._owner.Dispatcher.ProcessNonBlocking
+ self.current_recv_handler = recv_handler
+
+
+ def on_received_http(self, data, socket):
+ self.current_recv_socket = socket
+ self.current_recv_handler(data)
+
+
+ def disconnect(self, do_callback=True):
+ self.remove_bosh_wait_timeout()
+ if self.get_state() == DISCONNECTED: return
+ self.fd = -1
+ for s in self.http_socks:
+ s.disconnect(do_callback=False)
+ NonBlockingTransport.disconnect(self, do_callback)
def get_rand_number():
- # with 50-bit random initial rid, session would have to go up
- # to 7881299347898368 messages to raise rid over 2**53
- # (see http://www.xmpp.org/extensions/xep-0124.html#rids)
- # it's also used for sequence key initialization
- r = random.Random()
- r.seed()
- return r.getrandbits(50)
+ # with 50-bit random initial rid, session would have to go up
+ # to 7881299347898368 messages to raise rid over 2**53
+ # (see http://www.xmpp.org/extensions/xep-0124.html#rids)
+ # it's also used for sequence key initialization
+ r = random.Random()
+ r.seed()
+ return r.getrandbits(50)
class AckChecker():
- """
- Class for generating rids and generating and checking acknowledgements in
- BOSH messages
- """
- def __init__(self):
- self.rid = get_rand_number()
- self.ack = 1
- self.last_rids = {}
- self.not_acked = []
+ """
+ Class for generating rids and generating and checking acknowledgements in
+ BOSH messages
+ """
+ def __init__(self):
+ self.rid = get_rand_number()
+ self.ack = 1
+ self.last_rids = {}
+ self.not_acked = []
- def get_not_acked_rids(self): return [rid for rid, st in self.not_acked]
+ def get_not_acked_rids(self): return [rid for rid, st in self.not_acked]
- def backup_stanza(self, stanza, socket):
- socket.pending_requests += 1
- rid = self.get_rid()
- self.not_acked.append((rid, stanza))
- stanza.setAttr('rid', str(rid))
- self.last_rids[socket]=rid
+ def backup_stanza(self, stanza, socket):
+ socket.pending_requests += 1
+ rid = self.get_rid()
+ self.not_acked.append((rid, stanza))
+ stanza.setAttr('rid', str(rid))
+ self.last_rids[socket]=rid
- if self.rid != self.ack + 1:
- stanza.setAttr('ack', str(self.ack))
- return stanza
+ if self.rid != self.ack + 1:
+ stanza.setAttr('ack', str(self.ack))
+ return stanza
- def process_incoming_ack(self, socket, ack=None):
- socket.pending_requests -= 1
- if ack:
- ack = int(ack)
- else:
- ack = self.last_rids[socket]
+ def process_incoming_ack(self, socket, ack=None):
+ socket.pending_requests -= 1
+ if ack:
+ ack = int(ack)
+ else:
+ ack = self.last_rids[socket]
- i = len([rid for rid, st in self.not_acked if ack >= rid])
- self.not_acked = self.not_acked[i:]
+ i = len([rid for rid, st in self.not_acked if ack >= rid])
+ self.not_acked = self.not_acked[i:]
- self.ack = ack
+ self.ack = ack
- def get_rid(self):
- self.rid = self.rid + 1
- return self.rid
+ def get_rid(self):
+ self.rid = self.rid + 1
+ return self.rid
class KeyStack():
- """
- Class implementing key sequences for BOSH messages
- """
- def __init__(self, count):
- self.count = count
- self.keys = []
- self.reset()
- self.first_call = True
-
- def reset(self):
- seed = str(get_rand_number())
- self.keys = [sha1(seed).hexdigest()]
- for i in range(self.count-1):
- curr_seed = self.keys[i]
- self.keys.append(sha1(curr_seed).hexdigest())
-
- def get(self):
- if self.first_call:
- self.first_call = False
- return (None, self.keys.pop())
-
- if len(self.keys)>1:
- return (self.keys.pop(), None)
- else:
- last_key = self.keys.pop()
- self.reset()
- new_key = self.keys.pop()
- return (last_key, new_key)
+ """
+ Class implementing key sequences for BOSH messages
+ """
+ def __init__(self, count):
+ self.count = count
+ self.keys = []
+ self.reset()
+ self.first_call = True
+
+ def reset(self):
+ seed = str(get_rand_number())
+ self.keys = [sha1(seed).hexdigest()]
+ for i in range(self.count-1):
+ curr_seed = self.keys[i]
+ self.keys.append(sha1(curr_seed).hexdigest())
+
+ def get(self):
+ if self.first_call:
+ self.first_call = False
+ return (None, self.keys.pop())
+
+ if len(self.keys)>1:
+ return (self.keys.pop(), None)
+ else:
+ last_key = self.keys.pop()
+ self.reset()
+ new_key = self.keys.pop()
+ return (last_key, new_key)
# http://www.xmpp.org/extensions/xep-0124.html#errorstatus-terminal
bosh_errors = {
- 'n/a': 'none or unknown condition in terminating body stanza',
- 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.',
- 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.',
- 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.',
- 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.',
- 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.',
- 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid',
- 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.',
- 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).',
- 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.',
- 'remote-stream-error': 'Encapsulates an error in the protocol being transported.',
- 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the <uri/> child element.',
- 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.',
- 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the <body/> wrapper.'
+ 'n/a': 'none or unknown condition in terminating body stanza',
+ 'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.',
+ 'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.',
+ 'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.',
+ 'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.',
+ 'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.',
+ 'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid',
+ 'other-request': 'Another request being processed at the same time as this request caused the session to terminate.',
+ 'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).',
+ 'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.',
+ 'remote-stream-error': 'Encapsulates an error in the protocol being transported.',
+ 'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the <uri/> child element.',
+ 'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.',
+ 'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the <body/> wrapper.'
}
diff --git a/src/common/xmpp/c14n.py b/src/common/xmpp/c14n.py
index 521f3144b..1711c9899 100644
--- a/src/common/xmpp/c14n.py
+++ b/src/common/xmpp/c14n.py
@@ -25,38 +25,36 @@ XML canonicalisation methods (for XEP-0116)
from simplexml import ustr
def c14n(node, is_buggy):
- s = "<" + node.name
- if node.namespace:
- if not node.parent or node.parent.namespace != node.namespace:
- s = s + ' xmlns="%s"' % node.namespace
-
- sorted_attrs = sorted(node.attrs.keys())
- for key in sorted_attrs:
- if not is_buggy and key == 'xmlns':
- continue
- val = ustr(node.attrs[key])
- # like XMLescape() but with whitespace and without &gt;
- s = s + ' %s="%s"' % ( key, normalise_attr(val) )
- s = s + ">"
- cnt = 0
- if node.kids:
- for a in node.kids:
- if (len(node.data)-1) >= cnt:
- s = s + normalise_text(node.data[cnt])
- s = s + c14n(a, is_buggy)
- cnt=cnt+1
- if (len(node.data)-1) >= cnt: s = s + normalise_text(node.data[cnt])
- if not node.kids and s.endswith('>'):
- s=s[:-1]+' />'
- else:
- s = s + "</" + node.name + ">"
- return s.encode('utf-8')
+ s = "<" + node.name
+ if node.namespace:
+ if not node.parent or node.parent.namespace != node.namespace:
+ s = s + ' xmlns="%s"' % node.namespace
+
+ sorted_attrs = sorted(node.attrs.keys())
+ for key in sorted_attrs:
+ if not is_buggy and key == 'xmlns':
+ continue
+ val = ustr(node.attrs[key])
+ # like XMLescape() but with whitespace and without &gt;
+ s = s + ' %s="%s"' % ( key, normalise_attr(val) )
+ s = s + ">"
+ cnt = 0
+ if node.kids:
+ for a in node.kids:
+ if (len(node.data)-1) >= cnt:
+ s = s + normalise_text(node.data[cnt])
+ s = s + c14n(a, is_buggy)
+ cnt=cnt+1
+ if (len(node.data)-1) >= cnt: s = s + normalise_text(node.data[cnt])
+ if not node.kids and s.endswith('>'):
+ s=s[:-1]+' />'
+ else:
+ s = s + "</" + node.name + ">"
+ return s.encode('utf-8')
def normalise_attr(val):
- return val.replace('&', '&amp;').replace('<', '&lt;').replace('"', '&quot;').replace('\t', '&#x9;').replace('\n', '&#xA;').replace('\r', '&#xD;')
+ return val.replace('&', '&amp;').replace('<', '&lt;').replace('"', '&quot;').replace('\t', '&#x9;').replace('\n', '&#xA;').replace('\r', '&#xD;')
def normalise_text(val):
- return val.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('\r', '&#xD;')
-
+ return val.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('\r', '&#xD;')
-# vim: se ts=3:
diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py
index b2b1d3b35..61f6e1552 100644
--- a/src/common/xmpp/client_nb.py
+++ b/src/common/xmpp/client_nb.py
@@ -1,8 +1,8 @@
## client_nb.py
-## based on client.py, changes backported up to revision 1.60
+## based on client.py, changes backported up to revision 1.60
##
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-## modified by Dimitur Kirov <dkirov@gmail.com>
+## modified by Dimitur Kirov <dkirov@gmail.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
@@ -29,568 +29,566 @@ log = logging.getLogger('gajim.c.x.client_nb')
class NonBlockingClient:
- """
- Client class is XMPP connection mountpoint. Objects for authentication,
- network communication, roster, xml parsing ... are plugged to client object.
- Client implements the abstract behavior - mostly negotioation and callbacks
- handling, whereas underlying modules take care of feature-specific logic
- """
-
- def __init__(self, domain, idlequeue, caller=None):
- """
- Caches connection data
-
- :param domain: domain - for to: attribute (from account info)
- :param idlequeue: processing idlequeue
- :param caller: calling object - it has to implement methods
- _event_dispatcher which is called from dispatcher instance
- """
- self.Namespace = protocol.NS_CLIENT
- self.defaultNamespace = self.Namespace
-
- self.idlequeue = idlequeue
- self.disconnect_handlers = []
-
- self.Server = domain
- self.xmpp_hostname = None # FQDN hostname to connect to
-
- # caller is who initiated this client, it is in needed to register
- # the EventDispatcher
- self._caller = caller
- self._owner = self
- self._registered_name = None # our full jid, set after successful auth
- self.connected = ''
- self.socket = None
- self.on_connect = None
- self.on_proxy_failure = None
- self.on_connect_failure = None
- self.proxy = None
- self.got_features = False
- self.stream_started = False
- self.disconnecting = False
- self.protocol_type = 'XMPP'
-
- def disconnect(self, message=''):
- """
- Called on disconnection - disconnect callback is picked based on state of
- the client.
- """
- # to avoid recursive calls
- if self.disconnecting: return
-
- log.info('Disconnecting NBClient: %s' % message)
-
- if 'NonBlockingRoster' in self.__dict__:
- self.NonBlockingRoster.PlugOut()
- if 'NonBlockingBind' in self.__dict__:
- self.NonBlockingBind.PlugOut()
- if 'NonBlockingNonSASL' in self.__dict__:
- self.NonBlockingNonSASL.PlugOut()
- if 'SASL' in self.__dict__:
- self.SASL.PlugOut()
- if 'NonBlockingTCP' in self.__dict__:
- self.NonBlockingTCP.PlugOut()
- if 'NonBlockingHTTP' in self.__dict__:
- self.NonBlockingHTTP.PlugOut()
- if 'NonBlockingBOSH' in self.__dict__:
- self.NonBlockingBOSH.PlugOut()
- # FIXME: we never unplug dispatcher, only on next connect
- # See _xmpp_connect_machine and SASLHandler
-
- connected = self.connected
- stream_started = self.stream_started
-
- self.connected = ''
- self.stream_started = False
-
- self.disconnecting = True
-
- log.debug('Client disconnected..')
- if connected == '':
- # if we're disconnecting before connection to XMPP sever is opened,
- # we don't call disconnect handlers but on_connect_failure callback
- if self.proxy:
- # with proxy, we have different failure callback
- log.debug('calling on_proxy_failure cb')
- self.on_proxy_failure(reason=message)
- else:
- log.debug('calling on_connect_failure cb')
- self.on_connect_failure()
- else:
- # we are connected to XMPP server
- if not stream_started:
- # if error occur before XML stream was opened, e.g. no response on
- # init request, we call the on_connect_failure callback because
- # proper connection is not established yet and it's not a proxy
- # issue
- log.debug('calling on_connect_failure cb')
- self._caller.streamError = message
- self.on_connect_failure()
- else:
- # with open connection, we are calling the disconnect handlers
- for i in reversed(self.disconnect_handlers):
- log.debug('Calling disconnect handler %s' % i)
- i()
- self.disconnecting = False
-
- def connect(self, on_connect, on_connect_failure, hostname=None, port=5222,
- on_proxy_failure=None, proxy=None, secure_tuple=('plain', None,
- None)):
- """
- Open XMPP connection (open XML streams in both directions)
-
- :param on_connect: called after stream is successfully opened
- :param on_connect_failure: called when error occures during connection
- :param hostname: hostname of XMPP server from SRV request
- :param port: port number of XMPP server
- :param on_proxy_failure: called if error occurres during TCP connection to
- proxy server or during proxy connecting process
- :param proxy: dictionary with proxy data. It should contain at least
- values for keys 'host' and 'port' - connection details for proxy serve
- and optionally keys 'user' and 'pass' as proxy credentials
- :param secure_tuple: tuple of (desired connection type, cacerts, mycerts)
- connection type can be 'ssl' - TLS established after TCP connection,
- 'tls' - TLS established after negotiation with starttls, or 'plain'.
- cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more
- details
- """
- self.on_connect = on_connect
- self.on_connect_failure=on_connect_failure
- self.on_proxy_failure = on_proxy_failure
- self.desired_security, self.cacerts, self.mycerts = secure_tuple
- self.Connection = None
- self.Port = port
- self.proxy = proxy
-
- if hostname:
- self.xmpp_hostname = hostname
- else:
- self.xmpp_hostname = self.Server
-
- # We only check for SSL here as for TLS we will first have to start a
- # PLAIN connection and negotiate TLS afterwards.
- # establish_tls will instruct transport to start secure connection
- # directly
- establish_tls = self.desired_security == 'ssl'
- certs = (self.cacerts, self.mycerts)
-
- proxy_dict = {}
- tcp_host = self.xmpp_hostname
- tcp_port = self.Port
-
- if proxy:
- # with proxies, client connects to proxy instead of directly to
- # XMPP server ((hostname, port))
- # tcp_host is hostname of machine used for socket connection
- # (DNS request will be done for proxy or BOSH CM hostname)
- tcp_host, tcp_port, proxy_user, proxy_pass = \
- transports_nb.get_proxy_data_from_dict(proxy)
-
- if proxy['type'] == 'bosh':
- # Setup BOSH transport
- self.socket = bosh.NonBlockingBOSH.get_instance(
- on_disconnect=self.disconnect,
- raise_event=self.raise_event,
- idlequeue=self.idlequeue,
- estabilish_tls=establish_tls,
- certs=certs,
- proxy_creds=(proxy_user, proxy_pass),
- xmpp_server=(self.xmpp_hostname, self.Port),
- domain=self.Server,
- bosh_dict=proxy)
- self.protocol_type = 'BOSH'
- self.wait_for_restart_response = \
- proxy['bosh_wait_for_restart_response']
- else:
- # http proxy
- proxy_dict['type'] = proxy['type']
- proxy_dict['xmpp_server'] = (self.xmpp_hostname, self.Port)
- proxy_dict['credentials'] = (proxy_user, proxy_pass)
-
- if not proxy or proxy['type'] != 'bosh':
- # Setup ordinary TCP transport
- self.socket = transports_nb.NonBlockingTCP.get_instance(
- on_disconnect=self.disconnect,
- raise_event=self.raise_event,
- idlequeue=self.idlequeue,
- estabilish_tls=establish_tls,
- certs=certs,
- proxy_dict=proxy_dict)
-
- # plug transport into client as self.Connection
- self.socket.PlugIn(self)
-
- self._resolve_hostname(
- hostname=tcp_host,
- port=tcp_port,
- on_success=self._try_next_ip)
-
- def _resolve_hostname(self, hostname, port, on_success):
- """
- Wrapper for getaddinfo call
-
- FIXME: getaddinfo blocks
- """
- try:
- self.ip_addresses = socket.getaddrinfo(hostname, port,
- socket.AF_UNSPEC, socket.SOCK_STREAM)
- except socket.gaierror, (errnum, errstr):
- self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' %
- (self.Server, self.Port, hostname, errstr))
- else:
- on_success()
-
- def _try_next_ip(self, err_message=None):
- """
- Iterate over IP addresses tries to connect to it
- """
- if err_message:
- log.debug('While looping over DNS A records: %s' % err_message)
- if self.ip_addresses == []:
- msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port)
- msg = msg + ' Error for last IP: %s' % err_message
- self.disconnect(msg)
- else:
- self.current_ip = self.ip_addresses.pop(0)
- self.socket.connect(
- conn_5tuple=self.current_ip,
- on_connect=lambda: self._xmpp_connect(),
- on_connect_failure=self._try_next_ip)
-
- def incoming_stream_version(self):
- """
- Get version of xml stream
- """
- if 'version' in self.Dispatcher.Stream._document_attrs:
- return self.Dispatcher.Stream._document_attrs['version']
- else:
- return None
-
- def _xmpp_connect(self, socket_type=None):
- """
- Start XMPP connecting process - open the XML stream. Is called after TCP
- connection is established or after switch to TLS when successfully
- negotiated with <starttls>.
- """
- # socket_type contains info which transport connection was established
- if not socket_type:
- if self.Connection.ssl_lib:
- # When ssl_lib is set we connected via SSL
- socket_type = 'ssl'
- else:
- # PLAIN is default
- socket_type = 'plain'
- self.connected = socket_type
- self._xmpp_connect_machine()
-
- def _xmpp_connect_machine(self, mode=None, data=None):
- """
- Finite automaton taking care of stream opening and features tag handling.
- Calls _on_stream_start when stream is started, and disconnect() on
- failure.
- """
- log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' %
- (mode, str(data)[:20]))
-
- def on_next_receive(mode):
- """
- Set desired on_receive callback on transport based on the state of
- connect_machine.
- """
- log.info('setting %s on next receive' % mode)
- if mode is None:
- self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking
- else:
- self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data))
-
- if not mode:
- # starting state
- if self.__dict__.has_key('Dispatcher'):
- self.Dispatcher.PlugOut()
- self.got_features = False
- dispatcher_nb.Dispatcher.get_instance().PlugIn(self)
- on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES')
-
- elif mode == 'FAILURE':
- self.disconnect('During XMPP connect: %s' % data)
-
- elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES':
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not hasattr(self, 'Dispatcher') or \
- self.Dispatcher.Stream._document_attrs is None:
- self._xmpp_connect_machine(
- mode='FAILURE',
- data='Error on stream open')
- return
-
- # if terminating stanza was received after init request then client gets
- # disconnected from bosh transport plugin and we have to end the stream
- # negotiating process straight away.
- # fixes #4657
- if not self.connected: return
-
- if self.incoming_stream_version() == '1.0':
- if not self.got_features:
- on_next_receive('RECEIVE_STREAM_FEATURES')
- else:
- log.info('got STREAM FEATURES in first recv')
- self._xmpp_connect_machine(mode='STREAM_STARTED')
- else:
- log.info('incoming stream version less than 1.0')
- self._xmpp_connect_machine(mode='STREAM_STARTED')
-
- elif mode == 'RECEIVE_STREAM_FEATURES':
- if data:
- # sometimes <features> are received together with document
- # attributes and sometimes on next receive...
- self.Dispatcher.ProcessNonBlocking(data)
- if not self.got_features:
- self._xmpp_connect_machine(
- mode='FAILURE',
- data='Missing <features> in 1.0 stream')
- else:
- log.info('got STREAM FEATURES in second recv')
- self._xmpp_connect_machine(mode='STREAM_STARTED')
-
- elif mode == 'STREAM_STARTED':
- self._on_stream_start()
-
- def _on_stream_start(self):
- """
- Called after XMPP stream is opened. TLS negotiation may follow if
- supported and desired.
- """
- self.stream_started = True
- self.onreceive(None)
-
- if self.connected == 'plain':
- if self.desired_security == 'plain':
- # if we want and have plain connection, we're done now
- self._on_connect()
- else:
- # try to negotiate TLS
- if self.incoming_stream_version() != '1.0':
- # if stream version is less than 1.0, we can't do more
- log.warn('While connecting with type = "tls": stream version ' +
- 'is less than 1.0')
- self._on_connect()
- return
- if self.Dispatcher.Stream.features.getTag('starttls'):
- # Server advertises TLS support, start negotiation
- self.stream_started = False
- log.info('TLS supported by remote server. Requesting TLS start.')
- self._tls_negotiation_handler()
- else:
- log.warn('While connecting with type = "tls": TLS unsupported ' +
- 'by remote server')
- self._on_connect()
-
- elif self.connected in ['ssl', 'tls']:
- self._on_connect()
- else:
- assert False, 'Stream opened for unsupported connection'
-
- def _tls_negotiation_handler(self, con=None, tag=None):
- """
- Take care of TLS negotioation with <starttls>
- """
- log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag)
- if not con and not tag:
- # starting state when we send the <starttls>
- self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler,
- xmlns=NS_TLS)
- self.RegisterHandlerOnce('failure', self._tls_negotiation_handler,
- xmlns=NS_TLS)
- self.send('<starttls xmlns="%s"/>' % NS_TLS)
- else:
- # we got <proceed> or <failure>
- if tag.getNamespace() != NS_TLS:
- self.disconnect('Unknown namespace: %s' % tag.getNamespace())
- return
- tagname = tag.getName()
- if tagname == 'failure':
- self.disconnect('TLS <failure> received: %s' % tag)
- return
- log.info('Got starttls proceed response. Switching to TLS/SSL...')
- # following call wouldn't work for BOSH transport but it doesn't matter
- # because <starttls> negotiation with BOSH is forbidden
- self.Connection.tls_init(
- on_succ = lambda: self._xmpp_connect(socket_type='tls'),
- on_fail = lambda: self.disconnect('error while etabilishing TLS'))
-
- def _on_connect(self):
- """
- Preceed call of on_connect callback
- """
- self.onreceive(None)
- self.on_connect(self, self.connected)
-
- def raise_event(self, event_type, data):
- """
- Raise event to connection instance. DATA_SENT and DATA_RECIVED events
- are used in XML console to show XMPP traffic
- """
- log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data))
- if hasattr(self, 'Dispatcher'):
- self.Dispatcher.Event('', event_type, data)
+ """
+ Client class is XMPP connection mountpoint. Objects for authentication,
+ network communication, roster, xml parsing ... are plugged to client object.
+ Client implements the abstract behavior - mostly negotioation and callbacks
+ handling, whereas underlying modules take care of feature-specific logic
+ """
+
+ def __init__(self, domain, idlequeue, caller=None):
+ """
+ Caches connection data
+
+ :param domain: domain - for to: attribute (from account info)
+ :param idlequeue: processing idlequeue
+ :param caller: calling object - it has to implement methods
+ _event_dispatcher which is called from dispatcher instance
+ """
+ self.Namespace = protocol.NS_CLIENT
+ self.defaultNamespace = self.Namespace
+
+ self.idlequeue = idlequeue
+ self.disconnect_handlers = []
+
+ self.Server = domain
+ self.xmpp_hostname = None # FQDN hostname to connect to
+
+ # caller is who initiated this client, it is in needed to register
+ # the EventDispatcher
+ self._caller = caller
+ self._owner = self
+ self._registered_name = None # our full jid, set after successful auth
+ self.connected = ''
+ self.socket = None
+ self.on_connect = None
+ self.on_proxy_failure = None
+ self.on_connect_failure = None
+ self.proxy = None
+ self.got_features = False
+ self.stream_started = False
+ self.disconnecting = False
+ self.protocol_type = 'XMPP'
+
+ def disconnect(self, message=''):
+ """
+ Called on disconnection - disconnect callback is picked based on state of
+ the client.
+ """
+ # to avoid recursive calls
+ if self.disconnecting: return
+
+ log.info('Disconnecting NBClient: %s' % message)
+
+ if 'NonBlockingRoster' in self.__dict__:
+ self.NonBlockingRoster.PlugOut()
+ if 'NonBlockingBind' in self.__dict__:
+ self.NonBlockingBind.PlugOut()
+ if 'NonBlockingNonSASL' in self.__dict__:
+ self.NonBlockingNonSASL.PlugOut()
+ if 'SASL' in self.__dict__:
+ self.SASL.PlugOut()
+ if 'NonBlockingTCP' in self.__dict__:
+ self.NonBlockingTCP.PlugOut()
+ if 'NonBlockingHTTP' in self.__dict__:
+ self.NonBlockingHTTP.PlugOut()
+ if 'NonBlockingBOSH' in self.__dict__:
+ self.NonBlockingBOSH.PlugOut()
+ # FIXME: we never unplug dispatcher, only on next connect
+ # See _xmpp_connect_machine and SASLHandler
+
+ connected = self.connected
+ stream_started = self.stream_started
+
+ self.connected = ''
+ self.stream_started = False
+
+ self.disconnecting = True
+
+ log.debug('Client disconnected..')
+ if connected == '':
+ # if we're disconnecting before connection to XMPP sever is opened,
+ # we don't call disconnect handlers but on_connect_failure callback
+ if self.proxy:
+ # with proxy, we have different failure callback
+ log.debug('calling on_proxy_failure cb')
+ self.on_proxy_failure(reason=message)
+ else:
+ log.debug('calling on_connect_failure cb')
+ self.on_connect_failure()
+ else:
+ # we are connected to XMPP server
+ if not stream_started:
+ # if error occur before XML stream was opened, e.g. no response on
+ # init request, we call the on_connect_failure callback because
+ # proper connection is not established yet and it's not a proxy
+ # issue
+ log.debug('calling on_connect_failure cb')
+ self._caller.streamError = message
+ self.on_connect_failure()
+ else:
+ # with open connection, we are calling the disconnect handlers
+ for i in reversed(self.disconnect_handlers):
+ log.debug('Calling disconnect handler %s' % i)
+ i()
+ self.disconnecting = False
+
+ def connect(self, on_connect, on_connect_failure, hostname=None, port=5222,
+ on_proxy_failure=None, proxy=None, secure_tuple=('plain', None,
+ None)):
+ """
+ Open XMPP connection (open XML streams in both directions)
+
+ :param on_connect: called after stream is successfully opened
+ :param on_connect_failure: called when error occures during connection
+ :param hostname: hostname of XMPP server from SRV request
+ :param port: port number of XMPP server
+ :param on_proxy_failure: called if error occurres during TCP connection to
+ proxy server or during proxy connecting process
+ :param proxy: dictionary with proxy data. It should contain at least
+ values for keys 'host' and 'port' - connection details for proxy serve
+ and optionally keys 'user' and 'pass' as proxy credentials
+ :param secure_tuple: tuple of (desired connection type, cacerts, mycerts)
+ connection type can be 'ssl' - TLS established after TCP connection,
+ 'tls' - TLS established after negotiation with starttls, or 'plain'.
+ cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more
+ details
+ """
+ self.on_connect = on_connect
+ self.on_connect_failure=on_connect_failure
+ self.on_proxy_failure = on_proxy_failure
+ self.desired_security, self.cacerts, self.mycerts = secure_tuple
+ self.Connection = None
+ self.Port = port
+ self.proxy = proxy
+
+ if hostname:
+ self.xmpp_hostname = hostname
+ else:
+ self.xmpp_hostname = self.Server
+
+ # We only check for SSL here as for TLS we will first have to start a
+ # PLAIN connection and negotiate TLS afterwards.
+ # establish_tls will instruct transport to start secure connection
+ # directly
+ establish_tls = self.desired_security == 'ssl'
+ certs = (self.cacerts, self.mycerts)
+
+ proxy_dict = {}
+ tcp_host = self.xmpp_hostname
+ tcp_port = self.Port
+
+ if proxy:
+ # with proxies, client connects to proxy instead of directly to
+ # XMPP server ((hostname, port))
+ # tcp_host is hostname of machine used for socket connection
+ # (DNS request will be done for proxy or BOSH CM hostname)
+ tcp_host, tcp_port, proxy_user, proxy_pass = \
+ transports_nb.get_proxy_data_from_dict(proxy)
+
+ if proxy['type'] == 'bosh':
+ # Setup BOSH transport
+ self.socket = bosh.NonBlockingBOSH.get_instance(
+ on_disconnect=self.disconnect,
+ raise_event=self.raise_event,
+ idlequeue=self.idlequeue,
+ estabilish_tls=establish_tls,
+ certs=certs,
+ proxy_creds=(proxy_user, proxy_pass),
+ xmpp_server=(self.xmpp_hostname, self.Port),
+ domain=self.Server,
+ bosh_dict=proxy)
+ self.protocol_type = 'BOSH'
+ self.wait_for_restart_response = \
+ proxy['bosh_wait_for_restart_response']
+ else:
+ # http proxy
+ proxy_dict['type'] = proxy['type']
+ proxy_dict['xmpp_server'] = (self.xmpp_hostname, self.Port)
+ proxy_dict['credentials'] = (proxy_user, proxy_pass)
+
+ if not proxy or proxy['type'] != 'bosh':
+ # Setup ordinary TCP transport
+ self.socket = transports_nb.NonBlockingTCP.get_instance(
+ on_disconnect=self.disconnect,
+ raise_event=self.raise_event,
+ idlequeue=self.idlequeue,
+ estabilish_tls=establish_tls,
+ certs=certs,
+ proxy_dict=proxy_dict)
+
+ # plug transport into client as self.Connection
+ self.socket.PlugIn(self)
+
+ self._resolve_hostname(
+ hostname=tcp_host,
+ port=tcp_port,
+ on_success=self._try_next_ip)
+
+ def _resolve_hostname(self, hostname, port, on_success):
+ """
+ Wrapper for getaddinfo call
+
+ FIXME: getaddinfo blocks
+ """
+ try:
+ self.ip_addresses = socket.getaddrinfo(hostname, port,
+ socket.AF_UNSPEC, socket.SOCK_STREAM)
+ except socket.gaierror, (errnum, errstr):
+ self.disconnect(message='Lookup failure for %s:%s, hostname: %s - %s' %
+ (self.Server, self.Port, hostname, errstr))
+ else:
+ on_success()
+
+ def _try_next_ip(self, err_message=None):
+ """
+ Iterate over IP addresses tries to connect to it
+ """
+ if err_message:
+ log.debug('While looping over DNS A records: %s' % err_message)
+ if self.ip_addresses == []:
+ msg = 'Run out of hosts for name %s:%s.' % (self.Server, self.Port)
+ msg = msg + ' Error for last IP: %s' % err_message
+ self.disconnect(msg)
+ else:
+ self.current_ip = self.ip_addresses.pop(0)
+ self.socket.connect(
+ conn_5tuple=self.current_ip,
+ on_connect=lambda: self._xmpp_connect(),
+ on_connect_failure=self._try_next_ip)
+
+ def incoming_stream_version(self):
+ """
+ Get version of xml stream
+ """
+ if 'version' in self.Dispatcher.Stream._document_attrs:
+ return self.Dispatcher.Stream._document_attrs['version']
+ else:
+ return None
+
+ def _xmpp_connect(self, socket_type=None):
+ """
+ Start XMPP connecting process - open the XML stream. Is called after TCP
+ connection is established or after switch to TLS when successfully
+ negotiated with <starttls>.
+ """
+ # socket_type contains info which transport connection was established
+ if not socket_type:
+ if self.Connection.ssl_lib:
+ # When ssl_lib is set we connected via SSL
+ socket_type = 'ssl'
+ else:
+ # PLAIN is default
+ socket_type = 'plain'
+ self.connected = socket_type
+ self._xmpp_connect_machine()
+
+ def _xmpp_connect_machine(self, mode=None, data=None):
+ """
+ Finite automaton taking care of stream opening and features tag handling.
+ Calls _on_stream_start when stream is started, and disconnect() on
+ failure.
+ """
+ log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' %
+ (mode, str(data)[:20]))
+
+ def on_next_receive(mode):
+ """
+ Set desired on_receive callback on transport based on the state of
+ connect_machine.
+ """
+ log.info('setting %s on next receive' % mode)
+ if mode is None:
+ self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking
+ else:
+ self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data))
+
+ if not mode:
+ # starting state
+ if self.__dict__.has_key('Dispatcher'):
+ self.Dispatcher.PlugOut()
+ self.got_features = False
+ dispatcher_nb.Dispatcher.get_instance().PlugIn(self)
+ on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES')
+
+ elif mode == 'FAILURE':
+ self.disconnect('During XMPP connect: %s' % data)
+
+ elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES':
+ if data:
+ self.Dispatcher.ProcessNonBlocking(data)
+ if not hasattr(self, 'Dispatcher') or \
+ self.Dispatcher.Stream._document_attrs is None:
+ self._xmpp_connect_machine(
+ mode='FAILURE',
+ data='Error on stream open')
+ return
+
+ # if terminating stanza was received after init request then client gets
+ # disconnected from bosh transport plugin and we have to end the stream
+ # negotiating process straight away.
+ # fixes #4657
+ if not self.connected: return
+
+ if self.incoming_stream_version() == '1.0':
+ if not self.got_features:
+ on_next_receive('RECEIVE_STREAM_FEATURES')
+ else:
+ log.info('got STREAM FEATURES in first recv')
+ self._xmpp_connect_machine(mode='STREAM_STARTED')
+ else:
+ log.info('incoming stream version less than 1.0')
+ self._xmpp_connect_machine(mode='STREAM_STARTED')
+
+ elif mode == 'RECEIVE_STREAM_FEATURES':
+ if data:
+ # sometimes <features> are received together with document
+ # attributes and sometimes on next receive...
+ self.Dispatcher.ProcessNonBlocking(data)
+ if not self.got_features:
+ self._xmpp_connect_machine(
+ mode='FAILURE',
+ data='Missing <features> in 1.0 stream')
+ else:
+ log.info('got STREAM FEATURES in second recv')
+ self._xmpp_connect_machine(mode='STREAM_STARTED')
+
+ elif mode == 'STREAM_STARTED':
+ self._on_stream_start()
+
+ def _on_stream_start(self):
+ """
+ Called after XMPP stream is opened. TLS negotiation may follow if
+ supported and desired.
+ """
+ self.stream_started = True
+ self.onreceive(None)
+
+ if self.connected == 'plain':
+ if self.desired_security == 'plain':
+ # if we want and have plain connection, we're done now
+ self._on_connect()
+ else:
+ # try to negotiate TLS
+ if self.incoming_stream_version() != '1.0':
+ # if stream version is less than 1.0, we can't do more
+ log.warn('While connecting with type = "tls": stream version ' +
+ 'is less than 1.0')
+ self._on_connect()
+ return
+ if self.Dispatcher.Stream.features.getTag('starttls'):
+ # Server advertises TLS support, start negotiation
+ self.stream_started = False
+ log.info('TLS supported by remote server. Requesting TLS start.')
+ self._tls_negotiation_handler()
+ else:
+ log.warn('While connecting with type = "tls": TLS unsupported ' +
+ 'by remote server')
+ self._on_connect()
+
+ elif self.connected in ['ssl', 'tls']:
+ self._on_connect()
+ else:
+ assert False, 'Stream opened for unsupported connection'
+
+ def _tls_negotiation_handler(self, con=None, tag=None):
+ """
+ Take care of TLS negotioation with <starttls>
+ """
+ log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag)
+ if not con and not tag:
+ # starting state when we send the <starttls>
+ self.RegisterHandlerOnce('proceed', self._tls_negotiation_handler,
+ xmlns=NS_TLS)
+ self.RegisterHandlerOnce('failure', self._tls_negotiation_handler,
+ xmlns=NS_TLS)
+ self.send('<starttls xmlns="%s"/>' % NS_TLS)
+ else:
+ # we got <proceed> or <failure>
+ if tag.getNamespace() != NS_TLS:
+ self.disconnect('Unknown namespace: %s' % tag.getNamespace())
+ return
+ tagname = tag.getName()
+ if tagname == 'failure':
+ self.disconnect('TLS <failure> received: %s' % tag)
+ return
+ log.info('Got starttls proceed response. Switching to TLS/SSL...')
+ # following call wouldn't work for BOSH transport but it doesn't matter
+ # because <starttls> negotiation with BOSH is forbidden
+ self.Connection.tls_init(
+ on_succ = lambda: self._xmpp_connect(socket_type='tls'),
+ on_fail = lambda: self.disconnect('error while etabilishing TLS'))
+
+ def _on_connect(self):
+ """
+ Preceed call of on_connect callback
+ """
+ self.onreceive(None)
+ self.on_connect(self, self.connected)
+
+ def raise_event(self, event_type, data):
+ """
+ Raise event to connection instance. DATA_SENT and DATA_RECIVED events
+ are used in XML console to show XMPP traffic
+ """
+ log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data))
+ if hasattr(self, 'Dispatcher'):
+ self.Dispatcher.Event('', event_type, data)
###############################################################################
### follows code for authentication, resource bind, session and roster download
###############################################################################
- def auth(self, user, password, resource='', sasl=True, on_auth=None):
- """
- Authenticate connnection and bind resource. If resource is not provided
- random one or library name used
-
- :param user: XMPP username
- :param password: XMPP password
- :param resource: resource that shall be used for auth/connecting
- :param sasl: Boolean indicating if SASL shall be used. (default: True)
- :param on_auth: Callback, called after auth. On auth failure, argument
- is None.
- """
- self._User, self._Password = user, password
- self._Resource, self._sasl = resource, sasl
- self.on_auth = on_auth
- self._on_doc_attrs()
- return
-
- def _on_old_auth(self, res):
- """
- Callback used by NON-SASL auth. On auth failure, res is None
- """
- if res:
- self.connected += '+old_auth'
- self.on_auth(self, 'old_auth')
- else:
- self.on_auth(self, None)
-
- def _on_sasl_auth(self, res):
- """
- Used internally. On auth failure, res is None
- """
- self.onreceive(None)
- if res:
- self.connected += '+sasl'
- self.on_auth(self, 'sasl')
- else:
- self.on_auth(self, None)
-
- def _on_doc_attrs(self):
- """
- Plug authentication objects and start auth
- """
- if self._sasl:
- auth_nb.SASL.get_instance(self._User, self._Password,
- self._on_start_sasl).PlugIn(self)
- if not self._sasl or self.SASL.startsasl == 'not-supported':
- if not self._Resource:
- self._Resource = 'xmpppy'
- auth_nb.NonBlockingNonSASL.get_instance(self._User, self._Password,
- self._Resource, self._on_old_auth).PlugIn(self)
- return
- self.SASL.auth()
- return True
-
- def _on_start_sasl(self, data=None):
- """
- Callback used by SASL, called on each auth step
- """
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not 'SASL' in self.__dict__:
- # SASL is pluged out, possible disconnect
- return
- if self.SASL.startsasl == 'in-process':
- return
- self.onreceive(None)
- if self.SASL.startsasl == 'failure':
- # wrong user/pass, stop auth
- if 'SASL' in self.__dict__:
- self.SASL.PlugOut()
- self.connected = None # FIXME: is this intended? We use ''elsewhere
- self._on_sasl_auth(None)
- elif self.SASL.startsasl == 'success':
- auth_nb.NonBlockingBind.get_instance().PlugIn(self)
- self.onreceive(self._on_auth_bind)
- return True
-
- def _on_auth_bind(self, data):
- # FIXME: Why use this callback and not bind directly?
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if self.NonBlockingBind.bound is None:
- return
- self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth)
- return True
-
- def initRoster(self, version=''):
- """
- Plug in the roster
- """
- if not self.__dict__.has_key('NonBlockingRoster'):
- return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self)
-
- def getRoster(self, on_ready=None, force=False):
- """
- Return the Roster instance, previously plugging it in and requesting
- roster from server if needed
- """
- if self.__dict__.has_key('NonBlockingRoster'):
- return self.NonBlockingRoster.getRoster(on_ready, force)
- return None
-
- def sendPresence(self, jid=None, typ=None, requestRoster=0):
- """
- Send some specific presence state. Can also request roster from server if
- according agrument is set
- """
- if requestRoster:
- # FIXME: used somewhere?
- roster_nb.NonBlockingRoster.get_instance().PlugIn(self)
- self.send(dispatcher_nb.Presence(to=jid, typ=typ))
+ def auth(self, user, password, resource='', sasl=True, on_auth=None):
+ """
+ Authenticate connnection and bind resource. If resource is not provided
+ random one or library name used
+
+ :param user: XMPP username
+ :param password: XMPP password
+ :param resource: resource that shall be used for auth/connecting
+ :param sasl: Boolean indicating if SASL shall be used. (default: True)
+ :param on_auth: Callback, called after auth. On auth failure, argument
+ is None.
+ """
+ self._User, self._Password = user, password
+ self._Resource, self._sasl = resource, sasl
+ self.on_auth = on_auth
+ self._on_doc_attrs()
+ return
+
+ def _on_old_auth(self, res):
+ """
+ Callback used by NON-SASL auth. On auth failure, res is None
+ """
+ if res:
+ self.connected += '+old_auth'
+ self.on_auth(self, 'old_auth')
+ else:
+ self.on_auth(self, None)
+
+ def _on_sasl_auth(self, res):
+ """
+ Used internally. On auth failure, res is None
+ """
+ self.onreceive(None)
+ if res:
+ self.connected += '+sasl'
+ self.on_auth(self, 'sasl')
+ else:
+ self.on_auth(self, None)
+
+ def _on_doc_attrs(self):
+ """
+ Plug authentication objects and start auth
+ """
+ if self._sasl:
+ auth_nb.SASL.get_instance(self._User, self._Password,
+ self._on_start_sasl).PlugIn(self)
+ if not self._sasl or self.SASL.startsasl == 'not-supported':
+ if not self._Resource:
+ self._Resource = 'xmpppy'
+ auth_nb.NonBlockingNonSASL.get_instance(self._User, self._Password,
+ self._Resource, self._on_old_auth).PlugIn(self)
+ return
+ self.SASL.auth()
+ return True
+
+ def _on_start_sasl(self, data=None):
+ """
+ Callback used by SASL, called on each auth step
+ """
+ if data:
+ self.Dispatcher.ProcessNonBlocking(data)
+ if not 'SASL' in self.__dict__:
+ # SASL is pluged out, possible disconnect
+ return
+ if self.SASL.startsasl == 'in-process':
+ return
+ self.onreceive(None)
+ if self.SASL.startsasl == 'failure':
+ # wrong user/pass, stop auth
+ if 'SASL' in self.__dict__:
+ self.SASL.PlugOut()
+ self.connected = None # FIXME: is this intended? We use ''elsewhere
+ self._on_sasl_auth(None)
+ elif self.SASL.startsasl == 'success':
+ auth_nb.NonBlockingBind.get_instance().PlugIn(self)
+ self.onreceive(self._on_auth_bind)
+ return True
+
+ def _on_auth_bind(self, data):
+ # FIXME: Why use this callback and not bind directly?
+ if data:
+ self.Dispatcher.ProcessNonBlocking(data)
+ if self.NonBlockingBind.bound is None:
+ return
+ self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth)
+ return True
+
+ def initRoster(self, version=''):
+ """
+ Plug in the roster
+ """
+ if not self.__dict__.has_key('NonBlockingRoster'):
+ return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self)
+
+ def getRoster(self, on_ready=None, force=False):
+ """
+ Return the Roster instance, previously plugging it in and requesting
+ roster from server if needed
+ """
+ if self.__dict__.has_key('NonBlockingRoster'):
+ return self.NonBlockingRoster.getRoster(on_ready, force)
+ return None
+
+ def sendPresence(self, jid=None, typ=None, requestRoster=0):
+ """
+ Send some specific presence state. Can also request roster from server if
+ according agrument is set
+ """
+ if requestRoster:
+ # FIXME: used somewhere?
+ roster_nb.NonBlockingRoster.get_instance().PlugIn(self)
+ self.send(dispatcher_nb.Presence(to=jid, typ=typ))
###############################################################################
### following methods are moved from blocking client class of xmpppy
###############################################################################
- def RegisterDisconnectHandler(self,handler):
- """
- Register handler that will be called on disconnect
- """
- self.disconnect_handlers.append(handler)
-
- def UnregisterDisconnectHandler(self,handler):
- """
- Unregister handler that is called on disconnect
- """
- self.disconnect_handlers.remove(handler)
-
- def DisconnectHandler(self):
- """
- Default disconnect handler. Just raises an IOError. If you choosed to use
- this class in your production client, override this method or at least
- unregister it.
- """
- raise IOError('Disconnected from server.')
-
- def get_connect_type(self):
- """
- Return connection state. F.e.: None / 'tls' / 'plain+non_sasl'
- """
- return self.connected
-
- def get_peerhost(self):
- """
- Gets the ip address of the account, from which is made connection to the
- server (e.g. IP and port of gajim's socket)
-
- We will create listening socket on the same ip
- """
- # FIXME: tuple (ip, port) is expected (and checked for) but port num is
- # useless
- return self.socket.peerhost
-
-# vim: se ts=3:
+ def 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 DisconnectHandler(self):
+ """
+ Default disconnect handler. Just raises an IOError. If you choosed to use
+ this class in your production client, override this method or at least
+ unregister it.
+ """
+ raise IOError('Disconnected from server.')
+
+ def get_connect_type(self):
+ """
+ Return connection state. F.e.: None / 'tls' / 'plain+non_sasl'
+ """
+ return self.connected
+
+ def get_peerhost(self):
+ """
+ Gets the ip address of the account, from which is made connection to the
+ server (e.g. IP and port of gajim's socket)
+
+ We will create listening socket on the same ip
+ """
+ # FIXME: tuple (ip, port) is expected (and checked for) but port num is
+ # useless
+ return self.socket.peerhost
diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py
index 469c47f4a..c2944ad29 100644
--- a/src/common/xmpp/dispatcher_nb.py
+++ b/src/common/xmpp/dispatcher_nb.py
@@ -24,7 +24,7 @@ import simplexml, sys, locale
from xml.parsers.expat import ExpatError
from plugin import PlugIn
from protocol import (NS_STREAMS, NS_XMPP_STREAMS, NS_HTTP_BIND, Iq, Presence,
- Message, Protocol, Node, Error, ERR_FEATURE_NOT_IMPLEMENTED, StreamError)
+ Message, Protocol, Node, Error, ERR_FEATURE_NOT_IMPLEMENTED, StreamError)
import logging
log = logging.getLogger('gajim.c.x.dispatcher_nb')
@@ -36,566 +36,564 @@ XML_DECLARATION = '<?xml version=\'1.0\'?>'
# FIXME: ugly
class Dispatcher():
- """
- Why is this here - I needed to redefine Dispatcher for BOSH and easiest way
- was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble
- is that reference used to access dispatcher instance is in Client attribute
- named by __class__.__name__ of the dispatcher instance .. long story short:
+ """
+ Why is this here - I needed to redefine Dispatcher for BOSH and easiest way
+ was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble
+ is that reference used to access dispatcher instance is in Client attribute
+ named by __class__.__name__ of the dispatcher instance .. long story short:
- I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp
+ I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp
- If having two kinds of dispatcher will go well, I will rewrite the dispatcher
- references in other scripts
- """
+ If having two kinds of dispatcher will go well, I will rewrite the dispatcher
+ references in other scripts
+ """
- def PlugIn(self, client_obj, after_SASL=False, old_features=None):
- if client_obj.protocol_type == 'XMPP':
- XMPPDispatcher().PlugIn(client_obj)
- elif client_obj.protocol_type == 'BOSH':
- BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features)
- else:
- assert False # should never be reached
+ def PlugIn(self, client_obj, after_SASL=False, old_features=None):
+ if client_obj.protocol_type == 'XMPP':
+ XMPPDispatcher().PlugIn(client_obj)
+ elif client_obj.protocol_type == 'BOSH':
+ BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features)
+ else:
+ assert False # should never be reached
- @classmethod
- def get_instance(cls, *args, **kwargs):
- """
- Factory Method for object creation
+ @classmethod
+ def get_instance(cls, *args, **kwargs):
+ """
+ Factory Method for object creation
- Use this instead of directly initializing the class in order to make
- unit testing much easier.
- """
- return cls(*args, **kwargs)
+ Use this instead of directly initializing the class in order to make
+ unit testing much easier.
+ """
+ return cls(*args, **kwargs)
class XMPPDispatcher(PlugIn):
- """
- Handles XMPP stream and is the first who takes control over a fresh stanza
-
- Is plugged into NonBlockingClient but can be replugged to restart handled
- stream headers (used by SASL f.e.).
- """
-
- def __init__(self):
- PlugIn.__init__(self)
- self.handlers = {}
- self._expected = {}
- self._defaultHandler = None
- self._pendingExceptions = []
- self._eventHandler = None
- self._cycleHandlers = []
- self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler,
- self.RegisterEventHandler, self.UnregisterCycleHandler,
- self.RegisterCycleHandler, self.RegisterHandlerOnce,
- self.UnregisterHandler, self.RegisterProtocol,
- self.SendAndWaitForResponse, self.SendAndCallForResponse,
- self.getAnID, self.Event, self.send]
-
- def getAnID(self):
- global outgoingID
- outgoingID += 1
- return repr(outgoingID)
-
- def dumpHandlers(self):
- """
- Return set of user-registered callbacks in it's internal format. Used
- within the library to carry user handlers set over Dispatcher replugins
- """
- return self.handlers
-
- def restoreHandlers(self, handlers):
- """
- Restore user-registered callbacks structure from dump previously obtained
- via dumpHandlers. Used within the library to carry user handlers set over
- Dispatcher replugins.
- """
- self.handlers = handlers
-
- def _init(self):
- """
- Register default namespaces/protocols/handlers. Used internally
- """
- # FIXME: inject dependencies, do not rely that they are defined by our
- # owner
- self.RegisterNamespace('unknown')
- self.RegisterNamespace(NS_STREAMS)
- self.RegisterNamespace(self._owner.defaultNamespace)
- self.RegisterProtocol('iq', Iq)
- self.RegisterProtocol('presence', Presence)
- self.RegisterProtocol('message', Message)
- self.RegisterDefaultHandler(self.returnStanzaHandler)
- self.RegisterEventHandler(self._owner._caller._event_dispatcher)
- self.on_responses = {}
-
- def plugin(self, owner):
- """
- Plug the Dispatcher instance into Client class instance and send initial
- stream header. Used internally
- """
- self._init()
- self._owner.lastErrNode = None
- self._owner.lastErr = None
- self._owner.lastErrCode = None
- if hasattr(self._owner, 'StreamInit'):
- self._owner.StreamInit()
- else:
- self.StreamInit()
-
- def plugout(self):
- """
- Prepare instance to be destructed
- """
- self.Stream.dispatch = None
- self.Stream.features = None
- self.Stream.destroy()
- self._owner = None
- self.Stream = None
-
- def StreamInit(self):
- """
- Send an initial stream header
- """
- self.Stream = simplexml.NodeBuilder()
- self.Stream.dispatch = self.dispatch
- self.Stream._dispatch_depth = 2
- self.Stream.stream_header_received = self._check_stream_start
- self.Stream.features = None
- self._metastream = Node('stream:stream')
- self._metastream.setNamespace(self._owner.Namespace)
- self._metastream.setAttr('version', '1.0')
- self._metastream.setAttr('xmlns:stream', NS_STREAMS)
- self._metastream.setAttr('to', self._owner.Server)
- if locale.getdefaultlocale()[0]:
- self._metastream.setAttr('xml:lang',
- locale.getdefaultlocale()[0].split('_')[0])
- self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2]))
-
- def _check_stream_start(self, ns, tag, attrs):
- if ns != NS_STREAMS or tag!='stream':
- raise ValueError('Incorrect stream start: (%s,%s). Terminating.'
- % (tag, ns))
-
- def ProcessNonBlocking(self, data):
- """
- Check incoming stream for data waiting
-
- :param data: data received from transports/IO sockets
- :return:
- 1) length of processed data if some data were processed;
- 2) '0' string if no data were processed but link is alive;
- 3) 0 (zero) if underlying connection is closed.
- """
- # FIXME:
- # When an error occurs we disconnect the transport directly. Client's
- # disconnect method will never be called.
- # Is this intended?
- # also look at transports start_disconnect()
- for handler in self._cycleHandlers:
- handler(self)
- if len(self._pendingExceptions) > 0:
- _pendingException = self._pendingExceptions.pop()
- raise _pendingException[0], _pendingException[1], _pendingException[2]
- try:
- self.Stream.Parse(data)
- # end stream:stream tag received
- if self.Stream and self.Stream.has_received_endtag():
- self._owner.disconnect(self.Stream.streamError)
- return 0
- except ExpatError:
- log.error('Invalid XML received from server. Forcing disconnect.')
- self._owner.Connection.disconnect()
- return 0
- except ValueError, e:
- log.debug('ValueError: %s' % str(e))
- self._owner.Connection.pollend()
- return 0
- if len(self._pendingExceptions) > 0:
- _pendingException = self._pendingExceptions.pop()
- raise _pendingException[0], _pendingException[1], _pendingException[2]
- if len(data) == 0:
- return '0'
- return len(data)
-
- def RegisterNamespace(self, xmlns, order='info'):
- """
- Create internal structures for newly registered namespace
-
- You can register handlers for this namespace afterwards. By default
- one namespace is already registered
- (jabber:client or jabber:component:accept depending on context.
- """
- log.debug('Registering namespace "%s"' % xmlns)
- self.handlers[xmlns] = {}
- self.RegisterProtocol('unknown', Protocol, xmlns=xmlns)
- self.RegisterProtocol('default', Protocol, xmlns=xmlns)
-
- def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'):
- """
- Used to declare some top-level stanza name to dispatcher
-
- Needed to start registering handlers for such stanzas. Iq, message and
- presence protocols are registered by default.
- """
- if not xmlns:
- xmlns=self._owner.defaultNamespace
- log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns))
- self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]}
-
- def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='',
- makefirst=0, system=0):
- """
- Register handler for processing all stanzas for specified namespace
- """
- self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst,
- system)
-
- def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None,
- makefirst=False, system=False):
- """
- Register user callback as stanzas handler of declared type
-
- Callback arguments:
- dispatcher instance (for replying), incoming return of previous handlers.
- The callback must raise xmpp.NodeProcessed just before return if it wants
- to prevent other callbacks to be called with the same stanza as argument
- _and_, more importantly library from returning stanza to sender with error set.
-
- :param name: name of stanza. F.e. "iq".
- :param handler: user callback.
- :param typ: value of stanza's "type" attribute. If not specified any
- value will match
- :param ns: namespace of child that stanza must contain.
- :param makefirst: insert handler in the beginning of handlers list instead
- of adding it to the end. Note that more common handlers i.e. w/o "typ"
- and " will be called first nevertheless.
- :param system: call handler even if NodeProcessed Exception were raised
- already.
- """
- if not xmlns:
- xmlns=self._owner.defaultNamespace
- log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' %
- (handler, name, typ, ns, xmlns))
- if not typ and not ns:
- typ='default'
- if xmlns not in self.handlers:
- self.RegisterNamespace(xmlns,'warn')
- if name not in self.handlers[xmlns]:
- self.RegisterProtocol(name,Protocol,xmlns,'warn')
- if typ+ns not in self.handlers[xmlns][name]:
- self.handlers[xmlns][name][typ+ns]=[]
- if makefirst:
- self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler,
- 'system':system})
- else:
- self.handlers[xmlns][name][typ+ns].append({'func':handler,
- 'system':system})
-
- def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None,
- makefirst=0, system=0):
- """
- Unregister handler after first call (not implemented yet)
- """
- # FIXME Drop or implement
- if not xmlns:
- xmlns = self._owner.defaultNamespace
- self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
-
- def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None):
- """
- Unregister handler. "typ" and "ns" must be specified exactly the same as
- with registering.
- """
- if not xmlns:
- xmlns = self._owner.defaultNamespace
- if not typ and not ns:
- typ='default'
- if xmlns not in self.handlers:
- return
- if name not in self.handlers[xmlns]:
- return
- if typ+ns not in self.handlers[xmlns][name]:
- return
- for pack in self.handlers[xmlns][name][typ+ns]:
- if pack['func'] == handler:
- try:
- self.handlers[xmlns][name][typ+ns].remove(pack)
- except ValueError:
- pass
-
- def RegisterDefaultHandler(self, handler):
- """
- Specify the handler that will be used if no NodeProcessed exception were
- raised. This is returnStanzaHandler by default.
- """
- self._defaultHandler = handler
-
- def RegisterEventHandler(self, handler):
- """
- Register handler that will process events. F.e. "FILERECEIVED" event. See
- common/connection: _event_dispatcher()
- """
- self._eventHandler = handler
-
- def returnStanzaHandler(self, conn, stanza):
- """
- Return stanza back to the sender with <feature-not-implemented/> error
- set
- """
- if stanza.getType() in ('get','set'):
- conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED))
-
- def RegisterCycleHandler(self, handler):
- """
- Register handler that will be called on every Dispatcher.Process() call
- """
- if handler not in self._cycleHandlers:
- self._cycleHandlers.append(handler)
-
- def UnregisterCycleHandler(self, handler):
- """
- Unregister handler that will is called on every Dispatcher.Process() call
- """
- if handler in self._cycleHandlers:
- self._cycleHandlers.remove(handler)
-
- def Event(self, realm, event, data):
- """
- Raise some event
-
- :param realm: scope of event. Usually a namespace.
- :param event: the event itself. F.e. "SUCCESSFUL SEND".
- :param data: data that comes along with event. Depends on event.
- """
- if self._eventHandler:
- self._eventHandler(realm, event, data)
- else:
- log.warning('Received unhandled event: %s' % event)
-
- def dispatch(self, stanza, session=None, direct=0):
- """
- Main procedure that performs XMPP stanza recognition and calling
- apppropriate handlers for it. Called by simplexml
- """
- # FIXME: Where do we set session and direct. Why? What are those intended
- # to do?
-
- #log.info('dispatch called: stanza = %s, session = %s, direct= %s'
- # % (stanza, session, direct))
- if not session:
- session = self
- session.Stream._mini_dom = None
- name = stanza.getName()
-
- if name == 'features':
- self._owner.got_features = True
- session.Stream.features = stanza
-
- xmlns = stanza.getNamespace()
-
- # log.info('in dispatch, getting ns for %s, and the ns is %s'
- # % (stanza, xmlns))
- if xmlns not in self.handlers:
- log.warn("Unknown namespace: " + xmlns)
- xmlns = 'unknown'
- # features stanza has been handled before
- if name not in self.handlers[xmlns]:
- if name != 'features':
- log.warn("Unknown stanza: " + name)
- else:
- log.debug("Got %s/%s stanza" % (xmlns, name))
- name='unknown'
- else:
- log.debug("Got %s/%s stanza" % (xmlns, name))
-
- if stanza.__class__.__name__ == 'Node':
- # FIXME: this cannot work
- stanza=self.handlers[xmlns][name][type](node=stanza)
-
- typ = stanza.getType()
- if not typ:
- typ = ''
- stanza.props = stanza.getProperties()
- ID = stanza.getID()
-
- list_ = ['default'] # we will use all handlers:
- if typ in self.handlers[xmlns][name]:
- list_.append(typ) # from very common...
- for prop in stanza.props:
- if prop in self.handlers[xmlns][name]:
- list_.append(prop)
- if typ and typ+prop in self.handlers[xmlns][name]:
- list_.append(typ+prop) # ...to very particular
-
- chain = self.handlers[xmlns]['default']['default']
- for key in list_:
- if key:
- chain = chain + self.handlers[xmlns][name][key]
-
- if ID in session._expected:
- user = 0
- if isinstance(session._expected[ID], tuple):
- cb, args = session._expected[ID]
- log.debug("Expected stanza arrived. Callback %s(%s) found!" %
- (cb, args))
- try:
- cb(session,stanza,**args)
- except Exception, typ:
- if typ.__class__.__name__ != 'NodeProcessed':
- raise
- else:
- log.debug("Expected stanza arrived!")
- session._expected[ID] = stanza
- else:
- user = 1
- for handler in chain:
- if user or handler['system']:
- try:
- handler['func'](session, stanza)
- except Exception, typ:
- if typ.__class__.__name__ != 'NodeProcessed':
- self._pendingExceptions.insert(0, sys.exc_info())
- return
- user=0
- if user and self._defaultHandler:
- self._defaultHandler(session, stanza)
-
- def _WaitForData(self, data):
- """
- Internal wrapper around ProcessNonBlocking. Will check for
- """
- if data is None:
- return
- res = self.ProcessNonBlocking(data)
- # 0 result indicates that we have closed the connection, e.g.
- # we have released dispatcher, so self._owner has no methods
- if not res:
- return
- if 'remove_timeout' in self._owner.__dict__:
- # When we receive data after we started disconnecting, Transport may
- # already be plugged out
- self._owner.remove_timeout()
- for (_id, _iq) in self._expected.items():
- if _iq is None:
- # If the expected Stanza would have arrived, ProcessNonBlocking
- # would have placed the reply stanza in there
- continue
- if _id in self.on_responses:
- if len(self._expected) == 1:
- self._owner.onreceive(None)
- resp, args = self.on_responses[_id]
- del self.on_responses[_id]
- if args is None:
- resp(_iq)
- else:
- resp(self._owner, _iq, **args)
- del self._expected[_id]
-
- def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None):
- """
- Send stanza and wait for recipient's response to it. Will call transports
- on_timeout callback if response is not retrieved in time
-
- Be aware: Only timeout of latest call of SendAndWait is active.
- """
- if timeout is None:
- timeout = DEFAULT_TIMEOUT_SECONDS
- _waitid = self.send(stanza)
- if func:
- self.on_responses[_waitid] = (func, args)
- if timeout:
- self._owner.set_timeout(timeout)
- self._owner.onreceive(self._WaitForData)
- self._expected[_waitid] = None
- return _waitid
-
- def SendAndCallForResponse(self, stanza, func=None, args=None):
- """
- Put stanza on the wire and call back when recipient replies. Additional
- callback arguments can be specified in args
- """
- self.SendAndWaitForResponse(stanza, 0, func, args)
-
- def send(self, stanza, now=False):
- """
- Wrap transports send method when plugged into NonBlockingClient. Makes
- sure stanzas get ID and from tag.
- """
- ID = None
- if type(stanza) not in [type(''), type(u'')]:
- if isinstance(stanza, Protocol):
- ID = stanza.getID()
- if ID is None:
- stanza.setID(self.getAnID())
- ID = stanza.getID()
- if self._owner._registered_name and not stanza.getAttr('from'):
- stanza.setAttr('from', self._owner._registered_name)
- self._owner.Connection.send(stanza, now)
- return ID
+ """
+ Handles XMPP stream and is the first who takes control over a fresh stanza
+
+ Is plugged into NonBlockingClient but can be replugged to restart handled
+ stream headers (used by SASL f.e.).
+ """
+
+ def __init__(self):
+ PlugIn.__init__(self)
+ self.handlers = {}
+ self._expected = {}
+ self._defaultHandler = None
+ self._pendingExceptions = []
+ self._eventHandler = None
+ self._cycleHandlers = []
+ self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler,
+ self.RegisterEventHandler, self.UnregisterCycleHandler,
+ self.RegisterCycleHandler, self.RegisterHandlerOnce,
+ self.UnregisterHandler, self.RegisterProtocol,
+ self.SendAndWaitForResponse, self.SendAndCallForResponse,
+ self.getAnID, self.Event, self.send]
+
+ def getAnID(self):
+ global outgoingID
+ outgoingID += 1
+ return repr(outgoingID)
+
+ def dumpHandlers(self):
+ """
+ Return set of user-registered callbacks in it's internal format. Used
+ within the library to carry user handlers set over Dispatcher replugins
+ """
+ return self.handlers
+
+ def restoreHandlers(self, handlers):
+ """
+ Restore user-registered callbacks structure from dump previously obtained
+ via dumpHandlers. Used within the library to carry user handlers set over
+ Dispatcher replugins.
+ """
+ self.handlers = handlers
+
+ def _init(self):
+ """
+ Register default namespaces/protocols/handlers. Used internally
+ """
+ # FIXME: inject dependencies, do not rely that they are defined by our
+ # owner
+ self.RegisterNamespace('unknown')
+ self.RegisterNamespace(NS_STREAMS)
+ self.RegisterNamespace(self._owner.defaultNamespace)
+ self.RegisterProtocol('iq', Iq)
+ self.RegisterProtocol('presence', Presence)
+ self.RegisterProtocol('message', Message)
+ self.RegisterDefaultHandler(self.returnStanzaHandler)
+ self.RegisterEventHandler(self._owner._caller._event_dispatcher)
+ self.on_responses = {}
+
+ def plugin(self, owner):
+ """
+ Plug the Dispatcher instance into Client class instance and send initial
+ stream header. Used internally
+ """
+ self._init()
+ self._owner.lastErrNode = None
+ self._owner.lastErr = None
+ self._owner.lastErrCode = None
+ if hasattr(self._owner, 'StreamInit'):
+ self._owner.StreamInit()
+ else:
+ self.StreamInit()
+
+ def plugout(self):
+ """
+ Prepare instance to be destructed
+ """
+ self.Stream.dispatch = None
+ self.Stream.features = None
+ self.Stream.destroy()
+ self._owner = None
+ self.Stream = None
+
+ def StreamInit(self):
+ """
+ Send an initial stream header
+ """
+ self.Stream = simplexml.NodeBuilder()
+ self.Stream.dispatch = self.dispatch
+ self.Stream._dispatch_depth = 2
+ self.Stream.stream_header_received = self._check_stream_start
+ self.Stream.features = None
+ self._metastream = Node('stream:stream')
+ self._metastream.setNamespace(self._owner.Namespace)
+ self._metastream.setAttr('version', '1.0')
+ self._metastream.setAttr('xmlns:stream', NS_STREAMS)
+ self._metastream.setAttr('to', self._owner.Server)
+ if locale.getdefaultlocale()[0]:
+ self._metastream.setAttr('xml:lang',
+ locale.getdefaultlocale()[0].split('_')[0])
+ self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2]))
+
+ def _check_stream_start(self, ns, tag, attrs):
+ if ns != NS_STREAMS or tag!='stream':
+ raise ValueError('Incorrect stream start: (%s,%s). Terminating.'
+ % (tag, ns))
+
+ def ProcessNonBlocking(self, data):
+ """
+ Check incoming stream for data waiting
+
+ :param data: data received from transports/IO sockets
+ :return:
+ 1) length of processed data if some data were processed;
+ 2) '0' string if no data were processed but link is alive;
+ 3) 0 (zero) if underlying connection is closed.
+ """
+ # FIXME:
+ # When an error occurs we disconnect the transport directly. Client's
+ # disconnect method will never be called.
+ # Is this intended?
+ # also look at transports start_disconnect()
+ for handler in self._cycleHandlers:
+ handler(self)
+ if len(self._pendingExceptions) > 0:
+ _pendingException = self._pendingExceptions.pop()
+ raise _pendingException[0], _pendingException[1], _pendingException[2]
+ try:
+ self.Stream.Parse(data)
+ # end stream:stream tag received
+ if self.Stream and self.Stream.has_received_endtag():
+ self._owner.disconnect(self.Stream.streamError)
+ return 0
+ except ExpatError:
+ log.error('Invalid XML received from server. Forcing disconnect.')
+ self._owner.Connection.disconnect()
+ return 0
+ except ValueError, e:
+ log.debug('ValueError: %s' % str(e))
+ self._owner.Connection.pollend()
+ return 0
+ if len(self._pendingExceptions) > 0:
+ _pendingException = self._pendingExceptions.pop()
+ raise _pendingException[0], _pendingException[1], _pendingException[2]
+ if len(data) == 0:
+ return '0'
+ return len(data)
+
+ def RegisterNamespace(self, xmlns, order='info'):
+ """
+ Create internal structures for newly registered namespace
+
+ You can register handlers for this namespace afterwards. By default
+ one namespace is already registered
+ (jabber:client or jabber:component:accept depending on context.
+ """
+ log.debug('Registering namespace "%s"' % xmlns)
+ self.handlers[xmlns] = {}
+ self.RegisterProtocol('unknown', Protocol, xmlns=xmlns)
+ self.RegisterProtocol('default', Protocol, xmlns=xmlns)
+
+ def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'):
+ """
+ Used to declare some top-level stanza name to dispatcher
+
+ Needed to start registering handlers for such stanzas. Iq, message and
+ presence protocols are registered by default.
+ """
+ if not xmlns:
+ xmlns=self._owner.defaultNamespace
+ log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns))
+ self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]}
+
+ def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='',
+ makefirst=0, system=0):
+ """
+ Register handler for processing all stanzas for specified namespace
+ """
+ self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst,
+ system)
+
+ def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None,
+ makefirst=False, system=False):
+ """
+ Register user callback as stanzas handler of declared type
+
+ Callback arguments:
+ dispatcher instance (for replying), incoming return of previous handlers.
+ The callback must raise xmpp.NodeProcessed just before return if it wants
+ to prevent other callbacks to be called with the same stanza as argument
+ _and_, more importantly library from returning stanza to sender with error set.
+
+ :param name: name of stanza. F.e. "iq".
+ :param handler: user callback.
+ :param typ: value of stanza's "type" attribute. If not specified any
+ value will match
+ :param ns: namespace of child that stanza must contain.
+ :param makefirst: insert handler in the beginning of handlers list instead
+ of adding it to the end. Note that more common handlers i.e. w/o "typ"
+ and " will be called first nevertheless.
+ :param system: call handler even if NodeProcessed Exception were raised
+ already.
+ """
+ if not xmlns:
+ xmlns=self._owner.defaultNamespace
+ log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' %
+ (handler, name, typ, ns, xmlns))
+ if not typ and not ns:
+ typ='default'
+ if xmlns not in self.handlers:
+ self.RegisterNamespace(xmlns,'warn')
+ if name not in self.handlers[xmlns]:
+ self.RegisterProtocol(name,Protocol,xmlns,'warn')
+ if typ+ns not in self.handlers[xmlns][name]:
+ self.handlers[xmlns][name][typ+ns]=[]
+ if makefirst:
+ self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler,
+ 'system':system})
+ else:
+ self.handlers[xmlns][name][typ+ns].append({'func':handler,
+ 'system':system})
+
+ def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None,
+ makefirst=0, system=0):
+ """
+ Unregister handler after first call (not implemented yet)
+ """
+ # FIXME Drop or implement
+ if not xmlns:
+ xmlns = self._owner.defaultNamespace
+ self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
+
+ def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None):
+ """
+ Unregister handler. "typ" and "ns" must be specified exactly the same as
+ with registering.
+ """
+ if not xmlns:
+ xmlns = self._owner.defaultNamespace
+ if not typ and not ns:
+ typ='default'
+ if xmlns not in self.handlers:
+ return
+ if name not in self.handlers[xmlns]:
+ return
+ if typ+ns not in self.handlers[xmlns][name]:
+ return
+ for pack in self.handlers[xmlns][name][typ+ns]:
+ if pack['func'] == handler:
+ try:
+ self.handlers[xmlns][name][typ+ns].remove(pack)
+ except ValueError:
+ pass
+
+ def RegisterDefaultHandler(self, handler):
+ """
+ Specify the handler that will be used if no NodeProcessed exception were
+ raised. This is returnStanzaHandler by default.
+ """
+ self._defaultHandler = handler
+
+ def RegisterEventHandler(self, handler):
+ """
+ Register handler that will process events. F.e. "FILERECEIVED" event. See
+ common/connection: _event_dispatcher()
+ """
+ self._eventHandler = handler
+
+ def returnStanzaHandler(self, conn, stanza):
+ """
+ Return stanza back to the sender with <feature-not-implemented/> error
+ set
+ """
+ if stanza.getType() in ('get','set'):
+ conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED))
+
+ def RegisterCycleHandler(self, handler):
+ """
+ Register handler that will be called on every Dispatcher.Process() call
+ """
+ if handler not in self._cycleHandlers:
+ self._cycleHandlers.append(handler)
+
+ def UnregisterCycleHandler(self, handler):
+ """
+ Unregister handler that will is called on every Dispatcher.Process() call
+ """
+ if handler in self._cycleHandlers:
+ self._cycleHandlers.remove(handler)
+
+ def Event(self, realm, event, data):
+ """
+ Raise some event
+
+ :param realm: scope of event. Usually a namespace.
+ :param event: the event itself. F.e. "SUCCESSFUL SEND".
+ :param data: data that comes along with event. Depends on event.
+ """
+ if self._eventHandler:
+ self._eventHandler(realm, event, data)
+ else:
+ log.warning('Received unhandled event: %s' % event)
+
+ def dispatch(self, stanza, session=None, direct=0):
+ """
+ Main procedure that performs XMPP stanza recognition and calling
+ apppropriate handlers for it. Called by simplexml
+ """
+ # FIXME: Where do we set session and direct. Why? What are those intended
+ # to do?
+
+ #log.info('dispatch called: stanza = %s, session = %s, direct= %s'
+ # % (stanza, session, direct))
+ if not session:
+ session = self
+ session.Stream._mini_dom = None
+ name = stanza.getName()
+
+ if name == 'features':
+ self._owner.got_features = True
+ session.Stream.features = stanza
+
+ xmlns = stanza.getNamespace()
+
+ # log.info('in dispatch, getting ns for %s, and the ns is %s'
+ # % (stanza, xmlns))
+ if xmlns not in self.handlers:
+ log.warn("Unknown namespace: " + xmlns)
+ xmlns = 'unknown'
+ # features stanza has been handled before
+ if name not in self.handlers[xmlns]:
+ if name != 'features':
+ log.warn("Unknown stanza: " + name)
+ else:
+ log.debug("Got %s/%s stanza" % (xmlns, name))
+ name='unknown'
+ else:
+ log.debug("Got %s/%s stanza" % (xmlns, name))
+
+ if stanza.__class__.__name__ == 'Node':
+ # FIXME: this cannot work
+ stanza=self.handlers[xmlns][name][type](node=stanza)
+
+ typ = stanza.getType()
+ if not typ:
+ typ = ''
+ stanza.props = stanza.getProperties()
+ ID = stanza.getID()
+
+ list_ = ['default'] # we will use all handlers:
+ if typ in self.handlers[xmlns][name]:
+ list_.append(typ) # from very common...
+ for prop in stanza.props:
+ if prop in self.handlers[xmlns][name]:
+ list_.append(prop)
+ if typ and typ+prop in self.handlers[xmlns][name]:
+ list_.append(typ+prop) # ...to very particular
+
+ chain = self.handlers[xmlns]['default']['default']
+ for key in list_:
+ if key:
+ chain = chain + self.handlers[xmlns][name][key]
+
+ if ID in session._expected:
+ user = 0
+ if isinstance(session._expected[ID], tuple):
+ cb, args = session._expected[ID]
+ log.debug("Expected stanza arrived. Callback %s(%s) found!" %
+ (cb, args))
+ try:
+ cb(session,stanza,**args)
+ except Exception, typ:
+ if typ.__class__.__name__ != 'NodeProcessed':
+ raise
+ else:
+ log.debug("Expected stanza arrived!")
+ session._expected[ID] = stanza
+ else:
+ user = 1
+ for handler in chain:
+ if user or handler['system']:
+ try:
+ handler['func'](session, stanza)
+ except Exception, typ:
+ if typ.__class__.__name__ != 'NodeProcessed':
+ self._pendingExceptions.insert(0, sys.exc_info())
+ return
+ user=0
+ if user and self._defaultHandler:
+ self._defaultHandler(session, stanza)
+
+ def _WaitForData(self, data):
+ """
+ Internal wrapper around ProcessNonBlocking. Will check for
+ """
+ if data is None:
+ return
+ res = self.ProcessNonBlocking(data)
+ # 0 result indicates that we have closed the connection, e.g.
+ # we have released dispatcher, so self._owner has no methods
+ if not res:
+ return
+ if 'remove_timeout' in self._owner.__dict__:
+ # When we receive data after we started disconnecting, Transport may
+ # already be plugged out
+ self._owner.remove_timeout()
+ for (_id, _iq) in self._expected.items():
+ if _iq is None:
+ # If the expected Stanza would have arrived, ProcessNonBlocking
+ # would have placed the reply stanza in there
+ continue
+ if _id in self.on_responses:
+ if len(self._expected) == 1:
+ self._owner.onreceive(None)
+ resp, args = self.on_responses[_id]
+ del self.on_responses[_id]
+ if args is None:
+ resp(_iq)
+ else:
+ resp(self._owner, _iq, **args)
+ del self._expected[_id]
+
+ def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None):
+ """
+ Send stanza and wait for recipient's response to it. Will call transports
+ on_timeout callback if response is not retrieved in time
+
+ Be aware: Only timeout of latest call of SendAndWait is active.
+ """
+ if timeout is None:
+ timeout = DEFAULT_TIMEOUT_SECONDS
+ _waitid = self.send(stanza)
+ if func:
+ self.on_responses[_waitid] = (func, args)
+ if timeout:
+ self._owner.set_timeout(timeout)
+ self._owner.onreceive(self._WaitForData)
+ self._expected[_waitid] = None
+ return _waitid
+
+ def SendAndCallForResponse(self, stanza, func=None, args=None):
+ """
+ Put stanza on the wire and call back when recipient replies. Additional
+ callback arguments can be specified in args
+ """
+ self.SendAndWaitForResponse(stanza, 0, func, args)
+
+ def send(self, stanza, now=False):
+ """
+ Wrap transports send method when plugged into NonBlockingClient. Makes
+ sure stanzas get ID and from tag.
+ """
+ ID = None
+ if type(stanza) not in [type(''), type(u'')]:
+ if isinstance(stanza, Protocol):
+ ID = stanza.getID()
+ if ID is None:
+ stanza.setID(self.getAnID())
+ ID = stanza.getID()
+ if self._owner._registered_name and not stanza.getAttr('from'):
+ stanza.setAttr('from', self._owner._registered_name)
+ self._owner.Connection.send(stanza, now)
+ return ID
class BOSHDispatcher(XMPPDispatcher):
- def PlugIn(self, owner, after_SASL=False, old_features=None):
- self.old_features = old_features
- self.after_SASL = after_SASL
- XMPPDispatcher.PlugIn(self, owner)
-
- def StreamInit(self):
- """
- Send an initial stream header
- """
- self.Stream = simplexml.NodeBuilder()
- self.Stream.dispatch = self.dispatch
- self.Stream._dispatch_depth = 2
- self.Stream.stream_header_received = self._check_stream_start
- self.Stream.features = self.old_features
-
- self._metastream = Node('stream:stream')
- self._metastream.setNamespace(self._owner.Namespace)
- self._metastream.setAttr('version', '1.0')
- self._metastream.setAttr('xmlns:stream', NS_STREAMS)
- self._metastream.setAttr('to', self._owner.Server)
- if locale.getdefaultlocale()[0]:
- self._metastream.setAttr('xml:lang',
- locale.getdefaultlocale()[0].split('_')[0])
-
- self.restart = True
- self._owner.Connection.send_init(after_SASL=self.after_SASL)
-
- def StreamTerminate(self):
- """
- Send a stream terminator
- """
- self._owner.Connection.send_terminator()
-
- def ProcessNonBlocking(self, data=None):
- if self.restart:
- fromstream = self._metastream
- fromstream.setAttr('from', fromstream.getAttr('to'))
- fromstream.delAttr('to')
- data = '%s%s>%s' % (XML_DECLARATION,str(fromstream)[:-2] ,data)
- self.restart = False
- return XMPPDispatcher.ProcessNonBlocking(self, data)
-
- def dispatch(self, stanza, session=None, direct=0):
- if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND:
-
- stanza_attrs = stanza.getAttrs()
- if 'authid' in stanza_attrs:
- # should be only in init response
- # auth module expects id of stream in document attributes
- self.Stream._document_attrs['id'] = stanza_attrs['authid']
- self._owner.Connection.handle_body_attrs(stanza_attrs)
-
- children = stanza.getChildren()
- if children:
- for child in children:
- # if child doesn't have any ns specified, simplexml (or expat)
- # thinks it's of parent's (BOSH body) namespace, so we have to
- # rewrite it to jabber:client
- if child.getNamespace() == NS_HTTP_BIND:
- child.setNamespace(self._owner.defaultNamespace)
- XMPPDispatcher.dispatch(self, child, session, direct)
- else:
- XMPPDispatcher.dispatch(self, stanza, session, direct)
-
-# vim: se ts=3:
+ def PlugIn(self, owner, after_SASL=False, old_features=None):
+ self.old_features = old_features
+ self.after_SASL = after_SASL
+ XMPPDispatcher.PlugIn(self, owner)
+
+ def StreamInit(self):
+ """
+ Send an initial stream header
+ """
+ self.Stream = simplexml.NodeBuilder()
+ self.Stream.dispatch = self.dispatch
+ self.Stream._dispatch_depth = 2
+ self.Stream.stream_header_received = self._check_stream_start
+ self.Stream.features = self.old_features
+
+ self._metastream = Node('stream:stream')
+ self._metastream.setNamespace(self._owner.Namespace)
+ self._metastream.setAttr('version', '1.0')
+ self._metastream.setAttr('xmlns:stream', NS_STREAMS)
+ self._metastream.setAttr('to', self._owner.Server)
+ if locale.getdefaultlocale()[0]:
+ self._metastream.setAttr('xml:lang',
+ locale.getdefaultlocale()[0].split('_')[0])
+
+ self.restart = True
+ self._owner.Connection.send_init(after_SASL=self.after_SASL)
+
+ def StreamTerminate(self):
+ """
+ Send a stream terminator
+ """
+ self._owner.Connection.send_terminator()
+
+ def ProcessNonBlocking(self, data=None):
+ if self.restart:
+ fromstream = self._metastream
+ fromstream.setAttr('from', fromstream.getAttr('to'))
+ fromstream.delAttr('to')
+ data = '%s%s>%s' % (XML_DECLARATION,str(fromstream)[:-2] ,data)
+ self.restart = False
+ return XMPPDispatcher.ProcessNonBlocking(self, data)
+
+ def dispatch(self, stanza, session=None, direct=0):
+ if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND:
+
+ stanza_attrs = stanza.getAttrs()
+ if 'authid' in stanza_attrs:
+ # should be only in init response
+ # auth module expects id of stream in document attributes
+ self.Stream._document_attrs['id'] = stanza_attrs['authid']
+ self._owner.Connection.handle_body_attrs(stanza_attrs)
+
+ children = stanza.getChildren()
+ if children:
+ for child in children:
+ # if child doesn't have any ns specified, simplexml (or expat)
+ # thinks it's of parent's (BOSH body) namespace, so we have to
+ # rewrite it to jabber:client
+ if child.getNamespace() == NS_HTTP_BIND:
+ child.setNamespace(self._owner.defaultNamespace)
+ XMPPDispatcher.dispatch(self, child, session, direct)
+ else:
+ XMPPDispatcher.dispatch(self, stanza, session, direct)
diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py
index a2eb669ca..1a219a34e 100644
--- a/src/common/xmpp/features_nb.py
+++ b/src/common/xmpp/features_nb.py
@@ -23,13 +23,13 @@ Different stuff that wasn't worth separating it into modules
from protocol import NS_REGISTER, NS_PRIVACY, NS_DATA, Iq, isResultNode, Node
def _on_default_response(disp, iq, cb):
- def _on_response(resp):
- if isResultNode(resp):
- if cb:
- cb(True)
- elif cb:
- cb(False)
- disp.SendAndCallForResponse(iq, _on_response)
+ def _on_response(resp):
+ if isResultNode(resp):
+ if cb:
+ cb(True)
+ elif cb:
+ cb(False)
+ disp.SendAndCallForResponse(iq, _on_response)
###############################################################################
### Registration
@@ -38,75 +38,75 @@ def _on_default_response(disp, iq, cb):
REGISTER_DATA_RECEIVED = 'REGISTER DATA RECEIVED'
def getRegInfo(disp, host, info={}, sync=True):
- """
- Get registration form from remote host. Info dict can be prefilled
- :param disp: plugged dispatcher instance
- :param info: dict, like {'username':'joey'}.
-
- See JEP-0077 for details.
- """
- iq=Iq('get',NS_REGISTER,to=host)
- for i in info.keys():
- iq.setTagData(i,info[i])
- if sync:
- disp.SendAndCallForResponse(iq, lambda resp:
- _ReceivedRegInfo(disp.Dispatcher, resp, host))
- else:
- disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {'agent': host })
+ """
+ Get registration form from remote host. Info dict can be prefilled
+ :param disp: plugged dispatcher instance
+ :param info: dict, like {'username':'joey'}.
+
+ See JEP-0077 for details.
+ """
+ iq=Iq('get',NS_REGISTER,to=host)
+ for i in info.keys():
+ iq.setTagData(i,info[i])
+ if sync:
+ disp.SendAndCallForResponse(iq, lambda resp:
+ _ReceivedRegInfo(disp.Dispatcher, resp, host))
+ else:
+ disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {'agent': host })
def _ReceivedRegInfo(con, resp, agent):
- Iq('get',NS_REGISTER,to=agent)
- if not isResultNode(resp):
- error_msg = resp.getErrorMsg()
- con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg))
- return
- tag=resp.getTag('query',namespace=NS_REGISTER)
- if not tag:
- error_msg = resp.getErrorMsg()
- con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg))
- return
- df=tag.getTag('x',namespace=NS_DATA)
- if df:
- con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,df,True,''))
- return
- df={}
- for i in resp.getQueryPayload():
- if not isinstance(i, Node):
- continue
- df[i.getName()] = i.getData()
- con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,''))
+ Iq('get',NS_REGISTER,to=agent)
+ if not isResultNode(resp):
+ error_msg = resp.getErrorMsg()
+ con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg))
+ return
+ tag=resp.getTag('query',namespace=NS_REGISTER)
+ if not tag:
+ error_msg = resp.getErrorMsg()
+ con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,None,False,error_msg))
+ return
+ df=tag.getTag('x',namespace=NS_DATA)
+ if df:
+ con.Event(NS_REGISTER,REGISTER_DATA_RECEIVED,(agent,df,True,''))
+ return
+ df={}
+ for i in resp.getQueryPayload():
+ if not isinstance(i, Node):
+ continue
+ df[i.getName()] = i.getData()
+ con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,''))
def register(disp, host, info, cb, args=None):
- """
- Perform registration on remote server with provided info
-
- If registration fails you can get additional info from the dispatcher's
- owner attributes lastErrNode, lastErr and lastErrCode.
- """
- iq=Iq('set', NS_REGISTER, to=host)
- if not isinstance(info, dict):
- info=info.asDict()
- for i in info.keys():
- iq.setTag('query').setTagData(i,info[i])
- disp.SendAndCallForResponse(iq, cb, args)
+ """
+ Perform registration on remote server with provided info
+
+ If registration fails you can get additional info from the dispatcher's
+ owner attributes lastErrNode, lastErr and lastErrCode.
+ """
+ iq=Iq('set', NS_REGISTER, to=host)
+ if not isinstance(info, dict):
+ info=info.asDict()
+ for i in info.keys():
+ iq.setTag('query').setTagData(i,info[i])
+ disp.SendAndCallForResponse(iq, cb, args)
def unregister(disp, host, cb):
- """
- Unregisters with host (permanently removes account). Returns true on success
- """
- iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')])
- _on_default_response(disp, iq, cb)
+ """
+ Unregisters with host (permanently removes account). Returns true on success
+ """
+ iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')])
+ _on_default_response(disp, iq, cb)
def changePasswordTo(disp, newpassword, host=None, cb = None):
- """
- Changes password on specified or current (if not specified) server. Returns
- true on success.
- """
- if not host:
- host = disp._owner.Server
- iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username',
- payload=[disp._owner.Server]),Node('password',payload=[newpassword])])
- _on_default_response(disp, iq, cb)
+ """
+ Changes password on specified or current (if not specified) server. Returns
+ true on success.
+ """
+ if not host:
+ host = disp._owner.Server
+ iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username',
+ payload=[disp._owner.Server]),Node('password',payload=[newpassword])])
+ _on_default_response(disp, iq, cb)
###############################################################################
### Privacy List
@@ -123,97 +123,95 @@ PRIVACY_LIST_RECEIVED = 'PRIVACY LIST RECEIVED'
PRIVACY_LISTS_ACTIVE_DEFAULT = 'PRIVACY LISTS ACTIVE DEFAULT'
def getPrivacyLists(disp):
- """
- Request privacy lists from connected server. Returns dictionary of existing
- lists on success.
- """
- iq = Iq('get', NS_PRIVACY)
- def _on_response(resp):
- dict_ = {'lists': []}
- if not isResultNode(resp):
- disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False))
- return
- for list_ in resp.getQueryPayload():
- if list_.getName()=='list':
- dict_['lists'].append(list_.getAttr('name'))
- else:
- dict_[list_.getName()]=list_.getAttr('name')
- disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict_))
- disp.SendAndCallForResponse(iq, _on_response)
+ """
+ Request privacy lists from connected server. Returns dictionary of existing
+ lists on success.
+ """
+ iq = Iq('get', NS_PRIVACY)
+ def _on_response(resp):
+ dict_ = {'lists': []}
+ if not isResultNode(resp):
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False))
+ return
+ for list_ in resp.getQueryPayload():
+ if list_.getName()=='list':
+ dict_['lists'].append(list_.getAttr('name'))
+ else:
+ dict_[list_.getName()]=list_.getAttr('name')
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict_))
+ disp.SendAndCallForResponse(iq, _on_response)
def getActiveAndDefaultPrivacyLists(disp):
- iq = Iq('get', NS_PRIVACY)
- def _on_response(resp):
- dict_ = {'active': '', 'default': ''}
- if not isResultNode(resp):
- disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False))
- return
- for list_ in resp.getQueryPayload():
- if list_.getName() == 'active':
- dict_['active'] = list_.getAttr('name')
- elif list_.getName() == 'default':
- dict_['default'] = list_.getAttr('name')
- disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict_))
- disp.SendAndCallForResponse(iq, _on_response)
+ iq = Iq('get', NS_PRIVACY)
+ def _on_response(resp):
+ dict_ = {'active': '', 'default': ''}
+ if not isResultNode(resp):
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False))
+ return
+ for list_ in resp.getQueryPayload():
+ if list_.getName() == 'active':
+ dict_['active'] = list_.getAttr('name')
+ elif list_.getName() == 'default':
+ dict_['default'] = list_.getAttr('name')
+ disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict_))
+ disp.SendAndCallForResponse(iq, _on_response)
def getPrivacyList(disp, listname):
- """
- Request specific privacy list listname. Returns list of XML nodes (rules)
- taken from the server responce.
- """
- def _on_response(resp):
- if not isResultNode(resp):
- disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
- return
- disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp))
- iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})])
- disp.SendAndCallForResponse(iq, _on_response)
+ """
+ Request specific privacy list listname. Returns list of XML nodes (rules)
+ taken from the server responce.
+ """
+ def _on_response(resp):
+ if not isResultNode(resp):
+ disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
+ return
+ disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp))
+ iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})])
+ disp.SendAndCallForResponse(iq, _on_response)
def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
- """
- Switch privacy list 'listname' to specified type. By default the type is
- 'active'. Returns true on success.
- """
- if listname:
- attrs={'name':listname}
- else:
- attrs={}
- iq = Iq('set',NS_PRIVACY,payload=[Node(typ,attrs)])
- _on_default_response(disp, iq, cb)
+ """
+ Switch privacy list 'listname' to specified type. By default the type is
+ 'active'. Returns true on success.
+ """
+ if listname:
+ attrs={'name':listname}
+ else:
+ attrs={}
+ iq = Iq('set',NS_PRIVACY,payload=[Node(typ,attrs)])
+ _on_default_response(disp, iq, cb)
def setDefaultPrivacyList(disp, listname=None):
- """
- Set the default privacy list as 'listname'. Returns true on success
- """
- return setActivePrivacyList(disp, listname,'default')
+ """
+ Set the default privacy list as 'listname'. Returns true on success
+ """
+ return setActivePrivacyList(disp, listname,'default')
def setPrivacyList(disp, listname, tags):
- """
- Set the ruleset
-
- 'list' should be the simpleXML node formatted according to RFC 3921
- (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]).
-
- Returns true on success.
- """
- iq = Iq('set', NS_PRIVACY, xmlns = '')
- list_query = iq.getTag('query').setTag('list', {'name': listname})
- for item in tags:
- if 'type' in item and 'value' in item:
- item_tag = list_query.setTag('item', {'action': item['action'],
- 'order': item['order'], 'type': item['type'],
- 'value': item['value']})
- else:
- item_tag = list_query.setTag('item', {'action': item['action'],
- 'order': item['order']})
- if 'child' in item:
- for child_tag in item['child']:
- item_tag.setTag(child_tag)
- _on_default_response(disp, iq, None)
+ """
+ Set the ruleset
+
+ 'list' should be the simpleXML node formatted according to RFC 3921
+ (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]).
+
+ Returns true on success.
+ """
+ iq = Iq('set', NS_PRIVACY, xmlns = '')
+ list_query = iq.getTag('query').setTag('list', {'name': listname})
+ for item in tags:
+ if 'type' in item and 'value' in item:
+ item_tag = list_query.setTag('item', {'action': item['action'],
+ 'order': item['order'], 'type': item['type'],
+ 'value': item['value']})
+ else:
+ item_tag = list_query.setTag('item', {'action': item['action'],
+ 'order': item['order']})
+ if 'child' in item:
+ for child_tag in item['child']:
+ item_tag.setTag(child_tag)
+ _on_default_response(disp, iq, None)
def delPrivacyList(disp, listname, cb=None):
- ''' Deletes privacy list 'listname'. Returns true on success. '''
- iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})])
- _on_default_response(disp, iq, cb)
-
-# vim: se ts=3:
+ ''' Deletes privacy list 'listname'. Returns true on success. '''
+ iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})])
+ _on_default_response(disp, iq, cb)
diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py
index 8d61159da..27ae3835d 100644
--- a/src/common/xmpp/idlequeue.py
+++ b/src/common/xmpp/idlequeue.py
@@ -25,524 +25,522 @@ log = logging.getLogger('gajim.c.x.idlequeue')
# needed for get_idleqeue
try:
- import gobject
- HAVE_GOBJECT = True
+ import gobject
+ HAVE_GOBJECT = True
except ImportError:
- HAVE_GOBJECT = False
+ HAVE_GOBJECT = False
# needed for idlecommand
if os.name == 'nt':
- from subprocess import * # python24 only. we ask this for Windows
+ from subprocess import * # python24 only. we ask this for Windows
elif os.name == 'posix':
- import fcntl
+ import fcntl
-FLAG_WRITE = 20 # write only
-FLAG_READ = 19 # read only
-FLAG_READ_WRITE = 23 # read and write
-FLAG_CLOSE = 16 # wait for close
+FLAG_WRITE = 20 # write only
+FLAG_READ = 19 # read only
+FLAG_READ_WRITE = 23 # read and write
+FLAG_CLOSE = 16 # wait for close
-PENDING_READ = 3 # waiting read event
-PENDING_WRITE = 4 # waiting write event
-IS_CLOSED = 16 # channel closed
+PENDING_READ = 3 # waiting read event
+PENDING_WRITE = 4 # waiting write event
+IS_CLOSED = 16 # channel closed
def get_idlequeue():
- """
- Get an appropriate idlequeue
- """
- if os.name == 'nt':
- # gobject.io_add_watch does not work on windows
- return SelectIdleQueue()
- else:
- if HAVE_GOBJECT:
- # Gajim's default Idlequeue
- return GlibIdleQueue()
- else:
- # GUI less implementation
- return SelectIdleQueue()
+ """
+ Get an appropriate idlequeue
+ """
+ if os.name == 'nt':
+ # gobject.io_add_watch does not work on windows
+ return SelectIdleQueue()
+ else:
+ if HAVE_GOBJECT:
+ # Gajim's default Idlequeue
+ return GlibIdleQueue()
+ else:
+ # GUI less implementation
+ return SelectIdleQueue()
class IdleObject:
- """
- Idle listener interface. Listed methods are called by IdleQueue.
- """
-
- def __init__(self):
- self.fd = -1 #: filedescriptor, must be unique for each IdleObject
-
- def pollend(self):
- """
- Called on stream failure
- """
- pass
-
- def pollin(self):
- """
- Called on new read event
- """
- pass
-
- def pollout(self):
- """
- Called on new write event (connect in sockets is a pollout)
- """
- pass
-
- def read_timeout(self):
- """
- Called when timeout happened
- """
- pass
+ """
+ Idle listener interface. Listed methods are called by IdleQueue.
+ """
+
+ def __init__(self):
+ self.fd = -1 #: filedescriptor, must be unique for each IdleObject
+
+ def pollend(self):
+ """
+ Called on stream failure
+ """
+ pass
+
+ def pollin(self):
+ """
+ Called on new read event
+ """
+ pass
+
+ def pollout(self):
+ """
+ Called on new write event (connect in sockets is a pollout)
+ """
+ pass
+
+ def read_timeout(self):
+ """
+ Called when timeout happened
+ """
+ pass
class IdleCommand(IdleObject):
- """
- Can be subclassed to execute commands asynchronously by the idlequeue.
- Result will be optained via file descriptor of created pipe
- """
-
- def __init__(self, on_result):
- IdleObject.__init__(self)
- # how long (sec.) to wait for result ( 0 - forever )
- # it is a class var, instead of a constant and we can override it.
- self.commandtimeout = 0
- # when we have some kind of result (valid, ot not) we call this handler
- self.result_handler = on_result
- # if it is True, we can safetely execute the command
- self.canexecute = True
- self.idlequeue = None
- self.result =''
-
- def set_idlequeue(self, idlequeue):
- self.idlequeue = idlequeue
-
- def _return_result(self):
- if self.result_handler:
- self.result_handler(self.result)
- self.result_handler = None
-
- def _compose_command_args(self):
- return ['echo', 'da']
-
- def _compose_command_line(self):
- """
- Return one line representation of command and its arguments
- """
- return reduce(lambda left, right: left + ' ' + right,
- self._compose_command_args())
-
- def wait_child(self):
- if self.pipe.poll() is None:
- # result timeout
- if self.endtime < self.idlequeue.current_time():
- self._return_result()
- self.pipe.stdout.close()
- self.pipe.stdin.close()
- else:
- # child is still active, continue to wait
- self.idlequeue.set_alarm(self.wait_child, 0.1)
- else:
- # child has quit
- self.result = self.pipe.stdout.read()
- self._return_result()
- self.pipe.stdout.close()
- self.pipe.stdin.close()
-
- def start(self):
- if not self.canexecute:
- self.result = ''
- self._return_result()
- return
- if os.name == 'nt':
- self._start_nt()
- elif os.name == 'posix':
- self._start_posix()
-
- def _start_nt(self):
- # if gajim is started from noninteraactive shells stdin is closed and
- # cannot be forwarded, so we have to keep it open
- self.pipe = Popen(self._compose_command_args(), stdout=PIPE,
- bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE)
- if self.commandtimeout >= 0:
- self.endtime = self.idlequeue.current_time() + self.commandtimeout
- self.idlequeue.set_alarm(self.wait_child, 0.1)
-
- def _start_posix(self):
- self.pipe = os.popen(self._compose_command_line())
- self.fd = self.pipe.fileno()
- fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK)
- self.idlequeue.plug_idle(self, False, True)
- if self.commandtimeout >= 0:
- self.idlequeue.set_read_timeout(self.fd, self.commandtimeout)
-
- def end(self):
- self.idlequeue.unplug_idle(self.fd)
- try:
- self.pipe.close()
- except:
- pass
-
- def pollend(self):
- self.idlequeue.remove_timeout(self.fd)
- self.end()
- self._return_result()
-
- def pollin(self):
- try:
- res = self.pipe.read()
- except Exception, e:
- res = ''
- if res == '':
- return self.pollend()
- else:
- self.result += res
-
- def read_timeout(self):
- self.end()
- self._return_result()
+ """
+ Can be subclassed to execute commands asynchronously by the idlequeue.
+ Result will be optained via file descriptor of created pipe
+ """
+
+ def __init__(self, on_result):
+ IdleObject.__init__(self)
+ # how long (sec.) to wait for result ( 0 - forever )
+ # it is a class var, instead of a constant and we can override it.
+ self.commandtimeout = 0
+ # when we have some kind of result (valid, ot not) we call this handler
+ self.result_handler = on_result
+ # if it is True, we can safetely execute the command
+ self.canexecute = True
+ self.idlequeue = None
+ self.result =''
+
+ def set_idlequeue(self, idlequeue):
+ self.idlequeue = idlequeue
+
+ def _return_result(self):
+ if self.result_handler:
+ self.result_handler(self.result)
+ self.result_handler = None
+
+ def _compose_command_args(self):
+ return ['echo', 'da']
+
+ def _compose_command_line(self):
+ """
+ Return one line representation of command and its arguments
+ """
+ return reduce(lambda left, right: left + ' ' + right,
+ self._compose_command_args())
+
+ def wait_child(self):
+ if self.pipe.poll() is None:
+ # result timeout
+ if self.endtime < self.idlequeue.current_time():
+ self._return_result()
+ self.pipe.stdout.close()
+ self.pipe.stdin.close()
+ else:
+ # child is still active, continue to wait
+ self.idlequeue.set_alarm(self.wait_child, 0.1)
+ else:
+ # child has quit
+ self.result = self.pipe.stdout.read()
+ self._return_result()
+ self.pipe.stdout.close()
+ self.pipe.stdin.close()
+
+ def start(self):
+ if not self.canexecute:
+ self.result = ''
+ self._return_result()
+ return
+ if os.name == 'nt':
+ self._start_nt()
+ elif os.name == 'posix':
+ self._start_posix()
+
+ def _start_nt(self):
+ # if gajim is started from noninteraactive shells stdin is closed and
+ # cannot be forwarded, so we have to keep it open
+ self.pipe = Popen(self._compose_command_args(), stdout=PIPE,
+ bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE)
+ if self.commandtimeout >= 0:
+ self.endtime = self.idlequeue.current_time() + self.commandtimeout
+ self.idlequeue.set_alarm(self.wait_child, 0.1)
+
+ def _start_posix(self):
+ self.pipe = os.popen(self._compose_command_line())
+ self.fd = self.pipe.fileno()
+ fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK)
+ self.idlequeue.plug_idle(self, False, True)
+ if self.commandtimeout >= 0:
+ self.idlequeue.set_read_timeout(self.fd, self.commandtimeout)
+
+ def end(self):
+ self.idlequeue.unplug_idle(self.fd)
+ try:
+ self.pipe.close()
+ except:
+ pass
+
+ def pollend(self):
+ self.idlequeue.remove_timeout(self.fd)
+ self.end()
+ self._return_result()
+
+ def pollin(self):
+ try:
+ res = self.pipe.read()
+ except Exception, e:
+ res = ''
+ if res == '':
+ return self.pollend()
+ else:
+ self.result += res
+
+ def read_timeout(self):
+ self.end()
+ self._return_result()
class IdleQueue:
- """
- IdleQueue provide three distinct time based features. Uses select.poll()
-
- 1. Alarm timeout: Execute a callback after foo seconds
- 2. Timeout event: Call read_timeout() of an plugged object if a timeout
- has been set, but not removed in time.
- 3. Check file descriptor of plugged objects for read, write and error
- events
- """
-
- # (timeout, boolean)
- # Boolean is True if timeout is specified in seconds, False means miliseconds
- PROCESS_TIMEOUT = (200, False)
-
- def __init__(self):
- self.queue = {}
-
- # when there is a timeout it executes obj.read_timeout()
- # timeout is not removed automatically!
- # {fd1: {timeout1: func1, timeout2: func2}}
- # timout are unique (timeout1 must be != timeout2)
- # If func1 is None, read_time function is called
- self.read_timeouts = {}
-
- # cb, which are executed after XX sec., alarms are removed automatically
- self.alarms = {}
- self._init_idle()
-
- def _init_idle(self):
- """
- Hook method for subclassed. Will be called by __init__
- """
- self.selector = select.poll()
-
- def set_alarm(self, alarm_cb, seconds):
- """
- Set up a new alarm. alarm_cb will be called after specified seconds.
- """
- alarm_time = self.current_time() + seconds
- # almost impossible, but in case we have another alarm_cb at this time
- if alarm_time in self.alarms:
- self.alarms[alarm_time].append(alarm_cb)
- else:
- self.alarms[alarm_time] = [alarm_cb]
- return alarm_time
-
- def remove_alarm(self, alarm_cb, alarm_time):
- """
- Remove alarm callback alarm_cb scheduled on alarm_time. Returns True if
- it was removed sucessfully, otherwise False
- """
- if not alarm_time in self.alarms:
- return False
- i = -1
- for i in range(len(self.alarms[alarm_time])):
- # let's not modify the list inside the loop
- if self.alarms[alarm_time][i] is alarm_cb:
- break
- if i != -1:
- del self.alarms[alarm_time][i]
- if self.alarms[alarm_time] == []:
- del self.alarms[alarm_time]
- return True
- else:
- return False
-
- def remove_timeout(self, fd, timeout=None):
- """
- Remove the read timeout
- """
- log.info('read timeout removed for fd %s' % fd)
- if fd in self.read_timeouts:
- if timeout:
- if timeout in self.read_timeouts[fd]:
- del(self.read_timeouts[fd][timeout])
- if len(self.read_timeouts[fd]) == 0:
- del(self.read_timeouts[fd])
- else:
- del(self.read_timeouts[fd])
-
- def set_read_timeout(self, fd, seconds, func=None):
- """
- Seta a new timeout. If it is not removed after specified seconds,
- func or obj.read_timeout() will be called
-
- A filedescriptor fd can have several timeouts.
- """
- log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds)
- if func:
- log_txt += ' with function ' + str(func)
- log.info(log_txt)
- timeout = self.current_time() + seconds
- if fd in self.read_timeouts:
- self.read_timeouts[fd][timeout] = func
- else:
- self.read_timeouts[fd] = {timeout: func}
-
- def _check_time_events(self):
- """
- Execute and remove alarm callbacks and execute func() or read_timeout()
- for plugged objects if specified time has ellapsed
- """
- current_time = self.current_time()
-
- for fd, timeouts in self.read_timeouts.items():
- if fd not in self.queue:
- self.remove_timeout(fd)
- continue
- for timeout, func in timeouts.items():
- if timeout > current_time:
- continue
- if func:
- log.debug('Calling %s for fd %s' % (func, fd))
- func()
- else:
- log.debug('Calling read_timeout for fd %s' % fd)
- self.queue[fd].read_timeout()
- self.remove_timeout(fd, timeout)
-
- times = self.alarms.keys()
- for alarm_time in times:
- if alarm_time > current_time:
- continue
- if alarm_time in self.alarms:
- for callback in self.alarms[alarm_time]:
- callback()
- if alarm_time in self.alarms:
- del(self.alarms[alarm_time])
-
- def plug_idle(self, obj, writable=True, readable=True):
- """
- Plug an IdleObject into idlequeue. Filedescriptor fd must be set
-
- :param obj: the IdleObject
- :param writable: True if obj has data to sent
- :param readable: True if obj expects data to be reiceived
- """
- if obj.fd == -1:
- return
- if obj.fd in self.queue:
- self.unplug_idle(obj.fd)
- self.queue[obj.fd] = obj
- if writable:
- if not readable:
- flags = FLAG_WRITE
- else:
- flags = FLAG_READ_WRITE
- else:
- if readable:
- flags = FLAG_READ
- else:
- # when we paused a FT, we expect only a close event
- flags = FLAG_CLOSE
- self._add_idle(obj.fd, flags)
-
- def _add_idle(self, fd, flags):
- """
- Hook method for subclasses, called by plug_idle
- """
- self.selector.register(fd, flags)
-
- def unplug_idle(self, fd):
- """
- Remove plugged IdleObject, specified by filedescriptor fd
- """
- if fd in self.queue:
- del(self.queue[fd])
- self._remove_idle(fd)
-
- def current_time(self):
- from time import time
- return time()
-
- def _remove_idle(self, fd):
- """
- Hook method for subclassed, called by unplug_idle
- """
- self.selector.unregister(fd)
-
- def _process_events(self, fd, flags):
- obj = self.queue.get(fd)
- if obj is None:
- self.unplug_idle(fd)
- return False
-
- if flags & PENDING_READ:
- #print 'waiting read on %d, flags are %d' % (fd, flags)
- obj.pollin()
- return True
-
- elif flags & PENDING_WRITE:
- obj.pollout()
- return True
-
- elif flags & IS_CLOSED:
- # io error, don't expect more events
- self.remove_timeout(obj.fd)
- self.unplug_idle(obj.fd)
- obj.pollend()
- return False
-
- def process(self):
- """
- Process idlequeue. Check for any pending timeout or alarm events. Call
- IdleObjects on possible and requested read, write and error events on
- their file descriptors
-
- Call this in regular intervals.
- """
- if not self.queue:
- # check for timeouts/alert also when there are no active fds
- self._check_time_events()
- return True
- try:
- waiting_descriptors = self.selector.poll(0)
- except select.error, e:
- waiting_descriptors = []
- if e[0] != 4: # interrupt
- raise
- for fd, flags in waiting_descriptors:
- self._process_events(fd, flags)
- self._check_time_events()
- return True
+ """
+ IdleQueue provide three distinct time based features. Uses select.poll()
+
+ 1. Alarm timeout: Execute a callback after foo seconds
+ 2. Timeout event: Call read_timeout() of an plugged object if a timeout
+ has been set, but not removed in time.
+ 3. Check file descriptor of plugged objects for read, write and error
+ events
+ """
+
+ # (timeout, boolean)
+ # Boolean is True if timeout is specified in seconds, False means miliseconds
+ PROCESS_TIMEOUT = (200, False)
+
+ def __init__(self):
+ self.queue = {}
+
+ # when there is a timeout it executes obj.read_timeout()
+ # timeout is not removed automatically!
+ # {fd1: {timeout1: func1, timeout2: func2}}
+ # timout are unique (timeout1 must be != timeout2)
+ # If func1 is None, read_time function is called
+ self.read_timeouts = {}
+
+ # cb, which are executed after XX sec., alarms are removed automatically
+ self.alarms = {}
+ self._init_idle()
+
+ def _init_idle(self):
+ """
+ Hook method for subclassed. Will be called by __init__
+ """
+ self.selector = select.poll()
+
+ def set_alarm(self, alarm_cb, seconds):
+ """
+ Set up a new alarm. alarm_cb will be called after specified seconds.
+ """
+ alarm_time = self.current_time() + seconds
+ # almost impossible, but in case we have another alarm_cb at this time
+ if alarm_time in self.alarms:
+ self.alarms[alarm_time].append(alarm_cb)
+ else:
+ self.alarms[alarm_time] = [alarm_cb]
+ return alarm_time
+
+ def remove_alarm(self, alarm_cb, alarm_time):
+ """
+ Remove alarm callback alarm_cb scheduled on alarm_time. Returns True if
+ it was removed sucessfully, otherwise False
+ """
+ if not alarm_time in self.alarms:
+ return False
+ i = -1
+ for i in range(len(self.alarms[alarm_time])):
+ # let's not modify the list inside the loop
+ if self.alarms[alarm_time][i] is alarm_cb:
+ break
+ if i != -1:
+ del self.alarms[alarm_time][i]
+ if self.alarms[alarm_time] == []:
+ del self.alarms[alarm_time]
+ return True
+ else:
+ return False
+
+ def remove_timeout(self, fd, timeout=None):
+ """
+ Remove the read timeout
+ """
+ log.info('read timeout removed for fd %s' % fd)
+ if fd in self.read_timeouts:
+ if timeout:
+ if timeout in self.read_timeouts[fd]:
+ del(self.read_timeouts[fd][timeout])
+ if len(self.read_timeouts[fd]) == 0:
+ del(self.read_timeouts[fd])
+ else:
+ del(self.read_timeouts[fd])
+
+ def set_read_timeout(self, fd, seconds, func=None):
+ """
+ Seta a new timeout. If it is not removed after specified seconds,
+ func or obj.read_timeout() will be called
+
+ A filedescriptor fd can have several timeouts.
+ """
+ log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds)
+ if func:
+ log_txt += ' with function ' + str(func)
+ log.info(log_txt)
+ timeout = self.current_time() + seconds
+ if fd in self.read_timeouts:
+ self.read_timeouts[fd][timeout] = func
+ else:
+ self.read_timeouts[fd] = {timeout: func}
+
+ def _check_time_events(self):
+ """
+ Execute and remove alarm callbacks and execute func() or read_timeout()
+ for plugged objects if specified time has ellapsed
+ """
+ current_time = self.current_time()
+
+ for fd, timeouts in self.read_timeouts.items():
+ if fd not in self.queue:
+ self.remove_timeout(fd)
+ continue
+ for timeout, func in timeouts.items():
+ if timeout > current_time:
+ continue
+ if func:
+ log.debug('Calling %s for fd %s' % (func, fd))
+ func()
+ else:
+ log.debug('Calling read_timeout for fd %s' % fd)
+ self.queue[fd].read_timeout()
+ self.remove_timeout(fd, timeout)
+
+ times = self.alarms.keys()
+ for alarm_time in times:
+ if alarm_time > current_time:
+ continue
+ if alarm_time in self.alarms:
+ for callback in self.alarms[alarm_time]:
+ callback()
+ if alarm_time in self.alarms:
+ del(self.alarms[alarm_time])
+
+ def plug_idle(self, obj, writable=True, readable=True):
+ """
+ Plug an IdleObject into idlequeue. Filedescriptor fd must be set
+
+ :param obj: the IdleObject
+ :param writable: True if obj has data to sent
+ :param readable: True if obj expects data to be reiceived
+ """
+ if obj.fd == -1:
+ return
+ if obj.fd in self.queue:
+ self.unplug_idle(obj.fd)
+ self.queue[obj.fd] = obj
+ if writable:
+ if not readable:
+ flags = FLAG_WRITE
+ else:
+ flags = FLAG_READ_WRITE
+ else:
+ if readable:
+ flags = FLAG_READ
+ else:
+ # when we paused a FT, we expect only a close event
+ flags = FLAG_CLOSE
+ self._add_idle(obj.fd, flags)
+
+ def _add_idle(self, fd, flags):
+ """
+ Hook method for subclasses, called by plug_idle
+ """
+ self.selector.register(fd, flags)
+
+ def unplug_idle(self, fd):
+ """
+ Remove plugged IdleObject, specified by filedescriptor fd
+ """
+ if fd in self.queue:
+ del(self.queue[fd])
+ self._remove_idle(fd)
+
+ def current_time(self):
+ from time import time
+ return time()
+
+ def _remove_idle(self, fd):
+ """
+ Hook method for subclassed, called by unplug_idle
+ """
+ self.selector.unregister(fd)
+
+ def _process_events(self, fd, flags):
+ obj = self.queue.get(fd)
+ if obj is None:
+ self.unplug_idle(fd)
+ return False
+
+ if flags & PENDING_READ:
+ #print 'waiting read on %d, flags are %d' % (fd, flags)
+ obj.pollin()
+ return True
+
+ elif flags & PENDING_WRITE:
+ obj.pollout()
+ return True
+
+ elif flags & IS_CLOSED:
+ # io error, don't expect more events
+ self.remove_timeout(obj.fd)
+ self.unplug_idle(obj.fd)
+ obj.pollend()
+ return False
+
+ def process(self):
+ """
+ Process idlequeue. Check for any pending timeout or alarm events. Call
+ IdleObjects on possible and requested read, write and error events on
+ their file descriptors
+
+ Call this in regular intervals.
+ """
+ if not self.queue:
+ # check for timeouts/alert also when there are no active fds
+ self._check_time_events()
+ return True
+ try:
+ waiting_descriptors = self.selector.poll(0)
+ except select.error, e:
+ waiting_descriptors = []
+ if e[0] != 4: # interrupt
+ raise
+ for fd, flags in waiting_descriptors:
+ self._process_events(fd, flags)
+ self._check_time_events()
+ return True
class SelectIdleQueue(IdleQueue):
- """
- Extends IdleQueue to use select.select() for polling
-
- This class exisists for the sake of gtk2.8 on windows, which doesn't seem to
- support io_add_watch properly (yet)
- """
-
- def _init_idle(self):
- """
- Create a dict, which maps file/pipe/sock descriptor to glib event id
- """
- self.read_fds = {}
- self.write_fds = {}
- self.error_fds = {}
-
- def _add_idle(self, fd, flags):
- """
- This method is called when we plug a new idle object. Remove descriptor
- to read/write/error lists, according flags
- """
- if flags & 3:
- self.read_fds[fd] = fd
- if flags & 4:
- self.write_fds[fd] = fd
- self.error_fds[fd] = fd
-
- def _remove_idle(self, fd):
- """
- This method is called when we unplug a new idle object. Remove descriptor
- from read/write/error lists
- """
- if fd in self.read_fds:
- del(self.read_fds[fd])
- if fd in self.write_fds:
- del(self.write_fds[fd])
- if fd in self.error_fds:
- del(self.error_fds[fd])
-
- def process(self):
- if not self.write_fds and not self.read_fds:
- self._check_time_events()
- return True
- try:
- waiting_descriptors = select.select(self.read_fds.keys(),
- self.write_fds.keys(), self.error_fds.keys(), 0)
- except select.error, e:
- waiting_descriptors = ((),(),())
- if e[0] != 4: # interrupt
- raise
- for fd in waiting_descriptors[0]:
- q = self.queue.get(fd)
- if q:
- q.pollin()
- for fd in waiting_descriptors[1]:
- q = self.queue.get(fd)
- if q:
- q.pollout()
- for fd in waiting_descriptors[2]:
- q = self.queue.get(fd)
- if q:
- q.pollend()
- self._check_time_events()
- return True
+ """
+ Extends IdleQueue to use select.select() for polling
+
+ This class exisists for the sake of gtk2.8 on windows, which doesn't seem to
+ support io_add_watch properly (yet)
+ """
+
+ def _init_idle(self):
+ """
+ Create a dict, which maps file/pipe/sock descriptor to glib event id
+ """
+ self.read_fds = {}
+ self.write_fds = {}
+ self.error_fds = {}
+
+ def _add_idle(self, fd, flags):
+ """
+ This method is called when we plug a new idle object. Remove descriptor
+ to read/write/error lists, according flags
+ """
+ if flags & 3:
+ self.read_fds[fd] = fd
+ if flags & 4:
+ self.write_fds[fd] = fd
+ self.error_fds[fd] = fd
+
+ def _remove_idle(self, fd):
+ """
+ This method is called when we unplug a new idle object. Remove descriptor
+ from read/write/error lists
+ """
+ if fd in self.read_fds:
+ del(self.read_fds[fd])
+ if fd in self.write_fds:
+ del(self.write_fds[fd])
+ if fd in self.error_fds:
+ del(self.error_fds[fd])
+
+ def process(self):
+ if not self.write_fds and not self.read_fds:
+ self._check_time_events()
+ return True
+ try:
+ waiting_descriptors = select.select(self.read_fds.keys(),
+ self.write_fds.keys(), self.error_fds.keys(), 0)
+ except select.error, e:
+ waiting_descriptors = ((),(),())
+ if e[0] != 4: # interrupt
+ raise
+ for fd in waiting_descriptors[0]:
+ q = self.queue.get(fd)
+ if q:
+ q.pollin()
+ for fd in waiting_descriptors[1]:
+ q = self.queue.get(fd)
+ if q:
+ q.pollout()
+ for fd in waiting_descriptors[2]:
+ q = self.queue.get(fd)
+ if q:
+ q.pollend()
+ self._check_time_events()
+ return True
class GlibIdleQueue(IdleQueue):
- """
- Extends IdleQueue to use glib io_add_wath, instead of select/poll In another
- 'non gui' implementation of Gajim IdleQueue can be used safetly
- """
-
- # (timeout, boolean)
- # Boolean is True if timeout is specified in seconds, False means miliseconds
- PROCESS_TIMEOUT = (2, True)
-
- def _init_idle(self):
- """
- Creates a dict, which maps file/pipe/sock descriptor to glib event id
- """
- self.events = {}
- # time() is already called in glib, we just get the last value
- # overrides IdleQueue.current_time()
- self.current_time = gobject.get_current_time
-
- def _add_idle(self, fd, flags):
- """
- This method is called when we plug a new idle object. Start listening for
- events from fd
- """
- res = gobject.io_add_watch(fd, flags, self._process_events,
- priority=gobject.PRIORITY_LOW)
- # store the id of the watch, so that we can remove it on unplug
- self.events[fd] = res
-
- def _process_events(self, fd, flags):
- try:
- return IdleQueue._process_events(self, fd, flags)
- except Exception:
- self._remove_idle(fd)
- self._add_idle(fd, flags)
- raise
-
- def _remove_idle(self, fd):
- """
- This method is called when we unplug a new idle object. Stop listening
- for events from fd
- """
- if not fd in self.events:
- return
- gobject.source_remove(self.events[fd])
- del(self.events[fd])
-
- def process(self):
- self._check_time_events()
-
-
-# vim: se ts=3:
+ """
+ Extends IdleQueue to use glib io_add_wath, instead of select/poll In another
+ 'non gui' implementation of Gajim IdleQueue can be used safetly
+ """
+
+ # (timeout, boolean)
+ # Boolean is True if timeout is specified in seconds, False means miliseconds
+ PROCESS_TIMEOUT = (2, True)
+
+ def _init_idle(self):
+ """
+ Creates a dict, which maps file/pipe/sock descriptor to glib event id
+ """
+ self.events = {}
+ # time() is already called in glib, we just get the last value
+ # overrides IdleQueue.current_time()
+ self.current_time = gobject.get_current_time
+
+ def _add_idle(self, fd, flags):
+ """
+ This method is called when we plug a new idle object. Start listening for
+ events from fd
+ """
+ res = gobject.io_add_watch(fd, flags, self._process_events,
+ priority=gobject.PRIORITY_LOW)
+ # store the id of the watch, so that we can remove it on unplug
+ self.events[fd] = res
+
+ def _process_events(self, fd, flags):
+ try:
+ return IdleQueue._process_events(self, fd, flags)
+ except Exception:
+ self._remove_idle(fd)
+ self._add_idle(fd, flags)
+ raise
+
+ def _remove_idle(self, fd):
+ """
+ This method is called when we unplug a new idle object. Stop listening
+ for events from fd
+ """
+ if not fd in self.events:
+ return
+ gobject.source_remove(self.events[fd])
+ del(self.events[fd])
+
+ def process(self):
+ self._check_time_events()
+
diff --git a/src/common/xmpp/plugin.py b/src/common/xmpp/plugin.py
index 2e4368eb2..57ffdc0ac 100644
--- a/src/common/xmpp/plugin.py
+++ b/src/common/xmpp/plugin.py
@@ -22,77 +22,75 @@ import logging
log = logging.getLogger('gajim.c.x.plugin')
class PlugIn:
- """
- Abstract xmpppy plugin infrastructure code, providing plugging in/out and
- debugging functionality
+ """
+ Abstract xmpppy plugin infrastructure code, providing plugging in/out and
+ debugging functionality
- Inherit to develop pluggable objects. No code change on the owner class
- required (the object where we plug into)
+ Inherit to develop pluggable objects. No code change on the owner class
+ required (the object where we plug into)
- For every instance of PlugIn class the 'owner' is the class in what the plug
- was plugged.
- """
+ For every instance of PlugIn class the 'owner' is the class in what the plug
+ was plugged.
+ """
- def __init__(self):
- self._exported_methods=[]
+ def __init__(self):
+ self._exported_methods=[]
- def PlugIn(self, owner):
- """
- Attach to owner and register ourself and our _exported_methods in it.
- If defined by a subclass, call self.plugin(owner) to execute hook
- code after plugging
- """
- self._owner=owner
- log.info('Plugging %s __INTO__ %s' % (self, self._owner))
- if self.__class__.__name__ in owner.__dict__:
- log.debug('Plugging ignored: another instance already plugged.')
- return
- self._old_owners_methods=[]
- for method in self._exported_methods:
- if method.__name__ in owner.__dict__:
- self._old_owners_methods.append(owner.__dict__[method.__name__])
- owner.__dict__[method.__name__]=method
- if self.__class__.__name__.endswith('Dispatcher'):
- # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher
- # there must be a better way..
- owner.__dict__['Dispatcher']=self
- else:
- owner.__dict__[self.__class__.__name__]=self
+ def PlugIn(self, owner):
+ """
+ Attach to owner and register ourself and our _exported_methods in it.
+ If defined by a subclass, call self.plugin(owner) to execute hook
+ code after plugging
+ """
+ self._owner=owner
+ log.info('Plugging %s __INTO__ %s' % (self, self._owner))
+ if self.__class__.__name__ in owner.__dict__:
+ log.debug('Plugging ignored: another instance already plugged.')
+ return
+ self._old_owners_methods=[]
+ for method in self._exported_methods:
+ if method.__name__ in owner.__dict__:
+ self._old_owners_methods.append(owner.__dict__[method.__name__])
+ owner.__dict__[method.__name__]=method
+ if self.__class__.__name__.endswith('Dispatcher'):
+ # FIXME: I need BOSHDispatcher or XMPPDispatcher on .Dispatcher
+ # there must be a better way..
+ owner.__dict__['Dispatcher']=self
+ else:
+ owner.__dict__[self.__class__.__name__]=self
- # Execute hook
- if hasattr(self,'plugin'):
- return self.plugin(owner)
+ # Execute hook
+ if hasattr(self,'plugin'):
+ return self.plugin(owner)
- def PlugOut(self):
- """
- Unregister our _exported_methods from owner and detach from it.
- If defined by a subclass, call self.plugout() after unplugging to execute
- hook code
- """
- log.info('Plugging %s __OUT__ of %s.' % (self, self._owner))
- for method in self._exported_methods:
- del self._owner.__dict__[method.__name__]
- for method in self._old_owners_methods:
- self._owner.__dict__[method.__name__]=method
- # FIXME: Dispatcher workaround
- if self.__class__.__name__.endswith('Dispatcher'):
- del self._owner.__dict__['Dispatcher']
- else:
- del self._owner.__dict__[self.__class__.__name__]
- # Execute hook
- if hasattr(self,'plugout'):
- return self.plugout()
- del self._owner
+ def PlugOut(self):
+ """
+ Unregister our _exported_methods from owner and detach from it.
+ If defined by a subclass, call self.plugout() after unplugging to execute
+ hook code
+ """
+ log.info('Plugging %s __OUT__ of %s.' % (self, self._owner))
+ for method in self._exported_methods:
+ del self._owner.__dict__[method.__name__]
+ for method in self._old_owners_methods:
+ self._owner.__dict__[method.__name__]=method
+ # FIXME: Dispatcher workaround
+ if self.__class__.__name__.endswith('Dispatcher'):
+ del self._owner.__dict__['Dispatcher']
+ else:
+ del self._owner.__dict__[self.__class__.__name__]
+ # Execute hook
+ if hasattr(self,'plugout'):
+ return self.plugout()
+ del self._owner
- @classmethod
- def get_instance(cls, *args, **kwargs):
- """
- Factory Method for object creation
+ @classmethod
+ def get_instance(cls, *args, **kwargs):
+ """
+ Factory Method for object creation
- Use this instead of directly initializing the class in order to make
- unit testing easier. For testing, this method can be patched to inject
- mock objects.
- """
- return cls(*args, **kwargs)
-
-# vim: se ts=3:
+ Use this instead of directly initializing the class in order to make
+ unit testing easier. For testing, this method can be patched to inject
+ mock objects.
+ """
+ return cls(*args, **kwargs)
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 16b7ff058..763fc3bb7 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -23,47 +23,47 @@ sub- stanzas) handling routines
from simplexml import Node, NodeBuilder
import time
-NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108
-NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033
-NS_AGENTS ='jabber:iq:agents'
-NS_AMP ='http://jabber.org/protocol/amp'
+NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108
+NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033
+NS_AGENTS ='jabber:iq:agents'
+NS_AMP ='http://jabber.org/protocol/amp'
NS_AMP_ERRORS =NS_AMP+'#errors'
-NS_AUTH ='jabber:iq:auth'
-NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
-NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
-NS_BROWSE ='jabber:iq:browse'
-NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195
-NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
-NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115
-NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085
-NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194
-NS_CLIENT ='jabber:client'
-NS_COMMANDS ='http://jabber.org/protocol/commands'
+NS_AUTH ='jabber:iq:auth'
+NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
+NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
+NS_BROWSE ='jabber:iq:browse'
+NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195
+NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
+NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115
+NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085
+NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194
+NS_CLIENT ='jabber:client'
+NS_COMMANDS ='http://jabber.org/protocol/commands'
NS_COMPONENT_ACCEPT='jabber:component:accept'
NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0'
-NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138
+NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138
NS_CONFERENCE ='jabber:x:conference'
-NS_DATA ='jabber:x:data' # XEP-0004
-NS_DELAY ='jabber:x:delay'
-NS_DELAY2 ='urn:xmpp:delay'
-NS_DIALBACK ='jabber:server:dialback'
-NS_DISCO ='http://jabber.org/protocol/disco'
+NS_DATA ='jabber:x:data' # XEP-0004
+NS_DELAY ='jabber:x:delay'
+NS_DELAY2 ='urn:xmpp:delay'
+NS_DIALBACK ='jabber:server:dialback'
+NS_DISCO ='http://jabber.org/protocol/disco'
NS_DISCO_INFO =NS_DISCO+'#info'
NS_DISCO_ITEMS =NS_DISCO+'#items'
-NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027
-NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns'
+NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027
+NS_ESESSION ='http://www.xmpp.org/extensions/xep-0116.html#ns'
NS_ESESSION_INIT='http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116
-NS_EVENT ='jabber:x:event' # XEP-0022
-NS_FEATURE ='http://jabber.org/protocol/feature-neg'
-NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
-NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196
-NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080
-NS_GROUPCHAT ='gc-1.0'
-NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070
-NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124
-NS_IBB ='http://jabber.org/protocol/ibb'
-NS_INVISIBLE ='presence-invisible' # Jabberd2
-NS_IQ ='iq' # Jabberd2
+NS_EVENT ='jabber:x:event' # XEP-0022
+NS_FEATURE ='http://jabber.org/protocol/feature-neg'
+NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
+NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196
+NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080
+NS_GROUPCHAT ='gc-1.0'
+NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070
+NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124
+NS_IBB ='http://jabber.org/protocol/ibb'
+NS_INVISIBLE ='presence-invisible' # Jabberd2
+NS_IQ ='iq' # Jabberd2
NS_JINGLE ='urn:xmpp:jingle:1' # XEP-0166
NS_JINGLE_ERRORS='urn:xmpp:jingle:errors:1' # XEP-0166
NS_JINGLE_RTP ='urn:xmpp:jingle:apps:rtp:1' # XEP-0167
@@ -71,62 +71,62 @@ NS_JINGLE_RTP_AUDIO='urn:xmpp:jingle:apps:rtp:audio' # XEP-01
NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video' # XEP-0167
NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177
NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176
-NS_LAST ='jabber:iq:last'
-NS_LOCATION ='http://jabber.org/protocol/geoloc' # XEP-0080
-NS_MESSAGE ='message' # Jabberd2
-NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107
-NS_MUC ='http://jabber.org/protocol/muc'
-NS_MUC_USER =NS_MUC+'#user'
-NS_MUC_ADMIN =NS_MUC+'#admin'
-NS_MUC_OWNER =NS_MUC+'#owner'
+NS_LAST ='jabber:iq:last'
+NS_LOCATION ='http://jabber.org/protocol/geoloc' # XEP-0080
+NS_MESSAGE ='message' # Jabberd2
+NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107
+NS_MUC ='http://jabber.org/protocol/muc'
+NS_MUC_USER =NS_MUC+'#user'
+NS_MUC_ADMIN =NS_MUC+'#admin'
+NS_MUC_OWNER =NS_MUC+'#owner'
NS_MUC_UNIQUE =NS_MUC+'#unique'
NS_MUC_CONFIG =NS_MUC+'#roomconfig'
-NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172
-NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013
-NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112
-NS_PING ='urn:xmpp:ping' # SEP-0199
-NS_PRESENCE ='presence' # Jabberd2
-NS_PRIVACY ='jabber:iq:privacy'
-NS_PRIVATE ='jabber:iq:private'
-NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154
-NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060
+NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172
+NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # XEP-0013
+NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112
+NS_PING ='urn:xmpp:ping' # SEP-0199
+NS_PRESENCE ='presence' # Jabberd2
+NS_PRIVACY ='jabber:iq:privacy'
+NS_PRIVATE ='jabber:iq:private'
+NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154
+NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060
NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event'
-NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060
-NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060
-NS_REGISTER ='jabber:iq:register'
-NS_ROSTER ='jabber:iq:roster'
-NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144
-NS_RPC ='jabber:iq:rpc' # XEP-0009
-NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
-NS_SEARCH ='jabber:iq:search'
-NS_SERVER ='jabber:server'
-NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
-NS_SI ='http://jabber.org/protocol/si' # XEP-0096
-NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137
-NS_SIGNED ='jabber:x:signed' # XEP-0027
-NS_SSN ='urn:xmpp:ssn' # XEP-0155
-NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200
-NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
-NS_STREAM ='http://affinix.com/jabber/stream'
-NS_STREAMS ='http://etherx.jabber.org/streams'
-NS_TIME ='jabber:iq:time' # XEP-0900
-NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202
-NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
-NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118
-NS_VACATION ='http://jabber.org/protocol/vacation'
-NS_VCARD ='vcard-temp'
+NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060
+NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060
+NS_REGISTER ='jabber:iq:register'
+NS_ROSTER ='jabber:iq:roster'
+NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144
+NS_RPC ='jabber:iq:rpc' # XEP-0009
+NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
+NS_SEARCH ='jabber:iq:search'
+NS_SERVER ='jabber:server'
+NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
+NS_SI ='http://jabber.org/protocol/si' # XEP-0096
+NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137
+NS_SIGNED ='jabber:x:signed' # XEP-0027
+NS_SSN ='urn:xmpp:ssn' # XEP-0155
+NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200
+NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
+NS_STREAM ='http://affinix.com/jabber/stream'
+NS_STREAMS ='http://etherx.jabber.org/streams'
+NS_TIME ='jabber:iq:time' # XEP-0900
+NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202
+NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
+NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118
+NS_VACATION ='http://jabber.org/protocol/vacation'
+NS_VCARD ='vcard-temp'
NS_GMAILNOTIFY ='google:mail:notify'
NS_GTALKSETTING ='google:setting'
NS_VCARD_UPDATE =NS_VCARD+':x:update'
-NS_VERSION ='jabber:iq:version'
-NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197
-NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130
-NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071
-NS_XHTML = 'http://www.w3.org/1999/xhtml' # "
-NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141
-NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122
+NS_VERSION ='jabber:iq:version'
+NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197
+NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130
+NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071
+NS_XHTML = 'http://www.w3.org/1999/xhtml' # "
+NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141
+NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122
NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
-NS_RECEIPTS ='urn:xmpp:receipts'
+NS_RECEIPTS ='urn:xmpp:receipts'
xmpp_stream_error_conditions = '''
bad-format -- -- -- The entity has sent XML that cannot be processed.
@@ -189,1118 +189,1116 @@ temporary-auth-failure -- -- -- The authentication failed because of a tempora
ERRORS, _errorcodes = {}, {}
for ns, errname, errpool in ((NS_XMPP_STREAMS, 'STREAM', xmpp_stream_error_conditions),
- (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions),
- (NS_SASL ,'SASL' ,sasl_error_conditions)):
- for err in errpool.split('\n')[1:]:
- cond, code, typ, text = err.split(' -- ')
- name = errname + '_' + cond.upper().replace('-', '_')
- locals()[name] = ns + ' ' + cond
- ERRORS[ns + ' ' + cond] = [code, typ, text]
- if code:
- _errorcodes[code] = cond
+ (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions),
+ (NS_SASL ,'SASL' ,sasl_error_conditions)):
+ for err in errpool.split('\n')[1:]:
+ cond, code, typ, text = err.split(' -- ')
+ name = errname + '_' + cond.upper().replace('-', '_')
+ locals()[name] = ns + ' ' + cond
+ ERRORS[ns + ' ' + cond] = [code, typ, text]
+ if code:
+ _errorcodes[code] = cond
del ns, errname, errpool, err, cond, code, typ, text
def isResultNode(node):
- """
- Return true if the node is a positive reply
- """
- return node and node.getType() == 'result'
+ """
+ Return true if the node is a positive reply
+ """
+ return node and node.getType() == 'result'
def isErrorNode(node):
- """
- Return true if the node is a negative reply
- """
- return node and node.getType() == 'error'
+ """
+ Return true if the node is a negative reply
+ """
+ return node and node.getType() == 'error'
class NodeProcessed(Exception):
- """
- Exception that should be raised by handler when the handling should be
- stopped
- """
- pass
+ """
+ Exception that should be raised by handler when the handling should be
+ stopped
+ """
+ pass
class StreamError(Exception):
- """
- Base exception class for stream errors
- """
- pass
+ """
+ Base exception class for stream errors
+ """
+ pass
class BadFormat(StreamError):
- pass
+ pass
class BadNamespacePrefix(StreamError):
- pass
+ pass
class Conflict(StreamError):
- pass
+ pass
class ConnectionTimeout(StreamError):
- pass
+ pass
class HostGone(StreamError):
- pass
+ pass
class HostUnknown(StreamError):
- pass
+ pass
class ImproperAddressing(StreamError):
- pass
+ pass
class InternalServerError(StreamError):
- pass
+ pass
class InvalidFrom(StreamError):
- pass
+ pass
class InvalidID(StreamError):
- pass
+ pass
class InvalidNamespace(StreamError):
- pass
+ pass
class InvalidXML(StreamError):
- pass
+ pass
class NotAuthorized(StreamError):
- pass
+ pass
class PolicyViolation(StreamError):
- pass
+ pass
class RemoteConnectionFailed(StreamError):
- pass
+ pass
class ResourceConstraint(StreamError):
- pass
+ pass
class RestrictedXML(StreamError):
- pass
+ pass
class SeeOtherHost(StreamError):
- pass
+ pass
class SystemShutdown(StreamError):
- pass
+ pass
class UndefinedCondition(StreamError):
- pass
+ pass
class UnsupportedEncoding(StreamError):
- pass
+ pass
class UnsupportedStanzaType(StreamError):
- pass
+ pass
class UnsupportedVersion(StreamError):
- pass
+ pass
class XMLNotWellFormed(StreamError):
- pass
+ pass
stream_exceptions = {'bad-format': BadFormat,
- 'bad-namespace-prefix': BadNamespacePrefix,
- 'conflict': Conflict,
- 'connection-timeout': ConnectionTimeout,
- 'host-gone': HostGone,
- 'host-unknown': HostUnknown,
- 'improper-addressing': ImproperAddressing,
- 'internal-server-error': InternalServerError,
- 'invalid-from': InvalidFrom,
- 'invalid-id': InvalidID,
- 'invalid-namespace': InvalidNamespace,
- 'invalid-xml': InvalidXML,
- 'not-authorized': NotAuthorized,
- 'policy-violation': PolicyViolation,
- 'remote-connection-failed': RemoteConnectionFailed,
- 'resource-constraint': ResourceConstraint,
- 'restricted-xml': RestrictedXML,
- 'see-other-host': SeeOtherHost,
- 'system-shutdown': SystemShutdown,
- 'undefined-condition': UndefinedCondition,
- 'unsupported-encoding': UnsupportedEncoding,
- 'unsupported-stanza-type': UnsupportedStanzaType,
- 'unsupported-version': UnsupportedVersion,
- 'xml-not-well-formed': XMLNotWellFormed}
+ 'bad-namespace-prefix': BadNamespacePrefix,
+ 'conflict': Conflict,
+ 'connection-timeout': ConnectionTimeout,
+ 'host-gone': HostGone,
+ 'host-unknown': HostUnknown,
+ 'improper-addressing': ImproperAddressing,
+ 'internal-server-error': InternalServerError,
+ 'invalid-from': InvalidFrom,
+ 'invalid-id': InvalidID,
+ 'invalid-namespace': InvalidNamespace,
+ 'invalid-xml': InvalidXML,
+ 'not-authorized': NotAuthorized,
+ 'policy-violation': PolicyViolation,
+ 'remote-connection-failed': RemoteConnectionFailed,
+ 'resource-constraint': ResourceConstraint,
+ 'restricted-xml': RestrictedXML,
+ 'see-other-host': SeeOtherHost,
+ 'system-shutdown': SystemShutdown,
+ 'undefined-condition': UndefinedCondition,
+ 'unsupported-encoding': UnsupportedEncoding,
+ 'unsupported-stanza-type': UnsupportedStanzaType,
+ 'unsupported-version': UnsupportedVersion,
+ 'xml-not-well-formed': XMLNotWellFormed}
class JID:
- """
- JID can be built from string, modified, compared, serialised into string
- """
-
- def __init__(self, jid=None, node='', domain='', resource=''):
- """
- JID can be specified as string (jid argument) or as separate parts
-
- Examples:
- JID('node@domain/resource')
- JID(node='node',domain='domain.org')
- """
- if not jid and not domain:
- raise ValueError('JID must contain at least domain name')
- elif type(jid) == type(self):
- self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource
- elif domain:
- self.node, self.domain, self.resource = node, domain, resource
- else:
- if jid.find('@') + 1:
- self.node,jid = jid.split('@', 1)
- else:
- self.node = ''
- if jid.find('/')+1:
- self.domain, self.resource = jid.split('/',1)
- else:
- self.domain, self.resource = jid, ''
-
- def getNode(self):
- """
- Return the node part of the JID
- """
- return self.node
-
- def setNode(self, node):
- """
- Set the node part of the JID to new value. Specify None to remove the node part
- """
- self.node = node.lower()
-
- def getDomain(self):
- """
- Return the domain part of the JID
- """
- return self.domain
-
- def setDomain(self, domain):
- """
- Set the domain part of the JID to new value
- """
- self.domain = domain.lower()
-
- def getResource(self):
- """
- Return the resource part of the JID
- """
- return self.resource
-
- def setResource(self, resource):
- """
- Set the resource part of the JID to new value. Specify None to remove the
- resource part
- """
- self.resource = resource
-
- def getStripped(self):
- """
- Return the bare representation of JID. I.e. string value w/o resource
- """
- return self.__str__(0)
-
- def __eq__(self, other):
- """
- Compare the JID to another instance or to string for equality
- """
- try:
- other = JID(other)
- except ValueError:
- return 0
- return self.resource == other.resource and self.__str__(0) == other.__str__(0)
-
- def __ne__(self, other):
- """
- Compare the JID to another instance or to string for non-equality
- """
- return not self.__eq__(other)
-
- def bareMatch(self, other):
- """
- Compare the node and domain parts of the JID's for equality
- """
- return self.__str__(0) == JID(other).__str__(0)
-
- def __str__(self, wresource=1):
- """
- Serialise JID into string
- """
- if self.node:
- jid = self.node + '@' + self.domain
- else:
- jid = self.domain
- if wresource and self.resource:
- return jid + '/' + self.resource
- return jid
-
- def __hash__(self):
- """
- Produce hash of the JID, Allows to use JID objects as keys of the dictionary
- """
- return hash(str(self))
+ """
+ JID can be built from string, modified, compared, serialised into string
+ """
+
+ def __init__(self, jid=None, node='', domain='', resource=''):
+ """
+ JID can be specified as string (jid argument) or as separate parts
+
+ Examples:
+ JID('node@domain/resource')
+ JID(node='node',domain='domain.org')
+ """
+ if not jid and not domain:
+ raise ValueError('JID must contain at least domain name')
+ elif type(jid) == type(self):
+ self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource
+ elif domain:
+ self.node, self.domain, self.resource = node, domain, resource
+ else:
+ if jid.find('@') + 1:
+ self.node,jid = jid.split('@', 1)
+ else:
+ self.node = ''
+ if jid.find('/')+1:
+ self.domain, self.resource = jid.split('/',1)
+ else:
+ self.domain, self.resource = jid, ''
+
+ def getNode(self):
+ """
+ Return the node part of the JID
+ """
+ return self.node
+
+ def setNode(self, node):
+ """
+ Set the node part of the JID to new value. Specify None to remove the node part
+ """
+ self.node = node.lower()
+
+ def getDomain(self):
+ """
+ Return the domain part of the JID
+ """
+ return self.domain
+
+ def setDomain(self, domain):
+ """
+ Set the domain part of the JID to new value
+ """
+ self.domain = domain.lower()
+
+ def getResource(self):
+ """
+ Return the resource part of the JID
+ """
+ return self.resource
+
+ def setResource(self, resource):
+ """
+ Set the resource part of the JID to new value. Specify None to remove the
+ resource part
+ """
+ self.resource = resource
+
+ def getStripped(self):
+ """
+ Return the bare representation of JID. I.e. string value w/o resource
+ """
+ return self.__str__(0)
+
+ def __eq__(self, other):
+ """
+ Compare the JID to another instance or to string for equality
+ """
+ try:
+ other = JID(other)
+ except ValueError:
+ return 0
+ return self.resource == other.resource and self.__str__(0) == other.__str__(0)
+
+ def __ne__(self, other):
+ """
+ Compare the JID to another instance or to string for non-equality
+ """
+ return not self.__eq__(other)
+
+ def bareMatch(self, other):
+ """
+ Compare the node and domain parts of the JID's for equality
+ """
+ return self.__str__(0) == JID(other).__str__(0)
+
+ def __str__(self, wresource=1):
+ """
+ Serialise JID into string
+ """
+ if self.node:
+ jid = self.node + '@' + self.domain
+ else:
+ jid = self.domain
+ if wresource and self.resource:
+ return jid + '/' + self.resource
+ return jid
+
+ def __hash__(self):
+ """
+ Produce hash of the JID, Allows to use JID objects as keys of the dictionary
+ """
+ return hash(str(self))
class BOSHBody(Node):
- """
- <body> tag that wraps usual XMPP stanzas in XMPP over BOSH
- """
+ """
+ <body> tag that wraps usual XMPP stanzas in XMPP over BOSH
+ """
- def __init__(self, attrs={}, payload=[], node=None):
- Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node)
- self.setNamespace(NS_HTTP_BIND)
+ def __init__(self, attrs={}, payload=[], node=None):
+ Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node)
+ self.setNamespace(NS_HTTP_BIND)
class Protocol(Node):
- """
- A "stanza" object class. Contains methods that are common for presences, iqs
- and messages
- """
-
- def __init__(self, name=None, to=None, typ=None, frm=None, attrs={},
- payload=[], timestamp=None, xmlns=None, node=None):
- """
- Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'
-
- to is the value of 'to' attribure, 'typ' - 'type' attribute
- frn - from attribure, attrs - other attributes mapping,
- payload - same meaning as for simplexml payload definition
- timestamp - the time value that needs to be stamped over stanza
- xmlns - namespace of top stanza node
- node - parsed or unparsed stana to be taken as prototype.
- """
- if not attrs:
- attrs = {}
- if to:
- attrs['to'] = to
- if frm:
- attrs['from'] = frm
- if typ:
- attrs['type'] = typ
- Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
- if not node and xmlns:
- self.setNamespace(xmlns)
- if self['to']:
- self.setTo(self['to'])
- if self['from']:
- self.setFrom(self['from'])
- if node and type(self) == type(node) and self.__class__ == node.__class__ and self.attrs.has_key('id'):
- del self.attrs['id']
- self.timestamp = None
- for d in self.getTags('delay', namespace=NS_DELAY2):
- try:
- if d.getAttr('stamp') < self.getTimestamp2():
- self.setTimestamp(d.getAttr('stamp'))
- except Exception:
- pass
- if not self.timestamp:
- for x in self.getTags('x', namespace=NS_DELAY):
- try:
- if x.getAttr('stamp') < self.getTimestamp():
- self.setTimestamp(x.getAttr('stamp'))
- except Exception:
- pass
- if timestamp is not None:
- self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
-
- def getTo(self):
- """
- Return value of the 'to' attribute
- """
- try:
- return self['to']
- except:
- return None
-
- def getFrom(self):
- """
- Return value of the 'from' attribute
- """
- try:
- return self['from']
- except:
- return None
-
- def getTimestamp(self):
- """
- Return the timestamp in the 'yyyymmddThhmmss' format
- """
- if self.timestamp:
- return self.timestamp
- return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
-
- def getTimestamp2(self):
- """
- Return the timestamp in the 'yyyymmddThhmmss' format
- """
- if self.timestamp:
- return self.timestamp
- return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
-
- def getID(self):
- """
- Return the value of the 'id' attribute
- """
- return self.getAttr('id')
-
- def setTo(self, val):
- """
- Set the value of the 'to' attribute
- """
- self.setAttr('to', JID(val))
-
- def getType(self):
- """
- Return the value of the 'type' attribute
- """
- return self.getAttr('type')
-
- def setFrom(self, val):
- """
- Set the value of the 'from' attribute
- """
- self.setAttr('from', JID(val))
-
- def setType(self, val):
- """
- Set the value of the 'type' attribute
- """
- self.setAttr('type', val)
-
- def setID(self, val):
- """
- Set the value of the 'id' attribute
- """
- self.setAttr('id', val)
-
- def getError(self):
- """
- Return the error-condition (if present) or the textual description of the
- error (otherwise)
- """
- errtag = self.getTag('error')
- if errtag:
- for tag in errtag.getChildren():
- if tag.getName() <> 'text':
- return tag.getName()
- return errtag.getData()
-
- def getErrorMsg(self):
- """
- Return the textual description of the error (if present) or the error condition
- """
- errtag = self.getTag('error')
- if errtag:
- for tag in errtag.getChildren():
- if tag.getName() == 'text':
- return tag.getData()
- return self.getError()
-
- def getErrorCode(self):
- """
- Return the error code. Obsolete.
- """
- return self.getTagAttr('error','code')
-
- def setError(self,error,code=None):
- """
- Set the error code. Obsolete. Use error-conditions instead
- """
- if code:
- if str(code) in _errorcodes.keys():
- error = ErrorNode(_errorcodes[str(code)], text=error)
- else:
- error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ='cancel', text=error)
- elif type(error) in [type(''),type(u'')]:
- error=ErrorNode(error)
- self.setType('error')
- self.addChild(node=error)
-
- def setTimestamp(self, val=None):
- """
- Set the timestamp. timestamp should be the yyyymmddThhmmss string
- """
- if not val:
- val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
- self.timestamp=val
- self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY)
-
- def getProperties(self):
- """
- Return the list of namespaces to which belongs the direct childs of element
- """
- props = []
- for child in self.getChildren():
- prop = child.getNamespace()
- if prop not in props:
- props.append(prop)
- return props
-
- def __setitem__(self, item, val):
- """
- Set the item 'item' to the value 'val'
- """
- if item in ['to','from']:
- val = JID(val)
- return self.setAttr(item, val)
+ """
+ A "stanza" object class. Contains methods that are common for presences, iqs
+ and messages
+ """
+
+ def __init__(self, name=None, to=None, typ=None, frm=None, attrs={},
+ payload=[], timestamp=None, xmlns=None, node=None):
+ """
+ Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'
+
+ to is the value of 'to' attribure, 'typ' - 'type' attribute
+ frn - from attribure, attrs - other attributes mapping,
+ payload - same meaning as for simplexml payload definition
+ timestamp - the time value that needs to be stamped over stanza
+ xmlns - namespace of top stanza node
+ node - parsed or unparsed stana to be taken as prototype.
+ """
+ if not attrs:
+ attrs = {}
+ if to:
+ attrs['to'] = to
+ if frm:
+ attrs['from'] = frm
+ if typ:
+ attrs['type'] = typ
+ Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
+ if not node and xmlns:
+ self.setNamespace(xmlns)
+ if self['to']:
+ self.setTo(self['to'])
+ if self['from']:
+ self.setFrom(self['from'])
+ if node and type(self) == type(node) and self.__class__ == node.__class__ and self.attrs.has_key('id'):
+ del self.attrs['id']
+ self.timestamp = None
+ for d in self.getTags('delay', namespace=NS_DELAY2):
+ try:
+ if d.getAttr('stamp') < self.getTimestamp2():
+ self.setTimestamp(d.getAttr('stamp'))
+ except Exception:
+ pass
+ if not self.timestamp:
+ for x in self.getTags('x', namespace=NS_DELAY):
+ try:
+ if x.getAttr('stamp') < self.getTimestamp():
+ self.setTimestamp(x.getAttr('stamp'))
+ except Exception:
+ pass
+ if timestamp is not None:
+ self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
+
+ def getTo(self):
+ """
+ Return value of the 'to' attribute
+ """
+ try:
+ return self['to']
+ except:
+ return None
+
+ def getFrom(self):
+ """
+ Return value of the 'from' attribute
+ """
+ try:
+ return self['from']
+ except:
+ return None
+
+ def getTimestamp(self):
+ """
+ Return the timestamp in the 'yyyymmddThhmmss' format
+ """
+ if self.timestamp:
+ return self.timestamp
+ return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
+
+ def getTimestamp2(self):
+ """
+ Return the timestamp in the 'yyyymmddThhmmss' format
+ """
+ if self.timestamp:
+ return self.timestamp
+ return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
+
+ def getID(self):
+ """
+ Return the value of the 'id' attribute
+ """
+ return self.getAttr('id')
+
+ def setTo(self, val):
+ """
+ Set the value of the 'to' attribute
+ """
+ self.setAttr('to', JID(val))
+
+ def getType(self):
+ """
+ Return the value of the 'type' attribute
+ """
+ return self.getAttr('type')
+
+ def setFrom(self, val):
+ """
+ Set the value of the 'from' attribute
+ """
+ self.setAttr('from', JID(val))
+
+ def setType(self, val):
+ """
+ Set the value of the 'type' attribute
+ """
+ self.setAttr('type', val)
+
+ def setID(self, val):
+ """
+ Set the value of the 'id' attribute
+ """
+ self.setAttr('id', val)
+
+ def getError(self):
+ """
+ Return the error-condition (if present) or the textual description of the
+ error (otherwise)
+ """
+ errtag = self.getTag('error')
+ if errtag:
+ for tag in errtag.getChildren():
+ if tag.getName() <> 'text':
+ return tag.getName()
+ return errtag.getData()
+
+ def getErrorMsg(self):
+ """
+ Return the textual description of the error (if present) or the error condition
+ """
+ errtag = self.getTag('error')
+ if errtag:
+ for tag in errtag.getChildren():
+ if tag.getName() == 'text':
+ return tag.getData()
+ return self.getError()
+
+ def getErrorCode(self):
+ """
+ Return the error code. Obsolete.
+ """
+ return self.getTagAttr('error','code')
+
+ def setError(self,error,code=None):
+ """
+ Set the error code. Obsolete. Use error-conditions instead
+ """
+ if code:
+ if str(code) in _errorcodes.keys():
+ error = ErrorNode(_errorcodes[str(code)], text=error)
+ else:
+ error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ='cancel', text=error)
+ elif type(error) in [type(''),type(u'')]:
+ error=ErrorNode(error)
+ self.setType('error')
+ self.addChild(node=error)
+
+ def setTimestamp(self, val=None):
+ """
+ Set the timestamp. timestamp should be the yyyymmddThhmmss string
+ """
+ if not val:
+ val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
+ self.timestamp=val
+ self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY)
+
+ def getProperties(self):
+ """
+ Return the list of namespaces to which belongs the direct childs of element
+ """
+ props = []
+ for child in self.getChildren():
+ prop = child.getNamespace()
+ if prop not in props:
+ props.append(prop)
+ return props
+
+ def __setitem__(self, item, val):
+ """
+ Set the item 'item' to the value 'val'
+ """
+ if item in ['to','from']:
+ val = JID(val)
+ return self.setAttr(item, val)
class Message(Protocol):
- """
- XMPP Message stanza - "push" mechanism
- """
-
- def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None,
- attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT,
- node=None):
- """
- You can specify recipient, text of message, type of message any
- additional attributes, sender of the message, any additional payload
- (f.e. jabber:x:delay element) and namespace in one go.
-
- Alternatively you can pass in the other XML object as the 'node'
- parameted to replicate it as message
- """
- Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm,
- payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
- if body:
- self.setBody(body)
- if xhtml:
- self.setXHTML(xhtml)
- if subject is not None:
- self.setSubject(subject)
-
- def getBody(self):
- """
- Return text of the message
- """
- return self.getTagData('body')
-
- def getXHTML(self, xmllang=None):
- """
- Return serialized xhtml-im element text of the message
-
- TODO: Returning a DOM could make rendering faster.
- """
- xhtml = self.getTag('html')
- if xhtml:
- if xmllang:
- body = xhtml.getTag('body', attrs={'xml:lang': xmllang})
- else:
- body = xhtml.getTag('body')
- return str(body)
- return None
-
- def getSubject(self):
- """
- Return subject of the message
- """
- return self.getTagData('subject')
-
- def getThread(self):
- """
- Return thread of the message
- """
- return self.getTagData('thread')
-
- def setBody(self, val):
- """
- Set the text of the message"""
- self.setTagData('body', val)
-
- def setXHTML(self, val, xmllang=None):
- """
- Sets the xhtml text of the message (XEP-0071). The parameter is the
- "inner html" to the body.
- """
- try:
- if xmllang:
- dom = NodeBuilder('<body xmlns="%s" xml:lang="%s">%s</body>' % (NS_XHTML, xmllang, val)).getDom()
- else:
- dom = NodeBuilder('<body xmlns="%s">%s</body>, 0' % (NS_XHTM, val)).getDom()
- if self.getTag('html'):
- self.getTag('html').addChild(node=dom)
- else:
- self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom)
- except Exception, e:
- print "Error", e
- # FIXME: log. we could not set xhtml (parse error, whatever)
-
- def setSubject(self, val):
- """
- Set the subject of the message
- """
- self.setTagData('subject', val)
-
- def setThread(self, val):
- """
- Set the thread of the message
- """
- self.setTagData('thread', val)
-
- def buildReply(self, text=None):
- """
- Builds and returns another message object with specified text. The to,
- from and thread properties of new message are pre-set as reply to this
- message
- """
- m = Message(to=self.getFrom(), frm=self.getTo(), body=text)
- th = self.getThread()
- if th:
- m.setThread(th)
- return m
-
- def getStatusCode(self):
- """
- Return the status code of the message (for groupchat config change)
- """
- attrs = []
- for xtag in self.getTags('x'):
- for child in xtag.getTags('status'):
- attrs.append(child.getAttr('code'))
- return attrs
+ """
+ XMPP Message stanza - "push" mechanism
+ """
+
+ def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None,
+ attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT,
+ node=None):
+ """
+ You can specify recipient, text of message, type of message any
+ additional attributes, sender of the message, any additional payload
+ (f.e. jabber:x:delay element) and namespace in one go.
+
+ Alternatively you can pass in the other XML object as the 'node'
+ parameted to replicate it as message
+ """
+ Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm,
+ payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
+ if body:
+ self.setBody(body)
+ if xhtml:
+ self.setXHTML(xhtml)
+ if subject is not None:
+ self.setSubject(subject)
+
+ def getBody(self):
+ """
+ Return text of the message
+ """
+ return self.getTagData('body')
+
+ def getXHTML(self, xmllang=None):
+ """
+ Return serialized xhtml-im element text of the message
+
+ TODO: Returning a DOM could make rendering faster.
+ """
+ xhtml = self.getTag('html')
+ if xhtml:
+ if xmllang:
+ body = xhtml.getTag('body', attrs={'xml:lang': xmllang})
+ else:
+ body = xhtml.getTag('body')
+ return str(body)
+ return None
+
+ def getSubject(self):
+ """
+ Return subject of the message
+ """
+ return self.getTagData('subject')
+
+ def getThread(self):
+ """
+ Return thread of the message
+ """
+ return self.getTagData('thread')
+
+ def setBody(self, val):
+ """
+ Set the text of the message"""
+ self.setTagData('body', val)
+
+ def setXHTML(self, val, xmllang=None):
+ """
+ Sets the xhtml text of the message (XEP-0071). The parameter is the
+ "inner html" to the body.
+ """
+ try:
+ if xmllang:
+ dom = NodeBuilder('<body xmlns="%s" xml:lang="%s">%s</body>' % (NS_XHTML, xmllang, val)).getDom()
+ else:
+ dom = NodeBuilder('<body xmlns="%s">%s</body>, 0' % (NS_XHTM, val)).getDom()
+ if self.getTag('html'):
+ self.getTag('html').addChild(node=dom)
+ else:
+ self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom)
+ except Exception, e:
+ print "Error", e
+ # FIXME: log. we could not set xhtml (parse error, whatever)
+
+ def setSubject(self, val):
+ """
+ Set the subject of the message
+ """
+ self.setTagData('subject', val)
+
+ def setThread(self, val):
+ """
+ Set the thread of the message
+ """
+ self.setTagData('thread', val)
+
+ def buildReply(self, text=None):
+ """
+ Builds and returns another message object with specified text. The to,
+ from and thread properties of new message are pre-set as reply to this
+ message
+ """
+ m = Message(to=self.getFrom(), frm=self.getTo(), body=text)
+ th = self.getThread()
+ if th:
+ m.setThread(th)
+ return m
+
+ def getStatusCode(self):
+ """
+ Return the status code of the message (for groupchat config change)
+ """
+ attrs = []
+ for xtag in self.getTags('x'):
+ for child in xtag.getTags('status'):
+ attrs.append(child.getAttr('code'))
+ return attrs
class Presence(Protocol):
- def __init__(self, to=None, typ=None, priority=None, show=None, status=None,
- attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT,
- node=None):
- """
- You can specify recipient, type of message, priority, show and status
- values any additional attributes, sender of the presence, timestamp, any
- additional payload (f.e. jabber:x:delay element) and namespace in one go.
- Alternatively you can pass in the other XML object as the 'node'
- parameted to replicate it as presence
- """
- Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm,
- payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
- if priority:
- self.setPriority(priority)
- if show:
- self.setShow(show)
- if status:
- self.setStatus(status)
-
- def getPriority(self):
- """
- Return the priority of the message
- """
- return self.getTagData('priority')
-
- def getShow(self):
- """
- Return the show value of the message
- """
- return self.getTagData('show')
-
- def getStatus(self):
- """
- Return the status string of the message
- """
- return self.getTagData('status')
-
- def setPriority(self, val):
- """
- Set the priority of the message
- """
- self.setTagData('priority', val)
-
- def setShow(self, val):
- """
- Set the show value of the message
- """
- self.setTagData('show', val)
-
- def setStatus(self, val):
- """
- Set the status string of the message
- """
- self.setTagData('status', val)
-
- def _muc_getItemAttr(self, tag, attr):
- for xtag in self.getTags('x'):
- if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
- continue
- for child in xtag.getTags(tag):
- return child.getAttr(attr)
-
- def _muc_getSubTagDataAttr(self, tag, attr):
- for xtag in self.getTags('x'):
- if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
- continue
- for child in xtag.getTags('item'):
- for cchild in child.getTags(tag):
- return cchild.getData(), cchild.getAttr(attr)
- return None, None
-
- def getRole(self):
- """
- Return the presence role (for groupchat)
- """
- return self._muc_getItemAttr('item', 'role')
- def getAffiliation(self):
- """
- Return the presence affiliation (for groupchat)
- """
- return self._muc_getItemAttr('item', 'affiliation')
-
- def getNewNick(self):
- """
- Return the status code of the presence (for groupchat)
- """
- return self._muc_getItemAttr('item', 'nick')
-
- def getJid(self):
- """
- Return the presence jid (for groupchat)
- """
- return self._muc_getItemAttr('item', 'jid')
-
- def getReason(self):
- """
- Returns the reason of the presence (for groupchat)
- """
- return self._muc_getSubTagDataAttr('reason', '')[0]
-
- def getActor(self):
- """
- Return the reason of the presence (for groupchat)
- """
- return self._muc_getSubTagDataAttr('actor', 'jid')[1]
-
- def getStatusCode(self):
- """
- Return the status code of the presence (for groupchat)
- """
- attrs = []
- for xtag in self.getTags('x'):
- for child in xtag.getTags('status'):
- attrs.append(child.getAttr('code'))
- return attrs
+ def __init__(self, to=None, typ=None, priority=None, show=None, status=None,
+ attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT,
+ node=None):
+ """
+ You can specify recipient, type of message, priority, show and status
+ values any additional attributes, sender of the presence, timestamp, any
+ additional payload (f.e. jabber:x:delay element) and namespace in one go.
+ Alternatively you can pass in the other XML object as the 'node'
+ parameted to replicate it as presence
+ """
+ Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm,
+ payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
+ if priority:
+ self.setPriority(priority)
+ if show:
+ self.setShow(show)
+ if status:
+ self.setStatus(status)
+
+ def getPriority(self):
+ """
+ Return the priority of the message
+ """
+ return self.getTagData('priority')
+
+ def getShow(self):
+ """
+ Return the show value of the message
+ """
+ return self.getTagData('show')
+
+ def getStatus(self):
+ """
+ Return the status string of the message
+ """
+ return self.getTagData('status')
+
+ def setPriority(self, val):
+ """
+ Set the priority of the message
+ """
+ self.setTagData('priority', val)
+
+ def setShow(self, val):
+ """
+ Set the show value of the message
+ """
+ self.setTagData('show', val)
+
+ def setStatus(self, val):
+ """
+ Set the status string of the message
+ """
+ self.setTagData('status', val)
+
+ def _muc_getItemAttr(self, tag, attr):
+ for xtag in self.getTags('x'):
+ if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
+ continue
+ for child in xtag.getTags(tag):
+ return child.getAttr(attr)
+
+ def _muc_getSubTagDataAttr(self, tag, attr):
+ for xtag in self.getTags('x'):
+ if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN):
+ continue
+ for child in xtag.getTags('item'):
+ for cchild in child.getTags(tag):
+ return cchild.getData(), cchild.getAttr(attr)
+ return None, None
+
+ def getRole(self):
+ """
+ Return the presence role (for groupchat)
+ """
+ return self._muc_getItemAttr('item', 'role')
+ def getAffiliation(self):
+ """
+ Return the presence affiliation (for groupchat)
+ """
+ return self._muc_getItemAttr('item', 'affiliation')
+
+ def getNewNick(self):
+ """
+ Return the status code of the presence (for groupchat)
+ """
+ return self._muc_getItemAttr('item', 'nick')
+
+ def getJid(self):
+ """
+ Return the presence jid (for groupchat)
+ """
+ return self._muc_getItemAttr('item', 'jid')
+
+ def getReason(self):
+ """
+ Returns the reason of the presence (for groupchat)
+ """
+ return self._muc_getSubTagDataAttr('reason', '')[0]
+
+ def getActor(self):
+ """
+ Return the reason of the presence (for groupchat)
+ """
+ return self._muc_getSubTagDataAttr('actor', 'jid')[1]
+
+ def getStatusCode(self):
+ """
+ Return the status code of the presence (for groupchat)
+ """
+ attrs = []
+ for xtag in self.getTags('x'):
+ for child in xtag.getTags('status'):
+ attrs.append(child.getAttr('code'))
+ return attrs
class Iq(Protocol):
- """
- XMPP Iq object - get/set dialog mechanism
- """
-
- def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None,
- payload=[], xmlns=NS_CLIENT, node=None):
- """
- You can specify type, query namespace any additional attributes,
- recipient of the iq, sender of the iq, any additional payload (f.e.
- jabber:x:data node) and namespace in one go.
-
- Alternatively you can pass in the other XML object as the 'node'
- parameted to replicate it as an iq
- """
- Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
- if payload:
- self.setQueryPayload(payload)
- if queryNS:
- self.setQueryNS(queryNS)
-
- def getQueryNS(self):
- """
- Return the namespace of the 'query' child element
- """
- tag = self.getTag('query')
- if tag:
- return tag.getNamespace()
-
- def getQuerynode(self):
- """
- Return the 'node' attribute value of the 'query' child element
- """
- return self.getTagAttr('query', 'node')
-
- def getQueryPayload(self):
- """
- Return the 'query' child element payload
- """
- tag = self.getTag('query')
- if tag:
- return tag.getPayload()
-
- def getQueryChildren(self):
- """
- Return the 'query' child element child nodes
- """
- tag = self.getTag('query')
- if tag:
- return tag.getChildren()
-
- def setQueryNS(self, namespace):
- """
- Set the namespace of the 'query' child element
- """
- self.setTag('query').setNamespace(namespace)
-
- def setQueryPayload(self, payload):
- """
- Set the 'query' child element payload
- """
- self.setTag('query').setPayload(payload)
-
- def setQuerynode(self, node):
- """
- Set the 'node' attribute value of the 'query' child element
- """
- self.setTagAttr('query', 'node', node)
-
- def buildReply(self, typ):
- """
- Build and return another Iq object of specified type. The to, from and
- query child node of new Iq are pre-set as reply to this Iq.
- """
- iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id': self.getID()})
- if self.getTag('query'):
- iq.setQueryNS(self.getQueryNS())
- return iq
+ """
+ XMPP Iq object - get/set dialog mechanism
+ """
+
+ def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None,
+ payload=[], xmlns=NS_CLIENT, node=None):
+ """
+ You can specify type, query namespace any additional attributes,
+ recipient of the iq, sender of the iq, any additional payload (f.e.
+ jabber:x:data node) and namespace in one go.
+
+ Alternatively you can pass in the other XML object as the 'node'
+ parameted to replicate it as an iq
+ """
+ Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
+ if payload:
+ self.setQueryPayload(payload)
+ if queryNS:
+ self.setQueryNS(queryNS)
+
+ def getQueryNS(self):
+ """
+ Return the namespace of the 'query' child element
+ """
+ tag = self.getTag('query')
+ if tag:
+ return tag.getNamespace()
+
+ def getQuerynode(self):
+ """
+ Return the 'node' attribute value of the 'query' child element
+ """
+ return self.getTagAttr('query', 'node')
+
+ def getQueryPayload(self):
+ """
+ Return the 'query' child element payload
+ """
+ tag = self.getTag('query')
+ if tag:
+ return tag.getPayload()
+
+ def getQueryChildren(self):
+ """
+ Return the 'query' child element child nodes
+ """
+ tag = self.getTag('query')
+ if tag:
+ return tag.getChildren()
+
+ def setQueryNS(self, namespace):
+ """
+ Set the namespace of the 'query' child element
+ """
+ self.setTag('query').setNamespace(namespace)
+
+ def setQueryPayload(self, payload):
+ """
+ Set the 'query' child element payload
+ """
+ self.setTag('query').setPayload(payload)
+
+ def setQuerynode(self, node):
+ """
+ Set the 'node' attribute value of the 'query' child element
+ """
+ self.setTagAttr('query', 'node', node)
+
+ def buildReply(self, typ):
+ """
+ Build and return another Iq object of specified type. The to, from and
+ query child node of new Iq are pre-set as reply to this Iq.
+ """
+ iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id': self.getID()})
+ if self.getTag('query'):
+ iq.setQueryNS(self.getQueryNS())
+ return iq
class ErrorNode(Node):
- """
- XMPP-style error element
-
- In the case of stanza error should be attached to XMPP stanza.
- In the case of stream-level errors should be used separately.
- """
-
- def __init__(self, name, code=None, typ=None, text=None):
- """
- Mandatory parameter: name - name of error condition.
- Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.
- """
- if name in ERRORS:
- cod, type_, txt = ERRORS[name]
- ns = name.split()[0]
- else:
- cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', ''
- if typ:
- type_ = typ
- if code:
- cod = code
- if text:
- txt = text
- Node.__init__(self,'error', {}, [Node(name)])
- if type_:
- self.setAttr('type', type_)
- if not cod:
- self.setName('stream:error')
- if txt:
- self.addChild(node=Node(ns + ' text', {}, [txt]))
- if cod:
- self.setAttr('code', cod)
+ """
+ XMPP-style error element
+
+ In the case of stanza error should be attached to XMPP stanza.
+ In the case of stream-level errors should be used separately.
+ """
+
+ def __init__(self, name, code=None, typ=None, text=None):
+ """
+ Mandatory parameter: name - name of error condition.
+ Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.
+ """
+ if name in ERRORS:
+ cod, type_, txt = ERRORS[name]
+ ns = name.split()[0]
+ else:
+ cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', ''
+ if typ:
+ type_ = typ
+ if code:
+ cod = code
+ if text:
+ txt = text
+ Node.__init__(self,'error', {}, [Node(name)])
+ if type_:
+ self.setAttr('type', type_)
+ if not cod:
+ self.setName('stream:error')
+ if txt:
+ self.addChild(node=Node(ns + ' text', {}, [txt]))
+ if cod:
+ self.setAttr('code', cod)
class Error(Protocol):
- """
- Used to quickly transform received stanza into error reply
- """
-
- def __init__(self, node, error, reply=1):
- """
- Create error reply basing on the received 'node' stanza and the 'error'
- error condition
-
- If the 'node' is not the received stanza but locally created ('to' and
- 'from' fields needs not swapping) specify the 'reply' argument as false.
- """
- if reply:
- Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node)
- else:
- Protocol.__init__(self, node=node)
- self.setError(error)
- if node.getType() == 'error':
- self.__str__ = self.__dupstr__
-
- def __dupstr__(self, dup1=None, dup2=None):
- """
- Dummy function used as preventor of creating error node in reply to error
- node. I.e. you will not be able to serialise "double" error into string.
- """
- return ''
+ """
+ Used to quickly transform received stanza into error reply
+ """
+
+ def __init__(self, node, error, reply=1):
+ """
+ Create error reply basing on the received 'node' stanza and the 'error'
+ error condition
+
+ If the 'node' is not the received stanza but locally created ('to' and
+ 'from' fields needs not swapping) specify the 'reply' argument as false.
+ """
+ if reply:
+ Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node)
+ else:
+ Protocol.__init__(self, node=node)
+ self.setError(error)
+ if node.getType() == 'error':
+ self.__str__ = self.__dupstr__
+
+ def __dupstr__(self, dup1=None, dup2=None):
+ """
+ Dummy function used as preventor of creating error node in reply to error
+ node. I.e. you will not be able to serialise "double" error into string.
+ """
+ return ''
class DataField(Node):
- """
- This class is used in the DataForm class to describe the single data item
-
- If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then
- you will need to work with instances of this class.
- """
-
- def __init__(self, name=None, value=None, typ=None, required=0, desc=None,
- options=[], node=None):
- """
- Create new data field of specified name,value and type
-
- Also 'required','desc' and 'options' fields can be set. Alternatively
- other XML object can be passed in as the 'node' parameted to replicate it
- as a new datafiled.
- """
- Node.__init__(self, 'field', node=node)
- if name:
- self.setVar(name)
- if isinstance(value, (list, tuple)):
- self.setValues(value)
- elif value:
- self.setValue(value)
- if typ:
- self.setType(typ)
- elif not typ and not node:
- self.setType('text-single')
- if required:
- self.setRequired(required)
- if desc:
- self.setDesc(desc)
- if options:
- self.setOptions(options)
-
- def setRequired(self, req=1):
- """
- Change the state of the 'required' flag
- """
- if req:
- self.setTag('required')
- else:
- try:
- self.delChild('required')
- except ValueError:
- return
-
- def isRequired(self):
- """
- Return in this field a required one
- """
- return self.getTag('required')
-
- def setDesc(self, desc):
- """
- Set the description of this field
- """
- self.setTagData('desc', desc)
-
- def getDesc(self):
- """
- Return the description of this field
- """
- return self.getTagData('desc')
-
- def setValue(self, val):
- """
- Set the value of this field
- """
- self.setTagData('value', val)
-
- def getValue(self):
- return self.getTagData('value')
-
- def setValues(self, lst):
- """
- Set the values of this field as values-list. Replaces all previous filed
- values! If you need to just add a value - use addValue method
- """
- while self.getTag('value'):
- self.delChild('value')
- for val in lst:
- self.addValue(val)
-
- def addValue(self, val):
- """
- Add one more value to this field. Used in 'get' iq's or such
- """
- self.addChild('value', {}, [val])
-
- def getValues(self):
- """
- Return the list of values associated with this field
- """
- ret = []
- for tag in self.getTags('value'):
- ret.append(tag.getData())
- return ret
-
- def getOptions(self):
- """
- Return label-option pairs list associated with this field
- """
- ret = []
- for tag in self.getTags('option'):
- ret.append([tag.getAttr('label'), tag.getTagData('value')])
- return ret
-
- def setOptions(self, lst):
- """
- Set label-option pairs list associated with this field
- """
- while self.getTag('option'):
- self.delChild('option')
- for opt in lst:
- self.addOption(opt)
-
- def addOption(self, opt):
- """
- Add one more label-option pair to this field
- """
- if isinstance(opt, basestring):
- self.addChild('option').setTagData('value', opt)
- else:
- self.addChild('option', {'label': opt[0]}).setTagData('value', opt[1])
-
- def getType(self):
- """
- Get type of this field
- """
- return self.getAttr('type')
-
- def setType(self, val):
- """
- Set type of this field
- """
- return self.setAttr('type', val)
-
- def getVar(self):
- """
- Get 'var' attribute value of this field
- """
- return self.getAttr('var')
-
- def setVar(self, val):
- """
- Set 'var' attribute value of this field
- """
- return self.setAttr('var',val)
+ """
+ This class is used in the DataForm class to describe the single data item
+
+ If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then
+ you will need to work with instances of this class.
+ """
+
+ def __init__(self, name=None, value=None, typ=None, required=0, desc=None,
+ options=[], node=None):
+ """
+ Create new data field of specified name,value and type
+
+ Also 'required','desc' and 'options' fields can be set. Alternatively
+ other XML object can be passed in as the 'node' parameted to replicate it
+ as a new datafiled.
+ """
+ Node.__init__(self, 'field', node=node)
+ if name:
+ self.setVar(name)
+ if isinstance(value, (list, tuple)):
+ self.setValues(value)
+ elif value:
+ self.setValue(value)
+ if typ:
+ self.setType(typ)
+ elif not typ and not node:
+ self.setType('text-single')
+ if required:
+ self.setRequired(required)
+ if desc:
+ self.setDesc(desc)
+ if options:
+ self.setOptions(options)
+
+ def setRequired(self, req=1):
+ """
+ Change the state of the 'required' flag
+ """
+ if req:
+ self.setTag('required')
+ else:
+ try:
+ self.delChild('required')
+ except ValueError:
+ return
+
+ def isRequired(self):
+ """
+ Return in this field a required one
+ """
+ return self.getTag('required')
+
+ def setDesc(self, desc):
+ """
+ Set the description of this field
+ """
+ self.setTagData('desc', desc)
+
+ def getDesc(self):
+ """
+ Return the description of this field
+ """
+ return self.getTagData('desc')
+
+ def setValue(self, val):
+ """
+ Set the value of this field
+ """
+ self.setTagData('value', val)
+
+ def getValue(self):
+ return self.getTagData('value')
+
+ def setValues(self, lst):
+ """
+ Set the values of this field as values-list. Replaces all previous filed
+ values! If you need to just add a value - use addValue method
+ """
+ while self.getTag('value'):
+ self.delChild('value')
+ for val in lst:
+ self.addValue(val)
+
+ def addValue(self, val):
+ """
+ Add one more value to this field. Used in 'get' iq's or such
+ """
+ self.addChild('value', {}, [val])
+
+ def getValues(self):
+ """
+ Return the list of values associated with this field
+ """
+ ret = []
+ for tag in self.getTags('value'):
+ ret.append(tag.getData())
+ return ret
+
+ def getOptions(self):
+ """
+ Return label-option pairs list associated with this field
+ """
+ ret = []
+ for tag in self.getTags('option'):
+ ret.append([tag.getAttr('label'), tag.getTagData('value')])
+ return ret
+
+ def setOptions(self, lst):
+ """
+ Set label-option pairs list associated with this field
+ """
+ while self.getTag('option'):
+ self.delChild('option')
+ for opt in lst:
+ self.addOption(opt)
+
+ def addOption(self, opt):
+ """
+ Add one more label-option pair to this field
+ """
+ if isinstance(opt, basestring):
+ self.addChild('option').setTagData('value', opt)
+ else:
+ self.addChild('option', {'label': opt[0]}).setTagData('value', opt[1])
+
+ def getType(self):
+ """
+ Get type of this field
+ """
+ return self.getAttr('type')
+
+ def setType(self, val):
+ """
+ Set type of this field
+ """
+ return self.setAttr('type', val)
+
+ def getVar(self):
+ """
+ Get 'var' attribute value of this field
+ """
+ return self.getAttr('var')
+
+ def setVar(self, val):
+ """
+ Set 'var' attribute value of this field
+ """
+ return self.setAttr('var',val)
class DataForm(Node):
- """
- Used for manipulating dataforms in XMPP
-
- Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many
- other applications.
- """
- def __init__(self, typ=None, data=[], title=None, node=None):
- """
- Create new dataform of type 'typ'. 'data' is the list of DataField
- instances that this dataform contains, 'title' - the title string. You
- can specify the 'node' argument as the other node to be used as base for
- constructing this dataform
-
- title and instructions is optional and SHOULD NOT contain newlines.
- Several instructions MAY be present.
- 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' )
- 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.
- 'cancel' form can not contain any fields. All other forms contains AT LEAST one field.
- 'title' MAY be included in forms of type "form" and "result"
- """
- Node.__init__(self, 'x', node=node)
- if node:
- newkids = []
- for n in self.getChildren():
- if n.getName() == 'field':
- newkids.append(DataField(node=n))
- else:
- newkids.append(n)
- self.kids = newkids
- if typ:
- self.setType(typ)
- self.setNamespace(NS_DATA)
- if title:
- self.setTitle(title)
- if isinstance(data, dict):
- newdata = []
- for name in data.keys():
- newdata.append(DataField(name, data[name]))
- data = newdata
- for child in data:
- if isinstance(child, basestring):
- self.addInstructions(child)
- elif child.__class__.__name__ == 'DataField':
- self.kids.append(child)
- else:
- self.kids.append(DataField(node=child))
-
- def getType(self):
- """
- Return the type of dataform
- """
- return self.getAttr('type')
-
- def setType(self, typ):
- """
- Set the type of dataform
- """
- self.setAttr('type', typ)
-
- def getTitle(self):
- """
- Return the title of dataform
- """
- return self.getTagData('title')
-
- def setTitle(self, text):
- """
- Set the title of dataform
- """
- self.setTagData('title', text)
-
- def getInstructions(self):
- """
- Return the instructions of dataform
- """
- return self.getTagData('instructions')
-
- def setInstructions(self, text):
- """
- Set the instructions of dataform
- """
- self.setTagData('instructions', text)
-
- def addInstructions(self, text):
- """
- Add one more instruction to the dataform
- """
- self.addChild('instructions', {}, [text])
-
- def getField(self, name):
- """
- Return the datafield object with name 'name' (if exists)
- """
- return self.getTag('field', attrs={'var': name})
-
- def setField(self, name):
- """
- Create if nessessary or get the existing datafield object with name
- 'name' and return it
- """
- f = self.getField(name)
- if f:
- return f
- return self.addChild(node=DataField(name))
-
- def asDict(self):
- """
- Represent dataform as simple dictionary mapping of datafield names to
- their values
- """
- ret = {}
- for field in self.getTags('field'):
- name = field.getAttr('var')
- typ = field.getType()
- if isinstance(typ, basestring) and typ.endswith('-multi'):
- val = []
- for i in field.getTags('value'):
- val.append(i.getData())
- else:
- val = field.getTagData('value')
- ret[name] = val
- if self.getTag('instructions'):
- ret['instructions'] = self.getInstructions()
- return ret
-
- def __getitem__(self, name):
- """
- Simple dictionary interface for getting datafields values by their names
- """
- item = self.getField(name)
- if item:
- return item.getValue()
- raise IndexError('No such field')
-
- def __setitem__(self, name, val):
- """
- Simple dictionary interface for setting datafields values by their names
- """
- return self.setField(name).setValue(val)
-
-# vim: se ts=3:
+ """
+ Used for manipulating dataforms in XMPP
+
+ Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many
+ other applications.
+ """
+ def __init__(self, typ=None, data=[], title=None, node=None):
+ """
+ Create new dataform of type 'typ'. 'data' is the list of DataField
+ instances that this dataform contains, 'title' - the title string. You
+ can specify the 'node' argument as the other node to be used as base for
+ constructing this dataform
+
+ title and instructions is optional and SHOULD NOT contain newlines.
+ Several instructions MAY be present.
+ 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' )
+ 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.
+ 'cancel' form can not contain any fields. All other forms contains AT LEAST one field.
+ 'title' MAY be included in forms of type "form" and "result"
+ """
+ Node.__init__(self, 'x', node=node)
+ if node:
+ newkids = []
+ for n in self.getChildren():
+ if n.getName() == 'field':
+ newkids.append(DataField(node=n))
+ else:
+ newkids.append(n)
+ self.kids = newkids
+ if typ:
+ self.setType(typ)
+ self.setNamespace(NS_DATA)
+ if title:
+ self.setTitle(title)
+ if isinstance(data, dict):
+ newdata = []
+ for name in data.keys():
+ newdata.append(DataField(name, data[name]))
+ data = newdata
+ for child in data:
+ if isinstance(child, basestring):
+ self.addInstructions(child)
+ elif child.__class__.__name__ == 'DataField':
+ self.kids.append(child)
+ else:
+ self.kids.append(DataField(node=child))
+
+ def getType(self):
+ """
+ Return the type of dataform
+ """
+ return self.getAttr('type')
+
+ def setType(self, typ):
+ """
+ Set the type of dataform
+ """
+ self.setAttr('type', typ)
+
+ def getTitle(self):
+ """
+ Return the title of dataform
+ """
+ return self.getTagData('title')
+
+ def setTitle(self, text):
+ """
+ Set the title of dataform
+ """
+ self.setTagData('title', text)
+
+ def getInstructions(self):
+ """
+ Return the instructions of dataform
+ """
+ return self.getTagData('instructions')
+
+ def setInstructions(self, text):
+ """
+ Set the instructions of dataform
+ """
+ self.setTagData('instructions', text)
+
+ def addInstructions(self, text):
+ """
+ Add one more instruction to the dataform
+ """
+ self.addChild('instructions', {}, [text])
+
+ def getField(self, name):
+ """
+ Return the datafield object with name 'name' (if exists)
+ """
+ return self.getTag('field', attrs={'var': name})
+
+ def setField(self, name):
+ """
+ Create if nessessary or get the existing datafield object with name
+ 'name' and return it
+ """
+ f = self.getField(name)
+ if f:
+ return f
+ return self.addChild(node=DataField(name))
+
+ def asDict(self):
+ """
+ Represent dataform as simple dictionary mapping of datafield names to
+ their values
+ """
+ ret = {}
+ for field in self.getTags('field'):
+ name = field.getAttr('var')
+ typ = field.getType()
+ if isinstance(typ, basestring) and typ.endswith('-multi'):
+ val = []
+ for i in field.getTags('value'):
+ val.append(i.getData())
+ else:
+ val = field.getTagData('value')
+ ret[name] = val
+ if self.getTag('instructions'):
+ ret['instructions'] = self.getInstructions()
+ return ret
+
+ def __getitem__(self, name):
+ """
+ Simple dictionary interface for getting datafields values by their names
+ """
+ item = self.getField(name)
+ if item:
+ return item.getValue()
+ raise IndexError('No such field')
+
+ def __setitem__(self, name, val):
+ """
+ Simple dictionary interface for setting datafields values by their names
+ """
+ return self.setField(name).setValue(val)
diff --git a/src/common/xmpp/proxy_connectors.py b/src/common/xmpp/proxy_connectors.py
index b4e7acfcd..8234a79ea 100644
--- a/src/common/xmpp/proxy_connectors.py
+++ b/src/common/xmpp/proxy_connectors.py
@@ -27,214 +27,212 @@ import logging
log = logging.getLogger('gajim.c.x.proxy_connectors')
class ProxyConnector:
- """
- Interface for proxy-connecting object - when tunnneling XMPP over proxies,
- some connecting process usually has to be done before opening stream. Proxy
- connectors are used right after TCP connection is estabilished
- """
-
- def __init__(self, send_method, onreceive, old_on_receive, on_success,
- on_failure, xmpp_server, proxy_creds=(None,None)):
- """
- Creates proxy connector, starts connecting immediately and gives control
- back to transport afterwards
-
- :param send_method: transport send method
- :param onreceive: method to set on_receive callbacks
- :param old_on_receive: on_receive callback that should be set when
- proxy connection was successful
- :param on_success: called after proxy connection was successfully opened
- :param on_failure: called when errors occured while connecting
- :param xmpp_server: tuple of (hostname, port)
- :param proxy_creds: tuple of (proxy_user, proxy_credentials)
- """
- self.send = send_method
- self.onreceive = onreceive
- self.old_on_receive = old_on_receive
- self.on_success = on_success
- self.on_failure = on_failure
- self.xmpp_server = xmpp_server
- self.proxy_user, self.proxy_pass = proxy_creds
- self.old_on_receive = old_on_receive
-
- self.start_connecting()
-
- @classmethod
- def get_instance(cls, *args, **kwargs):
- """
- Factory Method for object creation
-
- Use this instead of directly initializing the class in order to make unit
- testing much easier.
- """
- return cls(*args, **kwargs)
-
- def start_connecting(self):
- raise NotImplementedError
-
- def connecting_over(self):
- self.onreceive(self.old_on_receive)
- self.on_success()
+ """
+ Interface for proxy-connecting object - when tunnneling XMPP over proxies,
+ some connecting process usually has to be done before opening stream. Proxy
+ connectors are used right after TCP connection is estabilished
+ """
+
+ def __init__(self, send_method, onreceive, old_on_receive, on_success,
+ on_failure, xmpp_server, proxy_creds=(None,None)):
+ """
+ Creates proxy connector, starts connecting immediately and gives control
+ back to transport afterwards
+
+ :param send_method: transport send method
+ :param onreceive: method to set on_receive callbacks
+ :param old_on_receive: on_receive callback that should be set when
+ proxy connection was successful
+ :param on_success: called after proxy connection was successfully opened
+ :param on_failure: called when errors occured while connecting
+ :param xmpp_server: tuple of (hostname, port)
+ :param proxy_creds: tuple of (proxy_user, proxy_credentials)
+ """
+ self.send = send_method
+ self.onreceive = onreceive
+ self.old_on_receive = old_on_receive
+ self.on_success = on_success
+ self.on_failure = on_failure
+ self.xmpp_server = xmpp_server
+ self.proxy_user, self.proxy_pass = proxy_creds
+ self.old_on_receive = old_on_receive
+
+ self.start_connecting()
+
+ @classmethod
+ def get_instance(cls, *args, **kwargs):
+ """
+ Factory Method for object creation
+
+ Use this instead of directly initializing the class in order to make unit
+ testing much easier.
+ """
+ return cls(*args, **kwargs)
+
+ def start_connecting(self):
+ raise NotImplementedError
+
+ def connecting_over(self):
+ self.onreceive(self.old_on_receive)
+ self.on_success()
class HTTPCONNECTConnector(ProxyConnector):
- def start_connecting(self):
- """
- Connect to a proxy, supply login and password to it (if were specified
- while creating instance). Instruct proxy to make connection to the target
- server.
- """
- log.info('Proxy server contacted, performing authentification')
- connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server,
- 'Proxy-Connection: Keep-Alive',
- 'Pragma: no-cache',
- 'Host: %s:%s' % self.xmpp_server,
- 'User-Agent: Gajim']
- if self.proxy_user and self.proxy_pass:
- credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
- credentials = base64.encodestring(credentials).strip()
- connector.append('Proxy-Authorization: Basic '+credentials)
- connector.append('\r\n')
- self.onreceive(self._on_headers_sent)
- self.send('\r\n'.join(connector))
-
- def _on_headers_sent(self, reply):
- if reply is None:
- return
- self.reply = reply.replace('\r', '')
- try:
- proto, code, desc = reply.split('\n')[0].split(' ', 2)
- except:
- log.error("_on_headers_sent:", exc_info=True)
- #traceback.print_exc()
- self.on_failure('Invalid proxy reply')
- return
- if code <> '200':
- log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc))
- self.on_failure('Invalid proxy reply')
- return
- if len(reply) != 2:
- pass
- self.connecting_over()
+ def start_connecting(self):
+ """
+ Connect to a proxy, supply login and password to it (if were specified
+ while creating instance). Instruct proxy to make connection to the target
+ server.
+ """
+ log.info('Proxy server contacted, performing authentification')
+ connector = ['CONNECT %s:%s HTTP/1.1' % self.xmpp_server,
+ 'Proxy-Connection: Keep-Alive',
+ 'Pragma: no-cache',
+ 'Host: %s:%s' % self.xmpp_server,
+ 'User-Agent: Gajim']
+ if self.proxy_user and self.proxy_pass:
+ credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
+ credentials = base64.encodestring(credentials).strip()
+ connector.append('Proxy-Authorization: Basic '+credentials)
+ connector.append('\r\n')
+ self.onreceive(self._on_headers_sent)
+ self.send('\r\n'.join(connector))
+
+ def _on_headers_sent(self, reply):
+ if reply is None:
+ return
+ self.reply = reply.replace('\r', '')
+ try:
+ proto, code, desc = reply.split('\n')[0].split(' ', 2)
+ except:
+ log.error("_on_headers_sent:", exc_info=True)
+ #traceback.print_exc()
+ self.on_failure('Invalid proxy reply')
+ return
+ if code <> '200':
+ log.error('Invalid proxy reply: %s %s %s' % (proto, code, desc))
+ self.on_failure('Invalid proxy reply')
+ return
+ if len(reply) != 2:
+ pass
+ self.connecting_over()
class SOCKS5Connector(ProxyConnector):
- """
- SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with
- (optionally) simple authentication (only USERNAME/PASSWORD auth)
- """
-
- def start_connecting(self):
- log.info('Proxy server contacted, performing authentification')
- if self.proxy_user and self.proxy_pass:
- to_send = '\x05\x02\x00\x02'
- else:
- to_send = '\x05\x01\x00'
- self.onreceive(self._on_greeting_sent)
- self.send(to_send)
-
- def _on_greeting_sent(self, reply):
- if reply is None:
- return
- if len(reply) != 2:
- self.on_failure('Invalid proxy reply')
- return
- if reply[0] != '\x05':
- log.info('Invalid proxy reply')
- self.on_failure('Invalid proxy reply')
- return
- if reply[1] == '\x00':
- return self._on_proxy_auth('\x01\x00')
- elif reply[1] == '\x02':
- to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\
- chr(len(self.proxy_pass)) + self.proxy_pass
- self.onreceive(self._on_proxy_auth)
- self.send(to_send)
- else:
- if reply[1] == '\xff':
- log.error('Authentification to proxy impossible: no acceptable '
- 'auth method')
- self.on_failure('Authentification to proxy impossible: no '
- 'acceptable authentification method')
- return
- log.error('Invalid proxy reply')
- self.on_failure('Invalid proxy reply')
- return
-
- def _on_proxy_auth(self, reply):
- if reply is None:
- return
- if len(reply) != 2:
- log.error('Invalid proxy reply')
- self.on_failure('Invalid proxy reply')
- return
- if reply[0] != '\x01':
- log.error('Invalid proxy reply')
- self.on_failure('Invalid proxy reply')
- return
- if reply[1] != '\x00':
- log.error('Authentification to proxy failed')
- self.on_failure('Authentification to proxy failed')
- return
- log.info('Authentification successfull. Jabber server contacted.')
- # Request connection
- req = "\x05\x01\x00"
- # If the given destination address is an IP address, we'll
- # use the IPv4 address request even if remote resolving was specified.
- try:
- self.ipaddr = socket.inet_aton(self.xmpp_server[0])
- req = req + "\x01" + self.ipaddr
- except socket.error:
- # Well it's not an IP number, so it's probably a DNS name.
-# if self.__proxy[3]==True:
- # Resolve remotely
- self.ipaddr = None
- req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0]
-# else:
-# # Resolve locally
-# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0]))
-# req = req + "\x01" + ipaddr
- req = req + struct.pack(">H", self.xmpp_server[1])
- self.onreceive(self._on_req_sent)
- self.send(req)
-
- def _on_req_sent(self, reply):
- if reply is None:
- return
- if len(reply) < 10:
- log.error('Invalid proxy reply')
- self.on_failure('Invalid proxy reply')
- return
- if reply[0] != '\x05':
- log.error('Invalid proxy reply')
- self.on_failure('Invalid proxy reply')
- return
- if reply[1] != "\x00":
- # Connection failed
- if ord(reply[1])<9:
- errors = ['general SOCKS server failure',
- 'connection not allowed by ruleset',
- 'Network unreachable',
- 'Host unreachable',
- 'Connection refused',
- 'TTL expired',
- 'Command not supported',
- 'Address type not supported'
- ]
- txt = errors[ord(reply[1])-1]
- else:
- txt = 'Invalid proxy reply'
- log.error(txt)
- self.on_failure(txt)
- return
- # Get the bound address/port
- elif reply[3] == "\x01":
- begin, end = 3, 7
- elif reply[3] == "\x03":
- begin, end = 4, 4 + reply[4]
- else:
- log.error('Invalid proxy reply')
- self.on_failure('Invalid proxy reply')
- return
- self.connecting_over()
-
-# vim: se ts=3:
+ """
+ SOCKS5 proxy connection class. Allows to use SOCKS5 proxies with
+ (optionally) simple authentication (only USERNAME/PASSWORD auth)
+ """
+
+ def start_connecting(self):
+ log.info('Proxy server contacted, performing authentification')
+ if self.proxy_user and self.proxy_pass:
+ to_send = '\x05\x02\x00\x02'
+ else:
+ to_send = '\x05\x01\x00'
+ self.onreceive(self._on_greeting_sent)
+ self.send(to_send)
+
+ def _on_greeting_sent(self, reply):
+ if reply is None:
+ return
+ if len(reply) != 2:
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[0] != '\x05':
+ log.info('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[1] == '\x00':
+ return self._on_proxy_auth('\x01\x00')
+ elif reply[1] == '\x02':
+ to_send = '\x01' + chr(len(self.proxy_user)) + self.proxy_user +\
+ chr(len(self.proxy_pass)) + self.proxy_pass
+ self.onreceive(self._on_proxy_auth)
+ self.send(to_send)
+ else:
+ if reply[1] == '\xff':
+ log.error('Authentification to proxy impossible: no acceptable '
+ 'auth method')
+ self.on_failure('Authentification to proxy impossible: no '
+ 'acceptable authentification method')
+ return
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+
+ def _on_proxy_auth(self, reply):
+ if reply is None:
+ return
+ if len(reply) != 2:
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[0] != '\x01':
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[1] != '\x00':
+ log.error('Authentification to proxy failed')
+ self.on_failure('Authentification to proxy failed')
+ return
+ log.info('Authentification successfull. Jabber server contacted.')
+ # Request connection
+ req = "\x05\x01\x00"
+ # If the given destination address is an IP address, we'll
+ # use the IPv4 address request even if remote resolving was specified.
+ try:
+ self.ipaddr = socket.inet_aton(self.xmpp_server[0])
+ req = req + "\x01" + self.ipaddr
+ except socket.error:
+ # Well it's not an IP number, so it's probably a DNS name.
+# if self.__proxy[3]==True:
+ # Resolve remotely
+ self.ipaddr = None
+ req = req + "\x03" + chr(len(self.xmpp_server[0])) + self.xmpp_server[0]
+# else:
+# # Resolve locally
+# self.ipaddr = socket.inet_aton(socket.gethostbyname(self.xmpp_server[0]))
+# req = req + "\x01" + ipaddr
+ req = req + struct.pack(">H", self.xmpp_server[1])
+ self.onreceive(self._on_req_sent)
+ self.send(req)
+
+ def _on_req_sent(self, reply):
+ if reply is None:
+ return
+ if len(reply) < 10:
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[0] != '\x05':
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ if reply[1] != "\x00":
+ # Connection failed
+ if ord(reply[1])<9:
+ errors = ['general SOCKS server failure',
+ 'connection not allowed by ruleset',
+ 'Network unreachable',
+ 'Host unreachable',
+ 'Connection refused',
+ 'TTL expired',
+ 'Command not supported',
+ 'Address type not supported'
+ ]
+ txt = errors[ord(reply[1])-1]
+ else:
+ txt = 'Invalid proxy reply'
+ log.error(txt)
+ self.on_failure(txt)
+ return
+ # Get the bound address/port
+ elif reply[3] == "\x01":
+ begin, end = 3, 7
+ elif reply[3] == "\x03":
+ begin, end = 4, 4 + reply[4]
+ else:
+ log.error('Invalid proxy reply')
+ self.on_failure('Invalid proxy reply')
+ return
+ self.connecting_over()
diff --git a/src/common/xmpp/roster_nb.py b/src/common/xmpp/roster_nb.py
index f343c8341..20e96e95c 100644
--- a/src/common/xmpp/roster_nb.py
+++ b/src/common/xmpp/roster_nb.py
@@ -1,8 +1,8 @@
## roster_nb.py
-## based on roster.py
+## based on roster.py
##
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-## modified by Dimitur Kirov <dkirov@gmail.com>
+## modified by Dimitur Kirov <dkirov@gmail.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
@@ -30,337 +30,335 @@ log = logging.getLogger('gajim.c.x.roster_nb')
class NonBlockingRoster(PlugIn):
- """
- Defines a plenty of methods that will allow you to manage roster. Also
- automatically track presences from remote JIDs taking into account that
- every JID can have multiple resources connected. Does not currently support
- 'error' presences. You can also use mapping interface for access to the
- internal representation of contacts in roster
- """
-
- def __init__(self, version=''):
- """
- Init internal variables
- """
- PlugIn.__init__(self)
- self.version = version
- self._data = {}
- self.set=None
- self._exported_methods=[self.getRoster]
- self.received_from_server = False
-
- def Request(self, force=0):
- """
- Request roster from server if it were not yet requested (or if the
- 'force' argument is set)
- """
- if self.set is None:
- self.set = 0
- elif not force:
- return
-
- iq = Iq('get',NS_ROSTER)
- iq.setTagAttr('query', 'ver', self.version)
- id_ = self._owner.getAnID()
- iq.setID(id_)
- self._owner.send(iq)
- log.info('Roster requested from server')
- return id_
-
- def RosterIqHandler(self, dis, stanza):
- """
- Subscription tracker. Used internally for setting items state in internal
- roster representation
- """
- sender = stanza.getAttr('from')
- if not sender is None and not sender.bareMatch(
- self._owner.User + '@' + self._owner.Server):
- return
- query = stanza.getTag('query')
- if query:
- self.received_from_server = True
- self.version = stanza.getTagAttr('query', 'ver')
- if self.version is None:
- self.version = ''
- for item in query.getTags('item'):
- jid=item.getAttr('jid')
- if item.getAttr('subscription')=='remove':
- if self._data.has_key(jid): del self._data[jid]
- # Looks like we have a workaround
- # raise NodeProcessed # a MUST
- log.info('Setting roster item %s...' % jid)
- if not self._data.has_key(jid): self._data[jid]={}
- self._data[jid]['name']=item.getAttr('name')
- self._data[jid]['ask']=item.getAttr('ask')
- self._data[jid]['subscription']=item.getAttr('subscription')
- self._data[jid]['groups']=[]
- if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
- for group in item.getTags('group'):
- if group.getData() not in self._data[jid]['groups']:
- self._data[jid]['groups'].append(group.getData())
- self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
- self.set=1
- # Looks like we have a workaround
- # raise NodeProcessed # a MUST. Otherwise you'll get back an <iq type='error'/>
-
- def PresenceHandler(self, dis, pres):
- """
- Presence tracker. Used internally for setting items' resources state in
- internal roster representation
- """
- if pres.getTag('x', namespace=NS_MUC_USER):
- return
- jid=pres.getFrom()
- if not jid:
- # If no from attribue, it's from server
- jid=self._owner.Server
- jid=JID(jid)
- if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
- if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
- self._data[jid.getStripped()]['resources']={}
- item=self._data[jid.getStripped()]
- typ=pres.getType()
-
- if not typ:
- log.info('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource()))
- item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
- if pres.getTag('show'): res['show']=pres.getShow()
- if pres.getTag('status'): res['status']=pres.getStatus()
- if pres.getTag('priority'): res['priority']=pres.getPriority()
- if not pres.getTimestamp(): pres.setTimestamp()
- res['timestamp']=pres.getTimestamp()
- elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
- # Need to handle type='error' also
-
- def _getItemData(self, jid, dataname):
- """
- Return specific jid's representation in internal format. Used internally
- """
- jid = jid[:(jid+'/').find('/')]
- return self._data[jid][dataname]
-
- def _getResourceData(self, jid, dataname):
- """
- Return specific jid's resource representation in internal format. Used
- internally
- """
- if jid.find('/') + 1:
- jid, resource = jid.split('/', 1)
- if self._data[jid]['resources'].has_key(resource):
- return self._data[jid]['resources'][resource][dataname]
- elif self._data[jid]['resources'].keys():
- lastpri = -129
- for r in self._data[jid]['resources'].keys():
- if int(self._data[jid]['resources'][r]['priority']) > lastpri:
- resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
- return self._data[jid]['resources'][resource][dataname]
-
- def delItem(self, jid):
- """
- Delete contact 'jid' from roster
- """
- self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid': jid, 'subscription': 'remove'})]))
-
- def getAsk(self, jid):
- """
- Return 'ask' value of contact 'jid'
- """
- return self._getItemData(jid, 'ask')
-
- def getGroups(self, jid):
- """
- Return groups list that contact 'jid' belongs to
- """
- return self._getItemData(jid, 'groups')
-
- def getName(self, jid):
- """
- Return name of contact 'jid'
- """
- return self._getItemData(jid, 'name')
-
- def getPriority(self, jid):
- """
- Return priority of contact 'jid'. 'jid' should be a full (not bare) JID
- """
- return self._getResourceData(jid, 'priority')
-
- def getRawRoster(self):
- """
- Return roster representation in internal format
- """
- return self._data
-
- def getRawItem(self, jid):
- """
- Return roster item 'jid' representation in internal format
- """
- return self._data[jid[:(jid+'/').find('/')]]
-
- def getShow(self, jid):
- """
- Return 'show' value of contact 'jid'. 'jid' should be a full (not bare)
- JID
- """
- return self._getResourceData(jid, 'show')
-
- def getStatus(self, jid):
- """
- Return 'status' value of contact 'jid'. 'jid' should be a full (not bare)
- JID
- """
- return self._getResourceData(jid, 'status')
-
- def getSubscription(self, jid):
- """
- Return 'subscription' value of contact 'jid'
- """
- return self._getItemData(jid, 'subscription')
-
- def getResources(self, jid):
- """
- Return list of connected resources of contact 'jid'
- """
- return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
-
- def setItem(self, jid, name=None, groups=[]):
- """
- Rename contact 'jid' and sets the groups list that it now belongs to
- """
- iq = Iq('set',NS_ROSTER)
- query = iq.getTag('query')
- attrs = {'jid': jid}
- if name:
- attrs['name'] = name
- item = query.setTag('item' ,attrs)
- for group in groups:
- item.addChild(node=Node('group', payload=[group]))
- self._owner.send(iq)
-
- def setItemMulti(self, items):
- """
- Rename multiple contacts and sets their group lists
- """
- iq = Iq('set', NS_ROSTER)
- query = iq.getTag('query')
- for i in items:
- attrs = {'jid': i['jid']}
- if i['name']:
- attrs['name'] = i['name']
- item = query.setTag('item', attrs)
- for group in i['groups']:
- item.addChild(node=Node('group', payload=[group]))
- self._owner.send(iq)
-
- def getItems(self):
- """
- Return list of all [bare] JIDs that the roster is currently tracks
- """
- return self._data.keys()
-
- def keys(self):
- """
- Same as getItems. Provided for the sake of dictionary interface
- """
- return self._data.keys()
-
- def __getitem__(self, item):
- """
- Get the contact in the internal format. Raises KeyError if JID 'item' is
- not in roster
- """
- return self._data[item]
-
- def getItem(self,item):
- """
- Get the contact in the internal format (or None if JID 'item' is not in
- roster)
- """
- if self._data.has_key(item):
- return self._data[item]
-
- def Subscribe(self, jid):
- """
- Send subscription request to JID 'jid'
- """
- self._owner.send(Presence(jid, 'subscribe'))
-
- def Unsubscribe(self,jid):
- """
- Ask for removing our subscription for JID 'jid'
- """
- self._owner.send(Presence(jid, 'unsubscribe'))
-
- def Authorize(self, jid):
- """
- Authorize JID 'jid'. Works only if these JID requested auth previously
- """
- self._owner.send(Presence(jid, 'subscribed'))
-
- def Unauthorize(self, jid):
- """
- Unauthorise JID 'jid'. Use for declining authorisation request or for
- removing existing authorization
- """
- self._owner.send(Presence(jid, 'unsubscribed'))
-
- def getRaw(self):
- """
- Return the internal data representation of the roster
- """
- return self._data
-
- def setRaw(self, data):
- """
- Return the internal data representation of the roster
- """
- self._data = data
- self._data[self._owner.User + '@' + self._owner.Server] = {
- 'resources': {},
- 'name': None,
- 'ask': None,
- 'subscription': None,
- 'groups': None
- }
- self.set = 1
-
- def plugin(self, owner, request=1):
- """
- Register presence and subscription trackers in the owner's dispatcher.
- Also request roster from server if the 'request' argument is set. Used
- internally
- """
- self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1)
- self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER)
- self._owner.RegisterHandler('presence', self.PresenceHandler)
- if request:
- return self.Request()
-
- def _on_roster_set(self, data):
- if data:
- self._owner.Dispatcher.ProcessNonBlocking(data)
- if not self.set:
- return
- self._owner.onreceive(None)
- if self.on_ready:
- self.on_ready(self)
- self.on_ready = None
- return True
-
- def getRoster(self, on_ready=None, force=False):
- """
- Request roster from server if neccessary and returns self
- """
- return_self = True
- if not self.set:
- self.on_ready = on_ready
- self._owner.onreceive(self._on_roster_set)
- return_self = False
- elif on_ready:
- on_ready(self)
- return_self = False
- if return_self or force:
- return self
- return None
-
-# vim: se ts=3:
+ """
+ Defines a plenty of methods that will allow you to manage roster. Also
+ automatically track presences from remote JIDs taking into account that
+ every JID can have multiple resources connected. Does not currently support
+ 'error' presences. You can also use mapping interface for access to the
+ internal representation of contacts in roster
+ """
+
+ def __init__(self, version=''):
+ """
+ Init internal variables
+ """
+ PlugIn.__init__(self)
+ self.version = version
+ self._data = {}
+ self.set=None
+ self._exported_methods=[self.getRoster]
+ self.received_from_server = False
+
+ def Request(self, force=0):
+ """
+ Request roster from server if it were not yet requested (or if the
+ 'force' argument is set)
+ """
+ if self.set is None:
+ self.set = 0
+ elif not force:
+ return
+
+ iq = Iq('get',NS_ROSTER)
+ iq.setTagAttr('query', 'ver', self.version)
+ id_ = self._owner.getAnID()
+ iq.setID(id_)
+ self._owner.send(iq)
+ log.info('Roster requested from server')
+ return id_
+
+ def RosterIqHandler(self, dis, stanza):
+ """
+ Subscription tracker. Used internally for setting items state in internal
+ roster representation
+ """
+ sender = stanza.getAttr('from')
+ if not sender is None and not sender.bareMatch(
+ self._owner.User + '@' + self._owner.Server):
+ return
+ query = stanza.getTag('query')
+ if query:
+ self.received_from_server = True
+ self.version = stanza.getTagAttr('query', 'ver')
+ if self.version is None:
+ self.version = ''
+ for item in query.getTags('item'):
+ jid=item.getAttr('jid')
+ if item.getAttr('subscription')=='remove':
+ if self._data.has_key(jid): del self._data[jid]
+ # Looks like we have a workaround
+ # raise NodeProcessed # a MUST
+ log.info('Setting roster item %s...' % jid)
+ if not self._data.has_key(jid): self._data[jid]={}
+ self._data[jid]['name']=item.getAttr('name')
+ self._data[jid]['ask']=item.getAttr('ask')
+ self._data[jid]['subscription']=item.getAttr('subscription')
+ self._data[jid]['groups']=[]
+ if not self._data[jid].has_key('resources'): self._data[jid]['resources']={}
+ for group in item.getTags('group'):
+ if group.getData() not in self._data[jid]['groups']:
+ self._data[jid]['groups'].append(group.getData())
+ self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
+ self.set=1
+ # Looks like we have a workaround
+ # raise NodeProcessed # a MUST. Otherwise you'll get back an <iq type='error'/>
+
+ def PresenceHandler(self, dis, pres):
+ """
+ Presence tracker. Used internally for setting items' resources state in
+ internal roster representation
+ """
+ if pres.getTag('x', namespace=NS_MUC_USER):
+ return
+ jid=pres.getFrom()
+ if not jid:
+ # If no from attribue, it's from server
+ jid=self._owner.Server
+ jid=JID(jid)
+ if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
+ if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
+ self._data[jid.getStripped()]['resources']={}
+ item=self._data[jid.getStripped()]
+ typ=pres.getType()
+
+ if not typ:
+ log.info('Setting roster item %s for resource %s...'%(jid.getStripped(),jid.getResource()))
+ item['resources'][jid.getResource()]=res={'show':None,'status':None,'priority':'0','timestamp':None}
+ if pres.getTag('show'): res['show']=pres.getShow()
+ if pres.getTag('status'): res['status']=pres.getStatus()
+ if pres.getTag('priority'): res['priority']=pres.getPriority()
+ if not pres.getTimestamp(): pres.setTimestamp()
+ res['timestamp']=pres.getTimestamp()
+ elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()]
+ # Need to handle type='error' also
+
+ def _getItemData(self, jid, dataname):
+ """
+ Return specific jid's representation in internal format. Used internally
+ """
+ jid = jid[:(jid+'/').find('/')]
+ return self._data[jid][dataname]
+
+ def _getResourceData(self, jid, dataname):
+ """
+ Return specific jid's resource representation in internal format. Used
+ internally
+ """
+ if jid.find('/') + 1:
+ jid, resource = jid.split('/', 1)
+ if self._data[jid]['resources'].has_key(resource):
+ return self._data[jid]['resources'][resource][dataname]
+ elif self._data[jid]['resources'].keys():
+ lastpri = -129
+ for r in self._data[jid]['resources'].keys():
+ if int(self._data[jid]['resources'][r]['priority']) > lastpri:
+ resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
+ return self._data[jid]['resources'][resource][dataname]
+
+ def delItem(self, jid):
+ """
+ Delete contact 'jid' from roster
+ """
+ self._owner.send(Iq('set', NS_ROSTER, payload=[Node('item', {'jid': jid, 'subscription': 'remove'})]))
+
+ def getAsk(self, jid):
+ """
+ Return 'ask' value of contact 'jid'
+ """
+ return self._getItemData(jid, 'ask')
+
+ def getGroups(self, jid):
+ """
+ Return groups list that contact 'jid' belongs to
+ """
+ return self._getItemData(jid, 'groups')
+
+ def getName(self, jid):
+ """
+ Return name of contact 'jid'
+ """
+ return self._getItemData(jid, 'name')
+
+ def getPriority(self, jid):
+ """
+ Return priority of contact 'jid'. 'jid' should be a full (not bare) JID
+ """
+ return self._getResourceData(jid, 'priority')
+
+ def getRawRoster(self):
+ """
+ Return roster representation in internal format
+ """
+ return self._data
+
+ def getRawItem(self, jid):
+ """
+ Return roster item 'jid' representation in internal format
+ """
+ return self._data[jid[:(jid+'/').find('/')]]
+
+ def getShow(self, jid):
+ """
+ Return 'show' value of contact 'jid'. 'jid' should be a full (not bare)
+ JID
+ """
+ return self._getResourceData(jid, 'show')
+
+ def getStatus(self, jid):
+ """
+ Return 'status' value of contact 'jid'. 'jid' should be a full (not bare)
+ JID
+ """
+ return self._getResourceData(jid, 'status')
+
+ def getSubscription(self, jid):
+ """
+ Return 'subscription' value of contact 'jid'
+ """
+ return self._getItemData(jid, 'subscription')
+
+ def getResources(self, jid):
+ """
+ Return list of connected resources of contact 'jid'
+ """
+ return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
+
+ def setItem(self, jid, name=None, groups=[]):
+ """
+ Rename contact 'jid' and sets the groups list that it now belongs to
+ """
+ iq = Iq('set',NS_ROSTER)
+ query = iq.getTag('query')
+ attrs = {'jid': jid}
+ if name:
+ attrs['name'] = name
+ item = query.setTag('item' ,attrs)
+ for group in groups:
+ item.addChild(node=Node('group', payload=[group]))
+ self._owner.send(iq)
+
+ def setItemMulti(self, items):
+ """
+ Rename multiple contacts and sets their group lists
+ """
+ iq = Iq('set', NS_ROSTER)
+ query = iq.getTag('query')
+ for i in items:
+ attrs = {'jid': i['jid']}
+ if i['name']:
+ attrs['name'] = i['name']
+ item = query.setTag('item', attrs)
+ for group in i['groups']:
+ item.addChild(node=Node('group', payload=[group]))
+ self._owner.send(iq)
+
+ def getItems(self):
+ """
+ Return list of all [bare] JIDs that the roster is currently tracks
+ """
+ return self._data.keys()
+
+ def keys(self):
+ """
+ Same as getItems. Provided for the sake of dictionary interface
+ """
+ return self._data.keys()
+
+ def __getitem__(self, item):
+ """
+ Get the contact in the internal format. Raises KeyError if JID 'item' is
+ not in roster
+ """
+ return self._data[item]
+
+ def getItem(self,item):
+ """
+ Get the contact in the internal format (or None if JID 'item' is not in
+ roster)
+ """
+ if self._data.has_key(item):
+ return self._data[item]
+
+ def Subscribe(self, jid):
+ """
+ Send subscription request to JID 'jid'
+ """
+ self._owner.send(Presence(jid, 'subscribe'))
+
+ def Unsubscribe(self,jid):
+ """
+ Ask for removing our subscription for JID 'jid'
+ """
+ self._owner.send(Presence(jid, 'unsubscribe'))
+
+ def Authorize(self, jid):
+ """
+ Authorize JID 'jid'. Works only if these JID requested auth previously
+ """
+ self._owner.send(Presence(jid, 'subscribed'))
+
+ def Unauthorize(self, jid):
+ """
+ Unauthorise JID 'jid'. Use for declining authorisation request or for
+ removing existing authorization
+ """
+ self._owner.send(Presence(jid, 'unsubscribed'))
+
+ def getRaw(self):
+ """
+ Return the internal data representation of the roster
+ """
+ return self._data
+
+ def setRaw(self, data):
+ """
+ Return the internal data representation of the roster
+ """
+ self._data = data
+ self._data[self._owner.User + '@' + self._owner.Server] = {
+ 'resources': {},
+ 'name': None,
+ 'ask': None,
+ 'subscription': None,
+ 'groups': None
+ }
+ self.set = 1
+
+ def plugin(self, owner, request=1):
+ """
+ Register presence and subscription trackers in the owner's dispatcher.
+ Also request roster from server if the 'request' argument is set. Used
+ internally
+ """
+ self._owner.RegisterHandler('iq', self.RosterIqHandler, 'result', NS_ROSTER, makefirst = 1)
+ self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER)
+ self._owner.RegisterHandler('presence', self.PresenceHandler)
+ if request:
+ return self.Request()
+
+ def _on_roster_set(self, data):
+ if data:
+ self._owner.Dispatcher.ProcessNonBlocking(data)
+ if not self.set:
+ return
+ self._owner.onreceive(None)
+ if self.on_ready:
+ self.on_ready(self)
+ self.on_ready = None
+ return True
+
+ def getRoster(self, on_ready=None, force=False):
+ """
+ Request roster from server if neccessary and returns self
+ """
+ return_self = True
+ if not self.set:
+ self.on_ready = on_ready
+ self._owner.onreceive(self._on_roster_set)
+ return_self = False
+ elif on_ready:
+ on_ready(self)
+ return_self = False
+ if return_self or force:
+ return self
+ return None
diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py
index 2ace6657a..d8a94122c 100644
--- a/src/common/xmpp/simplexml.py
+++ b/src/common/xmpp/simplexml.py
@@ -25,670 +25,668 @@ import logging
log = logging.getLogger('gajim.c.x.simplexml')
def XMLescape(txt):
- """
- Return provided string with symbols & < > " replaced by their respective XML
- entities
- """
- # replace also FORM FEED and ESC, because they are not valid XML chars
- return txt.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").replace(u'\x0C', "").replace(u'\x1B', "")
+ """
+ Return provided string with symbols & < > " replaced by their respective XML
+ entities
+ """
+ # replace also FORM FEED and ESC, because they are not valid XML chars
+ return txt.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").replace(u'\x0C', "").replace(u'\x1B', "")
ENCODING='utf-8'
def ustr(what):
- """
- Converts object "what" to unicode string using it's own __str__ method if
- accessible or unicode method otherwise
- """
- if isinstance(what, unicode):
- return what
- try:
- r = what.__str__()
- except AttributeError:
- r = str(what)
- if not isinstance(r, unicode):
- return unicode(r, ENCODING)
- return r
+ """
+ Converts object "what" to unicode string using it's own __str__ method if
+ accessible or unicode method otherwise
+ """
+ if isinstance(what, unicode):
+ return what
+ try:
+ r = what.__str__()
+ except AttributeError:
+ r = str(what)
+ if not isinstance(r, unicode):
+ return unicode(r, ENCODING)
+ return r
class Node(object):
- """
- Node class describes syntax of separate XML Node. It have a constructor that
- permits node creation from set of "namespace name", attributes and payload
- of text strings and other nodes. It does not natively support building node
- from text string and uses NodeBuilder class for that purpose. After
- creation node can be mangled in many ways so it can be completely changed.
- Also node can be serialised into string in one of two modes: default (where
- the textual representation of node describes it exactly) and "fancy" - with
- whitespace added to make indentation and thus make result more readable by
- human.
-
- Node class have attribute FORCE_NODE_RECREATION that is defaults to False
- thus enabling fast node replication from the some other node. The drawback
- of the fast way is that new node shares some info with the "original" node
- that is changing the one node may influence the other. Though it is rarely
- needed (in xmpppy it is never needed at all since I'm usually never using
- original node after replication (and using replication only to move upwards
- on the classes tree).
- """
-
- FORCE_NODE_RECREATION = 0
-
- def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None,
- node_built=False, node=None):
- """
- Takes "tag" argument as the name of node (prepended by namespace, if
- needed and separated from it by a space), attrs dictionary as the set of
- arguments, payload list as the set of textual strings and child nodes
- that this node carries within itself and "parent" argument that is
- another node that this one will be the child of. Also the __init__ can be
- provided with "node" argument that is either a text string containing
- exactly one node or another Node instance to begin with. If both "node"
- and other arguments is provided then the node initially created as
- replica of "node" provided and then modified to be compliant with other
- arguments.
- """
- if node:
- if self.FORCE_NODE_RECREATION and isinstance(node, Node):
- node = str(node)
- if not isinstance(node, Node):
- node = NodeBuilder(node,self)
- node_built = True
- else:
- self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = node.name, node.namespace, {}, [], [], node.parent, {}
- for key in node.attrs.keys():
- self.attrs[key] = node.attrs[key]
- for data in node.data:
- self.data.append(data)
- for kid in node.kids:
- self.kids.append(kid)
- for k,v in node.nsd.items():
- self.nsd[k] = v
- else:
- self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = 'tag', '', {}, [], [], None, {}
- if parent:
- self.parent = parent
- self.nsp_cache = {}
- if nsp:
- for k,v in nsp.items(): self.nsp_cache[k] = v
- for attr,val in attrs.items():
- if attr == 'xmlns':
- self.nsd[u''] = val
- elif attr.startswith('xmlns:'):
- self.nsd[attr[6:]] = val
- self.attrs[attr]=attrs[attr]
- if tag:
- if node_built:
- pfx,self.name = (['']+tag.split(':'))[-2:]
- self.namespace = self.lookup_nsp(pfx)
- else:
- if ' ' in tag:
- self.namespace,self.name = tag.split()
- else:
- self.name = tag
- if isinstance(payload, basestring): payload=[payload]
- for i in payload:
- if isinstance(i, Node):
- self.addChild(node=i)
- else:
- self.data.append(ustr(i))
-
- def lookup_nsp(self, pfx=''):
- ns = self.nsd.get(pfx,None)
- if ns is None:
- ns = self.nsp_cache.get(pfx,None)
- if ns is None:
- if self.parent:
- ns = self.parent.lookup_nsp(pfx)
- self.nsp_cache[pfx] = ns
- else:
- return 'http://www.gajim.org/xmlns/undeclared'
- return ns
-
- def __str__(self, fancy=0):
- """
- Method used to dump node into textual representation. If "fancy" argument
- is set to True produces indented output for readability
- """
- s = (fancy-1) * 2 * ' ' + "<" + self.name
- if self.namespace:
- if not self.parent or self.parent.namespace!=self.namespace:
- if 'xmlns' not in self.attrs:
- s = s + ' xmlns="%s"'%self.namespace
- for key in self.attrs.keys():
- val = ustr(self.attrs[key])
- s = s + ' %s="%s"' % ( key, XMLescape(val) )
- s = s + ">"
- cnt = 0
- if self.kids:
- if fancy: s = s + "\n"
- for a in self.kids:
- if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt])
- elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip())
- if isinstance(a, str) or isinstance(a, unicode):
- s = s + a.__str__()
- else:
- s = s + a.__str__(fancy and fancy+1)
- cnt=cnt+1
- if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt])
- elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip())
- if not self.kids and s.endswith('>'):
- s=s[:-1]+' />'
- if fancy: s = s + "\n"
- else:
- if fancy and not self.data: s = s + (fancy-1) * 2 * ' '
- s = s + "</" + self.name + ">"
- if fancy: s = s + "\n"
- return s
-
- def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None):
- """
- If "node" argument is provided, adds it as child node. Else creates new
- node from the other arguments' values and adds it as well
- """
- if 'xmlns' in attrs:
- raise AttributeError("Use namespace=x instead of attrs={'xmlns':x}")
- if node:
- newnode=node
- node.parent = self
- else: newnode=Node(tag=name, parent=self, attrs=attrs, payload=payload)
- if namespace:
- newnode.setNamespace(namespace)
- self.kids.append(newnode)
- return newnode
-
- def addData(self, data):
- """
- Add some CDATA to node
- """
- self.data.append(ustr(data))
-
- def clearData(self):
- """
- Remove all CDATA from the node
- """
- self.data = []
-
- def delAttr(self, key):
- """
- Delete an attribute "key"
- """
- del self.attrs[key]
-
- def delChild(self, node, attrs={}):
- """
- Delete the "node" from the node's childs list, if "node" is an instance.
- Else delete the first node that have specified name and (optionally)
- attributes
- """
- if not isinstance(node, Node):
- node = self.getTag(node,attrs)
- self.kids.remove(node)
- return node
-
- def getAttrs(self):
- """
- Return all node's attributes as dictionary
- """
- return self.attrs
-
- def getAttr(self, key):
- """
- Return value of specified attribute
- """
- return self.attrs.get(key)
-
- def getChildren(self):
- """
- Return all node's child nodes as list
- """
- return self.kids
-
- def getData(self):
- """
- Return all node CDATA as string (concatenated)
- """
- return ''.join(self.data)
-
- def getName(self):
- """
- Return the name of node
- """
- return self.name
-
- def getNamespace(self):
- """
- Return the namespace of node
- """
- return self.namespace
-
- def getParent(self):
- """
- Returns the parent of node (if present)
- """
- return self.parent
-
- def getPayload(self):
- """
- Return the payload of node i.e. list of child nodes and CDATA entries.
- F.e. for "<node>text1<nodea/><nodeb/> text2</node>" will be returned
- list: ['text1', <nodea instance>, <nodeb instance>, ' text2']
- """
- ret = []
- for i in range(len(self.kids)+len(self.data)+1):
- try:
- if self.data[i]:
- ret.append(self.data[i])
- except IndexError:
- pass
- try:
- ret.append(self.kids[i])
- except IndexError:
- pass
- return ret
-
- def getTag(self, name, attrs={}, namespace=None):
- """
- Filter all child nodes using specified arguments as filter. Return the
- first found or None if not found
- """
- return self.getTags(name, attrs, namespace, one=1)
-
- def getTagAttr(self, tag, attr):
- """
- Return attribute value of the child with specified name (or None if no
- such attribute)
- """
- try:
- return self.getTag(tag).attrs[attr]
- except:
- return None
-
- def getTagData(self,tag):
- """
- Return cocatenated CDATA of the child with specified name
- """
- try:
- return self.getTag(tag).getData()
- except Exception:
- return None
-
- def getTags(self, name, attrs={}, namespace=None, one=0):
- """
- Filter all child nodes using specified arguments as filter. Returns the
- list of nodes found
- """
- nodes = []
- for node in self.kids:
- if namespace and namespace != node.getNamespace():
- continue
- if node.getName() == name:
- for key in attrs.keys():
- if key not in node.attrs or node.attrs[key]!=attrs[key]:
- break
- else:
- nodes.append(node)
- if one and nodes:
- return nodes[0]
- if not one:
- return nodes
-
- def iterTags(self, name, attrs={}, namespace=None):
- """
- Iterate over all children using specified arguments as filter
- """
- for node in self.kids:
- if namespace is not None and namespace != node.getNamespace():
- continue
- if node.getName() == name:
- for key in attrs.keys():
- if key not in node.attrs or \
- node.attrs[key]!=attrs[key]:
- break
- else:
- yield node
-
- def setAttr(self, key, val):
- """
- Set attribute "key" with the value "val"
- """
- self.attrs[key] = val
-
- def setData(self, data):
- """
- Set node's CDATA to provided string. Resets all previous CDATA!
- """
- self.data = [ustr(data)]
-
- def setName(self, val):
- """
- Change the node name
- """
- self.name = val
-
- def setNamespace(self, namespace):
- """
- Changes the node namespace
- """
- self.namespace = namespace
-
- def setParent(self, node):
- """
- Set node's parent to "node". WARNING: do not checks if the parent already
- present and not removes the node from the list of childs of previous
- parent
- """
- self.parent = node
-
- def setPayload(self, payload, add=0):
- """
- Set node payload according to the list specified. WARNING: completely
- replaces all node's previous content. If you wish just to add child or
- CDATA - use addData or addChild methods
- """
- if isinstance(payload, basestring):
- payload = [payload]
- if add:
- self.kids += payload
- else:
- self.kids = payload
-
- def setTag(self, name, attrs={}, namespace=None):
- """
- Same as getTag but if the node with specified namespace/attributes not
- found, creates such node and returns it
- """
- node = self.getTags(name, attrs, namespace=namespace, one=1)
- if node:
- return node
- else:
- return self.addChild(name, attrs, namespace=namespace)
-
- def setTagAttr(self, tag, attr, val):
- """
- Create new node (if not already present) with name "tag" and set it's
- attribute "attr" to value "val"
- """
- try:
- self.getTag(tag).attrs[attr] = val
- except Exception:
- self.addChild(tag, attrs={attr: val})
-
- def setTagData(self, tag, val, attrs={}):
- """
- Creates new node (if not already present) with name "tag" and
- (optionally) attributes "attrs" and sets it's CDATA to string "val"
- """
- try:
- self.getTag(tag,attrs).setData(ustr(val))
- except Exception:
- self.addChild(tag,attrs,payload = [ustr(val)])
-
- def has_attr(self, key):
- """
- Check if node have attribute "key"
- """
- return key in self.attrs
-
- def __getitem__(self, item):
- """
- Return node's attribute "item" value
- """
- return self.getAttr(item)
-
- def __setitem__(self, item, val):
- """
- Set node's attribute "item" value
- """
- return self.setAttr(item, val)
-
- def __delitem__(self, item):
- """
- Delete node's attribute "item"
- """
- return self.delAttr(item)
-
- def __contains__(self, item):
- """
- Check if node has attribute "item"
- """
- return self.has_attr(item)
-
- def __getattr__(self, attr):
- """
- Reduce memory usage caused by T/NT classes - use memory only when needed
- """
- if attr == 'T':
- self.T = T(self)
- return self.T
- if attr == 'NT':
- self.NT = NT(self)
- return self.NT
- raise AttributeError
+ """
+ Node class describes syntax of separate XML Node. It have a constructor that
+ permits node creation from set of "namespace name", attributes and payload
+ of text strings and other nodes. It does not natively support building node
+ from text string and uses NodeBuilder class for that purpose. After
+ creation node can be mangled in many ways so it can be completely changed.
+ Also node can be serialised into string in one of two modes: default (where
+ the textual representation of node describes it exactly) and "fancy" - with
+ whitespace added to make indentation and thus make result more readable by
+ human.
+
+ Node class have attribute FORCE_NODE_RECREATION that is defaults to False
+ thus enabling fast node replication from the some other node. The drawback
+ of the fast way is that new node shares some info with the "original" node
+ that is changing the one node may influence the other. Though it is rarely
+ needed (in xmpppy it is never needed at all since I'm usually never using
+ original node after replication (and using replication only to move upwards
+ on the classes tree).
+ """
+
+ FORCE_NODE_RECREATION = 0
+
+ def __init__(self, tag=None, attrs={}, payload=[], parent=None, nsp=None,
+ node_built=False, node=None):
+ """
+ Takes "tag" argument as the name of node (prepended by namespace, if
+ needed and separated from it by a space), attrs dictionary as the set of
+ arguments, payload list as the set of textual strings and child nodes
+ that this node carries within itself and "parent" argument that is
+ another node that this one will be the child of. Also the __init__ can be
+ provided with "node" argument that is either a text string containing
+ exactly one node or another Node instance to begin with. If both "node"
+ and other arguments is provided then the node initially created as
+ replica of "node" provided and then modified to be compliant with other
+ arguments.
+ """
+ if node:
+ if self.FORCE_NODE_RECREATION and isinstance(node, Node):
+ node = str(node)
+ if not isinstance(node, Node):
+ node = NodeBuilder(node,self)
+ node_built = True
+ else:
+ self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = node.name, node.namespace, {}, [], [], node.parent, {}
+ for key in node.attrs.keys():
+ self.attrs[key] = node.attrs[key]
+ for data in node.data:
+ self.data.append(data)
+ for kid in node.kids:
+ self.kids.append(kid)
+ for k,v in node.nsd.items():
+ self.nsd[k] = v
+ else:
+ self.name, self.namespace, self.attrs, self.data, self.kids, self.parent, self.nsd = 'tag', '', {}, [], [], None, {}
+ if parent:
+ self.parent = parent
+ self.nsp_cache = {}
+ if nsp:
+ for k,v in nsp.items(): self.nsp_cache[k] = v
+ for attr,val in attrs.items():
+ if attr == 'xmlns':
+ self.nsd[u''] = val
+ elif attr.startswith('xmlns:'):
+ self.nsd[attr[6:]] = val
+ self.attrs[attr]=attrs[attr]
+ if tag:
+ if node_built:
+ pfx,self.name = (['']+tag.split(':'))[-2:]
+ self.namespace = self.lookup_nsp(pfx)
+ else:
+ if ' ' in tag:
+ self.namespace,self.name = tag.split()
+ else:
+ self.name = tag
+ if isinstance(payload, basestring): payload=[payload]
+ for i in payload:
+ if isinstance(i, Node):
+ self.addChild(node=i)
+ else:
+ self.data.append(ustr(i))
+
+ def lookup_nsp(self, pfx=''):
+ ns = self.nsd.get(pfx,None)
+ if ns is None:
+ ns = self.nsp_cache.get(pfx,None)
+ if ns is None:
+ if self.parent:
+ ns = self.parent.lookup_nsp(pfx)
+ self.nsp_cache[pfx] = ns
+ else:
+ return 'http://www.gajim.org/xmlns/undeclared'
+ return ns
+
+ def __str__(self, fancy=0):
+ """
+ Method used to dump node into textual representation. If "fancy" argument
+ is set to True produces indented output for readability
+ """
+ s = (fancy-1) * 2 * ' ' + "<" + self.name
+ if self.namespace:
+ if not self.parent or self.parent.namespace!=self.namespace:
+ if 'xmlns' not in self.attrs:
+ s = s + ' xmlns="%s"'%self.namespace
+ for key in self.attrs.keys():
+ val = ustr(self.attrs[key])
+ s = s + ' %s="%s"' % ( key, XMLescape(val) )
+ s = s + ">"
+ cnt = 0
+ if self.kids:
+ if fancy: s = s + "\n"
+ for a in self.kids:
+ if not fancy and (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt])
+ elif (len(self.data)-1)>=cnt: s=s+XMLescape(self.data[cnt].strip())
+ if isinstance(a, str) or isinstance(a, unicode):
+ s = s + a.__str__()
+ else:
+ s = s + a.__str__(fancy and fancy+1)
+ cnt=cnt+1
+ if not fancy and (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt])
+ elif (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt].strip())
+ if not self.kids and s.endswith('>'):
+ s=s[:-1]+' />'
+ if fancy: s = s + "\n"
+ else:
+ if fancy and not self.data: s = s + (fancy-1) * 2 * ' '
+ s = s + "</" + self.name + ">"
+ if fancy: s = s + "\n"
+ return s
+
+ def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None):
+ """
+ If "node" argument is provided, adds it as child node. Else creates new
+ node from the other arguments' values and adds it as well
+ """
+ if 'xmlns' in attrs:
+ raise AttributeError("Use namespace=x instead of attrs={'xmlns':x}")
+ if node:
+ newnode=node
+ node.parent = self
+ else: newnode=Node(tag=name, parent=self, attrs=attrs, payload=payload)
+ if namespace:
+ newnode.setNamespace(namespace)
+ self.kids.append(newnode)
+ return newnode
+
+ def addData(self, data):
+ """
+ Add some CDATA to node
+ """
+ self.data.append(ustr(data))
+
+ def clearData(self):
+ """
+ Remove all CDATA from the node
+ """
+ self.data = []
+
+ def delAttr(self, key):
+ """
+ Delete an attribute "key"
+ """
+ del self.attrs[key]
+
+ def delChild(self, node, attrs={}):
+ """
+ Delete the "node" from the node's childs list, if "node" is an instance.
+ Else delete the first node that have specified name and (optionally)
+ attributes
+ """
+ if not isinstance(node, Node):
+ node = self.getTag(node,attrs)
+ self.kids.remove(node)
+ return node
+
+ def getAttrs(self):
+ """
+ Return all node's attributes as dictionary
+ """
+ return self.attrs
+
+ def getAttr(self, key):
+ """
+ Return value of specified attribute
+ """
+ return self.attrs.get(key)
+
+ def getChildren(self):
+ """
+ Return all node's child nodes as list
+ """
+ return self.kids
+
+ def getData(self):
+ """
+ Return all node CDATA as string (concatenated)
+ """
+ return ''.join(self.data)
+
+ def getName(self):
+ """
+ Return the name of node
+ """
+ return self.name
+
+ def getNamespace(self):
+ """
+ Return the namespace of node
+ """
+ return self.namespace
+
+ def getParent(self):
+ """
+ Returns the parent of node (if present)
+ """
+ return self.parent
+
+ def getPayload(self):
+ """
+ Return the payload of node i.e. list of child nodes and CDATA entries.
+ F.e. for "<node>text1<nodea/><nodeb/> text2</node>" will be returned
+ list: ['text1', <nodea instance>, <nodeb instance>, ' text2']
+ """
+ ret = []
+ for i in range(len(self.kids)+len(self.data)+1):
+ try:
+ if self.data[i]:
+ ret.append(self.data[i])
+ except IndexError:
+ pass
+ try:
+ ret.append(self.kids[i])
+ except IndexError:
+ pass
+ return ret
+
+ def getTag(self, name, attrs={}, namespace=None):
+ """
+ Filter all child nodes using specified arguments as filter. Return the
+ first found or None if not found
+ """
+ return self.getTags(name, attrs, namespace, one=1)
+
+ def getTagAttr(self, tag, attr):
+ """
+ Return attribute value of the child with specified name (or None if no
+ such attribute)
+ """
+ try:
+ return self.getTag(tag).attrs[attr]
+ except:
+ return None
+
+ def getTagData(self,tag):
+ """
+ Return cocatenated CDATA of the child with specified name
+ """
+ try:
+ return self.getTag(tag).getData()
+ except Exception:
+ return None
+
+ def getTags(self, name, attrs={}, namespace=None, one=0):
+ """
+ Filter all child nodes using specified arguments as filter. Returns the
+ list of nodes found
+ """
+ nodes = []
+ for node in self.kids:
+ if namespace and namespace != node.getNamespace():
+ continue
+ if node.getName() == name:
+ for key in attrs.keys():
+ if key not in node.attrs or node.attrs[key]!=attrs[key]:
+ break
+ else:
+ nodes.append(node)
+ if one and nodes:
+ return nodes[0]
+ if not one:
+ return nodes
+
+ def iterTags(self, name, attrs={}, namespace=None):
+ """
+ Iterate over all children using specified arguments as filter
+ """
+ for node in self.kids:
+ if namespace is not None and namespace != node.getNamespace():
+ continue
+ if node.getName() == name:
+ for key in attrs.keys():
+ if key not in node.attrs or \
+ node.attrs[key]!=attrs[key]:
+ break
+ else:
+ yield node
+
+ def setAttr(self, key, val):
+ """
+ Set attribute "key" with the value "val"
+ """
+ self.attrs[key] = val
+
+ def setData(self, data):
+ """
+ Set node's CDATA to provided string. Resets all previous CDATA!
+ """
+ self.data = [ustr(data)]
+
+ def setName(self, val):
+ """
+ Change the node name
+ """
+ self.name = val
+
+ def setNamespace(self, namespace):
+ """
+ Changes the node namespace
+ """
+ self.namespace = namespace
+
+ def setParent(self, node):
+ """
+ Set node's parent to "node". WARNING: do not checks if the parent already
+ present and not removes the node from the list of childs of previous
+ parent
+ """
+ self.parent = node
+
+ def setPayload(self, payload, add=0):
+ """
+ Set node payload according to the list specified. WARNING: completely
+ replaces all node's previous content. If you wish just to add child or
+ CDATA - use addData or addChild methods
+ """
+ if isinstance(payload, basestring):
+ payload = [payload]
+ if add:
+ self.kids += payload
+ else:
+ self.kids = payload
+
+ def setTag(self, name, attrs={}, namespace=None):
+ """
+ Same as getTag but if the node with specified namespace/attributes not
+ found, creates such node and returns it
+ """
+ node = self.getTags(name, attrs, namespace=namespace, one=1)
+ if node:
+ return node
+ else:
+ return self.addChild(name, attrs, namespace=namespace)
+
+ def setTagAttr(self, tag, attr, val):
+ """
+ Create new node (if not already present) with name "tag" and set it's
+ attribute "attr" to value "val"
+ """
+ try:
+ self.getTag(tag).attrs[attr] = val
+ except Exception:
+ self.addChild(tag, attrs={attr: val})
+
+ def setTagData(self, tag, val, attrs={}):
+ """
+ Creates new node (if not already present) with name "tag" and
+ (optionally) attributes "attrs" and sets it's CDATA to string "val"
+ """
+ try:
+ self.getTag(tag,attrs).setData(ustr(val))
+ except Exception:
+ self.addChild(tag,attrs,payload = [ustr(val)])
+
+ def has_attr(self, key):
+ """
+ Check if node have attribute "key"
+ """
+ return key in self.attrs
+
+ def __getitem__(self, item):
+ """
+ Return node's attribute "item" value
+ """
+ return self.getAttr(item)
+
+ def __setitem__(self, item, val):
+ """
+ Set node's attribute "item" value
+ """
+ return self.setAttr(item, val)
+
+ def __delitem__(self, item):
+ """
+ Delete node's attribute "item"
+ """
+ return self.delAttr(item)
+
+ def __contains__(self, item):
+ """
+ Check if node has attribute "item"
+ """
+ return self.has_attr(item)
+
+ def __getattr__(self, attr):
+ """
+ Reduce memory usage caused by T/NT classes - use memory only when needed
+ """
+ if attr == 'T':
+ self.T = T(self)
+ return self.T
+ if attr == 'NT':
+ self.NT = NT(self)
+ return self.NT
+ raise AttributeError
class T:
- """
- Auxiliary class used to quick access to node's child nodes
- """
+ """
+ Auxiliary class used to quick access to node's child nodes
+ """
- def __init__(self, node):
- self.__dict__['node'] = node
+ def __init__(self, node):
+ self.__dict__['node'] = node
- def __getattr__(self, attr):
- return self.node.setTag(attr)
+ def __getattr__(self, attr):
+ return self.node.setTag(attr)
- def __setattr__(self, attr, val):
- if isinstance(val,Node):
- Node.__init__(self.node.setTag(attr), node=val)
- else:
- return self.node.setTagData(attr, val)
+ def __setattr__(self, attr, val):
+ if isinstance(val,Node):
+ Node.__init__(self.node.setTag(attr), node=val)
+ else:
+ return self.node.setTagData(attr, val)
- def __delattr__(self, attr):
- return self.node.delChild(attr)
+ def __delattr__(self, attr):
+ return self.node.delChild(attr)
class NT(T):
- """
- Auxiliary class used to quick create node's child nodes
- """
+ """
+ Auxiliary class used to quick create node's child nodes
+ """
- def __getattr__(self, attr):
- return self.node.addChild(attr)
+ def __getattr__(self, attr):
+ return self.node.addChild(attr)
- def __setattr__(self, attr, val):
- if isinstance(val,Node):
- self.node.addChild(attr,node=val)
- else:
- return self.node.addChild(attr, payload=[val])
+ def __setattr__(self, attr, val):
+ if isinstance(val,Node):
+ self.node.addChild(attr,node=val)
+ else:
+ return self.node.addChild(attr, payload=[val])
class NodeBuilder:
- """
- Builds a Node class minidom from data parsed to it. This class used for two
- purposes:
- 1. Creation an XML Node from a textual representation. F.e. reading a
- config file. See an XML2Node method.
- 2. Handling an incoming XML stream. This is done by mangling the
- __dispatch_depth parameter and redefining the dispatch method.
-
- You do not need to use this class directly if you do not designing your own
- XML handler
- """
-
- def __init__(self, data=None, initial_node=None):
- """
- Take two optional parameters: "data" and "initial_node"
-
- By default class initialised with empty Node class instance. Though, if
- "initial_node" is provided it used as "starting point". You can think
- about it as of "node upgrade". "data" (if provided) feeded to parser
- immidiatedly after instance init.
- """
- log.debug("Preparing to handle incoming XML stream.")
- self._parser = xml.parsers.expat.ParserCreate()
- self._parser.StartElementHandler = self.starttag
- self._parser.EndElementHandler = self.endtag
- self._parser.StartNamespaceDeclHandler = self.handle_namespace_start
- self._parser.CharacterDataHandler = self.handle_cdata
- self._parser.buffer_text = True
- self.Parse = self._parser.Parse
-
- self.__depth = 0
- self.__last_depth = 0
- self.__max_depth = 0
- self._dispatch_depth = 1
- self._document_attrs = None
- self._document_nsp = None
- self._mini_dom=initial_node
- self.last_is_data = 1
- self._ptr=None
- self.data_buffer = None
- self.streamError = ''
- if data:
- self._parser.Parse(data,1)
-
- def check_data_buffer(self):
- if self.data_buffer:
- self._ptr.data.append(''.join(self.data_buffer))
- del self.data_buffer[:]
- self.data_buffer = None
-
- def destroy(self):
- """
- Method used to allow class instance to be garbage-collected
- """
- self.check_data_buffer()
- self._parser.StartElementHandler = None
- self._parser.EndElementHandler = None
- self._parser.CharacterDataHandler = None
- self._parser.StartNamespaceDeclHandler = None
-
- def starttag(self, tag, attrs):
- """
- XML Parser callback. Used internally
- """
- self.check_data_buffer()
- self._inc_depth()
- log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`))
- if self.__depth == self._dispatch_depth:
- if not self._mini_dom :
- self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True)
- else:
- Node.__init__(self._mini_dom,tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True)
- self._ptr = self._mini_dom
- elif self.__depth > self._dispatch_depth:
- self._ptr.kids.append(Node(tag=tag,parent=self._ptr,attrs=attrs, node_built=True))
- self._ptr = self._ptr.kids[-1]
- if self.__depth == 1:
- self._document_attrs = {}
- self._document_nsp = {}
- nsp, name = (['']+tag.split(':'))[-2:]
- for attr,val in attrs.items():
- if attr == 'xmlns':
- self._document_nsp[u''] = val
- elif attr.startswith('xmlns:'):
- self._document_nsp[attr[6:]] = val
- else:
- self._document_attrs[attr] = val
- ns = self._document_nsp.get(nsp, 'http://www.gajim.org/xmlns/undeclared-root')
- try:
- self.stream_header_received(ns, name, attrs)
- except ValueError, e:
- self._document_attrs = None
- raise ValueError(str(e))
- if not self.last_is_data and self._ptr.parent:
- self._ptr.parent.data.append('')
- self.last_is_data = 0
-
- def endtag(self, tag ):
- """
- XML Parser callback. Used internally
- """
- log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag))
- self.check_data_buffer()
- if self.__depth == self._dispatch_depth:
- if self._mini_dom.getName() == 'error':
- children = self._mini_dom.getChildren()
- if children:
- self.streamError = children[0].getName()
- else:
- self.streamError = self._mini_dom.getData()
- self.dispatch(self._mini_dom)
- elif self.__depth > self._dispatch_depth:
- self._ptr = self._ptr.parent
- else:
- log.info("Got higher than dispatch level. Stream terminated?")
- self._dec_depth()
- self.last_is_data = 0
- if self.__depth == 0: self.stream_footer_received()
-
- def handle_cdata(self, data):
- if self.last_is_data:
- if self.data_buffer:
- self.data_buffer.append(data)
- elif self._ptr:
- self.data_buffer = [data]
- self.last_is_data = 1
-
- def handle_namespace_start(self, prefix, uri):
- """
- XML Parser callback. Used internally
- """
- self.check_data_buffer()
-
- def getDom(self):
- """
- Return just built Node
- """
- self.check_data_buffer()
- return self._mini_dom
-
- def dispatch(self, stanza):
- """
- Get called when the NodeBuilder reaches some level of depth on it's way
- up with the built node as argument. Can be redefined to convert incoming
- XML stanzas to program events
- """
- pass
-
- def stream_header_received(self, ns, tag, attrs):
- """
- Method called when stream just opened
- """
- self.check_data_buffer()
-
- def stream_footer_received(self):
- """
- Method called when stream just closed
- """
- self.check_data_buffer()
-
- def has_received_endtag(self, level=0):
- """
- Return True if at least one end tag was seen (at level)
- """
- return self.__depth <= level and self.__max_depth > level
-
- def _inc_depth(self):
- self.__last_depth = self.__depth
- self.__depth += 1
- self.__max_depth = max(self.__depth, self.__max_depth)
-
- def _dec_depth(self):
- self.__last_depth = self.__depth
- self.__depth -= 1
+ """
+ Builds a Node class minidom from data parsed to it. This class used for two
+ purposes:
+ 1. Creation an XML Node from a textual representation. F.e. reading a
+ config file. See an XML2Node method.
+ 2. Handling an incoming XML stream. This is done by mangling the
+ __dispatch_depth parameter and redefining the dispatch method.
+
+ You do not need to use this class directly if you do not designing your own
+ XML handler
+ """
+
+ def __init__(self, data=None, initial_node=None):
+ """
+ Take two optional parameters: "data" and "initial_node"
+
+ By default class initialised with empty Node class instance. Though, if
+ "initial_node" is provided it used as "starting point". You can think
+ about it as of "node upgrade". "data" (if provided) feeded to parser
+ immidiatedly after instance init.
+ """
+ log.debug("Preparing to handle incoming XML stream.")
+ self._parser = xml.parsers.expat.ParserCreate()
+ self._parser.StartElementHandler = self.starttag
+ self._parser.EndElementHandler = self.endtag
+ self._parser.StartNamespaceDeclHandler = self.handle_namespace_start
+ self._parser.CharacterDataHandler = self.handle_cdata
+ self._parser.buffer_text = True
+ self.Parse = self._parser.Parse
+
+ self.__depth = 0
+ self.__last_depth = 0
+ self.__max_depth = 0
+ self._dispatch_depth = 1
+ self._document_attrs = None
+ self._document_nsp = None
+ self._mini_dom=initial_node
+ self.last_is_data = 1
+ self._ptr=None
+ self.data_buffer = None
+ self.streamError = ''
+ if data:
+ self._parser.Parse(data,1)
+
+ def check_data_buffer(self):
+ if self.data_buffer:
+ self._ptr.data.append(''.join(self.data_buffer))
+ del self.data_buffer[:]
+ self.data_buffer = None
+
+ def destroy(self):
+ """
+ Method used to allow class instance to be garbage-collected
+ """
+ self.check_data_buffer()
+ self._parser.StartElementHandler = None
+ self._parser.EndElementHandler = None
+ self._parser.CharacterDataHandler = None
+ self._parser.StartNamespaceDeclHandler = None
+
+ def starttag(self, tag, attrs):
+ """
+ XML Parser callback. Used internally
+ """
+ self.check_data_buffer()
+ self._inc_depth()
+ log.info("STARTTAG.. DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`))
+ if self.__depth == self._dispatch_depth:
+ if not self._mini_dom :
+ self._mini_dom = Node(tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True)
+ else:
+ Node.__init__(self._mini_dom,tag=tag, attrs=attrs, nsp = self._document_nsp, node_built=True)
+ self._ptr = self._mini_dom
+ elif self.__depth > self._dispatch_depth:
+ self._ptr.kids.append(Node(tag=tag,parent=self._ptr,attrs=attrs, node_built=True))
+ self._ptr = self._ptr.kids[-1]
+ if self.__depth == 1:
+ self._document_attrs = {}
+ self._document_nsp = {}
+ nsp, name = (['']+tag.split(':'))[-2:]
+ for attr,val in attrs.items():
+ if attr == 'xmlns':
+ self._document_nsp[u''] = val
+ elif attr.startswith('xmlns:'):
+ self._document_nsp[attr[6:]] = val
+ else:
+ self._document_attrs[attr] = val
+ ns = self._document_nsp.get(nsp, 'http://www.gajim.org/xmlns/undeclared-root')
+ try:
+ self.stream_header_received(ns, name, attrs)
+ except ValueError, e:
+ self._document_attrs = None
+ raise ValueError(str(e))
+ if not self.last_is_data and self._ptr.parent:
+ self._ptr.parent.data.append('')
+ self.last_is_data = 0
+
+ def endtag(self, tag ):
+ """
+ XML Parser callback. Used internally
+ """
+ log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag))
+ self.check_data_buffer()
+ if self.__depth == self._dispatch_depth:
+ if self._mini_dom.getName() == 'error':
+ children = self._mini_dom.getChildren()
+ if children:
+ self.streamError = children[0].getName()
+ else:
+ self.streamError = self._mini_dom.getData()
+ self.dispatch(self._mini_dom)
+ elif self.__depth > self._dispatch_depth:
+ self._ptr = self._ptr.parent
+ else:
+ log.info("Got higher than dispatch level. Stream terminated?")
+ self._dec_depth()
+ self.last_is_data = 0
+ if self.__depth == 0: self.stream_footer_received()
+
+ def handle_cdata(self, data):
+ if self.last_is_data:
+ if self.data_buffer:
+ self.data_buffer.append(data)
+ elif self._ptr:
+ self.data_buffer = [data]
+ self.last_is_data = 1
+
+ def handle_namespace_start(self, prefix, uri):
+ """
+ XML Parser callback. Used internally
+ """
+ self.check_data_buffer()
+
+ def getDom(self):
+ """
+ Return just built Node
+ """
+ self.check_data_buffer()
+ return self._mini_dom
+
+ def dispatch(self, stanza):
+ """
+ Get called when the NodeBuilder reaches some level of depth on it's way
+ up with the built node as argument. Can be redefined to convert incoming
+ XML stanzas to program events
+ """
+ pass
+
+ def stream_header_received(self, ns, tag, attrs):
+ """
+ Method called when stream just opened
+ """
+ self.check_data_buffer()
+
+ def stream_footer_received(self):
+ """
+ Method called when stream just closed
+ """
+ self.check_data_buffer()
+
+ def has_received_endtag(self, level=0):
+ """
+ Return True if at least one end tag was seen (at level)
+ """
+ return self.__depth <= level and self.__max_depth > level
+
+ def _inc_depth(self):
+ self.__last_depth = self.__depth
+ self.__depth += 1
+ self.__max_depth = max(self.__depth, self.__max_depth)
+
+ def _dec_depth(self):
+ self.__last_depth = self.__depth
+ self.__depth -= 1
def XML2Node(xml):
- """
- Convert supplied textual string into XML node. Handy f.e. for reading
- configuration file. Raises xml.parser.expat.parsererror if provided string
- is not well-formed XML
- """
- return NodeBuilder(xml).getDom()
+ """
+ Convert supplied textual string into XML node. Handy f.e. for reading
+ configuration file. Raises xml.parser.expat.parsererror if provided string
+ is not well-formed XML
+ """
+ return NodeBuilder(xml).getDom()
def BadXML2Node(xml):
- """
- Convert supplied textual string into XML node. Survives if xml data is
- cutted half way round. I.e. "<html>some text <br>some more text". Will raise
- xml.parser.expat.parsererror on misplaced tags though. F.e. "<b>some text
- <br>some more text</b>" will not work
- """
- return NodeBuilder(xml).getDom()
-
-# vim: se ts=3:
+ """
+ Convert supplied textual string into XML node. Survives if xml data is
+ cutted half way round. I.e. "<html>some text <br>some more text". Will raise
+ xml.parser.expat.parsererror on misplaced tags though. F.e. "<b>some text
+ <br>some more text</b>" will not work
+ """
+ return NodeBuilder(xml).getDom()
diff --git a/src/common/xmpp/stringprepare.py b/src/common/xmpp/stringprepare.py
index 39759a513..5d0610d4b 100644
--- a/src/common/xmpp/stringprepare.py
+++ b/src/common/xmpp/stringprepare.py
@@ -26,187 +26,187 @@ import unicodedata
from encodings import idna
class ILookupTable:
- """
- Interface for character lookup classes
- """
+ """
+ Interface for character lookup classes
+ """
- def lookup(self, c):
- """
- Return whether character is in this table
- """
- pass
+ def lookup(self, c):
+ """
+ Return whether character is in this table
+ """
+ pass
class IMappingTable:
- """
- Interface for character mapping classes
- """
+ """
+ Interface for character mapping classes
+ """
- def map(self, c):
- """
- Return mapping for character
- """
- pass
+ def map(self, c):
+ """
+ Return mapping for character
+ """
+ pass
class LookupTableFromFunction:
- __implements__ = ILookupTable
+ __implements__ = ILookupTable
- def __init__(self, in_table_function):
- self.lookup = in_table_function
+ def __init__(self, in_table_function):
+ self.lookup = in_table_function
class LookupTable:
- __implements__ = ILookupTable
+ __implements__ = ILookupTable
- def __init__(self, table):
- self._table = table
+ def __init__(self, table):
+ self._table = table
- def lookup(self, c):
- return c in self._table
+ def lookup(self, c):
+ return c in self._table
class MappingTableFromFunction:
- __implements__ = IMappingTable
+ __implements__ = IMappingTable
- def __init__(self, map_table_function):
- self.map = map_table_function
+ def __init__(self, map_table_function):
+ self.map = map_table_function
class EmptyMappingTable:
- __implements__ = IMappingTable
+ __implements__ = IMappingTable
- def __init__(self, in_table_function):
- self._in_table_function = in_table_function
+ def __init__(self, in_table_function):
+ self._in_table_function = in_table_function
- def map(self, c):
- if self._in_table_function(c):
- return None
- else:
- return c
+ def map(self, c):
+ if self._in_table_function(c):
+ return None
+ else:
+ return c
class Profile:
- def __init__(self, mappings=[], normalize=True, prohibiteds=[],
- check_unassigneds=True, check_bidi=True):
- self.mappings = mappings
- self.normalize = normalize
- self.prohibiteds = prohibiteds
- self.do_check_unassigneds = check_unassigneds
- self.do_check_bidi = check_bidi
-
- def prepare(self, string):
- result = self.map(string)
- if self.normalize:
- result = unicodedata.normalize("NFKC", result)
- self.check_prohibiteds(result)
- if self.do_check_unassigneds:
- self.check_unassigneds(result)
- if self.do_check_bidi:
- self.check_bidirectionals(result)
- return result
-
- def map(self, string):
- result = []
-
- for c in string:
- result_c = c
-
- for mapping in self.mappings:
- result_c = mapping.map(c)
- if result_c != c:
- break
-
- if result_c is not None:
- result.append(result_c)
-
- return u"".join(result)
-
- def check_prohibiteds(self, string):
- for c in string:
- for table in self.prohibiteds:
- if table.lookup(c):
- raise UnicodeError, "Invalid character %s" % repr(c)
-
- def check_unassigneds(self, string):
- for c in string:
- if stringprep.in_table_a1(c):
- raise UnicodeError, "Unassigned code point %s" % repr(c)
-
- def check_bidirectionals(self, string):
- found_LCat = False
- found_RandALCat = False
-
- for c in string:
- if stringprep.in_table_d1(c):
- found_RandALCat = True
- if stringprep.in_table_d2(c):
- found_LCat = True
-
- if found_LCat and found_RandALCat:
- raise UnicodeError, "Violation of BIDI Requirement 2"
-
- if found_RandALCat and not (stringprep.in_table_d1(string[0]) and
- stringprep.in_table_d1(string[-1])):
- raise UnicodeError, "Violation of BIDI Requirement 3"
+ def __init__(self, mappings=[], normalize=True, prohibiteds=[],
+ check_unassigneds=True, check_bidi=True):
+ self.mappings = mappings
+ self.normalize = normalize
+ self.prohibiteds = prohibiteds
+ self.do_check_unassigneds = check_unassigneds
+ self.do_check_bidi = check_bidi
+
+ def prepare(self, string):
+ result = self.map(string)
+ if self.normalize:
+ result = unicodedata.normalize("NFKC", result)
+ self.check_prohibiteds(result)
+ if self.do_check_unassigneds:
+ self.check_unassigneds(result)
+ if self.do_check_bidi:
+ self.check_bidirectionals(result)
+ return result
+
+ def map(self, string):
+ result = []
+
+ for c in string:
+ result_c = c
+
+ for mapping in self.mappings:
+ result_c = mapping.map(c)
+ if result_c != c:
+ break
+
+ if result_c is not None:
+ result.append(result_c)
+
+ return u"".join(result)
+
+ def check_prohibiteds(self, string):
+ for c in string:
+ for table in self.prohibiteds:
+ if table.lookup(c):
+ raise UnicodeError, "Invalid character %s" % repr(c)
+
+ def check_unassigneds(self, string):
+ for c in string:
+ if stringprep.in_table_a1(c):
+ raise UnicodeError, "Unassigned code point %s" % repr(c)
+
+ def check_bidirectionals(self, string):
+ found_LCat = False
+ found_RandALCat = False
+
+ for c in string:
+ if stringprep.in_table_d1(c):
+ found_RandALCat = True
+ if stringprep.in_table_d2(c):
+ found_LCat = True
+
+ if found_LCat and found_RandALCat:
+ raise UnicodeError, "Violation of BIDI Requirement 2"
+
+ if found_RandALCat and not (stringprep.in_table_d1(string[0]) and
+ stringprep.in_table_d1(string[-1])):
+ raise UnicodeError, "Violation of BIDI Requirement 3"
class NamePrep:
- """
- Implements preparation of internationalized domain names
-
- This class implements preparing internationalized domain names using the
- rules defined in RFC 3491, section 4 (Conversion operations).
-
- We do not perform step 4 since we deal with unicode representations of
- domain names and do not convert from or to ASCII representations using
- punycode encoding. When such a conversion is needed, the L{idna} standard
- library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that
- L{idna} itself assumes UseSTD3ASCIIRules to be false.
-
- The following steps are performed by C{prepare()}:
-
- * Split the domain name in labels at the dots (RFC 3490, 3.1)
- * Apply nameprep proper on each label (RFC 3491)
- * Enforce the restrictions on ASCII characters in host names by
- assuming STD3ASCIIRules to be true. (STD 3)
- * Rejoin the labels using the label separator U+002E (full stop).
- """
-
- # Prohibited characters.
- prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) +
- range(0x2e, 0x2f + 1) +
- range(0x3a, 0x40 + 1) +
- range(0x5b, 0x60 + 1) +
- range(0x7b, 0x7f + 1) ]
-
- def prepare(self, string):
- result = []
-
- labels = idna.dots.split(string)
-
- if labels and len(labels[-1]) == 0:
- trailing_dot = '.'
- del labels[-1]
- else:
- trailing_dot = ''
-
- for label in labels:
- result.append(self.nameprep(label))
-
- return ".".join(result)+trailing_dot
-
- def check_prohibiteds(self, string):
- for c in string:
- if c in self.prohibiteds:
- raise UnicodeError, "Invalid character %s" % repr(c)
-
- def nameprep(self, label):
- label = idna.nameprep(label)
- self.check_prohibiteds(label)
- if label[0] == '-':
- raise UnicodeError, "Invalid leading hyphen-minus"
- if label[-1] == '-':
- raise UnicodeError, "Invalid trailing hyphen-minus"
- return label
+ """
+ Implements preparation of internationalized domain names
+
+ This class implements preparing internationalized domain names using the
+ rules defined in RFC 3491, section 4 (Conversion operations).
+
+ We do not perform step 4 since we deal with unicode representations of
+ domain names and do not convert from or to ASCII representations using
+ punycode encoding. When such a conversion is needed, the L{idna} standard
+ library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that
+ L{idna} itself assumes UseSTD3ASCIIRules to be false.
+
+ The following steps are performed by C{prepare()}:
+
+ * Split the domain name in labels at the dots (RFC 3490, 3.1)
+ * Apply nameprep proper on each label (RFC 3491)
+ * Enforce the restrictions on ASCII characters in host names by
+ assuming STD3ASCIIRules to be true. (STD 3)
+ * Rejoin the labels using the label separator U+002E (full stop).
+ """
+
+ # Prohibited characters.
+ prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) +
+ range(0x2e, 0x2f + 1) +
+ range(0x3a, 0x40 + 1) +
+ range(0x5b, 0x60 + 1) +
+ range(0x7b, 0x7f + 1) ]
+
+ def prepare(self, string):
+ result = []
+
+ labels = idna.dots.split(string)
+
+ if labels and len(labels[-1]) == 0:
+ trailing_dot = '.'
+ del labels[-1]
+ else:
+ trailing_dot = ''
+
+ for label in labels:
+ result.append(self.nameprep(label))
+
+ return ".".join(result)+trailing_dot
+
+ def check_prohibiteds(self, string):
+ for c in string:
+ if c in self.prohibiteds:
+ raise UnicodeError, "Invalid character %s" % repr(c)
+
+ def nameprep(self, label):
+ label = idna.nameprep(label)
+ self.check_prohibiteds(label)
+ if label[0] == '-':
+ raise UnicodeError, "Invalid leading hyphen-minus"
+ if label[-1] == '-':
+ raise UnicodeError, "Invalid trailing hyphen-minus"
+ return label
C_11 = LookupTableFromFunction(stringprep.in_table_c11)
C_12 = LookupTableFromFunction(stringprep.in_table_c12)
@@ -224,15 +224,13 @@ B_1 = EmptyMappingTable(stringprep.in_table_b1)
B_2 = MappingTableFromFunction(stringprep.map_table_b2)
nodeprep = Profile(mappings=[B_1, B_2],
- prohibiteds=[C_11, C_12, C_21, C_22,
- C_3, C_4, C_5, C_6, C_7, C_8, C_9,
- LookupTable([u'"', u'&', u"'", u'/',
- u':', u'<', u'>', u'@'])])
+ prohibiteds=[C_11, C_12, C_21, C_22,
+ C_3, C_4, C_5, C_6, C_7, C_8, C_9,
+ LookupTable([u'"', u'&', u"'", u'/',
+ u':', u'<', u'>', u'@'])])
resourceprep = Profile(mappings=[B_1,],
- prohibiteds=[C_12, C_21, C_22,
- C_3, C_4, C_5, C_6, C_7, C_8, C_9])
+ prohibiteds=[C_12, C_21, C_22,
+ C_3, C_4, C_5, C_6, C_7, C_8, C_9])
nameprep = NamePrep()
-
-# vim: se ts=3:
diff --git a/src/common/xmpp/tls_nb.py b/src/common/xmpp/tls_nb.py
index 8706e7fb7..9c54eb07d 100644
--- a/src/common/xmpp/tls_nb.py
+++ b/src/common/xmpp/tls_nb.py
@@ -33,390 +33,388 @@ PYOPENSSL = 'PYOPENSSL'
PYSTDLIB = 'PYSTDLIB'
try:
- #raise ImportError("Manually disabled PyOpenSSL")
- import OpenSSL.SSL
- import OpenSSL.crypto
- USE_PYOPENSSL = True
- log.info("PyOpenSSL loaded")
+ #raise ImportError("Manually disabled PyOpenSSL")
+ import OpenSSL.SSL
+ import OpenSSL.crypto
+ USE_PYOPENSSL = True
+ log.info("PyOpenSSL loaded")
except ImportError:
- log.debug("Import of PyOpenSSL failed:", exc_info=True)
+ log.debug("Import of PyOpenSSL failed:", exc_info=True)
- # FIXME: Remove these prints before release, replace with a warning dialog.
- print >> sys.stderr, "=" * 79
- print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
- print >> sys.stderr, "=" * 79
+ # FIXME: Remove these prints before release, replace with a warning dialog.
+ print >> sys.stderr, "=" * 79
+ print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)."
+ print >> sys.stderr, "=" * 79
def gattr(obj, attr, default=None):
- try:
- return getattr(obj, attr)
- except AttributeError:
- return default
+ try:
+ return getattr(obj, attr)
+ except AttributeError:
+ return default
class SSLWrapper:
- """
- Abstract SSLWrapper base class
- """
-
- class Error(IOError):
- """
- Generic SSL Error Wrapper
- """
-
- def __init__(self, sock=None, exc=None, errno=None, strerror=None,
- peer=None):
- self.parent = IOError
-
- errno = errno or gattr(exc, 'errno') or exc[0]
- strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
- if not isinstance(strerror, basestring):
- strerror = repr(strerror)
-
- self.sock = sock
- self.exc = exc
- self.peer = peer
- self.exc_name = None
- self.exc_args = None
- self.exc_str = None
- self.exc_repr = None
-
- if self.exc is not None:
- self.exc_name = str(self.exc.__class__)
- self.exc_args = gattr(self.exc, 'args')
- self.exc_str = str(self.exc)
- self.exc_repr = repr(self.exc)
- if not errno:
- try:
- if isinstance(exc, OpenSSL.SSL.SysCallError):
- if self.exc_args[0] > 0:
- errno = self.exc_args[0]
- strerror = self.exc_args[1]
- except: pass
-
- self.parent.__init__(self, errno, strerror)
-
- if self.peer is None and sock is not None:
- try:
- ppeer = self.sock.getpeername()
- if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
- and isinstance(ppeer[1], int):
- self.peer = ppeer
- except:
- pass
-
- def __str__(self):
- s = str(self.__class__)
- if self.peer:
- s += " for %s:%d" % self.peer
- if self.errno is not None:
- s += ": [Errno: %d]" % self.errno
- if self.strerror:
- s += " (%s)" % self.strerror
- if self.exc_name:
- s += ", Caused by %s" % self.exc_name
- if self.exc_str:
- if self.strerror:
- s += "(%s)" % self.exc_str
- else: s += "(%s)" % str(self.exc_args)
- return s
-
- def __init__(self, sslobj, sock=None):
- self.sslobj = sslobj
- self.sock = sock
- log.debug("%s.__init__ called with %s", self.__class__, sslobj)
-
- def recv(self, data, flags=None):
- """
- Receive wrapper for SSL object
-
- We can return None out of this function to signal that no data is
- available right now. Better than an exception, which differs
- depending on which SSL lib we're using. Unfortunately returning ''
- can indicate that the socket has been closed, so to be sure, we avoid
- this by returning None.
- """
- raise NotImplementedError
-
- def send(self, data, flags=None, now=False):
- """
- Send wrapper for SSL object
- """
- raise NotImplementedError
+ """
+ Abstract SSLWrapper base class
+ """
+
+ class Error(IOError):
+ """
+ Generic SSL Error Wrapper
+ """
+
+ def __init__(self, sock=None, exc=None, errno=None, strerror=None,
+ peer=None):
+ self.parent = IOError
+
+ errno = errno or gattr(exc, 'errno') or exc[0]
+ strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args')
+ if not isinstance(strerror, basestring):
+ strerror = repr(strerror)
+
+ self.sock = sock
+ self.exc = exc
+ self.peer = peer
+ self.exc_name = None
+ self.exc_args = None
+ self.exc_str = None
+ self.exc_repr = None
+
+ if self.exc is not None:
+ self.exc_name = str(self.exc.__class__)
+ self.exc_args = gattr(self.exc, 'args')
+ self.exc_str = str(self.exc)
+ self.exc_repr = repr(self.exc)
+ if not errno:
+ try:
+ if isinstance(exc, OpenSSL.SSL.SysCallError):
+ if self.exc_args[0] > 0:
+ errno = self.exc_args[0]
+ strerror = self.exc_args[1]
+ except: pass
+
+ self.parent.__init__(self, errno, strerror)
+
+ if self.peer is None and sock is not None:
+ try:
+ ppeer = self.sock.getpeername()
+ if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \
+ and isinstance(ppeer[1], int):
+ self.peer = ppeer
+ except:
+ pass
+
+ def __str__(self):
+ s = str(self.__class__)
+ if self.peer:
+ s += " for %s:%d" % self.peer
+ if self.errno is not None:
+ s += ": [Errno: %d]" % self.errno
+ if self.strerror:
+ s += " (%s)" % self.strerror
+ if self.exc_name:
+ s += ", Caused by %s" % self.exc_name
+ if self.exc_str:
+ if self.strerror:
+ s += "(%s)" % self.exc_str
+ else: s += "(%s)" % str(self.exc_args)
+ return s
+
+ def __init__(self, sslobj, sock=None):
+ self.sslobj = sslobj
+ self.sock = sock
+ log.debug("%s.__init__ called with %s", self.__class__, sslobj)
+
+ def recv(self, data, flags=None):
+ """
+ Receive wrapper for SSL object
+
+ We can return None out of this function to signal that no data is
+ available right now. Better than an exception, which differs
+ depending on which SSL lib we're using. Unfortunately returning ''
+ can indicate that the socket has been closed, so to be sure, we avoid
+ this by returning None.
+ """
+ raise NotImplementedError
+
+ def send(self, data, flags=None, now=False):
+ """
+ Send wrapper for SSL object
+ """
+ raise NotImplementedError
class PyOpenSSLWrapper(SSLWrapper):
- """
- Wrapper class for PyOpenSSL's recv() and send() methods
- """
-
- def __init__(self, *args):
- self.parent = SSLWrapper
- self.parent.__init__(self, *args)
-
- def is_numtoolarge(self, e):
- ''' Magic methods don't need documentation '''
- t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large')
- return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and
- isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and
- e.args[0][0] == e.args[0][1] == t)
-
- def recv(self, bufsize, flags=None):
- retval = None
- try:
- if flags is None:
- retval = self.sslobj.recv(bufsize)
- else:
- retval = self.sslobj.recv(bufsize, flags)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
- log.debug("Recv: Want-error: " + repr(e))
- except OpenSSL.SSL.SysCallError, e:
- log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e),
- exc_info=True)
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- except OpenSSL.SSL.ZeroReturnError, e:
- # end-of-connection raises ZeroReturnError instead of having the
- # connection's .recv() method return a zero-sized result.
- raise SSLWrapper.Error(self.sock or self.sslobj, e, -1)
- except OpenSSL.SSL.Error, e:
- if self.is_numtoolarge(e):
- # warn, but ignore this exception
- log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
- else:
- log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return retval
-
- def send(self, data, flags=None, now=False):
- try:
- if flags is None:
- return self.sslobj.send(data)
- else:
- return self.sslobj.send(data, flags)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
- #log.debug("Send: " + repr(e))
- time.sleep(0.1) # prevent 100% CPU usage
- except OpenSSL.SSL.SysCallError, e:
- log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e),
- exc_info=True)
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- except OpenSSL.SSL.Error, e:
- if self.is_numtoolarge(e):
- # warn, but ignore this exception
- log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
- else:
- log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return 0
+ """
+ Wrapper class for PyOpenSSL's recv() and send() methods
+ """
+
+ def __init__(self, *args):
+ self.parent = SSLWrapper
+ self.parent.__init__(self, *args)
+
+ def is_numtoolarge(self, e):
+ ''' Magic methods don't need documentation '''
+ t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large')
+ return (isinstance(e.args, (list, tuple)) and len(e.args) == 1 and
+ isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and
+ e.args[0][0] == e.args[0][1] == t)
+
+ def recv(self, bufsize, flags=None):
+ retval = None
+ try:
+ if flags is None:
+ retval = self.sslobj.recv(bufsize)
+ else:
+ retval = self.sslobj.recv(bufsize, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ log.debug("Recv: Want-error: " + repr(e))
+ except OpenSSL.SSL.SysCallError, e:
+ log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e),
+ exc_info=True)
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ except OpenSSL.SSL.ZeroReturnError, e:
+ # end-of-connection raises ZeroReturnError instead of having the
+ # connection's .recv() method return a zero-sized result.
+ raise SSLWrapper.Error(self.sock or self.sslobj, e, -1)
+ except OpenSSL.SSL.Error, e:
+ if self.is_numtoolarge(e):
+ # warn, but ignore this exception
+ log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)")
+ else:
+ log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True)
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return retval
+
+ def send(self, data, flags=None, now=False):
+ try:
+ if flags is None:
+ return self.sslobj.send(data)
+ else:
+ return self.sslobj.send(data, flags)
+ except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e:
+ #log.debug("Send: " + repr(e))
+ time.sleep(0.1) # prevent 100% CPU usage
+ except OpenSSL.SSL.SysCallError, e:
+ log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e),
+ exc_info=True)
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ except OpenSSL.SSL.Error, e:
+ if self.is_numtoolarge(e):
+ # warn, but ignore this exception
+ log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)")
+ else:
+ log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True)
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return 0
class StdlibSSLWrapper(SSLWrapper):
- """
- Wrapper class for Python socket.ssl read() and write() methods
- """
-
- def __init__(self, *args):
- self.parent = SSLWrapper
- self.parent.__init__(self, *args)
-
- def recv(self, bufsize, flags=None):
- # we simply ignore flags since ssl object doesn't support it
- try:
- return self.sslobj.read(bufsize)
- except socket.sslerror, e:
- log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True)
- if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return None
-
- def send(self, data, flags=None, now=False):
- # we simply ignore flags since ssl object doesn't support it
- try:
- return self.sslobj.write(data)
- except socket.sslerror, e:
- log.debug("Send: Caught socket.sslerror:", exc_info=True)
- if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
- raise SSLWrapper.Error(self.sock or self.sslobj, e)
- return 0
+ """
+ Wrapper class for Python socket.ssl read() and write() methods
+ """
+
+ def __init__(self, *args):
+ self.parent = SSLWrapper
+ self.parent.__init__(self, *args)
+
+ def recv(self, bufsize, flags=None):
+ # we simply ignore flags since ssl object doesn't support it
+ try:
+ return self.sslobj.read(bufsize)
+ except socket.sslerror, e:
+ log.debug("Recv: Caught socket.sslerror: " + repr(e), exc_info=True)
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return None
+
+ def send(self, data, flags=None, now=False):
+ # we simply ignore flags since ssl object doesn't support it
+ try:
+ return self.sslobj.write(data)
+ except socket.sslerror, e:
+ log.debug("Send: Caught socket.sslerror:", exc_info=True)
+ if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ raise SSLWrapper.Error(self.sock or self.sslobj, e)
+ return 0
class NonBlockingTLS(PlugIn):
- """
- TLS connection used to encrypts already estabilished tcp connection
-
- Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or
- PyOpenSSLWrapper.
- """
-
- def __init__(self, cacerts, mycerts):
- """
- :param cacerts: path to pem file with certificates of known XMPP servers
- :param mycerts: path to pem file with certificates of user trusted servers
- """
- PlugIn.__init__(self)
- self.cacerts = cacerts
- self.mycerts = mycerts
-
- # from ssl.h (partial extract)
- ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
- "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
- "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08,
- "SSL_CB_ALERT": 0x4000,
- "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
-
- def plugin(self, owner):
- """
- Use to PlugIn TLS into transport and start establishing immediately.
- Returns True if TLS/SSL was established correctly, otherwise False
- """
- log.info('Starting TLS estabilishing')
- try:
- res = self._startSSL()
- except Exception, e:
- log.error("PlugIn: while trying _startSSL():", exc_info=True)
- return False
- return res
-
- def _dumpX509(self, cert, stream=sys.stderr):
- print >> stream, "Digest (SHA-1):", cert.digest("sha1")
- print >> stream, "Digest (MD5):", cert.digest("md5")
- print >> stream, "Serial #:", cert.get_serial_number()
- print >> stream, "Version:", cert.get_version()
- print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No")
- print >> stream, "Subject:"
- self._dumpX509Name(cert.get_subject(), stream)
- print >> stream, "Issuer:"
- self._dumpX509Name(cert.get_issuer(), stream)
- self._dumpPKey(cert.get_pubkey(), stream)
-
- def _dumpX509Name(self, name, stream=sys.stderr):
- print >> stream, "X509Name:", str(name)
-
- def _dumpPKey(self, pkey, stream=sys.stderr):
- typedict = {OpenSSL.crypto.TYPE_RSA: "RSA",
- OpenSSL.crypto.TYPE_DSA: "DSA"}
- print >> stream, "PKey bits:", pkey.bits()
- print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(),
- "Unknown"), pkey.type())
-
- def _startSSL(self):
- """
- Immediatedly switch socket to TLS mode. Used internally
- """
- log.debug("_startSSL called")
-
- if USE_PYOPENSSL:
- result = self._startSSL_pyOpenSSL()
- else:
- result = self._startSSL_stdlib()
-
- if result:
- log.debug('Synchronous handshake completed')
- return True
- else:
- return False
-
- def _load_cert_file(self, cert_path, cert_store, logg=True):
- if not os.path.isfile(cert_path):
- return
- try:
- f = open(cert_path)
- except IOError, e:
- log.warning('Unable to open certificate file %s: %s' % \
- (cert_path, str(e)))
- return
- lines = f.readlines()
- i = 0
- begin = -1
- for line in lines:
- if 'BEGIN CERTIFICATE' in line:
- begin = i
- elif 'END CERTIFICATE' in line and begin > -1:
- cert = ''.join(lines[begin:i+2])
- try:
- x509cert = OpenSSL.crypto.load_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- cert_store.add_cert(x509cert)
- except OpenSSL.crypto.Error, exception_obj:
- if logg:
- log.warning('Unable to load a certificate from file %s: %s' %\
- (cert_path, exception_obj.args[0][0][2]))
- except:
- log.warning('Unknown error while loading certificate from file '
- '%s' % cert_path)
- begin = -1
- i += 1
-
- def _startSSL_pyOpenSSL(self):
- log.debug("_startSSL_pyOpenSSL called")
- tcpsock = self._owner
- # See http://docs.python.org/dev/library/ssl.html
- tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
- tcpsock.ssl_errnum = 0
- tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER,
- self._ssl_verify_callback)
- try:
- tcpsock._sslContext.load_verify_locations(self.cacerts)
- except:
- log.warning('Unable to load SSL certificates from file %s' % \
- os.path.abspath(self.cacerts))
- store = tcpsock._sslContext.get_cert_store()
- self._load_cert_file(self.mycerts, store)
- if os.path.isdir('/etc/ssl/certs'):
- for f in os.listdir('/etc/ssl/certs'):
- # We don't logg because there is a lot a duplicated certs in this
- # folder
- self._load_cert_file(os.path.join('/etc/ssl/certs', f), store,
- logg=False)
-
- tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext,
- tcpsock._sock)
- tcpsock._sslObj.set_connect_state() # set to client mode
- wrapper = PyOpenSSLWrapper(tcpsock._sslObj)
- tcpsock._recv = wrapper.recv
- tcpsock._send = wrapper.send
-
- log.debug("Initiating handshake...")
- tcpsock._sslObj.setblocking(True)
- try:
- tcpsock._sslObj.do_handshake()
- except:
- log.error('Error while TLS handshake: ', exc_info=True)
- return False
- tcpsock._sslObj.setblocking(False)
- self._owner.ssl_lib = PYOPENSSL
- return True
-
- def _startSSL_stdlib(self):
- log.debug("_startSSL_stdlib called")
- tcpsock=self._owner
- try:
- tcpsock._sock.setblocking(True)
- tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
- tcpsock._sock.setblocking(False)
- tcpsock._sslIssuer = tcpsock._sslObj.issuer()
- tcpsock._sslServer = tcpsock._sslObj.server()
- wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
- tcpsock._recv = wrapper.recv
- tcpsock._send = wrapper.send
- except:
- log.error("Exception caught in _startSSL_stdlib:", exc_info=True)
- return False
- self._owner.ssl_lib = PYSTDLIB
- return True
-
- def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
- # Exceptions can't propagate up through this callback, so print them here.
- try:
- self._owner.ssl_fingerprint_sha1 = cert.digest('sha1')
- if errnum == 0:
- return True
- self._owner.ssl_errnum = errnum
- self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- return True
- except:
- log.error("Exception caught in _ssl_info_callback:", exc_info=True)
- # Make sure something is printed, even if log is disabled.
- traceback.print_exc()
-
-# vim: se ts=3:
+ """
+ TLS connection used to encrypts already estabilished tcp connection
+
+ Can be plugged into NonBlockingTCP and will make use of StdlibSSLWrapper or
+ PyOpenSSLWrapper.
+ """
+
+ def __init__(self, cacerts, mycerts):
+ """
+ :param cacerts: path to pem file with certificates of known XMPP servers
+ :param mycerts: path to pem file with certificates of user trusted servers
+ """
+ PlugIn.__init__(self)
+ self.cacerts = cacerts
+ self.mycerts = mycerts
+
+ # from ssl.h (partial extract)
+ ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000,
+ "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02,
+ "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08,
+ "SSL_CB_ALERT": 0x4000,
+ "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20}
+
+ def plugin(self, owner):
+ """
+ Use to PlugIn TLS into transport and start establishing immediately.
+ Returns True if TLS/SSL was established correctly, otherwise False
+ """
+ log.info('Starting TLS estabilishing')
+ try:
+ res = self._startSSL()
+ except Exception, e:
+ log.error("PlugIn: while trying _startSSL():", exc_info=True)
+ return False
+ return res
+
+ def _dumpX509(self, cert, stream=sys.stderr):
+ print >> stream, "Digest (SHA-1):", cert.digest("sha1")
+ print >> stream, "Digest (MD5):", cert.digest("md5")
+ print >> stream, "Serial #:", cert.get_serial_number()
+ print >> stream, "Version:", cert.get_version()
+ print >> stream, "Expired:", ("Yes" if cert.has_expired() else "No")
+ print >> stream, "Subject:"
+ self._dumpX509Name(cert.get_subject(), stream)
+ print >> stream, "Issuer:"
+ self._dumpX509Name(cert.get_issuer(), stream)
+ self._dumpPKey(cert.get_pubkey(), stream)
+
+ def _dumpX509Name(self, name, stream=sys.stderr):
+ print >> stream, "X509Name:", str(name)
+
+ def _dumpPKey(self, pkey, stream=sys.stderr):
+ typedict = {OpenSSL.crypto.TYPE_RSA: "RSA",
+ OpenSSL.crypto.TYPE_DSA: "DSA"}
+ print >> stream, "PKey bits:", pkey.bits()
+ print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(),
+ "Unknown"), pkey.type())
+
+ def _startSSL(self):
+ """
+ Immediatedly switch socket to TLS mode. Used internally
+ """
+ log.debug("_startSSL called")
+
+ if USE_PYOPENSSL:
+ result = self._startSSL_pyOpenSSL()
+ else:
+ result = self._startSSL_stdlib()
+
+ if result:
+ log.debug('Synchronous handshake completed')
+ return True
+ else:
+ return False
+
+ def _load_cert_file(self, cert_path, cert_store, logg=True):
+ if not os.path.isfile(cert_path):
+ return
+ try:
+ f = open(cert_path)
+ except IOError, e:
+ log.warning('Unable to open certificate file %s: %s' % \
+ (cert_path, str(e)))
+ return
+ lines = f.readlines()
+ i = 0
+ begin = -1
+ for line in lines:
+ if 'BEGIN CERTIFICATE' in line:
+ begin = i
+ elif 'END CERTIFICATE' in line and begin > -1:
+ cert = ''.join(lines[begin:i+2])
+ try:
+ x509cert = OpenSSL.crypto.load_certificate(
+ OpenSSL.crypto.FILETYPE_PEM, cert)
+ cert_store.add_cert(x509cert)
+ except OpenSSL.crypto.Error, exception_obj:
+ if logg:
+ log.warning('Unable to load a certificate from file %s: %s' %\
+ (cert_path, exception_obj.args[0][0][2]))
+ except:
+ log.warning('Unknown error while loading certificate from file '
+ '%s' % cert_path)
+ begin = -1
+ i += 1
+
+ def _startSSL_pyOpenSSL(self):
+ log.debug("_startSSL_pyOpenSSL called")
+ tcpsock = self._owner
+ # See http://docs.python.org/dev/library/ssl.html
+ tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+ tcpsock.ssl_errnum = 0
+ tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER,
+ self._ssl_verify_callback)
+ try:
+ tcpsock._sslContext.load_verify_locations(self.cacerts)
+ except:
+ log.warning('Unable to load SSL certificates from file %s' % \
+ os.path.abspath(self.cacerts))
+ store = tcpsock._sslContext.get_cert_store()
+ self._load_cert_file(self.mycerts, store)
+ if os.path.isdir('/etc/ssl/certs'):
+ for f in os.listdir('/etc/ssl/certs'):
+ # We don't logg because there is a lot a duplicated certs in this
+ # folder
+ self._load_cert_file(os.path.join('/etc/ssl/certs', f), store,
+ logg=False)
+
+ tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext,
+ tcpsock._sock)
+ tcpsock._sslObj.set_connect_state() # set to client mode
+ wrapper = PyOpenSSLWrapper(tcpsock._sslObj)
+ tcpsock._recv = wrapper.recv
+ tcpsock._send = wrapper.send
+
+ log.debug("Initiating handshake...")
+ tcpsock._sslObj.setblocking(True)
+ try:
+ tcpsock._sslObj.do_handshake()
+ except:
+ log.error('Error while TLS handshake: ', exc_info=True)
+ return False
+ tcpsock._sslObj.setblocking(False)
+ self._owner.ssl_lib = PYOPENSSL
+ return True
+
+ def _startSSL_stdlib(self):
+ log.debug("_startSSL_stdlib called")
+ tcpsock=self._owner
+ try:
+ tcpsock._sock.setblocking(True)
+ tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
+ tcpsock._sock.setblocking(False)
+ tcpsock._sslIssuer = tcpsock._sslObj.issuer()
+ tcpsock._sslServer = tcpsock._sslObj.server()
+ wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock)
+ tcpsock._recv = wrapper.recv
+ tcpsock._send = wrapper.send
+ except:
+ log.error("Exception caught in _startSSL_stdlib:", exc_info=True)
+ return False
+ self._owner.ssl_lib = PYSTDLIB
+ return True
+
+ def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok):
+ # Exceptions can't propagate up through this callback, so print them here.
+ try:
+ self._owner.ssl_fingerprint_sha1 = cert.digest('sha1')
+ if errnum == 0:
+ return True
+ self._owner.ssl_errnum = errnum
+ self._owner.ssl_cert_pem = OpenSSL.crypto.dump_certificate(
+ OpenSSL.crypto.FILETYPE_PEM, cert)
+ return True
+ except:
+ log.error("Exception caught in _ssl_info_callback:", exc_info=True)
+ # Make sure something is printed, even if log is disabled.
+ traceback.print_exc()
diff --git a/src/common/xmpp/transports_nb.py b/src/common/xmpp/transports_nb.py
index dfe9870c6..e8b57e41d 100644
--- a/src/common/xmpp/transports_nb.py
+++ b/src/common/xmpp/transports_nb.py
@@ -41,40 +41,40 @@ import logging
log = logging.getLogger('gajim.c.x.transports_nb')
def urisplit(uri):
- """
- Function for splitting URI string to tuple (protocol, host, port, path).
- e.g. urisplit('http://httpcm.jabber.org:123/webclient') returns ('http',
- 'httpcm.jabber.org', 123, '/webclient') return 443 as default port if proto
- is https else 80
- """
- splitted = urlparse.urlsplit(uri)
- proto, host, path = splitted.scheme, splitted.hostname, splitted.path
- try:
- port = splitted.port
- except ValueError:
- log.warn('port cannot be extracted from BOSH URL %s, using default port' \
- % uri)
- port = ''
- if not port:
- if proto == 'https':
- port = 443
- else:
- port = 80
- return proto, host, port, path
+ """
+ Function for splitting URI string to tuple (protocol, host, port, path).
+ e.g. urisplit('http://httpcm.jabber.org:123/webclient') returns ('http',
+ 'httpcm.jabber.org', 123, '/webclient') return 443 as default port if proto
+ is https else 80
+ """
+ splitted = urlparse.urlsplit(uri)
+ proto, host, path = splitted.scheme, splitted.hostname, splitted.path
+ try:
+ port = splitted.port
+ except ValueError:
+ log.warn('port cannot be extracted from BOSH URL %s, using default port' \
+ % uri)
+ port = ''
+ if not port:
+ if proto == 'https':
+ port = 443
+ else:
+ port = 80
+ return proto, host, port, path
def get_proxy_data_from_dict(proxy):
- tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None
- proxy_type = proxy['type']
- if proxy_type == 'bosh' and not proxy['bosh_useproxy']:
- # with BOSH not over proxy we have to parse the hostname from BOSH URI
- proto, tcp_host, tcp_port, path = urisplit(proxy['bosh_uri'])
- else:
- # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy
- # machine
- tcp_host, tcp_port = proxy['host'], proxy['port']
- if proxy.get('useauth', False):
- proxy_user, proxy_pass = proxy['user'], proxy['pass']
- return tcp_host, tcp_port, proxy_user, proxy_pass
+ tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None
+ proxy_type = proxy['type']
+ if proxy_type == 'bosh' and not proxy['bosh_useproxy']:
+ # with BOSH not over proxy we have to parse the hostname from BOSH URI
+ proto, tcp_host, tcp_port, path = urisplit(proxy['bosh_uri'])
+ else:
+ # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy
+ # machine
+ tcp_host, tcp_port = proxy['host'], proxy['port']
+ if proxy.get('useauth', False):
+ proxy_user, proxy_pass = proxy['user'], proxy['pass']
+ return tcp_host, tcp_port, proxy_user, proxy_pass
#: timeout to connect to the server socket, it doesn't include auth
CONNECT_TIMEOUT_SECONDS = 30
@@ -102,686 +102,684 @@ CONNECTED = 'CONNECTED'
STATES = (DISCONNECTED, CONNECTING, PROXY_CONNECTING, CONNECTED, DISCONNECTING)
class NonBlockingTransport(PlugIn):
- """
- Abstract class representing a transport
-
- Subclasses CAN have different constructor signature but connect method SHOULD
- be the same.
- """
-
- def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
- certs):
- """
- Each trasport class can have different constructor but it has to have at
- least all the arguments of NonBlockingTransport constructor
-
- :param raise_event: callback for monitoring of sent and received data
- :param on_disconnect: callback called on disconnection during runtime
- :param idlequeue: processing idlequeue
- :param estabilish_tls: boolean whether to estabilish TLS connection after
- TCP connection is done
- :param certs: tuple of (cacerts, mycerts) see constructor of
- tls_nb.NonBlockingTLS for more details
- """
- PlugIn.__init__(self)
- self.raise_event = raise_event
- self.on_disconnect = on_disconnect
- self.on_connect = None
- self.on_connect_failure = None
- self.idlequeue = idlequeue
- self.on_receive = None
- self.server = None
- self.port = None
- self.conn_5tuple = None
- self.set_state(DISCONNECTED)
- self.estabilish_tls = estabilish_tls
- self.certs = certs
- # type of used ssl lib (if any) will be assigned to this member var
- self.ssl_lib = None
- self._exported_methods=[self.onreceive, self.set_send_timeout,
- self.set_send_timeout2, self.set_timeout, self.remove_timeout,
- self.start_disconnect]
-
- # time to wait for SOME stanza to come and then send keepalive
- self.sendtimeout = 0
-
- # in case we want to something different than sending keepalives
- self.on_timeout = None
- self.on_timeout2 = None
-
- def plugin(self, owner):
- owner.Connection = self
-
- def plugout(self):
- self._owner.Connection = None
- self._owner = None
- self.disconnect(do_callback=False)
-
- def connect(self, conn_5tuple, on_connect, on_connect_failure):
- """
- Creates and connects transport to server and port defined in conn_5tuple
- which should be item from list returned from getaddrinfo
-
- :param conn_5tuple: 5-tuple returned from getaddrinfo
- :param on_connect: callback called on successful connect to the server
- :param on_connect_failure: callback called on failure when connecting
- """
- self.on_connect = on_connect
- self.on_connect_failure = on_connect_failure
- self.server, self.port = conn_5tuple[4][:2]
- self.conn_5tuple = conn_5tuple
-
- def set_state(self, newstate):
- assert(newstate in STATES)
- self.state = newstate
-
- def get_state(self):
- return self.state
-
- def _on_connect(self):
- """
- Preceeds call of on_connect callback
- """
- # data is reference to socket wrapper instance. We don't need it in client
- # because
- self.set_state(CONNECTED)
- self.on_connect()
-
- def _on_connect_failure(self, err_message):
- """
- Preceeds call of on_connect_failure callback
- """
- # In case of error while connecting we need to disconnect transport
- # but we don't want to call DisconnectHandlers from client,
- # thus the do_callback=False
- self.disconnect(do_callback=False)
- self.on_connect_failure(err_message=err_message)
-
- def send(self, raw_data, now=False):
- if self.get_state() == DISCONNECTED:
- log.error('Unable to send %s \n because state is %s.' %
- (raw_data, self.get_state()))
-
- def disconnect(self, do_callback=True):
- self.set_state(DISCONNECTED)
- if do_callback:
- # invoke callback given in __init__
- self.on_disconnect()
-
- def onreceive(self, recv_handler):
- """
- Set the on_receive callback.
-
- onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is
- the default one that will decide what to do with received stanza based on
- its tag name and namespace.
-
- Do not confuse it with on_receive() method, which is the callback
- itself.
- """
- if not recv_handler:
- if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'):
- self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
- else:
- log.warning('No Dispatcher plugged. Received data will not be processed')
- self.on_receive = None
- return
- self.on_receive = recv_handler
-
- def _tcp_connecting_started(self):
- self.set_state(CONNECTING)
-
- def read_timeout(self):
- """
- Called when there's no response from server in defined timeout
- """
- if self.on_timeout:
- self.on_timeout()
- self.renew_send_timeout()
-
- def read_timeout2(self):
- """
- called when there's no response from server in defined timeout
- """
- if self.on_timeout2:
- self.on_timeout2()
- self.renew_send_timeout2()
-
- def renew_send_timeout(self):
- if self.on_timeout and self.sendtimeout > 0:
- self.set_timeout(self.sendtimeout)
-
- def renew_send_timeout2(self):
- if self.on_timeout2 and self.sendtimeout2 > 0:
- self.set_timeout2(self.sendtimeout2)
-
- def set_timeout(self, timeout):
- self.idlequeue.set_read_timeout(self.fd, timeout)
-
- def set_timeout2(self, timeout2):
- self.idlequeue.set_read_timeout(self.fd, timeout2, self.read_timeout2)
-
- def get_fd(self):
- pass
-
- def remove_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
-
- def set_send_timeout(self, timeout, on_timeout):
- self.sendtimeout = timeout
- if self.sendtimeout > 0:
- self.on_timeout = on_timeout
- else:
- self.on_timeout = None
-
- def set_send_timeout2(self, timeout2, on_timeout2):
- self.sendtimeout2 = timeout2
- if self.sendtimeout2 > 0:
- self.on_timeout2 = on_timeout2
- else:
- self.on_timeout2 = None
-
- # FIXME: where and why does this need to be called
- def start_disconnect(self):
- self.set_state(DISCONNECTING)
+ """
+ Abstract class representing a transport
+
+ Subclasses CAN have different constructor signature but connect method SHOULD
+ be the same.
+ """
+
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
+ certs):
+ """
+ Each trasport class can have different constructor but it has to have at
+ least all the arguments of NonBlockingTransport constructor
+
+ :param raise_event: callback for monitoring of sent and received data
+ :param on_disconnect: callback called on disconnection during runtime
+ :param idlequeue: processing idlequeue
+ :param estabilish_tls: boolean whether to estabilish TLS connection after
+ TCP connection is done
+ :param certs: tuple of (cacerts, mycerts) see constructor of
+ tls_nb.NonBlockingTLS for more details
+ """
+ PlugIn.__init__(self)
+ self.raise_event = raise_event
+ self.on_disconnect = on_disconnect
+ self.on_connect = None
+ self.on_connect_failure = None
+ self.idlequeue = idlequeue
+ self.on_receive = None
+ self.server = None
+ self.port = None
+ self.conn_5tuple = None
+ self.set_state(DISCONNECTED)
+ self.estabilish_tls = estabilish_tls
+ self.certs = certs
+ # type of used ssl lib (if any) will be assigned to this member var
+ self.ssl_lib = None
+ self._exported_methods=[self.onreceive, self.set_send_timeout,
+ self.set_send_timeout2, self.set_timeout, self.remove_timeout,
+ self.start_disconnect]
+
+ # time to wait for SOME stanza to come and then send keepalive
+ self.sendtimeout = 0
+
+ # in case we want to something different than sending keepalives
+ self.on_timeout = None
+ self.on_timeout2 = None
+
+ def plugin(self, owner):
+ owner.Connection = self
+
+ def plugout(self):
+ self._owner.Connection = None
+ self._owner = None
+ self.disconnect(do_callback=False)
+
+ def connect(self, conn_5tuple, on_connect, on_connect_failure):
+ """
+ Creates and connects transport to server and port defined in conn_5tuple
+ which should be item from list returned from getaddrinfo
+
+ :param conn_5tuple: 5-tuple returned from getaddrinfo
+ :param on_connect: callback called on successful connect to the server
+ :param on_connect_failure: callback called on failure when connecting
+ """
+ self.on_connect = on_connect
+ self.on_connect_failure = on_connect_failure
+ self.server, self.port = conn_5tuple[4][:2]
+ self.conn_5tuple = conn_5tuple
+
+ def set_state(self, newstate):
+ assert(newstate in STATES)
+ self.state = newstate
+
+ def get_state(self):
+ return self.state
+
+ def _on_connect(self):
+ """
+ Preceeds call of on_connect callback
+ """
+ # data is reference to socket wrapper instance. We don't need it in client
+ # because
+ self.set_state(CONNECTED)
+ self.on_connect()
+
+ def _on_connect_failure(self, err_message):
+ """
+ Preceeds call of on_connect_failure callback
+ """
+ # In case of error while connecting we need to disconnect transport
+ # but we don't want to call DisconnectHandlers from client,
+ # thus the do_callback=False
+ self.disconnect(do_callback=False)
+ self.on_connect_failure(err_message=err_message)
+
+ def send(self, raw_data, now=False):
+ if self.get_state() == DISCONNECTED:
+ log.error('Unable to send %s \n because state is %s.' %
+ (raw_data, self.get_state()))
+
+ def disconnect(self, do_callback=True):
+ self.set_state(DISCONNECTED)
+ if do_callback:
+ # invoke callback given in __init__
+ self.on_disconnect()
+
+ def onreceive(self, recv_handler):
+ """
+ Set the on_receive callback.
+
+ onreceive(None) sets callback to Dispatcher.ProcessNonBlocking which is
+ the default one that will decide what to do with received stanza based on
+ its tag name and namespace.
+
+ Do not confuse it with on_receive() method, which is the callback
+ itself.
+ """
+ if not recv_handler:
+ if hasattr(self, '_owner') and hasattr(self._owner, 'Dispatcher'):
+ self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
+ else:
+ log.warning('No Dispatcher plugged. Received data will not be processed')
+ self.on_receive = None
+ return
+ self.on_receive = recv_handler
+
+ def _tcp_connecting_started(self):
+ self.set_state(CONNECTING)
+
+ def read_timeout(self):
+ """
+ Called when there's no response from server in defined timeout
+ """
+ if self.on_timeout:
+ self.on_timeout()
+ self.renew_send_timeout()
+
+ def read_timeout2(self):
+ """
+ called when there's no response from server in defined timeout
+ """
+ if self.on_timeout2:
+ self.on_timeout2()
+ self.renew_send_timeout2()
+
+ def renew_send_timeout(self):
+ if self.on_timeout and self.sendtimeout > 0:
+ self.set_timeout(self.sendtimeout)
+
+ def renew_send_timeout2(self):
+ if self.on_timeout2 and self.sendtimeout2 > 0:
+ self.set_timeout2(self.sendtimeout2)
+
+ def set_timeout(self, timeout):
+ self.idlequeue.set_read_timeout(self.fd, timeout)
+
+ def set_timeout2(self, timeout2):
+ self.idlequeue.set_read_timeout(self.fd, timeout2, self.read_timeout2)
+
+ def get_fd(self):
+ pass
+
+ def remove_timeout(self):
+ self.idlequeue.remove_timeout(self.fd)
+
+ def set_send_timeout(self, timeout, on_timeout):
+ self.sendtimeout = timeout
+ if self.sendtimeout > 0:
+ self.on_timeout = on_timeout
+ else:
+ self.on_timeout = None
+
+ def set_send_timeout2(self, timeout2, on_timeout2):
+ self.sendtimeout2 = timeout2
+ if self.sendtimeout2 > 0:
+ self.on_timeout2 = on_timeout2
+ else:
+ self.on_timeout2 = None
+
+ # FIXME: where and why does this need to be called
+ def start_disconnect(self):
+ self.set_state(DISCONNECTING)
class NonBlockingTCP(NonBlockingTransport, IdleObject):
- """
- Non-blocking TCP socket wrapper
-
- It is used for simple XMPP connection. Can be connected via proxy and can
- estabilish TLS connection.
- """
- def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
- certs, proxy_dict=None):
- """
- :param proxy_dict: dictionary with proxy data as loaded from config file
- """
- NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
- estabilish_tls, certs)
- IdleObject.__init__(self)
-
- # queue with messages to be send
- self.sendqueue = []
-
- # bytes remained from the last send message
- self.sendbuff = ''
-
- self.proxy_dict = proxy_dict
- self.on_remote_disconnect = self.disconnect
-
- # FIXME: transport should not be aware xmpp
- def start_disconnect(self):
- NonBlockingTransport.start_disconnect(self)
- self.send('</stream:stream>', now=True)
- self.disconnect()
-
- def connect(self, conn_5tuple, on_connect, on_connect_failure):
- NonBlockingTransport.connect(self, conn_5tuple, on_connect,
- on_connect_failure)
- log.info('NonBlockingTCP Connect :: About to connect to %s:%s' %
- (self.server, self.port))
-
- try:
- self._sock = socket.socket(*conn_5tuple[:3])
- except socket.error, (errnum, errstr):
- self._on_connect_failure('NonBlockingTCP Connect: Error while creating\
- socket: %s %s' % (errnum, errstr))
- return
-
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.fd = self._sock.fileno()
-
- # we want to be notified when send is possible to connected socket because
- # it means the TCP connection is estabilished
- self._plug_idle(writable=True, readable=False)
- self.peerhost = None
-
- # variable for errno symbol that will be found from exception raised
- # from connect()
- errnum = 0
-
- # set timeout for TCP connecting - if nonblocking connect() fails, pollend
- # is called. If if succeeds pollout is called.
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS)
-
- try:
- self._sock.setblocking(False)
- self._sock.connect((self.server, self.port))
- except Exception, (errnum, errstr):
- pass
-
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- # connecting in progress
- log.info('After NB connect() of %s. "%s" raised => CONNECTING' %
- (id(self), errstr))
- self._tcp_connecting_started()
- return
-
- # if there was some other exception, call failure callback and unplug
- # transport which will also remove read_timeouts for descriptor
- self._on_connect_failure('Exception while connecting to %s:%s - %s %s' %
- (self.server, self.port, errnum, errstr))
-
- def _connect_to_proxy(self):
- self.set_state(PROXY_CONNECTING)
- if self.proxy_dict['type'] == 'socks5':
- proxyclass = proxy_connectors.SOCKS5Connector
- elif self.proxy_dict['type'] == 'http' :
- proxyclass = proxy_connectors.HTTPCONNECTConnector
- proxyclass.get_instance(
- send_method=self.send,
- onreceive=self.onreceive,
- old_on_receive=self.on_receive,
- on_success=self._on_connect,
- on_failure=self._on_connect_failure,
- xmpp_server=self.proxy_dict['xmpp_server'],
- proxy_creds=self.proxy_dict['credentials'])
-
- def _on_connect(self):
- """
- Preceed invoking of on_connect callback. TCP connection is already
- estabilished by this time
- """
- if self.estabilish_tls:
- self.tls_init(
- on_succ = lambda: NonBlockingTransport._on_connect(self),
- on_fail = lambda: self._on_connect_failure(
- 'error while estabilishing TLS'))
- else:
- NonBlockingTransport._on_connect(self)
-
- def tls_init(self, on_succ, on_fail):
- """
- Estabilishes TLS/SSL using this TCP connection by plugging a
- NonBlockingTLS module
- """
- cacerts, mycerts = self.certs
- result = tls_nb.NonBlockingTLS.get_instance(cacerts, mycerts).PlugIn(self)
- if result:
- on_succ()
- else:
- on_fail()
-
- def pollin(self):
- """
- Called by idlequeu when receive on plugged socket is possible
- """
- log.info('pollin called, state == %s' % self.get_state())
- self._do_receive()
-
- def pollout(self):
- """
- Called by idlequeu when send to plugged socket is possible
- """
- log.info('pollout called, state == %s' % self.get_state())
-
- if self.get_state() == CONNECTING:
- log.info('%s socket wrapper connected' % id(self))
- self.idlequeue.remove_timeout(self.fd)
- self._plug_idle(writable=False, readable=False)
- self.peerhost = self._sock.getsockname()
- if self.proxy_dict:
- self._connect_to_proxy()
- else:
- self._on_connect()
- else:
- self._do_send()
-
- def pollend(self):
- """
- Called by idlequeue on TCP connection errors
- """
- log.info('pollend called, state == %s' % self.get_state())
-
- if self.get_state() == CONNECTING:
- self._on_connect_failure('Error during connect to %s:%s' %
- (self.server, self.port))
- else:
- self.disconnect()
-
- def disconnect(self, do_callback=True):
- if self.get_state() == DISCONNECTED:
- return
- self.set_state(DISCONNECTED)
- self.idlequeue.unplug_idle(self.fd)
- if 'NonBlockingTLS' in self.__dict__:
- self.NonBlockingTLS.PlugOut()
- try:
- self._sock.shutdown(socket.SHUT_RDWR)
- self._sock.close()
- except socket.error, (errnum, errstr):
- log.error('Error while disconnecting socket: %s' % errstr)
- self.fd = -1
- NonBlockingTransport.disconnect(self, do_callback)
-
- def read_timeout(self):
- log.info('read_timeout called, state == %s' % self.get_state())
- if self.get_state() == CONNECTING:
- # if read_timeout is called during connecting, connect() didn't end yet
- # thus we have to call the tcp failure callback
- self._on_connect_failure('Error during connect to %s:%s' %
- (self.server, self.port))
- else:
- NonBlockingTransport.read_timeout(self)
-
- def set_timeout(self, timeout):
- if self.get_state() != DISCONNECTED and self.fd != -1:
- NonBlockingTransport.set_timeout(self, timeout)
- else:
- log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' %
- (self.get_state(), self.fd))
-
- def remove_timeout(self):
- if self.fd:
- NonBlockingTransport.remove_timeout(self)
- else:
- log.warn('remove_timeout: no self.fd state is %s' % self.get_state())
-
- def send(self, raw_data, now=False):
- """
- Append raw_data to the queue of messages to be send. If supplied data is
- unicode string, encode it to utf-8.
- """
- NonBlockingTransport.send(self, raw_data, now)
-
- r = self.encode_stanza(raw_data)
-
- if now:
- self.sendqueue.insert(0, r)
- self._do_send()
- else:
- self.sendqueue.append(r)
-
- self._plug_idle(writable=True, readable=True)
-
- def encode_stanza(self, stanza):
- """
- Encode str or unicode to utf-8
- """
- if isinstance(stanza, unicode):
- stanza = stanza.encode('utf-8')
- elif not isinstance(stanza, str):
- stanza = ustr(stanza).encode('utf-8')
- return stanza
-
- def _plug_idle(self, writable, readable):
- """
- Plug file descriptor of socket to Idlequeue
-
- Plugged socket will be watched for "send possible" or/and "recv possible"
- events. pollin() callback is invoked on "recv possible", pollout() on
- "send_possible".
-
- Plugged socket will always be watched for "error" event - in that case,
- pollend() is called.
- """
- log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable))
- self.idlequeue.plug_idle(self, writable, readable)
-
- def _do_send(self):
- """
- Called when send() to connected socket is possible. First message from
- sendqueue will be sent
- """
- if not self.sendbuff:
- if not self.sendqueue:
- log.warn('calling send on empty buffer and queue')
- self._plug_idle(writable=False, readable=True)
- return None
- self.sendbuff = self.sendqueue.pop(0)
- try:
- send_count = self._send(self.sendbuff)
- if send_count:
- sent_data = self.sendbuff[:send_count]
- self.sendbuff = self.sendbuff[send_count:]
- self._plug_idle(
- writable=((self.sendqueue!=[]) or (self.sendbuff!='')),
- readable=True)
- self.raise_event(DATA_SENT, sent_data)
-
- except socket.error, e:
- log.error('_do_send:', exc_info=True)
- traceback.print_exc()
- self.disconnect()
-
- def _do_receive(self):
- """
- Reads all pending incoming data. Will call owner's disconnected() method
- if appropriate
- """
- received = None
- errnum = 0
- errstr = 'No Error Set'
-
- try:
- # get as many bites, as possible, but not more than RECV_BUFSIZE
- received = self._recv(RECV_BUFSIZE)
- except socket.error, (errnum, errstr):
- log.info("_do_receive: got %s:" % received , exc_info=True)
- except tls_nb.SSLWrapper.Error, e:
- log.info("_do_receive, caught SSL error, got %s:" % received,
- exc_info=True)
- errnum, errstr = e.errno, e.strerror
-
- if received == '':
- errstr = 'zero bytes on recv'
-
- if (self.ssl_lib is None and received == '') or \
- (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \
- (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ):
- # 8 in stdlib: errstr == EOF occured in violation of protocol
- # -1 in pyopenssl: errstr == Unexpected EOF
- log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr))
- self.on_remote_disconnect()
- return
-
- if errnum:
- log.info("Connection to %s:%s lost: %s %s" % (self.server, self.port,
- errnum, errstr), exc_info=True)
- self.disconnect()
- return
-
- # this branch is for case of non-fatal SSL errors - None is returned from
- # recv() but no errnum is set
- if received is None:
- return
-
- # we have received some bytes, stop the timeout!
- self.remove_timeout()
- self.renew_send_timeout()
- self.renew_send_timeout2()
- # pass received data to owner
- if self.on_receive:
- self.raise_event(DATA_RECEIVED, received)
- self._on_receive(received)
- else:
- # This should never happen, so we need the debug.
- # (If there is no handler on receive specified, data is passed to
- # Dispatcher.ProcessNonBlocking)
- log.error('SOCKET %s Unhandled data received: %s' % (id(self),
- received))
- self.disconnect()
-
- def _on_receive(self, data):
- """
- Preceeds on_receive callback. It peels off and checks HTTP headers in
- HTTP classes, in here it just calls the callback
- """
- self.on_receive(data)
+ """
+ Non-blocking TCP socket wrapper
+
+ It is used for simple XMPP connection. Can be connected via proxy and can
+ estabilish TLS connection.
+ """
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
+ certs, proxy_dict=None):
+ """
+ :param proxy_dict: dictionary with proxy data as loaded from config file
+ """
+ NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue,
+ estabilish_tls, certs)
+ IdleObject.__init__(self)
+
+ # queue with messages to be send
+ self.sendqueue = []
+
+ # bytes remained from the last send message
+ self.sendbuff = ''
+
+ self.proxy_dict = proxy_dict
+ self.on_remote_disconnect = self.disconnect
+
+ # FIXME: transport should not be aware xmpp
+ def start_disconnect(self):
+ NonBlockingTransport.start_disconnect(self)
+ self.send('</stream:stream>', now=True)
+ self.disconnect()
+
+ def connect(self, conn_5tuple, on_connect, on_connect_failure):
+ NonBlockingTransport.connect(self, conn_5tuple, on_connect,
+ on_connect_failure)
+ log.info('NonBlockingTCP Connect :: About to connect to %s:%s' %
+ (self.server, self.port))
+
+ try:
+ self._sock = socket.socket(*conn_5tuple[:3])
+ except socket.error, (errnum, errstr):
+ self._on_connect_failure('NonBlockingTCP Connect: Error while creating\
+ socket: %s %s' % (errnum, errstr))
+ return
+
+ self._send = self._sock.send
+ self._recv = self._sock.recv
+ self.fd = self._sock.fileno()
+
+ # we want to be notified when send is possible to connected socket because
+ # it means the TCP connection is estabilished
+ self._plug_idle(writable=True, readable=False)
+ self.peerhost = None
+
+ # variable for errno symbol that will be found from exception raised
+ # from connect()
+ errnum = 0
+
+ # set timeout for TCP connecting - if nonblocking connect() fails, pollend
+ # is called. If if succeeds pollout is called.
+ self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS)
+
+ try:
+ self._sock.setblocking(False)
+ self._sock.connect((self.server, self.port))
+ except Exception, (errnum, errstr):
+ pass
+
+ if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
+ # connecting in progress
+ log.info('After NB connect() of %s. "%s" raised => CONNECTING' %
+ (id(self), errstr))
+ self._tcp_connecting_started()
+ return
+
+ # if there was some other exception, call failure callback and unplug
+ # transport which will also remove read_timeouts for descriptor
+ self._on_connect_failure('Exception while connecting to %s:%s - %s %s' %
+ (self.server, self.port, errnum, errstr))
+
+ def _connect_to_proxy(self):
+ self.set_state(PROXY_CONNECTING)
+ if self.proxy_dict['type'] == 'socks5':
+ proxyclass = proxy_connectors.SOCKS5Connector
+ elif self.proxy_dict['type'] == 'http' :
+ proxyclass = proxy_connectors.HTTPCONNECTConnector
+ proxyclass.get_instance(
+ send_method=self.send,
+ onreceive=self.onreceive,
+ old_on_receive=self.on_receive,
+ on_success=self._on_connect,
+ on_failure=self._on_connect_failure,
+ xmpp_server=self.proxy_dict['xmpp_server'],
+ proxy_creds=self.proxy_dict['credentials'])
+
+ def _on_connect(self):
+ """
+ Preceed invoking of on_connect callback. TCP connection is already
+ estabilished by this time
+ """
+ if self.estabilish_tls:
+ self.tls_init(
+ on_succ = lambda: NonBlockingTransport._on_connect(self),
+ on_fail = lambda: self._on_connect_failure(
+ 'error while estabilishing TLS'))
+ else:
+ NonBlockingTransport._on_connect(self)
+
+ def tls_init(self, on_succ, on_fail):
+ """
+ Estabilishes TLS/SSL using this TCP connection by plugging a
+ NonBlockingTLS module
+ """
+ cacerts, mycerts = self.certs
+ result = tls_nb.NonBlockingTLS.get_instance(cacerts, mycerts).PlugIn(self)
+ if result:
+ on_succ()
+ else:
+ on_fail()
+
+ def pollin(self):
+ """
+ Called by idlequeu when receive on plugged socket is possible
+ """
+ log.info('pollin called, state == %s' % self.get_state())
+ self._do_receive()
+
+ def pollout(self):
+ """
+ Called by idlequeu when send to plugged socket is possible
+ """
+ log.info('pollout called, state == %s' % self.get_state())
+
+ if self.get_state() == CONNECTING:
+ log.info('%s socket wrapper connected' % id(self))
+ self.idlequeue.remove_timeout(self.fd)
+ self._plug_idle(writable=False, readable=False)
+ self.peerhost = self._sock.getsockname()
+ if self.proxy_dict:
+ self._connect_to_proxy()
+ else:
+ self._on_connect()
+ else:
+ self._do_send()
+
+ def pollend(self):
+ """
+ Called by idlequeue on TCP connection errors
+ """
+ log.info('pollend called, state == %s' % self.get_state())
+
+ if self.get_state() == CONNECTING:
+ self._on_connect_failure('Error during connect to %s:%s' %
+ (self.server, self.port))
+ else:
+ self.disconnect()
+
+ def disconnect(self, do_callback=True):
+ if self.get_state() == DISCONNECTED:
+ return
+ self.set_state(DISCONNECTED)
+ self.idlequeue.unplug_idle(self.fd)
+ if 'NonBlockingTLS' in self.__dict__:
+ self.NonBlockingTLS.PlugOut()
+ try:
+ self._sock.shutdown(socket.SHUT_RDWR)
+ self._sock.close()
+ except socket.error, (errnum, errstr):
+ log.error('Error while disconnecting socket: %s' % errstr)
+ self.fd = -1
+ NonBlockingTransport.disconnect(self, do_callback)
+
+ def read_timeout(self):
+ log.info('read_timeout called, state == %s' % self.get_state())
+ if self.get_state() == CONNECTING:
+ # if read_timeout is called during connecting, connect() didn't end yet
+ # thus we have to call the tcp failure callback
+ self._on_connect_failure('Error during connect to %s:%s' %
+ (self.server, self.port))
+ else:
+ NonBlockingTransport.read_timeout(self)
+
+ def set_timeout(self, timeout):
+ if self.get_state() != DISCONNECTED and self.fd != -1:
+ NonBlockingTransport.set_timeout(self, timeout)
+ else:
+ log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' %
+ (self.get_state(), self.fd))
+
+ def remove_timeout(self):
+ if self.fd:
+ NonBlockingTransport.remove_timeout(self)
+ else:
+ log.warn('remove_timeout: no self.fd state is %s' % self.get_state())
+
+ def send(self, raw_data, now=False):
+ """
+ Append raw_data to the queue of messages to be send. If supplied data is
+ unicode string, encode it to utf-8.
+ """
+ NonBlockingTransport.send(self, raw_data, now)
+
+ r = self.encode_stanza(raw_data)
+
+ if now:
+ self.sendqueue.insert(0, r)
+ self._do_send()
+ else:
+ self.sendqueue.append(r)
+
+ self._plug_idle(writable=True, readable=True)
+
+ def encode_stanza(self, stanza):
+ """
+ Encode str or unicode to utf-8
+ """
+ if isinstance(stanza, unicode):
+ stanza = stanza.encode('utf-8')
+ elif not isinstance(stanza, str):
+ stanza = ustr(stanza).encode('utf-8')
+ return stanza
+
+ def _plug_idle(self, writable, readable):
+ """
+ Plug file descriptor of socket to Idlequeue
+
+ Plugged socket will be watched for "send possible" or/and "recv possible"
+ events. pollin() callback is invoked on "recv possible", pollout() on
+ "send_possible".
+
+ Plugged socket will always be watched for "error" event - in that case,
+ pollend() is called.
+ """
+ log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable))
+ self.idlequeue.plug_idle(self, writable, readable)
+
+ def _do_send(self):
+ """
+ Called when send() to connected socket is possible. First message from
+ sendqueue will be sent
+ """
+ if not self.sendbuff:
+ if not self.sendqueue:
+ log.warn('calling send on empty buffer and queue')
+ self._plug_idle(writable=False, readable=True)
+ return None
+ self.sendbuff = self.sendqueue.pop(0)
+ try:
+ send_count = self._send(self.sendbuff)
+ if send_count:
+ sent_data = self.sendbuff[:send_count]
+ self.sendbuff = self.sendbuff[send_count:]
+ self._plug_idle(
+ writable=((self.sendqueue!=[]) or (self.sendbuff!='')),
+ readable=True)
+ self.raise_event(DATA_SENT, sent_data)
+
+ except socket.error, e:
+ log.error('_do_send:', exc_info=True)
+ traceback.print_exc()
+ self.disconnect()
+
+ def _do_receive(self):
+ """
+ Reads all pending incoming data. Will call owner's disconnected() method
+ if appropriate
+ """
+ received = None
+ errnum = 0
+ errstr = 'No Error Set'
+
+ try:
+ # get as many bites, as possible, but not more than RECV_BUFSIZE
+ received = self._recv(RECV_BUFSIZE)
+ except socket.error, (errnum, errstr):
+ log.info("_do_receive: got %s:" % received , exc_info=True)
+ except tls_nb.SSLWrapper.Error, e:
+ log.info("_do_receive, caught SSL error, got %s:" % received,
+ exc_info=True)
+ errnum, errstr = e.errno, e.strerror
+
+ if received == '':
+ errstr = 'zero bytes on recv'
+
+ if (self.ssl_lib is None and received == '') or \
+ (self.ssl_lib == tls_nb.PYSTDLIB and errnum == 8 ) or \
+ (self.ssl_lib == tls_nb.PYOPENSSL and errnum == -1 ):
+ # 8 in stdlib: errstr == EOF occured in violation of protocol
+ # -1 in pyopenssl: errstr == Unexpected EOF
+ log.info("Disconnected by remote server: #%s, %s" % (errnum, errstr))
+ self.on_remote_disconnect()
+ return
+
+ if errnum:
+ log.info("Connection to %s:%s lost: %s %s" % (self.server, self.port,
+ errnum, errstr), exc_info=True)
+ self.disconnect()
+ return
+
+ # this branch is for case of non-fatal SSL errors - None is returned from
+ # recv() but no errnum is set
+ if received is None:
+ return
+
+ # we have received some bytes, stop the timeout!
+ self.remove_timeout()
+ self.renew_send_timeout()
+ self.renew_send_timeout2()
+ # pass received data to owner
+ if self.on_receive:
+ self.raise_event(DATA_RECEIVED, received)
+ self._on_receive(received)
+ else:
+ # This should never happen, so we need the debug.
+ # (If there is no handler on receive specified, data is passed to
+ # Dispatcher.ProcessNonBlocking)
+ log.error('SOCKET %s Unhandled data received: %s' % (id(self),
+ received))
+ self.disconnect()
+
+ def _on_receive(self, data):
+ """
+ Preceeds on_receive callback. It peels off and checks HTTP headers in
+ HTTP classes, in here it just calls the callback
+ """
+ self.on_receive(data)
class NonBlockingHTTP(NonBlockingTCP):
- """
- Socket wrapper that creates HTTP message out of sent data and peels-off HTTP
- headers from incoming messages
- """
-
- def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
- certs, on_http_request_possible, on_persistent_fallback, http_dict,
- proxy_dict=None):
- """
- :param on_http_request_possible: method to call when HTTP request to
- socket owned by transport is possible.
- :param on_persistent_fallback: callback called when server ends TCP
- connection. It doesn't have to be fatal for HTTP session.
- :param http_dict: dictionary with data for HTTP request and headers
- """
- NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue,
- estabilish_tls, certs, proxy_dict)
-
- self.http_protocol, self.http_host, self.http_port, self.http_path = \
- urisplit(http_dict['http_uri'])
- self.http_protocol = self.http_protocol or 'http'
- self.http_path = self.http_path or '/'
- self.http_version = http_dict['http_version']
- self.http_persistent = http_dict['http_persistent']
- self.add_proxy_headers = http_dict['add_proxy_headers']
-
- if 'proxy_user' in http_dict and 'proxy_pass' in http_dict:
- self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict[
- 'proxy_pass']
- else:
- self.proxy_user, self.proxy_pass = None, None
-
- # buffer for partial responses
- self.recvbuff = ''
- self.expected_length = 0
- self.pending_requests = 0
- self.on_http_request_possible = on_http_request_possible
- self.last_recv_time = 0
- self.close_current_connection = False
- self.on_remote_disconnect = lambda: on_persistent_fallback(self)
-
- def http_send(self, raw_data, now=False):
- self.send(self.build_http_message(raw_data), now)
-
- def _on_receive(self, data):
- """
- Preceeds passing received data to owner class. Gets rid of HTTP headers
- and checks them.
- """
- if self.get_state() == PROXY_CONNECTING:
- NonBlockingTCP._on_receive(self, data)
- return
-
- # append currently received data to HTTP msg in buffer
- self.recvbuff = '%s%s' % (self.recvbuff or '', data)
- statusline, headers, httpbody, buffer_rest = self.parse_http_message(
- self.recvbuff)
-
- if not (statusline and headers and httpbody):
- log.debug('Received incomplete HTTP response')
- return
-
- if statusline[1] != '200':
- log.error('HTTP Error: %s %s' % (statusline[1], statusline[2]))
- self.disconnect()
- return
- self.expected_length = int(headers['Content-Length'])
- if 'Connection' in headers and headers['Connection'].strip()=='close':
- self.close_current_connection = True
-
- if self.expected_length > len(httpbody):
- # If we haven't received the whole HTTP mess yet, let's end the thread.
- # It will be finnished from one of following recvs on plugged socket.
- log.info('not enough bytes in HTTP response - %d expected, got %d' %
- (self.expected_length, len(httpbody)))
- else:
- # First part of buffer has been extraced and is going to be handled,
- # remove it from buffer
- self.recvbuff = buffer_rest
-
- # everything was received
- self.expected_length = 0
-
- if not self.http_persistent or self.close_current_connection:
- # not-persistent connections disconnect after response
- self.disconnect(do_callback=False)
- self.close_current_connection = False
- self.last_recv_time = time.time()
- self.on_receive(data=httpbody, socket=self)
- self.on_http_request_possible()
-
- def build_http_message(self, httpbody, method='POST'):
- """
- Builds http message with given body. Values for headers and status line
- fields are taken from class variables
- """
- absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host,
- self.http_port, self.http_path)
- headers = ['%s %s %s' % (method, absolute_uri, self.http_version),
- 'Host: %s:%s' % (self.http_host, self.http_port),
- 'User-Agent: Gajim',
- 'Content-Type: text/xml; charset=utf-8',
- 'Content-Length: %s' % len(str(httpbody))]
- if self.add_proxy_headers:
- headers.append('Proxy-Connection: keep-alive')
- headers.append('Pragma: no-cache')
- if self.proxy_user and self.proxy_pass:
- credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
- credentials = base64.encodestring(credentials).strip()
- headers.append('Proxy-Authorization: Basic %s' % credentials)
- else:
- headers.append('Connection: Keep-Alive')
- headers.append('\r\n')
- headers = '\r\n'.join(headers)
- return('%s%s' % (headers, httpbody))
-
- def parse_http_message(self, message):
- """
- Split http message into a tuple:
- (statusline - list of e.g. ['HTTP/1.1', '200', 'OK'],
- headers - dictionary of headers e.g. {'Content-Length': '604',
- 'Content-Type': 'text/xml; charset=utf-8'},
- httpbody - string with http body)
- http_rest - what is left in the message after a full HTTP header + body
- """
- message = message.replace('\r','')
- message = message.lstrip('\n')
- splitted = message.split('\n\n')
- if len(splitted) < 2:
- # no complete http message. Keep filling the buffer until we find one
- buffer_rest = message
- return ('', '', '', buffer_rest)
- else:
- (header, httpbody) = splitted[:2]
- header = header.split('\n')
- statusline = header[0].split(' ', 2)
- header = header[1:]
- headers = {}
- for dummy in header:
- row = dummy.split(' ', 1)
- headers[row[0][:-1]] = row[1]
- body_size = headers['Content-Length']
- rest_splitted = splitted[2:]
- while (len(httpbody) < body_size) and rest_splitted:
- # Complete httpbody until it has the announced size
- httpbody = '\n\n'.join([httpbody, rest_splitted.pop(0)])
- buffer_rest = "\n\n".join(rest_splitted)
- return (statusline, headers, httpbody, buffer_rest)
+ """
+ Socket wrapper that creates HTTP message out of sent data and peels-off HTTP
+ headers from incoming messages
+ """
+
+ def __init__(self, raise_event, on_disconnect, idlequeue, estabilish_tls,
+ certs, on_http_request_possible, on_persistent_fallback, http_dict,
+ proxy_dict=None):
+ """
+ :param on_http_request_possible: method to call when HTTP request to
+ socket owned by transport is possible.
+ :param on_persistent_fallback: callback called when server ends TCP
+ connection. It doesn't have to be fatal for HTTP session.
+ :param http_dict: dictionary with data for HTTP request and headers
+ """
+ NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue,
+ estabilish_tls, certs, proxy_dict)
+
+ self.http_protocol, self.http_host, self.http_port, self.http_path = \
+ urisplit(http_dict['http_uri'])
+ self.http_protocol = self.http_protocol or 'http'
+ self.http_path = self.http_path or '/'
+ self.http_version = http_dict['http_version']
+ self.http_persistent = http_dict['http_persistent']
+ self.add_proxy_headers = http_dict['add_proxy_headers']
+
+ if 'proxy_user' in http_dict and 'proxy_pass' in http_dict:
+ self.proxy_user, self.proxy_pass = http_dict['proxy_user'], http_dict[
+ 'proxy_pass']
+ else:
+ self.proxy_user, self.proxy_pass = None, None
+
+ # buffer for partial responses
+ self.recvbuff = ''
+ self.expected_length = 0
+ self.pending_requests = 0
+ self.on_http_request_possible = on_http_request_possible
+ self.last_recv_time = 0
+ self.close_current_connection = False
+ self.on_remote_disconnect = lambda: on_persistent_fallback(self)
+
+ def http_send(self, raw_data, now=False):
+ self.send(self.build_http_message(raw_data), now)
+
+ def _on_receive(self, data):
+ """
+ Preceeds passing received data to owner class. Gets rid of HTTP headers
+ and checks them.
+ """
+ if self.get_state() == PROXY_CONNECTING:
+ NonBlockingTCP._on_receive(self, data)
+ return
+
+ # append currently received data to HTTP msg in buffer
+ self.recvbuff = '%s%s' % (self.recvbuff or '', data)
+ statusline, headers, httpbody, buffer_rest = self.parse_http_message(
+ self.recvbuff)
+
+ if not (statusline and headers and httpbody):
+ log.debug('Received incomplete HTTP response')
+ return
+
+ if statusline[1] != '200':
+ log.error('HTTP Error: %s %s' % (statusline[1], statusline[2]))
+ self.disconnect()
+ return
+ self.expected_length = int(headers['Content-Length'])
+ if 'Connection' in headers and headers['Connection'].strip()=='close':
+ self.close_current_connection = True
+
+ if self.expected_length > len(httpbody):
+ # If we haven't received the whole HTTP mess yet, let's end the thread.
+ # It will be finnished from one of following recvs on plugged socket.
+ log.info('not enough bytes in HTTP response - %d expected, got %d' %
+ (self.expected_length, len(httpbody)))
+ else:
+ # First part of buffer has been extraced and is going to be handled,
+ # remove it from buffer
+ self.recvbuff = buffer_rest
+
+ # everything was received
+ self.expected_length = 0
+
+ if not self.http_persistent or self.close_current_connection:
+ # not-persistent connections disconnect after response
+ self.disconnect(do_callback=False)
+ self.close_current_connection = False
+ self.last_recv_time = time.time()
+ self.on_receive(data=httpbody, socket=self)
+ self.on_http_request_possible()
+
+ def build_http_message(self, httpbody, method='POST'):
+ """
+ Builds http message with given body. Values for headers and status line
+ fields are taken from class variables
+ """
+ absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host,
+ self.http_port, self.http_path)
+ headers = ['%s %s %s' % (method, absolute_uri, self.http_version),
+ 'Host: %s:%s' % (self.http_host, self.http_port),
+ 'User-Agent: Gajim',
+ 'Content-Type: text/xml; charset=utf-8',
+ 'Content-Length: %s' % len(str(httpbody))]
+ if self.add_proxy_headers:
+ headers.append('Proxy-Connection: keep-alive')
+ headers.append('Pragma: no-cache')
+ if self.proxy_user and self.proxy_pass:
+ credentials = '%s:%s' % (self.proxy_user, self.proxy_pass)
+ credentials = base64.encodestring(credentials).strip()
+ headers.append('Proxy-Authorization: Basic %s' % credentials)
+ else:
+ headers.append('Connection: Keep-Alive')
+ headers.append('\r\n')
+ headers = '\r\n'.join(headers)
+ return('%s%s' % (headers, httpbody))
+
+ def parse_http_message(self, message):
+ """
+ Split http message into a tuple:
+ (statusline - list of e.g. ['HTTP/1.1', '200', 'OK'],
+ headers - dictionary of headers e.g. {'Content-Length': '604',
+ 'Content-Type': 'text/xml; charset=utf-8'},
+ httpbody - string with http body)
+ http_rest - what is left in the message after a full HTTP header + body
+ """
+ message = message.replace('\r','')
+ message = message.lstrip('\n')
+ splitted = message.split('\n\n')
+ if len(splitted) < 2:
+ # no complete http message. Keep filling the buffer until we find one
+ buffer_rest = message
+ return ('', '', '', buffer_rest)
+ else:
+ (header, httpbody) = splitted[:2]
+ header = header.split('\n')
+ statusline = header[0].split(' ', 2)
+ header = header[1:]
+ headers = {}
+ for dummy in header:
+ row = dummy.split(' ', 1)
+ headers[row[0][:-1]] = row[1]
+ body_size = headers['Content-Length']
+ rest_splitted = splitted[2:]
+ while (len(httpbody) < body_size) and rest_splitted:
+ # Complete httpbody until it has the announced size
+ httpbody = '\n\n'.join([httpbody, rest_splitted.pop(0)])
+ buffer_rest = "\n\n".join(rest_splitted)
+ return (statusline, headers, httpbody, buffer_rest)
class NonBlockingHTTPBOSH(NonBlockingHTTP):
- """
- Class for BOSH HTTP connections. Slightly redefines HTTP transport by
- calling bosh bodytag generating callback before putting data on wire
- """
-
- def set_stanza_build_cb(self, build_cb):
- self.build_cb = build_cb
-
- def _do_send(self):
- if self.state == PROXY_CONNECTING:
- NonBlockingTCP._do_send(self)
- return
- if not self.sendbuff:
- stanza = self.build_cb(socket=self)
- stanza = self.encode_stanza(stanza)
- stanza = self.build_http_message(httpbody=stanza)
- self.sendbuff = stanza
- NonBlockingTCP._do_send(self)
-
-# vim: se ts=3:
+ """
+ Class for BOSH HTTP connections. Slightly redefines HTTP transport by
+ calling bosh bodytag generating callback before putting data on wire
+ """
+
+ def set_stanza_build_cb(self, build_cb):
+ self.build_cb = build_cb
+
+ def _do_send(self):
+ if self.state == PROXY_CONNECTING:
+ NonBlockingTCP._do_send(self)
+ return
+ if not self.sendbuff:
+ stanza = self.build_cb(socket=self)
+ stanza = self.encode_stanza(stanza)
+ stanza = self.build_http_message(httpbody=stanza)
+ self.sendbuff = stanza
+ NonBlockingTCP._do_send(self)
diff --git a/src/common/zeroconf/__init__.py b/src/common/zeroconf/__init__.py
index bbd45f43d..e69de29bb 100644
--- a/src/common/zeroconf/__init__.py
+++ b/src/common/zeroconf/__init__.py
@@ -1,2 +0,0 @@
-
-# vim: se ts=3: \ No newline at end of file
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
index 96cbd7436..adc3f45d0 100644
--- a/src/common/zeroconf/client_zeroconf.py
+++ b/src/common/zeroconf/client_zeroconf.py
@@ -1,7 +1,7 @@
## common/zeroconf/client_zeroconf.py
##
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
-## 2006 Dimitur Kirov <dkirov@gmail.com>
+## 2006 Dimitur Kirov <dkirov@gmail.com>
##
## This file is part of Gajim.
##
@@ -46,762 +46,760 @@ CONNECT_TIMEOUT_SECONDS = 10
ACTIVITY_TIMEOUT_SECONDS = 30
class ZeroconfListener(IdleObject):
- def __init__(self, port, conn_holder):
- """
- Handle all incomming connections on ('0.0.0.0', port)
- """
- self.port = port
- self.queue_idx = -1
- #~ self.queue = None
- self.started = False
- self._sock = None
- self.fd = -1
- self.caller = conn_holder.caller
- self.conn_holder = conn_holder
-
- def bind(self):
- flags = socket.AI_PASSIVE
- if hasattr(socket, 'AI_ADDRCONFIG'):
- flags |= socket.AI_ADDRCONFIG
- ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, 0, flags)[0]
- self._serv = socket.socket(ai[0], ai[1])
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- # will fail when port is busy, or we don't have rights to bind
- try:
- self._serv.bind((ai[4][0], self.port))
- except Exception:
- # unable to bind, show error dialog
- return None
- self._serv.listen(socket.SOMAXCONN)
- self._serv.setblocking(False)
- self.fd = self._serv.fileno()
- gajim.idlequeue.plug_idle(self, False, True)
- self.started = True
-
- def pollend(self):
- """
- Called when we stop listening on (host, port)
- """
- self.disconnect()
-
- def pollin(self):
- """
- Accept a new incomming connection and notify queue
- """
- sock = self.accept_conn()
- # loop through roster to find who has connected to us
- from_jid = None
- ipaddr = sock[1][0]
- for jid in self.conn_holder.getRoster().keys():
- entry = self.conn_holder.getRoster().getItem(jid)
- if (entry['address'] == ipaddr):
- from_jid = jid
- break
- P2PClient(sock[0], ipaddr, sock[1][1], self.conn_holder, [], from_jid)
-
- def disconnect(self, message=''):
- """
- Free all resources, we are not listening anymore
- """
- log.info('Disconnecting ZeroconfListener: %s' % message)
- gajim.idlequeue.remove_timeout(self.fd)
- gajim.idlequeue.unplug_idle(self.fd)
- self.fd = -1
- self.started = False
- try:
- self._serv.close()
- except socket.error:
- pass
- self.conn_holder.kill_all_connections()
-
- def accept_conn(self):
- """
- Accept a new incoming connection
- """
- _sock = self._serv.accept()
- _sock[0].setblocking(False)
- return _sock
+ def __init__(self, port, conn_holder):
+ """
+ Handle all incomming connections on ('0.0.0.0', port)
+ """
+ self.port = port
+ self.queue_idx = -1
+ #~ self.queue = None
+ self.started = False
+ self._sock = None
+ self.fd = -1
+ self.caller = conn_holder.caller
+ self.conn_holder = conn_holder
+
+ def bind(self):
+ flags = socket.AI_PASSIVE
+ if hasattr(socket, 'AI_ADDRCONFIG'):
+ flags |= socket.AI_ADDRCONFIG
+ ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM, 0, flags)[0]
+ self._serv = socket.socket(ai[0], ai[1])
+ self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ # will fail when port is busy, or we don't have rights to bind
+ try:
+ self._serv.bind((ai[4][0], self.port))
+ except Exception:
+ # unable to bind, show error dialog
+ return None
+ self._serv.listen(socket.SOMAXCONN)
+ self._serv.setblocking(False)
+ self.fd = self._serv.fileno()
+ gajim.idlequeue.plug_idle(self, False, True)
+ self.started = True
+
+ def pollend(self):
+ """
+ Called when we stop listening on (host, port)
+ """
+ self.disconnect()
+
+ def pollin(self):
+ """
+ Accept a new incomming connection and notify queue
+ """
+ sock = self.accept_conn()
+ # loop through roster to find who has connected to us
+ from_jid = None
+ ipaddr = sock[1][0]
+ for jid in self.conn_holder.getRoster().keys():
+ entry = self.conn_holder.getRoster().getItem(jid)
+ if (entry['address'] == ipaddr):
+ from_jid = jid
+ break
+ P2PClient(sock[0], ipaddr, sock[1][1], self.conn_holder, [], from_jid)
+
+ def disconnect(self, message=''):
+ """
+ Free all resources, we are not listening anymore
+ """
+ log.info('Disconnecting ZeroconfListener: %s' % message)
+ gajim.idlequeue.remove_timeout(self.fd)
+ gajim.idlequeue.unplug_idle(self.fd)
+ self.fd = -1
+ self.started = False
+ try:
+ self._serv.close()
+ except socket.error:
+ pass
+ self.conn_holder.kill_all_connections()
+
+ def accept_conn(self):
+ """
+ Accept a new incoming connection
+ """
+ _sock = self._serv.accept()
+ _sock[0].setblocking(False)
+ return _sock
class P2PClient(IdleObject):
- def __init__(self, _sock, host, port, conn_holder, stanzaqueue=[], to=None,
- on_ok=None, on_not_ok=None):
- self._owner = self
- self.Namespace = 'jabber:client'
- self.protocol_type = 'XMPP'
- self.defaultNamespace = self.Namespace
- self._component = 0
- self._registered_name = None
- self._caller = conn_holder.caller
- self.conn_holder = conn_holder
- self.stanzaqueue = stanzaqueue
- self.to = to
- self.Server = host
- self.on_ok = on_ok
- self.on_not_ok = on_not_ok
- self.Connection = None
- self.sock_hash = None
- if _sock:
- self.sock_type = TYPE_SERVER
- else:
- self.sock_type = TYPE_CLIENT
- self.fd = -1
- conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect,
- self)
- if not self.conn_holder:
- # An error occured, disconnect() has been called
- if on_not_ok:
- on_not_ok('Connection to host could not be established.')
- return
- self.sock_hash = conn._sock.__hash__
- self.fd = conn.fd
- self.conn_holder.add_connection(self, self.Server, port, self.to)
- # count messages in queue
- for val in self.stanzaqueue:
- stanza, is_message = val
- if is_message:
- if self.fd == -1:
- if on_not_ok:
- on_not_ok('Connection to host could not be established.')
- return
- thread_id = stanza.getThread()
- id_ = stanza.getID()
- if not id_:
- id_ = self.Dispatcher.getAnID()
- if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
- self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
- thread_id))
- else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
- thread_id)]
-
- self.on_responses = {}
-
- def add_stanza(self, stanza, is_message=False):
- if self.Connection:
- if self.Connection.state == -1:
- return False
- self.send(stanza, is_message)
- else:
- self.stanzaqueue.append((stanza, is_message))
-
- if is_message:
- thread_id = stanza.getThread()
- id_ = stanza.getID()
- if not id_:
- id_ = self.Dispatcher.getAnID()
- if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
- self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
- thread_id))
- else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
- thread_id)]
-
- return True
-
- def on_message_sent(self, connection_id):
- id_, thread_id = \
- self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0)
- if self.on_ok:
- self.on_ok(id_)
- # use on_ok only on first message. For others it's called in
- # ClientZeroconf
- self.on_ok = None
-
- def on_connect(self, conn):
- self.Connection = conn
- self.Connection.PlugIn(self)
- dispatcher_nb.Dispatcher().PlugIn(self)
- self._register_handlers()
-
- def StreamInit(self):
- """
- Send an initial stream header
- """
- self.Dispatcher.Stream = simplexml.NodeBuilder()
- self.Dispatcher.Stream._dispatch_depth = 2
- self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
- self.Dispatcher.Stream.stream_header_received = self._check_stream_start
- self.Dispatcher.Stream.features = None
- if self.sock_type == TYPE_CLIENT:
- self.send_stream_header()
-
- def send_stream_header(self):
- self.Dispatcher._metastream = Node('stream:stream')
- self.Dispatcher._metastream.setNamespace(self.Namespace)
- self.Dispatcher._metastream.setAttr('version', '1.0')
- self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS)
- self.Dispatcher._metastream.setAttr('from', self.conn_holder.zeroconf.name)
- if self.to:
- self.Dispatcher._metastream.setAttr('to', self.to)
- self.Dispatcher.send("<?xml version='1.0'?>%s>" % str(
- self.Dispatcher._metastream)[:-2])
-
- def _check_stream_start(self, ns, tag, attrs):
- if ns != NS_STREAMS or tag != 'stream':
- log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag, ns), 'error')
- self.Connection.disconnect()
- if self.on_not_ok:
- self.on_not_ok('Connection to host could not be established: Incorrect answer from server.')
- return
- if self.sock_type == TYPE_SERVER:
- if attrs.has_key('from'):
- self.to = attrs['from']
- self.send_stream_header()
- if attrs.has_key('version') and attrs['version'] == '1.0':
- # other part supports stream features
- features = Node('stream:features')
- self.Dispatcher.send(features)
- while self.stanzaqueue:
- stanza, is_message = self.stanzaqueue.pop(0)
- self.send(stanza, is_message)
- elif self.sock_type == TYPE_CLIENT:
- while self.stanzaqueue:
- stanza, is_message = self.stanzaqueue.pop(0)
- self.send(stanza, is_message)
-
- def on_disconnect(self):
- if self.conn_holder:
- if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
- del self.conn_holder.ids_of_awaiting_messages[self.fd]
- self.conn_holder.remove_connection(self.sock_hash)
- if self.__dict__.has_key('Dispatcher'):
- self.Dispatcher.PlugOut()
- if self.__dict__.has_key('P2PConnection'):
- self.P2PConnection.PlugOut()
- self.Connection = None
- self._caller = None
- self.conn_holder = None
-
- def force_disconnect(self):
- if self.Connection:
- self.disconnect()
- else:
- self.on_disconnect()
-
- def _on_receive_document_attrs(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not hasattr(self, 'Dispatcher') or \
- self.Dispatcher.Stream._document_attrs is None:
- return
- self.onreceive(None)
- if self.Dispatcher.Stream._document_attrs.has_key('version') and \
- self.Dispatcher.Stream._document_attrs['version'] == '1.0':
- #~ self.onreceive(self._on_receive_stream_features)
- #XXX continue with TLS
- return
- self.onreceive(None)
- return True
-
- def remove_timeout(self):
- pass
-
- def _register_handlers(self):
- self._caller.peerhost = self.Connection._sock.getsockname()
- self.RegisterHandler('message', lambda conn, data:self._caller._messageCB(
- self.Server, conn, data))
- self.RegisterHandler('iq', self._caller._siSetCB, 'set',
- common.xmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._siErrorCB, 'error',
- common.xmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._siResultCB, 'result',
- common.xmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set',
- common.xmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result',
- common.xmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
- common.xmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get',
- common.xmpp.NS_DISCO_ITEMS)
+ def __init__(self, _sock, host, port, conn_holder, stanzaqueue=[], to=None,
+ on_ok=None, on_not_ok=None):
+ self._owner = self
+ self.Namespace = 'jabber:client'
+ self.protocol_type = 'XMPP'
+ self.defaultNamespace = self.Namespace
+ self._component = 0
+ self._registered_name = None
+ self._caller = conn_holder.caller
+ self.conn_holder = conn_holder
+ self.stanzaqueue = stanzaqueue
+ self.to = to
+ self.Server = host
+ self.on_ok = on_ok
+ self.on_not_ok = on_not_ok
+ self.Connection = None
+ self.sock_hash = None
+ if _sock:
+ self.sock_type = TYPE_SERVER
+ else:
+ self.sock_type = TYPE_CLIENT
+ self.fd = -1
+ conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect,
+ self)
+ if not self.conn_holder:
+ # An error occured, disconnect() has been called
+ if on_not_ok:
+ on_not_ok('Connection to host could not be established.')
+ return
+ self.sock_hash = conn._sock.__hash__
+ self.fd = conn.fd
+ self.conn_holder.add_connection(self, self.Server, port, self.to)
+ # count messages in queue
+ for val in self.stanzaqueue:
+ stanza, is_message = val
+ if is_message:
+ if self.fd == -1:
+ if on_not_ok:
+ on_not_ok('Connection to host could not be established.')
+ return
+ thread_id = stanza.getThread()
+ id_ = stanza.getID()
+ if not id_:
+ id_ = self.Dispatcher.getAnID()
+ if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
+ self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
+ thread_id))
+ else:
+ self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
+ thread_id)]
+
+ self.on_responses = {}
+
+ def add_stanza(self, stanza, is_message=False):
+ if self.Connection:
+ if self.Connection.state == -1:
+ return False
+ self.send(stanza, is_message)
+ else:
+ self.stanzaqueue.append((stanza, is_message))
+
+ if is_message:
+ thread_id = stanza.getThread()
+ id_ = stanza.getID()
+ if not id_:
+ id_ = self.Dispatcher.getAnID()
+ if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
+ self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
+ thread_id))
+ else:
+ self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
+ thread_id)]
+
+ return True
+
+ def on_message_sent(self, connection_id):
+ id_, thread_id = \
+ self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0)
+ if self.on_ok:
+ self.on_ok(id_)
+ # use on_ok only on first message. For others it's called in
+ # ClientZeroconf
+ self.on_ok = None
+
+ def on_connect(self, conn):
+ self.Connection = conn
+ self.Connection.PlugIn(self)
+ dispatcher_nb.Dispatcher().PlugIn(self)
+ self._register_handlers()
+
+ def StreamInit(self):
+ """
+ Send an initial stream header
+ """
+ self.Dispatcher.Stream = simplexml.NodeBuilder()
+ self.Dispatcher.Stream._dispatch_depth = 2
+ self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
+ self.Dispatcher.Stream.stream_header_received = self._check_stream_start
+ self.Dispatcher.Stream.features = None
+ if self.sock_type == TYPE_CLIENT:
+ self.send_stream_header()
+
+ def send_stream_header(self):
+ self.Dispatcher._metastream = Node('stream:stream')
+ self.Dispatcher._metastream.setNamespace(self.Namespace)
+ self.Dispatcher._metastream.setAttr('version', '1.0')
+ self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS)
+ self.Dispatcher._metastream.setAttr('from', self.conn_holder.zeroconf.name)
+ if self.to:
+ self.Dispatcher._metastream.setAttr('to', self.to)
+ self.Dispatcher.send("<?xml version='1.0'?>%s>" % str(
+ self.Dispatcher._metastream)[:-2])
+
+ def _check_stream_start(self, ns, tag, attrs):
+ if ns != NS_STREAMS or tag != 'stream':
+ log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag, ns), 'error')
+ self.Connection.disconnect()
+ if self.on_not_ok:
+ self.on_not_ok('Connection to host could not be established: Incorrect answer from server.')
+ return
+ if self.sock_type == TYPE_SERVER:
+ if attrs.has_key('from'):
+ self.to = attrs['from']
+ self.send_stream_header()
+ if attrs.has_key('version') and attrs['version'] == '1.0':
+ # other part supports stream features
+ features = Node('stream:features')
+ self.Dispatcher.send(features)
+ while self.stanzaqueue:
+ stanza, is_message = self.stanzaqueue.pop(0)
+ self.send(stanza, is_message)
+ elif self.sock_type == TYPE_CLIENT:
+ while self.stanzaqueue:
+ stanza, is_message = self.stanzaqueue.pop(0)
+ self.send(stanza, is_message)
+
+ def on_disconnect(self):
+ if self.conn_holder:
+ if self.conn_holder.ids_of_awaiting_messages.has_key(self.fd):
+ del self.conn_holder.ids_of_awaiting_messages[self.fd]
+ self.conn_holder.remove_connection(self.sock_hash)
+ if self.__dict__.has_key('Dispatcher'):
+ self.Dispatcher.PlugOut()
+ if self.__dict__.has_key('P2PConnection'):
+ self.P2PConnection.PlugOut()
+ self.Connection = None
+ self._caller = None
+ self.conn_holder = None
+
+ def force_disconnect(self):
+ if self.Connection:
+ self.disconnect()
+ else:
+ self.on_disconnect()
+
+ def _on_receive_document_attrs(self, data):
+ if data:
+ self.Dispatcher.ProcessNonBlocking(data)
+ if not hasattr(self, 'Dispatcher') or \
+ self.Dispatcher.Stream._document_attrs is None:
+ return
+ self.onreceive(None)
+ if self.Dispatcher.Stream._document_attrs.has_key('version') and \
+ self.Dispatcher.Stream._document_attrs['version'] == '1.0':
+ #~ self.onreceive(self._on_receive_stream_features)
+ #XXX continue with TLS
+ return
+ self.onreceive(None)
+ return True
+
+ def remove_timeout(self):
+ pass
+
+ def _register_handlers(self):
+ self._caller.peerhost = self.Connection._sock.getsockname()
+ self.RegisterHandler('message', lambda conn, data:self._caller._messageCB(
+ self.Server, conn, data))
+ self.RegisterHandler('iq', self._caller._siSetCB, 'set',
+ common.xmpp.NS_SI)
+ self.RegisterHandler('iq', self._caller._siErrorCB, 'error',
+ common.xmpp.NS_SI)
+ self.RegisterHandler('iq', self._caller._siResultCB, 'result',
+ common.xmpp.NS_SI)
+ self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set',
+ common.xmpp.NS_BYTESTREAM)
+ self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result',
+ common.xmpp.NS_BYTESTREAM)
+ self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
+ common.xmpp.NS_BYTESTREAM)
+ self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get',
+ common.xmpp.NS_DISCO_ITEMS)
class P2PConnection(IdleObject, PlugIn):
- def __init__(self, sock_hash, _sock, host=None, port=None, caller=None,
- on_connect=None, client=None):
- IdleObject.__init__(self)
- self._owner = client
- PlugIn.__init__(self)
- self.sendqueue = []
- self.sendbuff = None
- self.buff_is_message = False
- self._sock = _sock
- self.sock_hash = None
- self.host, self.port = host, port
- self.on_connect = on_connect
- self.client = client
- self.writable = False
- self.readable = False
- self._exported_methods = [self.send, self.disconnect, self.onreceive]
- self.on_receive = None
- if _sock:
- self._sock = _sock
- self.state = 1
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.on_connect(self)
- else:
- self.state = 0
- try:
- self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- except socket.gaierror, e:
- log.info('Lookup failure for %s: %s[%s]', host, e[1], repr(e[0]),
- exc_info=True)
- else:
- self.connect_to_next_ip()
-
- def connect_to_next_ip(self):
- if len(self.ais) == 0:
- log.error('Connection failure to %s', self.host, exc_info=True)
- self.disconnect()
- return
- ai = self.ais.pop(0)
- log.info('Trying to connect to %s through %s:%s', self.host, ai[4][0],
- ai[4][1], exc_info=True)
- try:
- self._sock = socket.socket(*ai[:3])
- self._sock.setblocking(False)
- self._server = ai[4]
- except socket.error:
- if sys.exc_value[0] != errno.EINPROGRESS:
- # for all errors, we try other addresses
- self.connect_to_next_ip()
- return
- self.fd = self._sock.fileno()
- gajim.idlequeue.plug_idle(self, True, False)
- self.set_timeout(CONNECT_TIMEOUT_SECONDS)
- self.do_connect()
-
- def set_timeout(self, timeout):
- gajim.idlequeue.remove_timeout(self.fd)
- if self.state >= 0:
- gajim.idlequeue.set_read_timeout(self.fd, timeout)
-
- def plugin(self, owner):
- self.onreceive(owner._on_receive_document_attrs)
- self._plug_idle()
- return True
-
- def plugout(self):
- """
- Disconnect from the remote server and unregister self.disconnected method
- from the owner's dispatcher
- """
- self.disconnect()
- self._owner = None
-
- def onreceive(self, recv_handler):
- if not recv_handler:
- if hasattr(self._owner, 'Dispatcher'):
- self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
- else:
- self.on_receive = None
- return
- _tmp = self.on_receive
- # make sure this cb is not overriden by recursive calls
- if not recv_handler(None) and _tmp == self.on_receive:
- self.on_receive = recv_handler
-
- def send(self, packet, is_message=False, now=False):
- """
- Append stanza to the queue of messages to be send if now is False, else
- send it instantly
-
- If supplied data is unicode string, encode it to UTF-8.
- """
- print 'ici'
- if self.state <= 0:
- return
-
- r = packet
-
- if isinstance(r, unicode):
- r = r.encode('utf-8')
- elif not isinstance(r, str):
- r = ustr(r).encode('utf-8')
-
- if now:
- self.sendqueue.insert(0, (r, is_message))
- self._do_send()
- else:
- self.sendqueue.append((r, is_message))
- self._plug_idle()
-
- def read_timeout(self):
- ids = self.client.conn_holder.ids_of_awaiting_messages
- if self.fd in ids and len(ids[self.fd]) > 0:
- for (id_, thread_id) in ids[self.fd]:
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to,
- thread_id))
- else:
- self._owner.on_not_ok('conenction timeout')
- ids[self.fd] = []
- self.pollend()
-
- def do_connect(self):
- errnum = 0
- try:
- self._sock.connect(self._server)
- self._sock.setblocking(False)
- except Exception, ee:
- (errnum, errstr) = ee
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- return
- # win32 needs this
- elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0:
- log.error('Could not connect to %s: %s [%s]', self.host, errnum,
- errstr)
- self.connect_to_next_ip()
- return
- else: # socket is already connected
- self._sock.setblocking(False)
- self.state = 1 # connected
- # we are connected
- self.on_connect(self)
-
- def pollout(self):
- if self.state == 0:
- self.do_connect()
- return
- gajim.idlequeue.remove_timeout(self.fd)
- self._do_send()
-
- def pollend(self):
- self.state = -1
- self.disconnect()
-
- def pollin(self):
- """
- Reads all pending incoming data. Call owner's disconnected() method if
- appropriate
- """
- received = ''
- errnum = 0
- try:
- # get as many bites, as possible, but not more than RECV_BUFSIZE
- received = self._sock.recv(MAX_BUFF_LEN)
- except Exception, e:
- if len(e.args) > 0 and isinstance(e.args[0], int):
- errnum = e[0]
- # "received" will be empty anyhow
- if errnum == socket.SSL_ERROR_WANT_READ:
- pass
- elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
- self.pollend()
- # don't proccess result, cas it will raise error
- return
- elif not received :
- if errnum != socket.SSL_ERROR_EOF:
- # 8 EOF occurred in violation of protocol
- self.pollend()
- if self.state >= 0:
- self.disconnect()
- return
-
- if self.state < 0:
- return
- if self.on_receive:
- if self._owner.sock_type == TYPE_CLIENT:
- self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
- if received.strip():
- log.debug('received: %s', received)
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
- self.on_receive(received)
- else:
- # This should never happed, so we need the debug
- log.error('Unhandled data received: %s' % received)
- self.disconnect()
- return True
-
- def disconnect(self, message=''):
- """
- Close the socket
- """
- gajim.idlequeue.remove_timeout(self.fd)
- gajim.idlequeue.unplug_idle(self.fd)
- try:
- self._sock.shutdown(socket.SHUT_RDWR)
- self._sock.close()
- except socket.error:
- # socket is already closed
- pass
- self.fd = -1
- self.state = -1
- if self._owner:
- self._owner.on_disconnect()
-
- def _do_send(self):
- if not self.sendbuff:
- if not self.sendqueue:
- return None # nothing to send
- self.sendbuff, self.buff_is_message = self.sendqueue.pop(0)
- self.sent_data = self.sendbuff
- try:
- send_count = self._sock.send(self.sendbuff)
- if send_count:
- self.sendbuff = self.sendbuff[send_count:]
- if not self.sendbuff and not self.sendqueue:
- if self.state < 0:
- gajim.idlequeue.unplug_idle(self.fd)
- self._on_send()
- self.disconnect()
- return
- # we are not waiting for write
- self._plug_idle()
- self._on_send()
-
- except socket.error, e:
- if e[0] == socket.SSL_ERROR_WANT_WRITE:
- return True
- if self.state < 0:
- self.disconnect()
- return
- self._on_send_failure()
- return
- if self._owner.sock_type == TYPE_CLIENT:
- self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
- return True
-
- def _plug_idle(self):
- readable = self.state != 0
- if self.sendqueue or self.sendbuff:
- writable = True
- else:
- writable = False
- if self.writable != writable or self.readable != readable:
- gajim.idlequeue.plug_idle(self, writable, readable)
-
-
- def _on_send(self):
- if self.sent_data and self.sent_data.strip():
- log.debug('sent: %s' % self.sent_data)
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
- self.sent_data = None
- if self.buff_is_message:
- self._owner.on_message_sent(self.fd)
- self.buff_is_message = False
-
- def _on_send_failure(self):
- log.error('Socket error while sending data')
- self._owner.on_disconnect()
- self.sent_data = None
+ def __init__(self, sock_hash, _sock, host=None, port=None, caller=None,
+ on_connect=None, client=None):
+ IdleObject.__init__(self)
+ self._owner = client
+ PlugIn.__init__(self)
+ self.sendqueue = []
+ self.sendbuff = None
+ self.buff_is_message = False
+ self._sock = _sock
+ self.sock_hash = None
+ self.host, self.port = host, port
+ self.on_connect = on_connect
+ self.client = client
+ self.writable = False
+ self.readable = False
+ self._exported_methods = [self.send, self.disconnect, self.onreceive]
+ self.on_receive = None
+ if _sock:
+ self._sock = _sock
+ self.state = 1
+ self._sock.setblocking(False)
+ self.fd = self._sock.fileno()
+ self.on_connect(self)
+ else:
+ self.state = 0
+ try:
+ self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM)
+ except socket.gaierror, e:
+ log.info('Lookup failure for %s: %s[%s]', host, e[1], repr(e[0]),
+ exc_info=True)
+ else:
+ self.connect_to_next_ip()
+
+ def connect_to_next_ip(self):
+ if len(self.ais) == 0:
+ log.error('Connection failure to %s', self.host, exc_info=True)
+ self.disconnect()
+ return
+ ai = self.ais.pop(0)
+ log.info('Trying to connect to %s through %s:%s', self.host, ai[4][0],
+ ai[4][1], exc_info=True)
+ try:
+ self._sock = socket.socket(*ai[:3])
+ self._sock.setblocking(False)
+ self._server = ai[4]
+ except socket.error:
+ if sys.exc_value[0] != errno.EINPROGRESS:
+ # for all errors, we try other addresses
+ self.connect_to_next_ip()
+ return
+ self.fd = self._sock.fileno()
+ gajim.idlequeue.plug_idle(self, True, False)
+ self.set_timeout(CONNECT_TIMEOUT_SECONDS)
+ self.do_connect()
+
+ def set_timeout(self, timeout):
+ gajim.idlequeue.remove_timeout(self.fd)
+ if self.state >= 0:
+ gajim.idlequeue.set_read_timeout(self.fd, timeout)
+
+ def plugin(self, owner):
+ self.onreceive(owner._on_receive_document_attrs)
+ self._plug_idle()
+ return True
+
+ def plugout(self):
+ """
+ Disconnect from the remote server and unregister self.disconnected method
+ from the owner's dispatcher
+ """
+ self.disconnect()
+ self._owner = None
+
+ def onreceive(self, recv_handler):
+ if not recv_handler:
+ if hasattr(self._owner, 'Dispatcher'):
+ self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
+ else:
+ self.on_receive = None
+ return
+ _tmp = self.on_receive
+ # make sure this cb is not overriden by recursive calls
+ if not recv_handler(None) and _tmp == self.on_receive:
+ self.on_receive = recv_handler
+
+ def send(self, packet, is_message=False, now=False):
+ """
+ Append stanza to the queue of messages to be send if now is False, else
+ send it instantly
+
+ If supplied data is unicode string, encode it to UTF-8.
+ """
+ print 'ici'
+ if self.state <= 0:
+ return
+
+ r = packet
+
+ if isinstance(r, unicode):
+ r = r.encode('utf-8')
+ elif not isinstance(r, str):
+ r = ustr(r).encode('utf-8')
+
+ if now:
+ self.sendqueue.insert(0, (r, is_message))
+ self._do_send()
+ else:
+ self.sendqueue.append((r, is_message))
+ self._plug_idle()
+
+ def read_timeout(self):
+ ids = self.client.conn_holder.ids_of_awaiting_messages
+ if self.fd in ids and len(ids[self.fd]) > 0:
+ for (id_, thread_id) in ids[self.fd]:
+ if hasattr(self._owner, 'Dispatcher'):
+ self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to,
+ thread_id))
+ else:
+ self._owner.on_not_ok('conenction timeout')
+ ids[self.fd] = []
+ self.pollend()
+
+ def do_connect(self):
+ errnum = 0
+ try:
+ self._sock.connect(self._server)
+ self._sock.setblocking(False)
+ except Exception, ee:
+ (errnum, errstr) = ee
+ if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
+ return
+ # win32 needs this
+ elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0:
+ log.error('Could not connect to %s: %s [%s]', self.host, errnum,
+ errstr)
+ self.connect_to_next_ip()
+ return
+ else: # socket is already connected
+ self._sock.setblocking(False)
+ self.state = 1 # connected
+ # we are connected
+ self.on_connect(self)
+
+ def pollout(self):
+ if self.state == 0:
+ self.do_connect()
+ return
+ gajim.idlequeue.remove_timeout(self.fd)
+ self._do_send()
+
+ def pollend(self):
+ self.state = -1
+ self.disconnect()
+
+ def pollin(self):
+ """
+ Reads all pending incoming data. Call owner's disconnected() method if
+ appropriate
+ """
+ received = ''
+ errnum = 0
+ try:
+ # get as many bites, as possible, but not more than RECV_BUFSIZE
+ received = self._sock.recv(MAX_BUFF_LEN)
+ except Exception, e:
+ if len(e.args) > 0 and isinstance(e.args[0], int):
+ errnum = e[0]
+ # "received" will be empty anyhow
+ if errnum == socket.SSL_ERROR_WANT_READ:
+ pass
+ elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
+ self.pollend()
+ # don't proccess result, cas it will raise error
+ return
+ elif not received :
+ if errnum != socket.SSL_ERROR_EOF:
+ # 8 EOF occurred in violation of protocol
+ self.pollend()
+ if self.state >= 0:
+ self.disconnect()
+ return
+
+ if self.state < 0:
+ return
+ if self.on_receive:
+ if self._owner.sock_type == TYPE_CLIENT:
+ self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
+ if received.strip():
+ log.debug('received: %s', received)
+ if hasattr(self._owner, 'Dispatcher'):
+ self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
+ self.on_receive(received)
+ else:
+ # This should never happed, so we need the debug
+ log.error('Unhandled data received: %s' % received)
+ self.disconnect()
+ return True
+
+ def disconnect(self, message=''):
+ """
+ Close the socket
+ """
+ gajim.idlequeue.remove_timeout(self.fd)
+ gajim.idlequeue.unplug_idle(self.fd)
+ try:
+ self._sock.shutdown(socket.SHUT_RDWR)
+ self._sock.close()
+ except socket.error:
+ # socket is already closed
+ pass
+ self.fd = -1
+ self.state = -1
+ if self._owner:
+ self._owner.on_disconnect()
+
+ def _do_send(self):
+ if not self.sendbuff:
+ if not self.sendqueue:
+ return None # nothing to send
+ self.sendbuff, self.buff_is_message = self.sendqueue.pop(0)
+ self.sent_data = self.sendbuff
+ try:
+ send_count = self._sock.send(self.sendbuff)
+ if send_count:
+ self.sendbuff = self.sendbuff[send_count:]
+ if not self.sendbuff and not self.sendqueue:
+ if self.state < 0:
+ gajim.idlequeue.unplug_idle(self.fd)
+ self._on_send()
+ self.disconnect()
+ return
+ # we are not waiting for write
+ self._plug_idle()
+ self._on_send()
+
+ except socket.error, e:
+ if e[0] == socket.SSL_ERROR_WANT_WRITE:
+ return True
+ if self.state < 0:
+ self.disconnect()
+ return
+ self._on_send_failure()
+ return
+ if self._owner.sock_type == TYPE_CLIENT:
+ self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
+ return True
+
+ def _plug_idle(self):
+ readable = self.state != 0
+ if self.sendqueue or self.sendbuff:
+ writable = True
+ else:
+ writable = False
+ if self.writable != writable or self.readable != readable:
+ gajim.idlequeue.plug_idle(self, writable, readable)
+
+
+ def _on_send(self):
+ if self.sent_data and self.sent_data.strip():
+ log.debug('sent: %s' % self.sent_data)
+ if hasattr(self._owner, 'Dispatcher'):
+ self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
+ self.sent_data = None
+ if self.buff_is_message:
+ self._owner.on_message_sent(self.fd)
+ self.buff_is_message = False
+
+ def _on_send_failure(self):
+ log.error('Socket error while sending data')
+ self._owner.on_disconnect()
+ self.sent_data = None
class ClientZeroconf:
- def __init__(self, caller):
- self.caller = caller
- self.zeroconf = None
- self.roster = None
- self.last_msg = ''
- self.connections = {}
- self.recipient_to_hash = {}
- self.ip_to_hash = {}
- self.hash_to_port = {}
- self.listener = None
- self.ids_of_awaiting_messages = {}
- self.disconnect_handlers = []
- self.disconnecting = False
-
- def connect(self, show, msg):
- self.port = self.start_listener(self.caller.port)
- if not self.port:
- return False
- self.zeroconf_init(show, msg)
- if not self.zeroconf.connect():
- self.disconnect()
- return None
- self.roster = roster_zeroconf.Roster(self.zeroconf)
- return True
-
- def remove_announce(self):
- if self.zeroconf:
- return self.zeroconf.remove_announce()
-
- def announce(self):
- if self.zeroconf:
- return self.zeroconf.announce()
-
- def set_show_msg(self, show, msg):
- if self.zeroconf:
- self.zeroconf.txt['msg'] = msg
- self.last_msg = msg
- return self.zeroconf.update_txt(show)
-
- def resolve_all(self):
- if self.zeroconf:
- self.zeroconf.resolve_all()
-
- def reannounce(self, txt):
- self.remove_announce()
- self.zeroconf.txt = txt
- self.zeroconf.port = self.port
- self.zeroconf.username = self.caller.username
- return self.announce()
-
- def zeroconf_init(self, show, msg):
- self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service,
- self.caller._on_remove_service, self.caller._on_name_conflictCB,
- self.caller._on_disconnected, self.caller._on_error,
- self.caller.username, self.caller.host, self.port)
- self.zeroconf.txt['msg'] = msg
- self.zeroconf.txt['status'] = show
- self.zeroconf.txt['1st'] = self.caller.first
- self.zeroconf.txt['last'] = self.caller.last
- self.zeroconf.txt['jid'] = self.caller.jabber_id
- self.zeroconf.txt['email'] = self.caller.email
- self.zeroconf.username = self.caller.username
- self.zeroconf.host = self.caller.host
- self.zeroconf.port = self.port
- self.last_msg = msg
-
- def disconnect(self):
- # to avoid recursive calls
- if self.disconnecting:
- return
- if self.listener:
- self.listener.disconnect()
- self.listener = None
- if self.zeroconf:
- self.zeroconf.disconnect()
- self.zeroconf = None
- if self.roster:
- self.roster.zeroconf = None
- self.roster._data = None
- self.roster = None
- self.disconnecting = True
- for i in reversed(self.disconnect_handlers):
- log.debug('Calling disconnect handler %s' % i)
- i()
- self.disconnecting = False
-
- def start_disconnect(self):
- self.disconnect()
-
- def kill_all_connections(self):
- for connection in self.connections.values():
- connection.force_disconnect()
-
- def add_connection(self, connection, ip, port, recipient):
- sock_hash=connection.sock_hash
- if sock_hash not in self.connections:
- self.connections[sock_hash] = connection
- self.ip_to_hash[ip] = sock_hash
- self.hash_to_port[sock_hash] = port
- if recipient:
- self.recipient_to_hash[recipient] = sock_hash
-
- def remove_connection(self, sock_hash):
- if sock_hash in self.connections:
- del self.connections[sock_hash]
- for i in self.recipient_to_hash:
- if self.recipient_to_hash[i] == sock_hash:
- del self.recipient_to_hash[i]
- break
- for i in self.ip_to_hash:
- if self.ip_to_hash[i] == sock_hash:
- del self.ip_to_hash[i]
- break
- if self.hash_to_port.has_key(sock_hash):
- del self.hash_to_port[sock_hash]
-
- def start_listener(self, port):
- for p in range(port, port + 5):
- self.listener = ZeroconfListener(p, self)
- self.listener.bind()
- if self.listener.started:
- return p
- self.listener = None
- return False
-
- def getRoster(self):
- if self.roster:
- return self.roster.getRoster()
- return {}
-
- def send(self, stanza, is_message=False, now=False, on_ok=None,
- on_not_ok=None):
- stanza.setFrom(self.roster.zeroconf.name)
- to = stanza.getTo()
-
- try:
- item = self.roster[to]
- except KeyError:
- # Contact offline
- return -1
-
- # look for hashed connections
- if to in self.recipient_to_hash:
- conn = self.connections[self.recipient_to_hash[to]]
- id_ = stanza.getID() or ''
- if conn.add_stanza(stanza, is_message):
- if on_ok:
- on_ok(id_)
- return
-
- if item['address'] in self.ip_to_hash:
- hash_ = self.ip_to_hash[item['address']]
- if self.hash_to_port[hash_] == item['port']:
- conn = self.connections[hash_]
- id_ = stanza.getID() or ''
- if conn.add_stanza(stanza, is_message):
- if on_ok:
- on_ok(id_)
- return
-
- # otherwise open new connection
- if not stanza.getID():
- stanza.setID('zero')
- P2PClient(None, item['address'], item['port'], self,
- [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok)
-
- def RegisterDisconnectHandler(self, handler):
- """
- Register handler that will be called on disconnect
- """
- self.disconnect_handlers.append(handler)
-
- def UnregisterDisconnectHandler(self, handler):
- """
- Unregister handler that is called on disconnect
- """
- self.disconnect_handlers.remove(handler)
-
- def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None):
- """
- Send stanza and wait for recipient's response to it. Will call transports
- on_timeout callback if response is not retrieved in time
-
- Be aware: Only timeout of latest call of SendAndWait is active.
- """
-# if timeout is None:
-# timeout = DEFAULT_TIMEOUT_SECONDS
- def on_ok(_waitid):
-# if timeout:
-# self._owner.set_timeout(timeout)
- to = stanza.getTo()
- conn = None
- if to in self.recipient_to_hash:
- conn = self.connections[self.recipient_to_hash[to]]
- elif item['address'] in self.ip_to_hash:
- hash_ = self.ip_to_hash[item['address']]
- if self.hash_to_port[hash_] == item['port']:
- conn = self.connections[hash_]
- if func:
- conn.Dispatcher.on_responses[_waitid] = (func, args)
- conn.onreceive(conn.Dispatcher._WaitForData)
- conn.Dispatcher._expected[_waitid] = None
- self.send(stanza, on_ok=on_ok)
-
- def SendAndCallForResponse(self, stanza, func=None, args=None):
- """
- Put stanza on the wire and call back when recipient replies. Additional
- callback arguments can be specified in args.
- """
- self.SendAndWaitForResponse(stanza, 0, func, args)
-
-# vim: se ts=3:
+ def __init__(self, caller):
+ self.caller = caller
+ self.zeroconf = None
+ self.roster = None
+ self.last_msg = ''
+ self.connections = {}
+ self.recipient_to_hash = {}
+ self.ip_to_hash = {}
+ self.hash_to_port = {}
+ self.listener = None
+ self.ids_of_awaiting_messages = {}
+ self.disconnect_handlers = []
+ self.disconnecting = False
+
+ def connect(self, show, msg):
+ self.port = self.start_listener(self.caller.port)
+ if not self.port:
+ return False
+ self.zeroconf_init(show, msg)
+ if not self.zeroconf.connect():
+ self.disconnect()
+ return None
+ self.roster = roster_zeroconf.Roster(self.zeroconf)
+ return True
+
+ def remove_announce(self):
+ if self.zeroconf:
+ return self.zeroconf.remove_announce()
+
+ def announce(self):
+ if self.zeroconf:
+ return self.zeroconf.announce()
+
+ def set_show_msg(self, show, msg):
+ if self.zeroconf:
+ self.zeroconf.txt['msg'] = msg
+ self.last_msg = msg
+ return self.zeroconf.update_txt(show)
+
+ def resolve_all(self):
+ if self.zeroconf:
+ self.zeroconf.resolve_all()
+
+ def reannounce(self, txt):
+ self.remove_announce()
+ self.zeroconf.txt = txt
+ self.zeroconf.port = self.port
+ self.zeroconf.username = self.caller.username
+ return self.announce()
+
+ def zeroconf_init(self, show, msg):
+ self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service,
+ self.caller._on_remove_service, self.caller._on_name_conflictCB,
+ self.caller._on_disconnected, self.caller._on_error,
+ self.caller.username, self.caller.host, self.port)
+ self.zeroconf.txt['msg'] = msg
+ self.zeroconf.txt['status'] = show
+ self.zeroconf.txt['1st'] = self.caller.first
+ self.zeroconf.txt['last'] = self.caller.last
+ self.zeroconf.txt['jid'] = self.caller.jabber_id
+ self.zeroconf.txt['email'] = self.caller.email
+ self.zeroconf.username = self.caller.username
+ self.zeroconf.host = self.caller.host
+ self.zeroconf.port = self.port
+ self.last_msg = msg
+
+ def disconnect(self):
+ # to avoid recursive calls
+ if self.disconnecting:
+ return
+ if self.listener:
+ self.listener.disconnect()
+ self.listener = None
+ if self.zeroconf:
+ self.zeroconf.disconnect()
+ self.zeroconf = None
+ if self.roster:
+ self.roster.zeroconf = None
+ self.roster._data = None
+ self.roster = None
+ self.disconnecting = True
+ for i in reversed(self.disconnect_handlers):
+ log.debug('Calling disconnect handler %s' % i)
+ i()
+ self.disconnecting = False
+
+ def start_disconnect(self):
+ self.disconnect()
+
+ def kill_all_connections(self):
+ for connection in self.connections.values():
+ connection.force_disconnect()
+
+ def add_connection(self, connection, ip, port, recipient):
+ sock_hash=connection.sock_hash
+ if sock_hash not in self.connections:
+ self.connections[sock_hash] = connection
+ self.ip_to_hash[ip] = sock_hash
+ self.hash_to_port[sock_hash] = port
+ if recipient:
+ self.recipient_to_hash[recipient] = sock_hash
+
+ def remove_connection(self, sock_hash):
+ if sock_hash in self.connections:
+ del self.connections[sock_hash]
+ for i in self.recipient_to_hash:
+ if self.recipient_to_hash[i] == sock_hash:
+ del self.recipient_to_hash[i]
+ break
+ for i in self.ip_to_hash:
+ if self.ip_to_hash[i] == sock_hash:
+ del self.ip_to_hash[i]
+ break
+ if self.hash_to_port.has_key(sock_hash):
+ del self.hash_to_port[sock_hash]
+
+ def start_listener(self, port):
+ for p in range(port, port + 5):
+ self.listener = ZeroconfListener(p, self)
+ self.listener.bind()
+ if self.listener.started:
+ return p
+ self.listener = None
+ return False
+
+ def getRoster(self):
+ if self.roster:
+ return self.roster.getRoster()
+ return {}
+
+ def send(self, stanza, is_message=False, now=False, on_ok=None,
+ on_not_ok=None):
+ stanza.setFrom(self.roster.zeroconf.name)
+ to = stanza.getTo()
+
+ try:
+ item = self.roster[to]
+ except KeyError:
+ # Contact offline
+ return -1
+
+ # look for hashed connections
+ if to in self.recipient_to_hash:
+ conn = self.connections[self.recipient_to_hash[to]]
+ id_ = stanza.getID() or ''
+ if conn.add_stanza(stanza, is_message):
+ if on_ok:
+ on_ok(id_)
+ return
+
+ if item['address'] in self.ip_to_hash:
+ hash_ = self.ip_to_hash[item['address']]
+ if self.hash_to_port[hash_] == item['port']:
+ conn = self.connections[hash_]
+ id_ = stanza.getID() or ''
+ if conn.add_stanza(stanza, is_message):
+ if on_ok:
+ on_ok(id_)
+ return
+
+ # otherwise open new connection
+ if not stanza.getID():
+ stanza.setID('zero')
+ P2PClient(None, item['address'], item['port'], self,
+ [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok)
+
+ def RegisterDisconnectHandler(self, handler):
+ """
+ Register handler that will be called on disconnect
+ """
+ self.disconnect_handlers.append(handler)
+
+ def UnregisterDisconnectHandler(self, handler):
+ """
+ Unregister handler that is called on disconnect
+ """
+ self.disconnect_handlers.remove(handler)
+
+ def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None):
+ """
+ Send stanza and wait for recipient's response to it. Will call transports
+ on_timeout callback if response is not retrieved in time
+
+ Be aware: Only timeout of latest call of SendAndWait is active.
+ """
+# if timeout is None:
+# timeout = DEFAULT_TIMEOUT_SECONDS
+ def on_ok(_waitid):
+# if timeout:
+# self._owner.set_timeout(timeout)
+ to = stanza.getTo()
+ conn = None
+ if to in self.recipient_to_hash:
+ conn = self.connections[self.recipient_to_hash[to]]
+ elif item['address'] in self.ip_to_hash:
+ hash_ = self.ip_to_hash[item['address']]
+ if self.hash_to_port[hash_] == item['port']:
+ conn = self.connections[hash_]
+ if func:
+ conn.Dispatcher.on_responses[_waitid] = (func, args)
+ conn.onreceive(conn.Dispatcher._WaitForData)
+ conn.Dispatcher._expected[_waitid] = None
+ self.send(stanza, on_ok=on_ok)
+
+ def SendAndCallForResponse(self, stanza, func=None, args=None):
+ """
+ Put stanza on the wire and call back when recipient replies. Additional
+ callback arguments can be specified in args.
+ """
+ self.SendAndWaitForResponse(stanza, 0, func, args)
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
index 0d45379c3..9d9f8356a 100644
--- a/src/common/zeroconf/connection_handlers_zeroconf.py
+++ b/src/common/zeroconf/connection_handlers_zeroconf.py
@@ -2,10 +2,10 @@
## Copyright (C) 2006 Gajim Team
##
## Contributors for this file:
-## - Yann Leboulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <nkour@jabber.org>
-## - Dimitur Kirov <dkirov@gmail.com>
-## - Travis Shirk <travis@pobox.com>
+## - Yann Leboulanger <asterix@lagaule.org>
+## - Nikos Kouremenos <nkour@jabber.org>
+## - Dimitur Kirov <dkirov@gmail.com>
+## - Travis Shirk <travis@pobox.com>
## - Stefan Bethge <stefan@lanpartei.de>
##
## This file is part of Gajim.
@@ -41,153 +41,151 @@ import logging
log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible']
+ 'invisible']
# kind of events we can wait for an answer
VCARD_PUBLISHED = 'vcard_published'
VCARD_ARRIVED = 'vcard_arrived'
AGENT_REMOVED = 'agent_removed'
HAS_IDLE = True
try:
- import idle
+ import idle
except Exception:
- log.debug(_('Unable to load idle module'))
- HAS_IDLE = False
+ log.debug(_('Unable to load idle module'))
+ HAS_IDLE = False
from common import connection_handlers
from session import ChatControlSession
class ConnectionVcard(connection_handlers.ConnectionVcard):
- def add_sha(self, p, send_caps = True):
- return p
+ def add_sha(self, p, send_caps = True):
+ return p
- def add_caps(self, p):
- return p
+ def add_caps(self, p):
+ return p
- def request_vcard(self, jid = None, is_fake_jid = False):
- pass
+ def request_vcard(self, jid = None, is_fake_jid = False):
+ pass
- def send_vcard(self, vcard):
- pass
+ def send_vcard(self, vcard):
+ pass
class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestreamZeroconf,
ConnectionCommands, ConnectionPEP, connection_handlers.ConnectionHandlersBase):
- def __init__(self):
- ConnectionVcard.__init__(self)
- ConnectionBytestreamZeroconf.__init__(self)
- ConnectionCommands.__init__(self)
- connection_handlers.ConnectionHandlersBase.__init__(self)
-
- try:
- idle.init()
- except Exception:
- global HAS_IDLE
- HAS_IDLE = False
-
- def _messageCB(self, ip, con, msg):
- """
- Called when we receive a message
- """
- log.debug('Zeroconf MessageCB')
-
- frm = msg.getFrom()
- mtype = msg.getType()
- thread_id = msg.getThread()
-
- if not mtype:
- mtype = 'normal'
-
- if frm is None:
- for key in self.connection.zeroconf.contacts:
- if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
- frm = key
-
- frm = unicode(frm)
-
- session = self.get_or_create_session(frm, thread_id)
-
- if thread_id and not session.received_thread_id:
- session.received_thread_id = True
-
- if msg.getTag('feature') and msg.getTag('feature').namespace == \
- common.xmpp.NS_FEATURE:
- if gajim.HAVE_PYCRYPTO:
- self._FeatureNegCB(con, msg, session)
- return
-
- if msg.getTag('init') and msg.getTag('init').namespace == \
- common.xmpp.NS_ESESSION_INIT:
- self._InitE2ECB(con, msg, session)
-
- encrypted = False
- tim = msg.getTimestamp()
- tim = helpers.datetime_tuple(tim)
- tim = time.localtime(timegm(tim))
-
- if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO):
- encrypted = True
-
- try:
- msg = session.decrypt_stanza(msg)
- except Exception:
- self.dispatch('FAILED_DECRYPT', (frm, tim))
-
- msgtxt = msg.getBody()
- subject = msg.getSubject() # if not there, it's None
-
- # invitations
- invite = None
- encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
-
- if not encTag:
- invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
- if invite and not invite.getTag('invite'):
- invite = None
-
- if encTag and self.USE_GPG:
- #decrypt
- encmsg = encTag.getData()
-
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- if keyID:
- decmsg = self.gpg.decrypt(encmsg, keyID)
- # \x00 chars are not allowed in C (so in GTK)
- msgtxt = decmsg.replace('\x00', '')
- encrypted = True
-
- if mtype == 'error':
- self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject)
- else:
- # XXX this shouldn't be hardcoded
- if isinstance(session, ChatControlSession):
- session.received(frm, msgtxt, tim, encrypted, msg)
- else:
- session.received(msg)
- # END messageCB
-
- def store_metacontacts(self, tags):
- """
- Fake empty method
- """
- # serverside metacontacts are not supported with zeroconf
- # (there is no server)
- pass
-
- def _DiscoverItemsGetCB(self, con, iq_obj):
- log.debug('DiscoverItemsGetCB')
-
- if not self.connection or self.connected < 2:
- return
-
- if self.commandItemsQuery(con, iq_obj):
- raise common.xmpp.NodeProcessed
- node = iq_obj.getTagAttr('query', 'node')
- if node is None:
- result = iq_obj.buildReply('result')
- self.connection.send(result)
- raise common.xmpp.NodeProcessed
- if node==common.xmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
- raise common.xmpp.NodeProcessed
-
-# vim: se ts=3:
+ def __init__(self):
+ ConnectionVcard.__init__(self)
+ ConnectionBytestreamZeroconf.__init__(self)
+ ConnectionCommands.__init__(self)
+ connection_handlers.ConnectionHandlersBase.__init__(self)
+
+ try:
+ idle.init()
+ except Exception:
+ global HAS_IDLE
+ HAS_IDLE = False
+
+ def _messageCB(self, ip, con, msg):
+ """
+ Called when we receive a message
+ """
+ log.debug('Zeroconf MessageCB')
+
+ frm = msg.getFrom()
+ mtype = msg.getType()
+ thread_id = msg.getThread()
+
+ if not mtype:
+ mtype = 'normal'
+
+ if frm is None:
+ for key in self.connection.zeroconf.contacts:
+ if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
+ frm = key
+
+ frm = unicode(frm)
+
+ session = self.get_or_create_session(frm, thread_id)
+
+ if thread_id and not session.received_thread_id:
+ session.received_thread_id = True
+
+ if msg.getTag('feature') and msg.getTag('feature').namespace == \
+ common.xmpp.NS_FEATURE:
+ if gajim.HAVE_PYCRYPTO:
+ self._FeatureNegCB(con, msg, session)
+ return
+
+ if msg.getTag('init') and msg.getTag('init').namespace == \
+ common.xmpp.NS_ESESSION_INIT:
+ self._InitE2ECB(con, msg, session)
+
+ encrypted = False
+ tim = msg.getTimestamp()
+ tim = helpers.datetime_tuple(tim)
+ tim = time.localtime(timegm(tim))
+
+ if msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO):
+ encrypted = True
+
+ try:
+ msg = session.decrypt_stanza(msg)
+ except Exception:
+ self.dispatch('FAILED_DECRYPT', (frm, tim))
+
+ msgtxt = msg.getBody()
+ subject = msg.getSubject() # if not there, it's None
+
+ # invitations
+ invite = None
+ encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
+
+ if not encTag:
+ invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
+ if invite and not invite.getTag('invite'):
+ invite = None
+
+ if encTag and self.USE_GPG:
+ #decrypt
+ encmsg = encTag.getData()
+
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ if keyID:
+ decmsg = self.gpg.decrypt(encmsg, keyID)
+ # \x00 chars are not allowed in C (so in GTK)
+ msgtxt = decmsg.replace('\x00', '')
+ encrypted = True
+
+ if mtype == 'error':
+ self.dispatch_error_msg(msg, msgtxt, session, frm, tim, subject)
+ else:
+ # XXX this shouldn't be hardcoded
+ if isinstance(session, ChatControlSession):
+ session.received(frm, msgtxt, tim, encrypted, msg)
+ else:
+ session.received(msg)
+ # END messageCB
+
+ def store_metacontacts(self, tags):
+ """
+ Fake empty method
+ """
+ # serverside metacontacts are not supported with zeroconf
+ # (there is no server)
+ pass
+
+ def _DiscoverItemsGetCB(self, con, iq_obj):
+ log.debug('DiscoverItemsGetCB')
+
+ if not self.connection or self.connected < 2:
+ return
+
+ if self.commandItemsQuery(con, iq_obj):
+ raise common.xmpp.NodeProcessed
+ node = iq_obj.getTagAttr('query', 'node')
+ if node is None:
+ result = iq_obj.buildReply('result')
+ self.connection.send(result)
+ raise common.xmpp.NodeProcessed
+ if node==common.xmpp.NS_COMMANDS:
+ self.commandListQuery(con, iq_obj)
+ raise common.xmpp.NodeProcessed
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
index 2059e9b82..6076f8b13 100644
--- a/src/common/zeroconf/connection_zeroconf.py
+++ b/src/common/zeroconf/connection_zeroconf.py
@@ -1,10 +1,10 @@
-## common/zeroconf/connection_zeroconf.py
+## common/zeroconf/connection_zeroconf.py
##
## Contributors for this file:
-## - Yann Leboulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <nkour@jabber.org>
-## - Dimitur Kirov <dkirov@gmail.com>
-## - Travis Shirk <travis@pobox.com>
+## - Yann Leboulanger <asterix@lagaule.org>
+## - Nikos Kouremenos <nkour@jabber.org>
+## - Dimitur Kirov <dkirov@gmail.com>
+## - Travis Shirk <travis@pobox.com>
## - Stefan Bethge <stefan@lanpartei.de>
##
## Copyright (C) 2003-2004 Yann Leboulanger <asterix@lagaule.org>
@@ -39,7 +39,7 @@ random.seed()
import signal
if os.name != 'nt':
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
import getpass
import gobject
@@ -51,316 +51,314 @@ from common.zeroconf import zeroconf
from connection_handlers_zeroconf import *
class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
- def __init__(self, name):
- ConnectionHandlersZeroconf.__init__(self)
- # system username
- self.username = None
- self.server_resource = '' # zeroconf has no resource, fake an empty one
- self.call_resolve_timeout = False
- # we don't need a password, but must be non-empty
- self.password = 'zeroconf'
- self.autoconnect = False
-
- CommonConnection.__init__(self, name)
- self.is_zeroconf = True
-
- def get_config_values_or_default(self):
- """
- Get name, host, port from config, or create zeroconf account with default
- values
- """
- if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
- gajim.log.debug('Creating zeroconf account')
- gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'autoconnect', True)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for',
- '')
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password',
- 'zeroconf')
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'sync_with_global_status', True)
-
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port', 5298)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'is_zeroconf', True)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'use_ft_proxies', False)
- #XXX make sure host is US-ASCII
- self.host = unicode(socket.gethostname())
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname',
- self.host)
- self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- self.autoconnect = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'autoconnect')
- self.sync_with_global_status = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
- self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_first_name')
- self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_last_name')
- self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_jabber_id')
- self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_email')
-
- if not self.username:
- self.username = unicode(getpass.getuser())
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name',
- self.username)
- else:
- self.username = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'name')
- # END __init__
-
- 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')
-
- self.disconnect()
- self.change_status(self.old_show, self.status)
-
- def disable_account(self):
- self.disconnect()
-
- def _on_resolve_timeout(self):
- if self.connected:
- self.connection.resolve_all()
- diffs = self.roster.getDiffs()
- for key in diffs:
- self.roster.setItem(key)
- self.dispatch('ROSTER_INFO', (key, self.roster.getName(key),
- 'both', 'no', self.roster.getGroups(key)))
- self.dispatch('NOTIFY', (key, self.roster.getStatus(key),
- self.roster.getMessage(key), 'local', 0, None, 0, None))
- #XXX open chat windows don't get refreshed (full name), add that
- return self.call_resolve_timeout
-
- # callbacks called from zeroconf
- def _on_new_service(self, jid):
- self.roster.setItem(jid)
- self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no',
- self.roster.getGroups(jid)))
- self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid),
- self.roster.getMessage(jid), 'local', 0, None, 0, None))
-
- def _on_remove_service(self, jid):
- self.roster.delItem(jid)
- # 'NOTIFY' (account, (jid, status, status message, resource, priority,
- # keyID, timestamp, contact_nickname))
- self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None))
-
- def _disconnectedReconnCB(self):
- """
- Called when we are disconnected. Comes from network manager for example
- we don't try to reconnect, network manager will tell us when we can
- """
- if gajim.account_is_connected(self.name):
- # we cannot change our status to offline or connecting
- # after we auth to server
- self.old_show = STATUS_LIST[self.connected]
- self.connected = 0
- self.dispatch('STATUS', 'offline')
- # random number to show we wait network manager to send us a reconenct
- self.time_to_reconnect = 5
- self.on_purpose = False
-
- def _on_name_conflictCB(self, alt_name):
- self.disconnect()
- self.dispatch('STATUS', 'offline')
- self.dispatch('ZC_NAME_CONFLICT', alt_name)
-
- def _on_error(self, message):
- self.dispatch('ERROR', (_('Avahi error'),
- _('%s\nLink-local messaging might not work properly.') % message))
-
- def connect(self, show='online', msg=''):
- self.get_config_values_or_default()
- if not self.connection:
- self.connection = client_zeroconf.ClientZeroconf(self)
- if not zeroconf.test_zeroconf():
- self.dispatch('STATUS', 'offline')
- self.status = 'offline'
- self.dispatch('CONNECTION_LOST',
- (_('Could not connect to "%s"') % self.name,
- _('Please check if Avahi or Bonjour is installed.')))
- self.disconnect()
- return
- result = self.connection.connect(show, msg)
- if not result:
- self.dispatch('STATUS', 'offline')
- self.status = 'offline'
- if result is False:
- self.dispatch('CONNECTION_LOST',
- (_('Could not start local service'),
- _('Unable to bind to port %d.' % self.port)))
- else: # result is None
- self.dispatch('CONNECTION_LOST',
- (_('Could not start local service'),
- _('Please check if avahi-daemon is running.')))
- self.disconnect()
- return
- else:
- self.connection.announce()
- self.roster = self.connection.getRoster()
- self.dispatch('ROSTER', self.roster)
-
- # display contacts already detected and resolved
- for jid in self.roster.keys():
- self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both',
- 'no', self.roster.getGroups(jid)))
- self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid),
- self.roster.getMessage(jid), 'local', 0, None, 0, None))
-
- self.connected = STATUS_LIST.index(show)
-
- # refresh all contacts data every five seconds
- self.call_resolve_timeout = True
- gobject.timeout_add_seconds(5, self._on_resolve_timeout)
- return True
-
- def disconnect(self, on_purpose=False):
- self.connected = 0
- self.time_to_reconnect = None
- if self.connection:
- self.connection.disconnect()
- self.connection = None
- # stop calling the timeout
- self.call_resolve_timeout = False
-
- def reannounce(self):
- if self.connected:
- txt = {}
- txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_first_name')
- txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_last_name')
- txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_jabber_id')
- txt['email'] = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
- self.connection.reannounce(txt)
-
- def update_details(self):
- if self.connection:
- port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- if port != self.port:
- self.port = port
- last_msg = self.connection.last_msg
- self.disconnect()
- if not self.connect(self.status, last_msg):
- return
- if self.status != 'invisible':
- self.connection.announce()
- else:
- self.reannounce()
-
- def connect_and_init(self, show, msg, sign_msg):
- # to check for errors from zeroconf
- check = True
- if not self.connect(show, msg):
- return
- if show != 'invisible':
- check = self.connection.announce()
- else:
- self.connected = STATUS_LIST.index(show)
- self.dispatch('SIGNED_IN', ())
-
- # stay offline when zeroconf does something wrong
- if check:
- self.dispatch('STATUS', show)
- else:
- # show notification that avahi or system bus is down
- self.dispatch('STATUS', 'offline')
- self.status = 'offline'
- self.dispatch('CONNECTION_LOST',
- (_('Could not change status of account "%s"') % self.name,
- _('Please check if avahi-daemon is running.')))
-
- 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=[]):
-
- def on_send_ok(msg_id):
- self.dispatch('MSGSENT', (jid, msg, keyID))
- if callback:
- callback(msg_id, *callback_args)
-
- self.log_message(jid, msg, forward_from, session, original_message,
- subject, type_)
-
- def on_send_not_ok(reason):
- reason += ' ' + _('Your message could not be sent.')
- self.dispatch('MSGERROR', [jid, -1, reason, None, None, session])
-
- def cb(jid, msg, keyID, forward_from, session, original_message, subject,
- type_, msg_iq):
- ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok,
- on_not_ok=on_send_not_ok)
-
- 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
- if not self.connection:
- return
- if not isinstance(stanza, common.xmpp.Node):
- stanza = common.xmpp.Protocol(node=stanza)
- self.connection.send(stanza)
-
- def _event_dispatcher(self, realm, event, data):
- CommonConnection._event_dispatcher(self, realm, event, data)
- if realm == '':
- if event == common.xmpp.transports_nb.DATA_ERROR:
- thread_id = data[1]
- frm = unicode(data[0])
- session = self.get_or_create_session(frm, thread_id)
- self.dispatch('MSGERROR', [frm, -1,
- _('Connection to host could not be established: Timeout while '
- 'sending data.'), None, None, session])
+ def __init__(self, name):
+ ConnectionHandlersZeroconf.__init__(self)
+ # system username
+ self.username = None
+ self.server_resource = '' # zeroconf has no resource, fake an empty one
+ self.call_resolve_timeout = False
+ # we don't need a password, but must be non-empty
+ self.password = 'zeroconf'
+ self.autoconnect = False
+
+ CommonConnection.__init__(self, name)
+ self.is_zeroconf = True
+
+ def get_config_values_or_default(self):
+ """
+ Get name, host, port from config, or create zeroconf account with default
+ values
+ """
+ if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
+ gajim.log.debug('Creating zeroconf account')
+ gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'autoconnect', True)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for',
+ '')
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password',
+ 'zeroconf')
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'sync_with_global_status', True)
+
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'custom_port', 5298)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'is_zeroconf', True)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'use_ft_proxies', False)
+ #XXX make sure host is US-ASCII
+ self.host = unicode(socket.gethostname())
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname',
+ self.host)
+ self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'custom_port')
+ self.autoconnect = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'autoconnect')
+ self.sync_with_global_status = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
+ self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_first_name')
+ self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_last_name')
+ self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_jabber_id')
+ self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_email')
+
+ if not self.username:
+ self.username = unicode(getpass.getuser())
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name',
+ self.username)
+ else:
+ self.username = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'name')
+ # END __init__
+
+ def check_jid(self, jid):
+ return jid
+
+ def _reconnect(self):
+ # Do not try to reco while we are already trying
+ self.time_to_reconnect = None
+ gajim.log.debug('reconnect')
+
+ self.disconnect()
+ self.change_status(self.old_show, self.status)
+
+ def disable_account(self):
+ self.disconnect()
+
+ def _on_resolve_timeout(self):
+ if self.connected:
+ self.connection.resolve_all()
+ diffs = self.roster.getDiffs()
+ for key in diffs:
+ self.roster.setItem(key)
+ self.dispatch('ROSTER_INFO', (key, self.roster.getName(key),
+ 'both', 'no', self.roster.getGroups(key)))
+ self.dispatch('NOTIFY', (key, self.roster.getStatus(key),
+ self.roster.getMessage(key), 'local', 0, None, 0, None))
+ #XXX open chat windows don't get refreshed (full name), add that
+ return self.call_resolve_timeout
+
+ # callbacks called from zeroconf
+ def _on_new_service(self, jid):
+ self.roster.setItem(jid)
+ self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no',
+ self.roster.getGroups(jid)))
+ self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid),
+ self.roster.getMessage(jid), 'local', 0, None, 0, None))
+
+ def _on_remove_service(self, jid):
+ self.roster.delItem(jid)
+ # 'NOTIFY' (account, (jid, status, status message, resource, priority,
+ # keyID, timestamp, contact_nickname))
+ self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None))
+
+ def _disconnectedReconnCB(self):
+ """
+ Called when we are disconnected. Comes from network manager for example
+ we don't try to reconnect, network manager will tell us when we can
+ """
+ if gajim.account_is_connected(self.name):
+ # we cannot change our status to offline or connecting
+ # after we auth to server
+ self.old_show = STATUS_LIST[self.connected]
+ self.connected = 0
+ self.dispatch('STATUS', 'offline')
+ # random number to show we wait network manager to send us a reconenct
+ self.time_to_reconnect = 5
+ self.on_purpose = False
+
+ def _on_name_conflictCB(self, alt_name):
+ self.disconnect()
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('ZC_NAME_CONFLICT', alt_name)
+
+ def _on_error(self, message):
+ self.dispatch('ERROR', (_('Avahi error'),
+ _('%s\nLink-local messaging might not work properly.') % message))
+
+ def connect(self, show='online', msg=''):
+ self.get_config_values_or_default()
+ if not self.connection:
+ self.connection = client_zeroconf.ClientZeroconf(self)
+ if not zeroconf.test_zeroconf():
+ self.dispatch('STATUS', 'offline')
+ self.status = 'offline'
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not connect to "%s"') % self.name,
+ _('Please check if Avahi or Bonjour is installed.')))
+ self.disconnect()
+ return
+ result = self.connection.connect(show, msg)
+ if not result:
+ self.dispatch('STATUS', 'offline')
+ self.status = 'offline'
+ if result is False:
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not start local service'),
+ _('Unable to bind to port %d.' % self.port)))
+ else: # result is None
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not start local service'),
+ _('Please check if avahi-daemon is running.')))
+ self.disconnect()
+ return
+ else:
+ self.connection.announce()
+ self.roster = self.connection.getRoster()
+ self.dispatch('ROSTER', self.roster)
+
+ # display contacts already detected and resolved
+ for jid in self.roster.keys():
+ self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both',
+ 'no', self.roster.getGroups(jid)))
+ self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid),
+ self.roster.getMessage(jid), 'local', 0, None, 0, None))
+
+ self.connected = STATUS_LIST.index(show)
+
+ # refresh all contacts data every five seconds
+ self.call_resolve_timeout = True
+ gobject.timeout_add_seconds(5, self._on_resolve_timeout)
+ return True
+
+ def disconnect(self, on_purpose=False):
+ self.connected = 0
+ self.time_to_reconnect = None
+ if self.connection:
+ self.connection.disconnect()
+ self.connection = None
+ # stop calling the timeout
+ self.call_resolve_timeout = False
+
+ def reannounce(self):
+ if self.connected:
+ txt = {}
+ txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_first_name')
+ txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_last_name')
+ txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_jabber_id')
+ txt['email'] = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
+ self.connection.reannounce(txt)
+
+ def update_details(self):
+ if self.connection:
+ port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'custom_port')
+ if port != self.port:
+ self.port = port
+ last_msg = self.connection.last_msg
+ self.disconnect()
+ if not self.connect(self.status, last_msg):
+ return
+ if self.status != 'invisible':
+ self.connection.announce()
+ else:
+ self.reannounce()
+
+ def connect_and_init(self, show, msg, sign_msg):
+ # to check for errors from zeroconf
+ check = True
+ if not self.connect(show, msg):
+ return
+ if show != 'invisible':
+ check = self.connection.announce()
+ else:
+ self.connected = STATUS_LIST.index(show)
+ self.dispatch('SIGNED_IN', ())
+
+ # stay offline when zeroconf does something wrong
+ if check:
+ self.dispatch('STATUS', show)
+ else:
+ # show notification that avahi or system bus is down
+ self.dispatch('STATUS', 'offline')
+ self.status = 'offline'
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not change status of account "%s"') % self.name,
+ _('Please check if avahi-daemon is running.')))
+
+ 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=[]):
+
+ def on_send_ok(msg_id):
+ self.dispatch('MSGSENT', (jid, msg, keyID))
+ if callback:
+ callback(msg_id, *callback_args)
+
+ self.log_message(jid, msg, forward_from, session, original_message,
+ subject, type_)
+
+ def on_send_not_ok(reason):
+ reason += ' ' + _('Your message could not be sent.')
+ self.dispatch('MSGERROR', [jid, -1, reason, None, None, session])
+
+ def cb(jid, msg, keyID, forward_from, session, original_message, subject,
+ type_, msg_iq):
+ ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok,
+ on_not_ok=on_send_not_ok)
+
+ 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
+ if not self.connection:
+ return
+ if not isinstance(stanza, common.xmpp.Node):
+ stanza = common.xmpp.Protocol(node=stanza)
+ self.connection.send(stanza)
+
+ def _event_dispatcher(self, realm, event, data):
+ CommonConnection._event_dispatcher(self, realm, event, data)
+ if realm == '':
+ if event == common.xmpp.transports_nb.DATA_ERROR:
+ thread_id = data[1]
+ frm = unicode(data[0])
+ session = self.get_or_create_session(frm, thread_id)
+ self.dispatch('MSGERROR', [frm, -1,
+ _('Connection to host could not be established: Timeout while '
+ 'sending data.'), None, None, session])
# END ConnectionZeroconf
-
-# vim: se ts=3:
diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py
index 4fe05aee1..17325192e 100644
--- a/src/common/zeroconf/roster_zeroconf.py
+++ b/src/common/zeroconf/roster_zeroconf.py
@@ -21,145 +21,143 @@
from common.zeroconf import zeroconf
class Roster:
- def __init__(self, zeroconf):
- self._data = None
- self.zeroconf = zeroconf # our zeroconf instance
-
- def update_roster(self):
- for val in self.zeroconf.contacts.values():
- self.setItem(val[zeroconf.C_NAME])
-
- def getRoster(self):
- #print 'roster_zeroconf.py: getRoster'
- if self._data is None:
- self._data = {}
- self.update_roster()
- return self
-
- def getDiffs(self):
- """
- Update the roster with new data and return dict with jid -> new status
- pairs to do notifications and stuff
- """
- diffs = {}
- old_data = self._data.copy()
- self.update_roster()
- for key in old_data.keys():
- if key in self._data:
- if old_data[key] != self._data[key]:
- diffs[key] = self._data[key]['status']
- #print 'roster_zeroconf.py: diffs:' + str(diffs)
- return diffs
-
- def setItem(self, jid, name='', groups=''):
- #print 'roster_zeroconf.py: setItem %s' % jid
- contact = self.zeroconf.get_contact(jid)
- if not contact:
- return
-
- host, address, port = contact[4:7]
- txt = contact[8]
-
- self._data[jid]={}
- self._data[jid]['ask'] = 'none'
- self._data[jid]['subscription'] = 'both'
- self._data[jid]['groups'] = []
- self._data[jid]['resources'] = {}
- self._data[jid]['address'] = address
- self._data[jid]['host'] = host
- self._data[jid]['port'] = port
- txt_dict = self.zeroconf.txt_array_to_dict(txt)
- status = txt_dict.get('status', '')
- if not status:
- status = 'avail'
- nm = txt_dict.get('1st', '')
- if 'last' in txt_dict:
- if nm != '':
- nm += ' '
- nm += txt_dict['last']
- if nm:
- self._data[jid]['name'] = nm
- else:
- self._data[jid]['name'] = jid
- if status == 'avail':
- status = 'online'
- self._data[jid]['txt_dict'] = txt_dict
- if 'msg' not in self._data[jid]['txt_dict']:
- self._data[jid]['txt_dict']['msg'] = ''
- self._data[jid]['status'] = status
- self._data[jid]['show'] = status
-
- def setItemMulti(self, items):
- for i in items:
- self.setItem(jid=i['jid'], name=i['name'], groups=i['groups'])
-
- def delItem(self, jid):
- #print 'roster_zeroconf.py: delItem %s' % jid
- if jid in self._data:
- del self._data[jid]
-
- def getItem(self, jid):
- #print 'roster_zeroconf.py: getItem: %s' % jid
- if jid in self._data:
- return self._data[jid]
-
- def __getitem__(self, jid):
- #print 'roster_zeroconf.py: __getitem__'
- return self._data[jid]
-
- def getItems(self):
- #print 'roster_zeroconf.py: getItems'
- # Return list of all [bare] JIDs that the roster currently tracks.
- return self._data.keys()
-
- def keys(self):
- #print 'roster_zeroconf.py: keys'
- return self._data.keys()
-
- def getRaw(self):
- #print 'roster_zeroconf.py: getRaw'
- return self._data
-
- def getResources(self, jid):
- #print 'roster_zeroconf.py: getResources(%s)' % jid
- return {}
-
- def getGroups(self, jid):
- return self._data[jid]['groups']
-
- def getName(self, jid):
- if jid in self._data:
- return self._data[jid]['name']
-
- def getStatus(self, jid):
- if jid in self._data:
- return self._data[jid]['status']
-
- def getMessage(self, jid):
- if jid in self._data:
- return self._data[jid]['txt_dict']['msg']
-
- def getShow(self, jid):
- #print 'roster_zeroconf.py: getShow'
- return self.getStatus(jid)
-
- def getPriority(self, jid):
- return 5
-
- def getSubscription(self, jid):
- #print 'roster_zeroconf.py: getSubscription'
- return 'both'
-
- def Subscribe(self, jid):
- pass
-
- def Unsubscribe(self, jid):
- pass
-
- def Authorize(self, jid):
- pass
-
- def Unauthorize(self, jid):
- pass
-
-# vim: se ts=3:
+ def __init__(self, zeroconf):
+ self._data = None
+ self.zeroconf = zeroconf # our zeroconf instance
+
+ def update_roster(self):
+ for val in self.zeroconf.contacts.values():
+ self.setItem(val[zeroconf.C_NAME])
+
+ def getRoster(self):
+ #print 'roster_zeroconf.py: getRoster'
+ if self._data is None:
+ self._data = {}
+ self.update_roster()
+ return self
+
+ def getDiffs(self):
+ """
+ Update the roster with new data and return dict with jid -> new status
+ pairs to do notifications and stuff
+ """
+ diffs = {}
+ old_data = self._data.copy()
+ self.update_roster()
+ for key in old_data.keys():
+ if key in self._data:
+ if old_data[key] != self._data[key]:
+ diffs[key] = self._data[key]['status']
+ #print 'roster_zeroconf.py: diffs:' + str(diffs)
+ return diffs
+
+ def setItem(self, jid, name='', groups=''):
+ #print 'roster_zeroconf.py: setItem %s' % jid
+ contact = self.zeroconf.get_contact(jid)
+ if not contact:
+ return
+
+ host, address, port = contact[4:7]
+ txt = contact[8]
+
+ self._data[jid]={}
+ self._data[jid]['ask'] = 'none'
+ self._data[jid]['subscription'] = 'both'
+ self._data[jid]['groups'] = []
+ self._data[jid]['resources'] = {}
+ self._data[jid]['address'] = address
+ self._data[jid]['host'] = host
+ self._data[jid]['port'] = port
+ txt_dict = self.zeroconf.txt_array_to_dict(txt)
+ status = txt_dict.get('status', '')
+ if not status:
+ status = 'avail'
+ nm = txt_dict.get('1st', '')
+ if 'last' in txt_dict:
+ if nm != '':
+ nm += ' '
+ nm += txt_dict['last']
+ if nm:
+ self._data[jid]['name'] = nm
+ else:
+ self._data[jid]['name'] = jid
+ if status == 'avail':
+ status = 'online'
+ self._data[jid]['txt_dict'] = txt_dict
+ if 'msg' not in self._data[jid]['txt_dict']:
+ self._data[jid]['txt_dict']['msg'] = ''
+ self._data[jid]['status'] = status
+ self._data[jid]['show'] = status
+
+ def setItemMulti(self, items):
+ for i in items:
+ self.setItem(jid=i['jid'], name=i['name'], groups=i['groups'])
+
+ def delItem(self, jid):
+ #print 'roster_zeroconf.py: delItem %s' % jid
+ if jid in self._data:
+ del self._data[jid]
+
+ def getItem(self, jid):
+ #print 'roster_zeroconf.py: getItem: %s' % jid
+ if jid in self._data:
+ return self._data[jid]
+
+ def __getitem__(self, jid):
+ #print 'roster_zeroconf.py: __getitem__'
+ return self._data[jid]
+
+ def getItems(self):
+ #print 'roster_zeroconf.py: getItems'
+ # Return list of all [bare] JIDs that the roster currently tracks.
+ return self._data.keys()
+
+ def keys(self):
+ #print 'roster_zeroconf.py: keys'
+ return self._data.keys()
+
+ def getRaw(self):
+ #print 'roster_zeroconf.py: getRaw'
+ return self._data
+
+ def getResources(self, jid):
+ #print 'roster_zeroconf.py: getResources(%s)' % jid
+ return {}
+
+ def getGroups(self, jid):
+ return self._data[jid]['groups']
+
+ def getName(self, jid):
+ if jid in self._data:
+ return self._data[jid]['name']
+
+ def getStatus(self, jid):
+ if jid in self._data:
+ return self._data[jid]['status']
+
+ def getMessage(self, jid):
+ if jid in self._data:
+ return self._data[jid]['txt_dict']['msg']
+
+ def getShow(self, jid):
+ #print 'roster_zeroconf.py: getShow'
+ return self.getStatus(jid)
+
+ def getPriority(self, jid):
+ return 5
+
+ def getSubscription(self, jid):
+ #print 'roster_zeroconf.py: getSubscription'
+ return 'both'
+
+ def Subscribe(self, jid):
+ pass
+
+ def Unsubscribe(self, jid):
+ pass
+
+ def Authorize(self, jid):
+ pass
+
+ def Unauthorize(self, jid):
+ pass
diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py
index a99f0390d..36c2d8ab5 100644
--- a/src/common/zeroconf/zeroconf.py
+++ b/src/common/zeroconf/zeroconf.py
@@ -21,29 +21,27 @@ C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \
C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9)
def test_avahi():
- try:
- import avahi
- except ImportError:
- return False
- return True
+ try:
+ import avahi
+ except ImportError:
+ return False
+ return True
def test_bonjour():
- try:
- import pybonjour
- except ImportError:
- return False
- except WindowsError:
- return False
- return True
+ try:
+ import pybonjour
+ except ImportError:
+ return False
+ except WindowsError:
+ return False
+ return True
def test_zeroconf():
- return test_avahi() or test_bonjour()
+ return test_avahi() or test_bonjour()
if test_avahi():
- from common.zeroconf import zeroconf_avahi
- Zeroconf = zeroconf_avahi.Zeroconf
+ from common.zeroconf import zeroconf_avahi
+ Zeroconf = zeroconf_avahi.Zeroconf
elif test_bonjour():
- from common.zeroconf import zeroconf_bonjour
- Zeroconf = zeroconf_bonjour.Zeroconf
-
-# vim: se ts=3:
+ from common.zeroconf import zeroconf_bonjour
+ Zeroconf = zeroconf_bonjour.Zeroconf
diff --git a/src/common/zeroconf/zeroconf_avahi.py b/src/common/zeroconf/zeroconf_avahi.py
index 1db21adb4..33c0878ec 100644
--- a/src/common/zeroconf/zeroconf_avahi.py
+++ b/src/common/zeroconf/zeroconf_avahi.py
@@ -21,433 +21,431 @@ import logging
log = logging.getLogger('gajim.c.z.zeroconf_avahi')
try:
- import dbus.glib
+ import dbus.glib
except ImportError, e:
- pass
+ pass
from common.zeroconf.zeroconf import C_BARE_NAME, C_INTERFACE, C_PROTOCOL, C_DOMAIN
class Zeroconf:
- def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
- disconnected_CB, error_CB, name, host, port):
- self.avahi = None
- self.domain = None # specific domain to browse
- self.stype = '_presence._tcp'
- self.port = port # listening port that gets announced
- self.username = name
- self.host = host
- self.txt = {} # service data
-
- #XXX these CBs should be set to None when we destroy the object
- # (go offline), because they create a circular reference
- self.new_serviceCB = new_serviceCB
- self.remove_serviceCB = remove_serviceCB
- self.name_conflictCB = name_conflictCB
- self.disconnected_CB = disconnected_CB
- self.error_CB = error_CB
-
- self.service_browser = None
- self.domain_browser = None
- self.bus = None
- self.server = None
- self.contacts = {} # all current local contacts with data
- self.entrygroup = None
- self.connected = False
- self.announced = False
- self.invalid_self_contact = {}
-
-
- ## handlers for dbus callbacks
- def entrygroup_commit_error_CB(self, err):
- # left blank for possible later usage
- pass
-
- def error_callback1(self, err):
- log.debug('Error while resolving: ' + str(err))
-
- def error_callback(self, err):
- log.debug(str(err))
- # timeouts are non-critical
- if str(err) != 'Timeout reached':
- self.disconnect()
- self.disconnected_CB()
-
- def new_service_callback(self, interface, protocol, name, stype, domain,
- flags):
- log.debug('Found service %s in domain %s on %i.%i.' % (name, domain,
- interface, protocol))
- if not self.connected:
- return
-
- # synchronous resolving
- self.server.ResolveService( int(interface), int(protocol), name, stype,
- domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
- reply_handler=self.service_resolved_callback,
- error_handler=self.error_callback1)
-
- def remove_service_callback(self, interface, protocol, name, stype, domain,
- flags):
- log.debug('Service %s in domain %s on %i.%i disappeared.' % (name,
- domain, interface, protocol))
- if not self.connected:
- return
- if name != self.name:
- for key in self.contacts.keys():
- if self.contacts[key][C_BARE_NAME] == name:
- del self.contacts[key]
- self.remove_serviceCB(key)
- return
-
- def new_service_type(self, interface, protocol, stype, domain, flags):
- # Are we already browsing this domain for this type?
- if self.service_browser:
- return
-
- object_path = self.server.ServiceBrowserNew(interface, protocol, \
- stype, domain, dbus.UInt32(0))
-
- self.service_browser = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, object_path),
- self.avahi.DBUS_INTERFACE_SERVICE_BROWSER)
- self.service_browser.connect_to_signal('ItemNew',
- self.new_service_callback)
- self.service_browser.connect_to_signal('ItemRemove',
- self.remove_service_callback)
- self.service_browser.connect_to_signal('Failure', self.error_callback)
-
- def new_domain_callback(self,interface, protocol, domain, flags):
- if domain != 'local':
- self.browse_domain(interface, protocol, domain)
-
- def txt_array_to_dict(self, txt_array):
- txt_dict = {}
- for els in txt_array:
- key, val = '', None
- for c in els:
- c = chr(c)
- if val is None:
- if c == '=':
- val = ''
- else:
- key += c
- else:
- val += c
- if val is None: # missing '='
- val = ''
- txt_dict[key] = val.decode('utf-8', 'ignore')
- return txt_dict
-
- def service_resolved_callback(self, interface, protocol, name, stype, domain,
- host, aprotocol, address, port, txt, flags):
- log.debug('Service data for service %s in domain %s on %i.%i:'
- % (name, domain, interface, protocol))
- log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address,
- port, self.txt_array_to_dict(txt)))
- if not self.connected:
- return
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- self.contacts[name] = (name, domain, interface, protocol, host,
- address, port, bare_name, txt)
- self.new_serviceCB(name)
- else:
- # remember data
- # In case this is not our own record but of another
- # gajim instance on the same machine,
- # it will be used when we get a new name.
- self.invalid_self_contact[name] = (name, domain, interface, protocol,
- host, address, port, bare_name, txt)
-
-
- # different handler when resolving all contacts
- def service_resolved_all_callback(self, interface, protocol, name, stype,
- domain, host, aprotocol, address, port, txt, flags):
- if not self.connected:
- return
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
- self.contacts[name] = (name, domain, interface, protocol, host, address,
- port, bare_name, txt)
-
- def service_added_callback(self):
- log.debug('Service successfully added')
-
- def service_committed_callback(self):
- log.debug('Service successfully committed')
-
- def service_updated_callback(self):
- log.debug('Service successfully updated')
-
- def service_add_fail_callback(self, err):
- log.debug('Error while adding service. %s' % str(err))
- if 'Local name collision' in str(err):
- alternative_name = self.server.GetAlternativeServiceName(self.username)
- self.name_conflictCB(alternative_name)
- return
- self.error_CB(_('Error while adding service. %s') % str(err))
- self.disconnect()
-
- def server_state_changed_callback(self, state, error):
- log.debug('server state changed to %s' % state)
- if state == self.avahi.SERVER_RUNNING:
- self.create_service()
- elif state in (self.avahi.SERVER_COLLISION,
- self.avahi.SERVER_REGISTERING):
- self.disconnect()
- self.entrygroup.Reset()
- elif state == self.avahi.CLIENT_FAILURE:
- # does it ever go here?
- log.debug('CLIENT FAILURE')
-
- def entrygroup_state_changed_callback(self, state, error):
- # the name is already present, so recreate
- if state == self.avahi.ENTRY_GROUP_COLLISION:
- log.debug('zeroconf.py: local name collision')
- self.service_add_fail_callback('Local name collision')
- elif state == self.avahi.ENTRY_GROUP_FAILURE:
- self.disconnect()
- self.entrygroup.Reset()
- log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that'
- ' should not happen)')
-
- # make zeroconf-valid names
- def replace_show(self, show):
- if show in ['chat', 'online', '']:
- return 'avail'
- elif show == 'xa':
- return 'away'
- return show
-
- def avahi_txt(self):
- utf8_dict = {}
- for key in self.txt:
- val = self.txt[key]
- if isinstance(val, unicode):
- utf8_dict[key] = val.encode('utf-8')
- else:
- utf8_dict[key] = val
- return self.avahi.dict_to_txt_array(utf8_dict)
-
- def create_service(self):
- try:
- if not self.entrygroup:
- # create an EntryGroup for publishing
- self.entrygroup = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, self.server.EntryGroupNew()),
- self.avahi.DBUS_INTERFACE_ENTRY_GROUP)
- self.entrygroup.connect_to_signal('StateChanged',
- self.entrygroup_state_changed_callback)
-
- txt = {}
-
- # remove empty keys
- for key,val in self.txt.iteritems():
- if val:
- txt[key] = val
-
- txt['port.p2pj'] = self.port
- txt['version'] = 1
- txt['txtvers'] = 1
-
- # replace gajim's show messages with compatible ones
- if 'status' in self.txt:
- txt['status'] = self.replace_show(self.txt['status'])
- else:
- txt['status'] = 'avail'
-
- self.txt = txt
- log.debug('Publishing service %s of type %s' % (self.name,
- self.stype))
- self.entrygroup.AddService(self.avahi.IF_UNSPEC,
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
- '', dbus.UInt16(self.port), self.avahi_txt(),
- reply_handler=self.service_added_callback,
- error_handler=self.service_add_fail_callback)
-
- self.entrygroup.Commit(reply_handler=self.service_committed_callback,
- error_handler=self.entrygroup_commit_error_CB)
-
- return True
-
- except dbus.DBusException, e:
- log.debug(str(e))
- return False
-
- def announce(self):
- if not self.connected:
- return False
-
- state = self.server.GetState()
- if state == self.avahi.SERVER_RUNNING:
- self.create_service()
- self.announced = True
- return True
-
- def remove_announce(self):
- if self.announced == False:
- return False
- try:
- if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE:
- self.entrygroup.Reset()
- self.entrygroup.Free()
- # .Free() has mem leaks
- self.entrygroup._obj._bus = None
- self.entrygroup._obj = None
- self.entrygroup = None
- self.announced = False
-
- return True
- else:
- return False
- except dbus.DBusException:
- log.debug("Can't remove service. That should not happen")
-
- def browse_domain(self, interface, protocol, domain):
- self.new_service_type(interface, protocol, self.stype, domain, '')
-
- def avahi_dbus_connect_cb(self, a, connect, disconnect):
- if connect != "":
- log.debug('Lost connection to avahi-daemon')
- self.disconnect()
- if self.disconnected_CB:
- self.disconnected_CB()
- else:
- log.debug('We are connected to avahi-daemon')
-
- # connect to dbus
- def connect_dbus(self):
- try:
- import dbus
- except ImportError:
- log.debug('Error: python-dbus needs to be installed. No '
- 'zeroconf support.')
- return False
- if self.bus:
- return True
- try:
- self.bus = dbus.SystemBus()
- self.bus.add_signal_receiver(self.avahi_dbus_connect_cb,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='org.freedesktop.Avahi')
- except Exception, e:
- # System bus is not present
- self.bus = None
- log.debug(str(e))
- return False
- else:
- return True
-
- # connect to avahi
- def connect_avahi(self):
- if not self.connect_dbus():
- return False
- try:
- import avahi
- self.avahi = avahi
- except ImportError:
- log.debug('Error: python-avahi needs to be installed. No '
- 'zeroconf support.')
- return False
-
- if self.server:
- return True
- try:
- self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME,
- self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER)
- self.server.connect_to_signal('StateChanged',
- self.server_state_changed_callback)
- except Exception, e:
- # Avahi service is not present
- self.server = None
- log.debug(str(e))
- return False
- else:
- return True
-
- def connect(self):
- self.name = self.username + '@' + self.host # service name
- if not self.connect_avahi():
- return False
-
- self.connected = True
- # start browsing
- if self.domain is None:
- # Explicitly browse .local
- self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
- 'local')
-
- # Browse for other browsable domains
- self.domain_browser = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, self.server.DomainBrowserNew(
- self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '',
- self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))),
- self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
- self.domain_browser.connect_to_signal('ItemNew',
- self.new_domain_callback)
- self.domain_browser.connect_to_signal('Failure', self.error_callback)
- else:
- self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
- self.domain)
-
- return True
-
- def disconnect(self):
- if self.connected:
- self.connected = False
- if self.service_browser:
- self.service_browser.Free()
- self.service_browser._obj._bus = None
- self.service_browser._obj = None
- if self.domain_browser:
- self.domain_browser.Free()
- self.domain_browser._obj._bus = None
- self.domain_browser._obj = None
- self.remove_announce()
- self.server._obj._bus = None
- self.server._obj = None
- self.server = None
- self.service_browser = None
- self.domain_browser = None
-
- # refresh txt data of all contacts manually (no callback available)
- def resolve_all(self):
- if not self.connected:
- return
- for val in self.contacts.values():
- self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]),
- val[C_BARE_NAME], self.stype, val[C_DOMAIN],
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
- reply_handler=self.service_resolved_all_callback,
- error_handler=self.error_callback)
-
- def get_contacts(self):
- return self.contacts
-
- def get_contact(self, jid):
- if not jid in self.contacts:
- return None
- return self.contacts[jid]
-
- def update_txt(self, show = None):
- if show:
- self.txt['status'] = self.replace_show(show)
-
- txt = self.avahi_txt()
- if self.connected and self.entrygroup:
- self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC,
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
- txt, reply_handler=self.service_updated_callback,
- error_handler=self.error_callback)
- return True
- else:
- return False
+ def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
+ disconnected_CB, error_CB, name, host, port):
+ self.avahi = None
+ self.domain = None # specific domain to browse
+ self.stype = '_presence._tcp'
+ self.port = port # listening port that gets announced
+ self.username = name
+ self.host = host
+ self.txt = {} # service data
+
+ #XXX these CBs should be set to None when we destroy the object
+ # (go offline), because they create a circular reference
+ self.new_serviceCB = new_serviceCB
+ self.remove_serviceCB = remove_serviceCB
+ self.name_conflictCB = name_conflictCB
+ self.disconnected_CB = disconnected_CB
+ self.error_CB = error_CB
+
+ self.service_browser = None
+ self.domain_browser = None
+ self.bus = None
+ self.server = None
+ self.contacts = {} # all current local contacts with data
+ self.entrygroup = None
+ self.connected = False
+ self.announced = False
+ self.invalid_self_contact = {}
+
+
+ ## handlers for dbus callbacks
+ def entrygroup_commit_error_CB(self, err):
+ # left blank for possible later usage
+ pass
+
+ def error_callback1(self, err):
+ log.debug('Error while resolving: ' + str(err))
+
+ def error_callback(self, err):
+ log.debug(str(err))
+ # timeouts are non-critical
+ if str(err) != 'Timeout reached':
+ self.disconnect()
+ self.disconnected_CB()
+
+ def new_service_callback(self, interface, protocol, name, stype, domain,
+ flags):
+ log.debug('Found service %s in domain %s on %i.%i.' % (name, domain,
+ interface, protocol))
+ if not self.connected:
+ return
+
+ # synchronous resolving
+ self.server.ResolveService( int(interface), int(protocol), name, stype,
+ domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
+ reply_handler=self.service_resolved_callback,
+ error_handler=self.error_callback1)
+
+ def remove_service_callback(self, interface, protocol, name, stype, domain,
+ flags):
+ log.debug('Service %s in domain %s on %i.%i disappeared.' % (name,
+ domain, interface, protocol))
+ if not self.connected:
+ return
+ if name != self.name:
+ for key in self.contacts.keys():
+ if self.contacts[key][C_BARE_NAME] == name:
+ del self.contacts[key]
+ self.remove_serviceCB(key)
+ return
+
+ def new_service_type(self, interface, protocol, stype, domain, flags):
+ # Are we already browsing this domain for this type?
+ if self.service_browser:
+ return
+
+ object_path = self.server.ServiceBrowserNew(interface, protocol, \
+ stype, domain, dbus.UInt32(0))
+
+ self.service_browser = dbus.Interface(self.bus.get_object(
+ self.avahi.DBUS_NAME, object_path),
+ self.avahi.DBUS_INTERFACE_SERVICE_BROWSER)
+ self.service_browser.connect_to_signal('ItemNew',
+ self.new_service_callback)
+ self.service_browser.connect_to_signal('ItemRemove',
+ self.remove_service_callback)
+ self.service_browser.connect_to_signal('Failure', self.error_callback)
+
+ def new_domain_callback(self,interface, protocol, domain, flags):
+ if domain != 'local':
+ self.browse_domain(interface, protocol, domain)
+
+ def txt_array_to_dict(self, txt_array):
+ txt_dict = {}
+ for els in txt_array:
+ key, val = '', None
+ for c in els:
+ c = chr(c)
+ if val is None:
+ if c == '=':
+ val = ''
+ else:
+ key += c
+ else:
+ val += c
+ if val is None: # missing '='
+ val = ''
+ txt_dict[key] = val.decode('utf-8', 'ignore')
+ return txt_dict
+
+ def service_resolved_callback(self, interface, protocol, name, stype, domain,
+ host, aprotocol, address, port, txt, flags):
+ log.debug('Service data for service %s in domain %s on %i.%i:'
+ % (name, domain, interface, protocol))
+ log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address,
+ port, self.txt_array_to_dict(txt)))
+ if not self.connected:
+ return
+ bare_name = name
+ if name.find('@') == -1:
+ name = name + '@' + name
+
+ # we don't want to see ourselves in the list
+ if name != self.name:
+ self.contacts[name] = (name, domain, interface, protocol, host,
+ address, port, bare_name, txt)
+ self.new_serviceCB(name)
+ else:
+ # remember data
+ # In case this is not our own record but of another
+ # gajim instance on the same machine,
+ # it will be used when we get a new name.
+ self.invalid_self_contact[name] = (name, domain, interface, protocol,
+ host, address, port, bare_name, txt)
+
+
+ # different handler when resolving all contacts
+ def service_resolved_all_callback(self, interface, protocol, name, stype,
+ domain, host, aprotocol, address, port, txt, flags):
+ if not self.connected:
+ return
+ bare_name = name
+ if name.find('@') == -1:
+ name = name + '@' + name
+ self.contacts[name] = (name, domain, interface, protocol, host, address,
+ port, bare_name, txt)
+
+ def service_added_callback(self):
+ log.debug('Service successfully added')
+
+ def service_committed_callback(self):
+ log.debug('Service successfully committed')
+
+ def service_updated_callback(self):
+ log.debug('Service successfully updated')
+
+ def service_add_fail_callback(self, err):
+ log.debug('Error while adding service. %s' % str(err))
+ if 'Local name collision' in str(err):
+ alternative_name = self.server.GetAlternativeServiceName(self.username)
+ self.name_conflictCB(alternative_name)
+ return
+ self.error_CB(_('Error while adding service. %s') % str(err))
+ self.disconnect()
+
+ def server_state_changed_callback(self, state, error):
+ log.debug('server state changed to %s' % state)
+ if state == self.avahi.SERVER_RUNNING:
+ self.create_service()
+ elif state in (self.avahi.SERVER_COLLISION,
+ self.avahi.SERVER_REGISTERING):
+ self.disconnect()
+ self.entrygroup.Reset()
+ elif state == self.avahi.CLIENT_FAILURE:
+ # does it ever go here?
+ log.debug('CLIENT FAILURE')
+
+ def entrygroup_state_changed_callback(self, state, error):
+ # the name is already present, so recreate
+ if state == self.avahi.ENTRY_GROUP_COLLISION:
+ log.debug('zeroconf.py: local name collision')
+ self.service_add_fail_callback('Local name collision')
+ elif state == self.avahi.ENTRY_GROUP_FAILURE:
+ self.disconnect()
+ self.entrygroup.Reset()
+ log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that'
+ ' should not happen)')
+
+ # make zeroconf-valid names
+ def replace_show(self, show):
+ if show in ['chat', 'online', '']:
+ return 'avail'
+ elif show == 'xa':
+ return 'away'
+ return show
+
+ def avahi_txt(self):
+ utf8_dict = {}
+ for key in self.txt:
+ val = self.txt[key]
+ if isinstance(val, unicode):
+ utf8_dict[key] = val.encode('utf-8')
+ else:
+ utf8_dict[key] = val
+ return self.avahi.dict_to_txt_array(utf8_dict)
+
+ def create_service(self):
+ try:
+ if not self.entrygroup:
+ # create an EntryGroup for publishing
+ self.entrygroup = dbus.Interface(self.bus.get_object(
+ self.avahi.DBUS_NAME, self.server.EntryGroupNew()),
+ self.avahi.DBUS_INTERFACE_ENTRY_GROUP)
+ self.entrygroup.connect_to_signal('StateChanged',
+ self.entrygroup_state_changed_callback)
+
+ txt = {}
+
+ # remove empty keys
+ for key,val in self.txt.iteritems():
+ if val:
+ txt[key] = val
+
+ txt['port.p2pj'] = self.port
+ txt['version'] = 1
+ txt['txtvers'] = 1
+
+ # replace gajim's show messages with compatible ones
+ if 'status' in self.txt:
+ txt['status'] = self.replace_show(self.txt['status'])
+ else:
+ txt['status'] = 'avail'
+
+ self.txt = txt
+ log.debug('Publishing service %s of type %s' % (self.name,
+ self.stype))
+ self.entrygroup.AddService(self.avahi.IF_UNSPEC,
+ self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
+ '', dbus.UInt16(self.port), self.avahi_txt(),
+ reply_handler=self.service_added_callback,
+ error_handler=self.service_add_fail_callback)
+
+ self.entrygroup.Commit(reply_handler=self.service_committed_callback,
+ error_handler=self.entrygroup_commit_error_CB)
+
+ return True
+
+ except dbus.DBusException, e:
+ log.debug(str(e))
+ return False
+
+ def announce(self):
+ if not self.connected:
+ return False
+
+ state = self.server.GetState()
+ if state == self.avahi.SERVER_RUNNING:
+ self.create_service()
+ self.announced = True
+ return True
+
+ def remove_announce(self):
+ if self.announced == False:
+ return False
+ try:
+ if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE:
+ self.entrygroup.Reset()
+ self.entrygroup.Free()
+ # .Free() has mem leaks
+ self.entrygroup._obj._bus = None
+ self.entrygroup._obj = None
+ self.entrygroup = None
+ self.announced = False
+
+ return True
+ else:
+ return False
+ except dbus.DBusException:
+ log.debug("Can't remove service. That should not happen")
+
+ def browse_domain(self, interface, protocol, domain):
+ self.new_service_type(interface, protocol, self.stype, domain, '')
+
+ def avahi_dbus_connect_cb(self, a, connect, disconnect):
+ if connect != "":
+ log.debug('Lost connection to avahi-daemon')
+ self.disconnect()
+ if self.disconnected_CB:
+ self.disconnected_CB()
+ else:
+ log.debug('We are connected to avahi-daemon')
+
+ # connect to dbus
+ def connect_dbus(self):
+ try:
+ import dbus
+ except ImportError:
+ log.debug('Error: python-dbus needs to be installed. No '
+ 'zeroconf support.')
+ return False
+ if self.bus:
+ return True
+ try:
+ self.bus = dbus.SystemBus()
+ self.bus.add_signal_receiver(self.avahi_dbus_connect_cb,
+ 'NameOwnerChanged', 'org.freedesktop.DBus',
+ arg0='org.freedesktop.Avahi')
+ except Exception, e:
+ # System bus is not present
+ self.bus = None
+ log.debug(str(e))
+ return False
+ else:
+ return True
+
+ # connect to avahi
+ def connect_avahi(self):
+ if not self.connect_dbus():
+ return False
+ try:
+ import avahi
+ self.avahi = avahi
+ except ImportError:
+ log.debug('Error: python-avahi needs to be installed. No '
+ 'zeroconf support.')
+ return False
+
+ if self.server:
+ return True
+ try:
+ self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME,
+ self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER)
+ self.server.connect_to_signal('StateChanged',
+ self.server_state_changed_callback)
+ except Exception, e:
+ # Avahi service is not present
+ self.server = None
+ log.debug(str(e))
+ return False
+ else:
+ return True
+
+ def connect(self):
+ self.name = self.username + '@' + self.host # service name
+ if not self.connect_avahi():
+ return False
+
+ self.connected = True
+ # start browsing
+ if self.domain is None:
+ # Explicitly browse .local
+ self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
+ 'local')
+
+ # Browse for other browsable domains
+ self.domain_browser = dbus.Interface(self.bus.get_object(
+ self.avahi.DBUS_NAME, self.server.DomainBrowserNew(
+ self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '',
+ self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))),
+ self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
+ self.domain_browser.connect_to_signal('ItemNew',
+ self.new_domain_callback)
+ self.domain_browser.connect_to_signal('Failure', self.error_callback)
+ else:
+ self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
+ self.domain)
+
+ return True
+
+ def disconnect(self):
+ if self.connected:
+ self.connected = False
+ if self.service_browser:
+ self.service_browser.Free()
+ self.service_browser._obj._bus = None
+ self.service_browser._obj = None
+ if self.domain_browser:
+ self.domain_browser.Free()
+ self.domain_browser._obj._bus = None
+ self.domain_browser._obj = None
+ self.remove_announce()
+ self.server._obj._bus = None
+ self.server._obj = None
+ self.server = None
+ self.service_browser = None
+ self.domain_browser = None
+
+ # refresh txt data of all contacts manually (no callback available)
+ def resolve_all(self):
+ if not self.connected:
+ return
+ for val in self.contacts.values():
+ self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]),
+ val[C_BARE_NAME], self.stype, val[C_DOMAIN],
+ self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
+ reply_handler=self.service_resolved_all_callback,
+ error_handler=self.error_callback)
+
+ def get_contacts(self):
+ return self.contacts
+
+ def get_contact(self, jid):
+ if not jid in self.contacts:
+ return None
+ return self.contacts[jid]
+
+ def update_txt(self, show = None):
+ if show:
+ self.txt['status'] = self.replace_show(show)
+
+ txt = self.avahi_txt()
+ if self.connected and self.entrygroup:
+ self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC,
+ self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
+ txt, reply_handler=self.service_updated_callback,
+ error_handler=self.error_callback)
+ return True
+ else:
+ return False
# END Zeroconf
-
-# vim: se ts=3:
diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py
index 772c9b2f0..337b72a12 100644
--- a/src/common/zeroconf/zeroconf_bonjour.py
+++ b/src/common/zeroconf/zeroconf_bonjour.py
@@ -23,313 +23,311 @@ import re
from common.zeroconf.zeroconf import C_BARE_NAME, C_DOMAIN
try:
- import pybonjour
+ import pybonjour
except ImportError, e:
- pass
+ pass
resolve_timeout = 1
class Zeroconf:
- def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
- disconnected_CB, error_CB, name, host, port):
- self.domain = None # specific domain to browse
- self.stype = '_presence._tcp'
- self.port = port # listening port that gets announced
- self.username = name
- self.host = host
- self.txt = pybonjour.TXTRecord() # service data
-
- # XXX these CBs should be set to None when we destroy the object
- # (go offline), because they create a circular reference
- self.new_serviceCB = new_serviceCB
- self.remove_serviceCB = remove_serviceCB
- self.name_conflictCB = name_conflictCB
- self.disconnected_CB = disconnected_CB
- self.error_CB = error_CB
-
- self.contacts = {} # all current local contacts with data
- self.connected = False
- self.announced = False
- self.invalid_self_contact = {}
- self.resolved = []
-
-
- def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain):
- gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype))
- if not self.connected:
- return
- if errorCode != pybonjour.kDNSServiceErr_NoError:
- return
- if not (flags & pybonjour.kDNSServiceFlagsAdd):
- self.remove_service_callback(serviceName)
- return
-
- # asynchronous resolving
- resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback)
-
- try:
- while not self.resolved:
- ready = select.select([resolve_sdRef], [], [], resolve_timeout)
- if resolve_sdRef not in ready[0]:
- gajim.log.debug('Resolve timed out')
- break
- pybonjour.DNSServiceProcessResult(resolve_sdRef)
- else:
- self.resolved.pop()
- finally:
- resolve_sdRef.close()
-
- def remove_service_callback(self, name):
- gajim.log.debug('Service %s disappeared.' % name)
- if not self.connected:
- return
- if name != self.name:
- for key in self.contacts.keys():
- if self.contacts[key][C_BARE_NAME] == name:
- del self.contacts[key]
- self.remove_serviceCB(key)
- return
-
- def new_domain_callback(self,interface, protocol, domain, flags):
- if domain != "local":
- self.browse_domain(interface, protocol, domain)
-
- # takes a TXTRecord instance
- def txt_array_to_dict(self, txt):
- items = pybonjour.TXTRecord.parse(txt)._items
- return dict((v[0], v[1]) for v in items.values())
-
- def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname,
- hosttarget, port, txtRecord):
-
- # TODO: do proper decoding...
- escaping= {
- r'\.': '.',
- r'\032': ' ',
- r'\064': '@',
- }
-
- # Split on '.' but do not split on '\.'
- result = re.split('(?<!\\\\)\.', fullname)
- name = result[0]
- protocol, domain = result[2:4]
-
- # Replace the escaped values
- for src, trg in escaping.items():
- name = name.replace(src, trg)
-
- txt = pybonjour.TXTRecord.parse(txtRecord)
-
- gajim.log.debug('Service data for service %s on %i:' % (fullname, interfaceIndex))
- gajim.log.debug('Host %s, port %i, TXT data: %s' % (hosttarget, port, txt._items))
-
- if not self.connected:
- return
-
- bare_name = name
- if '@' not in name:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- self.contacts[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord)
-
- self.new_serviceCB(name)
- else:
- # remember data
- # In case this is not our own record but of another
- # gajim instance on the same machine,
- # it will be used when we get a new name.
- self.invalid_self_contact[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord)
- # count services
- self.resolved.append(True)
-
- # different handler when resolving all contacts
- def service_resolved_all_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord):
- if not self.connected:
- return
-
- escaping= {
- r'\.': '.',
- r'\032': ' ',
- r'\064': '@',
- }
-
- name, stype, protocol, domain, dummy = fullname.split('.')
-
- # Replace the escaped values
- for src, trg in escaping.items():
- name = name.replace(src, trg)
-
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- self.contacts[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord)
-
-
- def service_added_callback(self, sdRef, flags, errorCode, name, regtype, domain):
- if errorCode == pybonjour.kDNSServiceErr_NoError:
- gajim.log.debug('Service successfully added')
-
- def service_add_fail_callback(self, err):
- if err[0][0] == pybonjour.kDNSServiceErr_NameConflict:
- gajim.log.debug('Error while adding service. %s' % str(err))
- parts = self.username.split(' ')
-
- #check if last part is a number and if, increment it
- try:
- stripped = str(int(parts[-1]))
- except Exception:
- stripped = 1
- alternative_name = self.username + str(stripped+1)
- self.name_conflictCB(alternative_name)
- return
- self.error_CB(_('Error while adding service. %s') % str(err))
- self.disconnect()
-
- # make zeroconf-valid names
- def replace_show(self, show):
- if show in ['chat', 'online', '']:
- return 'avail'
- elif show == 'xa':
- return 'away'
- return show
-
- def create_service(self):
- txt = {}
-
- #remove empty keys
- for key,val in self.txt:
- if val:
- txt[key] = val
-
- txt['port.p2pj'] = self.port
- txt['version'] = 1
- txt['txtvers'] = 1
-
- # replace gajim's show messages with compatible ones
- if 'status' in self.txt:
- txt['status'] = self.replace_show(self.txt['status'])
- else:
- txt['status'] = 'avail'
-
- self.txt = pybonjour.TXTRecord(txt, strict=True)
-
- try:
- sdRef = pybonjour.DNSServiceRegister(name = self.name,
- regtype = self.stype, port = self.port, txtRecord = self.txt,
- callBack = self.service_added_callback)
- self.service_sdRef = sdRef
- except pybonjour.BonjourError, e:
- self.service_add_fail_callback(e)
- else:
- gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype))
-
- ready = select.select([sdRef], [], [], resolve_timeout)
- if sdRef in ready[0]:
- pybonjour.DNSServiceProcessResult(sdRef)
-
- def announce(self):
- if not self.connected:
- return False
-
- self.create_service()
- self.announced = True
- return True
-
- def remove_announce(self):
- if not self.announced:
- return False
- try:
- self.service_sdRef.close()
- self.announced = False
- return True
- except pybonjour.BonjourError, e:
- gajim.log.debug(e)
- return False
-
-
- def connect(self):
- self.name = self.username + '@' + self.host # service name
-
- self.connected = True
-
- # start browsing
- if self.domain is None:
- # Explicitly browse .local
- self.browse_domain()
-
- # Browse for other browsable domains
- #self.domain_sdRef = pybonjour.DNSServiceEnumerateDomains(flags, interfaceIndex=0, callBack=self.new_domain_callback)
-
- else:
- self.browse_domain(self.domain)
-
- return True
-
- def disconnect(self):
- if self.connected:
- self.connected = False
- self.browse_sdRef.close()
- self.remove_announce()
-
-
- def browse_domain(self, domain=None):
- gajim.log.debug('starting to browse')
- try:
- self.browse_sdRef = pybonjour.DNSServiceBrowse(regtype=self.stype, domain=domain, callBack=self.browse_callback)
- except pybonjour.BonjourError, e:
- self.error_CB("Error while browsing: %s" % e)
-
- def browse_loop(self):
- ready = select.select([self.browse_sdRef], [], [], 2)
- if self.browse_sdRef in ready[0]:
- pybonjour.DNSServiceProcessResult(self.browse_sdRef)
-
- # refresh txt data of all contacts manually (no callback available)
- def resolve_all(self):
- if not self.connected:
- return
-
- # for now put here as this is synchronous
- self.browse_loop()
-
- for val in self.contacts.values():
- resolve_sdRef = pybonjour.DNSServiceResolve(0,
- pybonjour.kDNSServiceInterfaceIndexAny, val[C_BARE_NAME],
- self.stype + '.', val[C_DOMAIN] + '.',
- self.service_resolved_all_callback)
-
- try:
- ready = select.select([resolve_sdRef], [], [], resolve_timeout)
- if resolve_sdRef not in ready[0]:
- gajim.log.debug('Resolve timed out (in resolve_all)')
- break
- pybonjour.DNSServiceProcessResult(resolve_sdRef)
- finally:
- resolve_sdRef.close()
-
- def get_contacts(self):
- return self.contacts
-
- def get_contact(self, jid):
- if not jid in self.contacts:
- return None
- return self.contacts[jid]
-
- def update_txt(self, show = None):
- if show:
- self.txt['status'] = self.replace_show(show)
-
- try:
- pybonjour.DNSServiceUpdateRecord(self.service_sdRef, None, 0, self.txt)
- except pybonjour.BonjourError:
- return False
- return True
-
-
-# vim: se ts=3:
+ def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
+ disconnected_CB, error_CB, name, host, port):
+ self.domain = None # specific domain to browse
+ self.stype = '_presence._tcp'
+ self.port = port # listening port that gets announced
+ self.username = name
+ self.host = host
+ self.txt = pybonjour.TXTRecord() # service data
+
+ # XXX these CBs should be set to None when we destroy the object
+ # (go offline), because they create a circular reference
+ self.new_serviceCB = new_serviceCB
+ self.remove_serviceCB = remove_serviceCB
+ self.name_conflictCB = name_conflictCB
+ self.disconnected_CB = disconnected_CB
+ self.error_CB = error_CB
+
+ self.contacts = {} # all current local contacts with data
+ self.connected = False
+ self.announced = False
+ self.invalid_self_contact = {}
+ self.resolved = []
+
+
+ def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain):
+ gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype))
+ if not self.connected:
+ return
+ if errorCode != pybonjour.kDNSServiceErr_NoError:
+ return
+ if not (flags & pybonjour.kDNSServiceFlagsAdd):
+ self.remove_service_callback(serviceName)
+ return
+
+ # asynchronous resolving
+ resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback)
+
+ try:
+ while not self.resolved:
+ ready = select.select([resolve_sdRef], [], [], resolve_timeout)
+ if resolve_sdRef not in ready[0]:
+ gajim.log.debug('Resolve timed out')
+ break
+ pybonjour.DNSServiceProcessResult(resolve_sdRef)
+ else:
+ self.resolved.pop()
+ finally:
+ resolve_sdRef.close()
+
+ def remove_service_callback(self, name):
+ gajim.log.debug('Service %s disappeared.' % name)
+ if not self.connected:
+ return
+ if name != self.name:
+ for key in self.contacts.keys():
+ if self.contacts[key][C_BARE_NAME] == name:
+ del self.contacts[key]
+ self.remove_serviceCB(key)
+ return
+
+ def new_domain_callback(self,interface, protocol, domain, flags):
+ if domain != "local":
+ self.browse_domain(interface, protocol, domain)
+
+ # takes a TXTRecord instance
+ def txt_array_to_dict(self, txt):
+ items = pybonjour.TXTRecord.parse(txt)._items
+ return dict((v[0], v[1]) for v in items.values())
+
+ def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname,
+ hosttarget, port, txtRecord):
+
+ # TODO: do proper decoding...
+ escaping= {
+ r'\.': '.',
+ r'\032': ' ',
+ r'\064': '@',
+ }
+
+ # Split on '.' but do not split on '\.'
+ result = re.split('(?<!\\\\)\.', fullname)
+ name = result[0]
+ protocol, domain = result[2:4]
+
+ # Replace the escaped values
+ for src, trg in escaping.items():
+ name = name.replace(src, trg)
+
+ txt = pybonjour.TXTRecord.parse(txtRecord)
+
+ gajim.log.debug('Service data for service %s on %i:' % (fullname, interfaceIndex))
+ gajim.log.debug('Host %s, port %i, TXT data: %s' % (hosttarget, port, txt._items))
+
+ if not self.connected:
+ return
+
+ bare_name = name
+ if '@' not in name:
+ name = name + '@' + name
+
+ # we don't want to see ourselves in the list
+ if name != self.name:
+ self.contacts[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord)
+
+ self.new_serviceCB(name)
+ else:
+ # remember data
+ # In case this is not our own record but of another
+ # gajim instance on the same machine,
+ # it will be used when we get a new name.
+ self.invalid_self_contact[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord)
+ # count services
+ self.resolved.append(True)
+
+ # different handler when resolving all contacts
+ def service_resolved_all_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord):
+ if not self.connected:
+ return
+
+ escaping= {
+ r'\.': '.',
+ r'\032': ' ',
+ r'\064': '@',
+ }
+
+ name, stype, protocol, domain, dummy = fullname.split('.')
+
+ # Replace the escaped values
+ for src, trg in escaping.items():
+ name = name.replace(src, trg)
+
+ bare_name = name
+ if name.find('@') == -1:
+ name = name + '@' + name
+
+ # we don't want to see ourselves in the list
+ if name != self.name:
+ self.contacts[name] = (name, domain, interfaceIndex, protocol, hosttarget, hosttarget, port, bare_name, txtRecord)
+
+
+ def service_added_callback(self, sdRef, flags, errorCode, name, regtype, domain):
+ if errorCode == pybonjour.kDNSServiceErr_NoError:
+ gajim.log.debug('Service successfully added')
+
+ def service_add_fail_callback(self, err):
+ if err[0][0] == pybonjour.kDNSServiceErr_NameConflict:
+ gajim.log.debug('Error while adding service. %s' % str(err))
+ parts = self.username.split(' ')
+
+ #check if last part is a number and if, increment it
+ try:
+ stripped = str(int(parts[-1]))
+ except Exception:
+ stripped = 1
+ alternative_name = self.username + str(stripped+1)
+ self.name_conflictCB(alternative_name)
+ return
+ self.error_CB(_('Error while adding service. %s') % str(err))
+ self.disconnect()
+
+ # make zeroconf-valid names
+ def replace_show(self, show):
+ if show in ['chat', 'online', '']:
+ return 'avail'
+ elif show == 'xa':
+ return 'away'
+ return show
+
+ def create_service(self):
+ txt = {}
+
+ #remove empty keys
+ for key,val in self.txt:
+ if val:
+ txt[key] = val
+
+ txt['port.p2pj'] = self.port
+ txt['version'] = 1
+ txt['txtvers'] = 1
+
+ # replace gajim's show messages with compatible ones
+ if 'status' in self.txt:
+ txt['status'] = self.replace_show(self.txt['status'])
+ else:
+ txt['status'] = 'avail'
+
+ self.txt = pybonjour.TXTRecord(txt, strict=True)
+
+ try:
+ sdRef = pybonjour.DNSServiceRegister(name = self.name,
+ regtype = self.stype, port = self.port, txtRecord = self.txt,
+ callBack = self.service_added_callback)
+ self.service_sdRef = sdRef
+ except pybonjour.BonjourError, e:
+ self.service_add_fail_callback(e)
+ else:
+ gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype))
+
+ ready = select.select([sdRef], [], [], resolve_timeout)
+ if sdRef in ready[0]:
+ pybonjour.DNSServiceProcessResult(sdRef)
+
+ def announce(self):
+ if not self.connected:
+ return False
+
+ self.create_service()
+ self.announced = True
+ return True
+
+ def remove_announce(self):
+ if not self.announced:
+ return False
+ try:
+ self.service_sdRef.close()
+ self.announced = False
+ return True
+ except pybonjour.BonjourError, e:
+ gajim.log.debug(e)
+ return False
+
+
+ def connect(self):
+ self.name = self.username + '@' + self.host # service name
+
+ self.connected = True
+
+ # start browsing
+ if self.domain is None:
+ # Explicitly browse .local
+ self.browse_domain()
+
+ # Browse for other browsable domains
+ #self.domain_sdRef = pybonjour.DNSServiceEnumerateDomains(flags, interfaceIndex=0, callBack=self.new_domain_callback)
+
+ else:
+ self.browse_domain(self.domain)
+
+ return True
+
+ def disconnect(self):
+ if self.connected:
+ self.connected = False
+ self.browse_sdRef.close()
+ self.remove_announce()
+
+
+ def browse_domain(self, domain=None):
+ gajim.log.debug('starting to browse')
+ try:
+ self.browse_sdRef = pybonjour.DNSServiceBrowse(regtype=self.stype, domain=domain, callBack=self.browse_callback)
+ except pybonjour.BonjourError, e:
+ self.error_CB("Error while browsing: %s" % e)
+
+ def browse_loop(self):
+ ready = select.select([self.browse_sdRef], [], [], 2)
+ if self.browse_sdRef in ready[0]:
+ pybonjour.DNSServiceProcessResult(self.browse_sdRef)
+
+ # refresh txt data of all contacts manually (no callback available)
+ def resolve_all(self):
+ if not self.connected:
+ return
+
+ # for now put here as this is synchronous
+ self.browse_loop()
+
+ for val in self.contacts.values():
+ resolve_sdRef = pybonjour.DNSServiceResolve(0,
+ pybonjour.kDNSServiceInterfaceIndexAny, val[C_BARE_NAME],
+ self.stype + '.', val[C_DOMAIN] + '.',
+ self.service_resolved_all_callback)
+
+ try:
+ ready = select.select([resolve_sdRef], [], [], resolve_timeout)
+ if resolve_sdRef not in ready[0]:
+ gajim.log.debug('Resolve timed out (in resolve_all)')
+ break
+ pybonjour.DNSServiceProcessResult(resolve_sdRef)
+ finally:
+ resolve_sdRef.close()
+
+ def get_contacts(self):
+ return self.contacts
+
+ def get_contact(self, jid):
+ if not jid in self.contacts:
+ return None
+ return self.contacts[jid]
+
+ def update_txt(self, show = None):
+ if show:
+ self.txt['status'] = self.replace_show(show)
+
+ try:
+ pybonjour.DNSServiceUpdateRecord(self.service_sdRef, None, 0, self.txt)
+ except pybonjour.BonjourError:
+ return False
+ return True
+
diff --git a/src/config.py b/src/config.py
index f104d0956..743930bbd 100644
--- a/src/config.py
+++ b/src/config.py
@@ -46,10 +46,10 @@ import chat_control
import dataforms_widget
try:
- import gtkspell
- HAS_GTK_SPELL = True
+ import gtkspell
+ HAS_GTK_SPELL = True
except ImportError:
- HAS_GTK_SPELL = False
+ HAS_GTK_SPELL = False
from common import helpers
from common import gajim
@@ -61,3988 +61,3987 @@ from common import GnuPG
from common import ged
try:
- from common.multimedia_helpers import AudioInputManager, AudioOutputManager
- from common.multimedia_helpers import VideoInputManager, VideoOutputManager
- HAS_GST = True
+ from common.multimedia_helpers import AudioInputManager, AudioOutputManager
+ from common.multimedia_helpers import VideoInputManager, VideoOutputManager
+ HAS_GST = True
except ImportError:
- HAS_GST = False
+ HAS_GST = False
from common.exceptions import GajimGeneralException
#---------- PreferencesWindow class -------------#
class PreferencesWindow:
- """
- Class for Preferences window
- """
-
- def on_preferences_window_destroy(self, widget):
- """
- Close window
- """
- del gajim.interface.instances['preferences']
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def __init__(self):
- """
- Initialize Preferences window
- """
- self.xml = gtkgui_helpers.get_gtk_builder('preferences_window.ui')
- self.window = self.xml.get_object('preferences_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.notebook = self.xml.get_object('preferences_notebook')
- self.one_window_type_combobox =\
- self.xml.get_object('one_window_type_combobox')
- self.iconset_combobox = self.xml.get_object('iconset_combobox')
- self.notify_on_signin_checkbutton = self.xml.get_object(
- 'notify_on_signin_checkbutton')
- self.notify_on_signout_checkbutton = self.xml.get_object(
- 'notify_on_signout_checkbutton')
- self.auto_popup_away_checkbutton = self.xml.get_object(
- 'auto_popup_away_checkbutton')
- self.sound_dnd_checkbutton = self.xml.get_object('sound_dnd_checkbutton')
- self.auto_away_checkbutton = self.xml.get_object('auto_away_checkbutton')
- self.auto_away_time_spinbutton = self.xml.get_object(
- 'auto_away_time_spinbutton')
- self.auto_away_message_entry = self.xml.get_object(
- 'auto_away_message_entry')
- self.auto_xa_checkbutton = self.xml.get_object('auto_xa_checkbutton')
- self.auto_xa_time_spinbutton = self.xml.get_object(
- 'auto_xa_time_spinbutton')
- self.auto_xa_message_entry = self.xml.get_object('auto_xa_message_entry')
-
- ### General tab ###
- # Display avatars in roster
- st = gajim.config.get('show_avatars_in_roster')
- self.xml.get_object('show_avatars_in_roster_checkbutton'). \
- set_active(st)
-
- # Display status msg under contact name in roster
- st = gajim.config.get('show_status_msgs_in_roster')
- self.xml.get_object('show_status_msgs_in_roster_checkbutton'). \
- set_active( st)
-
- # Display mood in roster
- st = gajim.config.get('show_mood_in_roster')
- self.xml.get_object('show_mood_in_roster_checkbutton'). \
- set_active(st)
-
- # Display activity in roster
- st = gajim.config.get('show_activity_in_roster')
- self.xml.get_object('show_activity_in_roster_checkbutton'). \
- set_active(st)
-
- # Display tunes in roster
- st = gajim.config.get('show_tunes_in_roster')
- self.xml.get_object('show_tunes_in_roster_checkbutton'). \
- set_active(st)
-
- # Display location in roster
- st = gajim.config.get('show_location_in_roster')
- self.xml.get_object('show_location_in_roster_checkbutton'). \
- set_active(st)
-
- # Sort contacts by show
- st = gajim.config.get('sort_by_show_in_roster')
- self.xml.get_object('sort_by_show_in_roster_checkbutton').set_active(st)
- st = gajim.config.get('sort_by_show_in_muc')
- self.xml.get_object('sort_by_show_in_muc_checkbutton').set_active(st)
-
- # emoticons
- emoticons_combobox = self.xml.get_object('emoticons_combobox')
- emoticons_list = os.listdir(os.path.join(gajim.DATA_DIR, 'emoticons'))
- # user themes
- if os.path.isdir(gajim.MY_EMOTS_PATH):
- emoticons_list += os.listdir(gajim.MY_EMOTS_PATH)
- renderer_text = gtk.CellRendererText()
- emoticons_combobox.pack_start(renderer_text, True)
- emoticons_combobox.add_attribute(renderer_text, 'text', 0)
- model = gtk.ListStore(str)
- emoticons_combobox.set_model(model)
- l = []
- for dir_ in emoticons_list:
- if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'emoticons', dir_)) \
- and not os.path.isdir(os.path.join(gajim.MY_EMOTS_PATH, dir_)) :
- continue
- if dir_ != '.svn':
- l.append(dir_)
- l.append(_('Disabled'))
- for i in xrange(len(l)):
- model.append([l[i]])
- if gajim.config.get('emoticons_theme') == l[i]:
- emoticons_combobox.set_active(i)
- if not gajim.config.get('emoticons_theme'):
- emoticons_combobox.set_active(len(l)-1)
-
- # Set default for single window type
- choices = common.config.opt_one_window_types
- type_ = gajim.config.get('one_message_window')
- if type_ in choices:
- self.one_window_type_combobox.set_active(choices.index(type_))
- else:
- self.one_window_type_combobox.set_active(0)
-
- # Compact View
- st = gajim.config.get('compact_view')
- self.xml.get_object('compact_view_checkbutton').set_active(st)
-
- # Ignore XHTML
- st = gajim.config.get('ignore_incoming_xhtml')
- self.xml.get_object('xhtml_checkbutton').set_active(st)
-
- # use speller
- if HAS_GTK_SPELL:
- st = gajim.config.get('use_speller')
- self.xml.get_object('speller_checkbutton').set_active(st)
- else:
- self.xml.get_object('speller_checkbutton').set_sensitive(False)
-
- ### Style tab ###
- # Themes
- theme_combobox = self.xml.get_object('theme_combobox')
- cell = gtk.CellRendererText()
- theme_combobox.pack_start(cell, True)
- theme_combobox.add_attribute(cell, 'text', 0)
- self.update_theme_list()
-
- # iconset
- iconsets_list = os.listdir(os.path.join(gajim.DATA_DIR, 'iconsets'))
- if os.path.isdir(gajim.MY_ICONSETS_PATH):
- iconsets_list += os.listdir(gajim.MY_ICONSETS_PATH)
- # new model, image in 0, string in 1
- model = gtk.ListStore(gtk.Image, str)
- renderer_image = cell_renderer_image.CellRendererImage(0, 0)
- renderer_text = gtk.CellRendererText()
- renderer_text.set_property('xpad', 5)
- self.iconset_combobox.pack_start(renderer_image, expand = False)
- self.iconset_combobox.pack_start(renderer_text, expand = True)
- self.iconset_combobox.set_attributes(renderer_text, text = 1)
- self.iconset_combobox.add_attribute(renderer_image, 'image', 0)
- self.iconset_combobox.set_model(model)
- l = []
- for dir in iconsets_list:
- if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', dir)) \
- and not os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, dir)):
- continue
- if dir != '.svn' and dir != 'transports':
- l.append(dir)
- if l.count == 0:
- l.append(' ')
- for i in xrange(len(l)):
- preview = gtk.Image()
- files = []
- files.append(os.path.join(helpers.get_iconset_path(l[i]), '16x16',
- 'online.png'))
- files.append(os.path.join(helpers.get_iconset_path(l[i]), '16x16',
- 'online.gif'))
- for file_ in files:
- if os.path.exists(file_):
- preview.set_from_file(file_)
- model.append([preview, l[i]])
- if gajim.config.get('iconset') == l[i]:
- self.iconset_combobox.set_active(i)
-
- # Use transports iconsets
- st = gajim.config.get('use_transports_iconsets')
- self.xml.get_object('transports_iconsets_checkbutton').set_active(st)
-
- # Color widgets
- self.draw_color_widgets()
-
- # Font for messages
- font = gajim.config.get('conversation_font')
- # try to set default font for the current desktop env
- fontbutton = self.xml.get_object('conversation_fontbutton')
- if font == '':
- fontbutton.set_sensitive(False)
- self.xml.get_object('default_chat_font').set_active(True)
- else:
- fontbutton.set_font_name(font)
-
- ### Personal Events tab ###
- # outgoing send chat state notifications
- st = gajim.config.get('outgoing_chat_state_notifications')
- combo = self.xml.get_object('outgoing_chat_states_combobox')
- if st == 'all':
- combo.set_active(0)
- elif st == 'composing_only':
- combo.set_active(1)
- else: # disabled
- combo.set_active(2)
-
- # displayed send chat state notifications
- st = gajim.config.get('displayed_chat_state_notifications')
- combo = self.xml.get_object('displayed_chat_states_combobox')
- if st == 'all':
- combo.set_active(0)
- elif st == 'composing_only':
- combo.set_active(1)
- else: # disabled
- combo.set_active(2)
-
-
- ### Notifications tab ###
- # On new event
- on_event_combobox = self.xml.get_object('on_event_combobox')
- if gajim.config.get('autopopup'):
- on_event_combobox.set_active(0)
- elif gajim.config.get('notify_on_new_message'):
- on_event_combobox.set_active(1)
- else:
- on_event_combobox.set_active(2)
-
- # notify on online statuses
- st = gajim.config.get('notify_on_signin')
- self.notify_on_signin_checkbutton.set_active(st)
-
- # notify on offline statuses
- st = gajim.config.get('notify_on_signout')
- self.notify_on_signout_checkbutton.set_active(st)
-
- # autopopupaway
- st = gajim.config.get('autopopupaway')
- self.auto_popup_away_checkbutton.set_active(st)
-
- # sounddnd
- st = gajim.config.get('sounddnd')
- self.sound_dnd_checkbutton.set_active(st)
-
- # Systray
- systray_combobox = self.xml.get_object('systray_combobox')
- if gajim.config.get('trayicon') == 'never':
- systray_combobox.set_active(0)
- elif gajim.config.get('trayicon') == 'on_event':
- systray_combobox.set_active(1)
- else:
- systray_combobox.set_active(2)
-
- # sounds
- if gajim.config.get('sounds_on'):
- self.xml.get_object('play_sounds_checkbutton').set_active(True)
- else:
- self.xml.get_object('manage_sounds_button').set_sensitive(False)
-
- # Notify user of new gmail e-mail messages,
- # make checkbox sensitive if user has a gtalk account
- frame_gmail = self.xml.get_object('frame_gmail')
- notify_gmail_checkbutton = self.xml.get_object('notify_gmail_checkbutton')
- notify_gmail_extra_checkbutton = self.xml.get_object(
- 'notify_gmail_extra_checkbutton')
-
- for account in gajim.config.get_per('accounts'):
- jid = gajim.get_jid_from_account(account)
- if gajim.get_server_from_jid(jid) in gajim.gmail_domains:
- frame_gmail.set_sensitive(True)
- st = gajim.config.get('notify_on_new_gmail_email')
- notify_gmail_checkbutton.set_active(st)
- st = gajim.config.get('notify_on_new_gmail_email_extra')
- notify_gmail_extra_checkbutton.set_active(st)
- break
-
- #### Status tab ###
- # Autoaway
- st = gajim.config.get('autoaway')
- self.auto_away_checkbutton.set_active(st)
-
- # Autoawaytime
- st = gajim.config.get('autoawaytime')
- self.auto_away_time_spinbutton.set_value(st)
- self.auto_away_time_spinbutton.set_sensitive(gajim.config.get('autoaway'))
-
- # autoaway message
- st = gajim.config.get('autoaway_message')
- self.auto_away_message_entry.set_text(st)
- self.auto_away_message_entry.set_sensitive(gajim.config.get('autoaway'))
-
- # Autoxa
- st = gajim.config.get('autoxa')
- self.auto_xa_checkbutton.set_active(st)
-
- # Autoxatime
- st = gajim.config.get('autoxatime')
- self.auto_xa_time_spinbutton.set_value(st)
- self.auto_xa_time_spinbutton.set_sensitive(gajim.config.get('autoxa'))
-
- # autoxa message
- st = gajim.config.get('autoxa_message')
- self.auto_xa_message_entry.set_text(st)
- self.auto_xa_message_entry.set_sensitive(gajim.config.get('autoxa'))
-
- from common import sleepy
- if not sleepy.SUPPORTED:
- self.xml.get_object('autoaway_table').set_sensitive(False)
-
- # ask_status when online / offline
- st = gajim.config.get('ask_online_status')
- self.xml.get_object('prompt_online_status_message_checkbutton').\
- set_active(st)
- st = gajim.config.get('ask_offline_status')
- self.xml.get_object('prompt_offline_status_message_checkbutton').\
- set_active(st)
-
- # Default Status messages
- self.default_msg_tree = self.xml.get_object('default_msg_treeview')
- col2 = self.default_msg_tree.rc_get_style().bg[gtk.STATE_ACTIVE].\
- to_string()
- # (status, translated_status, message, enabled)
- model = gtk.ListStore(str, str, str, bool)
- self.default_msg_tree.set_model(model)
- col = gtk.TreeViewColumn(_('Status'))
- col.set_resizable(True)
- self.default_msg_tree.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, False)
- col.set_attributes(renderer, text = 1)
- col = gtk.TreeViewColumn(_('Default Message'))
- col.set_resizable(True)
- self.default_msg_tree.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.set_attributes(renderer, text = 2)
- renderer.connect('edited', self.on_default_msg_cell_edited)
- renderer.set_property('editable', True)
- renderer.set_property('cell-background', col2)
- col = gtk.TreeViewColumn(_('Enabled'))
- col.set_resizable(True)
- self.default_msg_tree.append_column(col)
- renderer = gtk.CellRendererToggle()
- col.pack_start(renderer, False)
- col.set_attributes(renderer, active = 3)
- renderer.set_property('activatable', True)
- renderer.connect('toggled', self.default_msg_toggled_cb)
- self.fill_default_msg_treeview()
-
- # Status messages
- self.msg_tree = self.xml.get_object('msg_treeview')
- model = gtk.ListStore(str, str)
- self.msg_tree.set_model(model)
- col = gtk.TreeViewColumn('name')
- self.msg_tree.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.set_attributes(renderer, text = 0)
- renderer.connect('edited', self.on_msg_cell_edited)
- renderer.set_property('editable', True)
- self.fill_msg_treeview()
- buf = self.xml.get_object('msg_textview').get_buffer()
- buf.connect('changed', self.on_msg_textview_changed)
-
- ### Audio / Video tab ###
- def create_av_combobox(opt_name, device_dict):
- combobox = self.xml.get_object(opt_name + '_combobox')
- cell = gtk.CellRendererText()
- combobox.pack_start(cell, True)
- combobox.add_attribute(cell, 'text', 0)
- model = gtk.ListStore(str, str)
- combobox.set_model(model)
-
- for index, (name, value) in enumerate(sorted(device_dict.iteritems())):
- model.append((name, value))
- if gajim.config.get(opt_name + '_device') == value:
- combobox.set_active(index)
-
- if HAS_GST:
- create_av_combobox('audio_input', AudioInputManager().get_devices())
- create_av_combobox('audio_output', AudioOutputManager().get_devices())
- create_av_combobox('video_input', VideoInputManager().get_devices())
- create_av_combobox('video_output', VideoOutputManager().get_devices())
- else:
- for opt_name in ('audio_input', 'audio_output', 'video_input',
- 'video_output'):
- combobox = self.xml.get_object(opt_name + '_combobox')
- combobox.set_sensitive(False)
-
- # STUN
- cb = self.xml.get_object('stun_checkbutton')
- st = gajim.config.get('use_stun_server')
- cb.set_active(st)
-
- entry = self.xml.get_object('stun_server_entry')
- entry.set_text(gajim.config.get('stun_server'))
- if not st:
- entry.set_sensitive(False)
-
- ### Advanced tab ###
- # open links with
- if os.name == 'nt':
- applications_frame = self.xml.get_object('applications_frame')
- applications_frame.set_no_show_all(True)
- applications_frame.hide()
- else:
- self.applications_combobox = self.xml.get_object(
- 'applications_combobox')
- self.xml.get_object('custom_apps_frame').hide()
- self.xml.get_object('custom_apps_frame').set_no_show_all(True)
-
- if gajim.config.get('autodetect_browser_mailer'):
- self.applications_combobox.set_active(0)
- # else autodetect_browser_mailer is False.
- # so user has 'Always Use GNOME/KDE/Xfce' or Custom
- elif gajim.config.get('openwith') == 'gnome-open':
- self.applications_combobox.set_active(1)
- elif gajim.config.get('openwith') == 'kfmclient exec':
- self.applications_combobox.set_active(2)
- elif gajim.config.get('openwith') == 'exo-open':
- self.applications_combobox.set_active(3)
- elif gajim.config.get('openwith') == 'custom':
- self.applications_combobox.set_active(4)
- self.xml.get_object('custom_apps_frame').show()
-
- self.xml.get_object('custom_browser_entry').set_text(
- gajim.config.get('custombrowser'))
- self.xml.get_object('custom_mail_client_entry').set_text(
- gajim.config.get('custommailapp'))
- self.xml.get_object('custom_file_manager_entry').set_text(
- gajim.config.get('custom_file_manager'))
-
- # log status changes of contacts
- st = gajim.config.get('log_contact_status_changes')
- self.xml.get_object('log_show_changes_checkbutton').set_active(st)
-
- # log encrypted chat sessions
- w = self.xml.get_object('log_encrypted_chats_checkbutton')
- st = self.get_per_account_option('log_encrypted_sessions')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- # send os info
- w = self.xml.get_object('send_os_info_checkbutton')
- st = self.get_per_account_option('send_os_info')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- # send idle time
- w = self.xml.get_object('send_idle_time_checkbutton')
- st = self.get_per_account_option('send_idle_time')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- # check if gajm is default
- st = gajim.config.get('check_if_gajim_is_default')
- self.xml.get_object('check_default_client_checkbutton').set_active(st)
-
- # Ignore messages from unknown contacts
- w = self.xml.get_object('ignore_events_from_unknown_contacts_checkbutton')
- st = self.get_per_account_option('ignore_unknown_contacts')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- self.xml.connect_signals(self)
-
- self.msg_tree.get_model().connect('row-changed',
- self.on_msg_treemodel_row_changed)
- self.msg_tree.get_model().connect('row-deleted',
- self.on_msg_treemodel_row_deleted)
- self.default_msg_tree.get_model().connect('row-changed',
- self.on_default_msg_treemodel_row_changed)
-
- self.theme_preferences = None
- self.sounds_preferences = None
-
- self.notebook.set_current_page(0)
-
- self.window.show_all()
- gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
-
- def on_preferences_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- 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"
- """
- if len(gajim.connections) == 0:
- # a non existant key return default value
- return gajim.config.get_per('accounts', '__default__', opt)
- val = None
- for account in gajim.connections:
- v = gajim.config.get_per('accounts', account, opt)
- if val is None:
- val = v
- elif val != v:
- return 'mixed'
- return val
-
- def on_checkbutton_toggled(self, widget, config_name,
- change_sensitivity_widgets=None):
- gajim.config.set(config_name, widget.get_active())
- if change_sensitivity_widgets:
- for w in change_sensitivity_widgets:
- w.set_sensitive(widget.get_active())
- gajim.interface.save_config()
-
- def on_per_account_checkbutton_toggled(self, widget, config_name,
- change_sensitivity_widgets=None):
- for account in gajim.connections:
- gajim.config.set_per('accounts', account, config_name,
- widget.get_active())
- if change_sensitivity_widgets:
- for w in change_sensitivity_widgets:
- w.set_sensitive(widget.get_active())
- gajim.interface.save_config()
-
- def _get_all_controls(self):
- for ctrl in gajim.interface.msg_win_mgr.get_controls():
- yield ctrl
- for account in gajim.connections:
- for ctrl in gajim.interface.minimized_controls[account].values():
- yield ctrl
-
- def _get_all_muc_controls(self):
- for ctrl in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC):
- yield ctrl
- for account in gajim.connections:
- for ctrl in gajim.interface.minimized_controls[account].values():
- yield ctrl
-
- def on_sort_by_show_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sort_by_show_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
-
- def on_sort_by_show_in_muc_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sort_by_show_in_muc')
- # Redraw groupchats
- for ctrl in self._get_all_muc_controls():
- ctrl.draw_roster()
-
- def on_show_avatars_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_avatars_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
- # Redraw groupchats (in an ugly way)
- for ctrl in self._get_all_muc_controls():
- ctrl.draw_roster()
-
- def on_show_status_msgs_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_status_msgs_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
- for ctrl in self._get_all_muc_controls():
- ctrl.update_ui()
-
- def on_show_mood_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_mood_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
-
- def on_show_activity_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_activity_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
-
- def on_show_tunes_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_tunes_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
-
- def on_show_location_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_location_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
-
- def on_emoticons_combobox_changed(self, widget):
- active = widget.get_active()
- model = widget.get_model()
- emot_theme = model[active][0].decode('utf-8')
- if emot_theme == _('Disabled'):
- gajim.config.set('emoticons_theme', '')
- else:
- gajim.config.set('emoticons_theme', emot_theme)
-
- gajim.interface.init_emoticons(need_reload = True)
- gajim.interface.make_regexps()
- self.toggle_emoticons()
-
- def toggle_emoticons(self):
- """
- Update emoticons state in Opened Chat Windows
- """
- for ctrl in self._get_all_controls():
- ctrl.toggle_emoticons()
-
- def on_one_window_type_combo_changed(self, widget):
- active = widget.get_active()
- config_type = common.config.opt_one_window_types[active]
- gajim.config.set('one_message_window', config_type)
- gajim.interface.save_config()
- gajim.interface.msg_win_mgr.reconfig()
-
- def on_compact_view_checkbutton_toggled(self, widget):
- active = widget.get_active()
- for ctrl in self._get_all_controls():
- ctrl.chat_buttons_set_visible(active)
- gajim.config.set('compact_view', active)
- gajim.interface.save_config()
-
- def on_xhtml_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml')
- helpers.update_optional_features()
-
- def apply_speller(self):
- for ctrl in self._get_all_controls():
- if isinstance(ctrl, chat_control.ChatControlBase):
- try:
- spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
- except (TypeError, RuntimeError, OSError):
- spell_obj = None
-
- if not spell_obj:
- ctrl.set_speller()
-
- def remove_speller(self):
- for ctrl in self._get_all_controls():
- if isinstance(ctrl, chat_control.ChatControlBase):
- try:
- spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
- except (TypeError, RuntimeError):
- spell_obj = None
- if spell_obj:
- spell_obj.detach()
-
- def on_speller_checkbutton_toggled(self, widget):
- active = widget.get_active()
- gajim.config.set('use_speller', active)
- gajim.interface.save_config()
- if active:
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- tv = gtk.TextView()
- try:
- gtkspell.Spell(tv, lang)
- except (TypeError, RuntimeError, OSError):
- dialogs.ErrorDialog(
- _('Dictionary for lang %s not available') % lang,
- _('You have to install %s dictionary to use spellchecking, or '
- 'choose another language by setting the speller_language option.'
- ) % lang)
- gajim.config.set('use_speller', False)
- widget.set_active(False)
- else:
- gajim.config.set('speller_language', lang)
- self.apply_speller()
- else:
- self.remove_speller()
-
- def on_theme_combobox_changed(self, widget):
- model = widget.get_model()
- active = widget.get_active()
- config_theme = model[active][0].decode('utf-8').replace(' ', '_')
-
- gajim.config.set('roster_theme', config_theme)
-
- # begin repainting themed widgets throughout
- gajim.interface.roster.repaint_themed_widgets()
- gajim.interface.roster.change_roster_style(None)
- gajim.interface.save_config()
-
- def update_theme_list(self):
- theme_combobox = self.xml.get_object('theme_combobox')
- model = gtk.ListStore(str)
- theme_combobox.set_model(model)
- i = 0
- for config_theme in gajim.config.get_per('themes'):
- theme = config_theme.replace('_', ' ')
- model.append([theme])
- if gajim.config.get('roster_theme') == config_theme:
- theme_combobox.set_active(i)
- i += 1
-
- def on_manage_theme_button_clicked(self, widget):
- if self.theme_preferences is None:
- self.theme_preferences = dialogs.GajimThemesWindow()
- else:
- self.theme_preferences.window.present()
- self.theme_preferences.select_active_theme()
-
- def on_iconset_combobox_changed(self, widget):
- model = widget.get_model()
- active = widget.get_active()
- icon_string = model[active][1].decode('utf-8')
- gajim.config.set('iconset', icon_string)
- gtkgui_helpers.reload_jabber_state_images()
- gajim.interface.save_config()
-
- def on_transports_iconsets_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'use_transports_iconsets')
- gtkgui_helpers.reload_jabber_state_images()
-
- def on_outgoing_chat_states_combobox_changed(self, widget):
- active = widget.get_active()
- old_value = gajim.config.get('outgoing_chat_state_notifications')
- if active == 0: # all
- gajim.config.set('outgoing_chat_state_notifications', 'all')
- elif active == 1: # only composing
- gajim.config.set('outgoing_chat_state_notifications', 'composing_only')
- else: # disabled
- gajim.config.set('outgoing_chat_state_notifications', 'disabled')
- new_value = gajim.config.get('outgoing_chat_state_notifications')
- if 'disabled' in (old_value, new_value):
- # we changed from disabled to sth else or vice versa
- helpers.update_optional_features()
-
- def on_displayed_chat_states_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 0: # all
- gajim.config.set('displayed_chat_state_notifications', 'all')
- elif active == 1: # only composing
- gajim.config.set('displayed_chat_state_notifications',
- 'composing_only')
- else: # disabled
- gajim.config.set('displayed_chat_state_notifications', 'disabled')
-
- def on_ignore_events_from_unknown_contacts_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'ignore_unknown_contacts')
-
- def on_on_event_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 0:
- gajim.config.set('autopopup', True)
- gajim.config.set('notify_on_new_message', False)
- elif active == 1:
- gajim.config.set('autopopup', False)
- gajim.config.set('notify_on_new_message', True)
- else:
- gajim.config.set('autopopup', False)
- gajim.config.set('notify_on_new_message', False)
-
- def on_notify_on_signin_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_signin')
-
- def on_notify_on_signout_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_signout')
-
- def on_auto_popup_away_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'autopopupaway')
-
- def on_sound_dnd_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sounddnd')
-
- def on_systray_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 0:
- gajim.config.set('trayicon', 'never')
- gajim.interface.systray_enabled = False
- gajim.interface.systray.hide_icon()
- elif active == 1:
- gajim.config.set('trayicon', 'on_event')
- gajim.interface.systray_enabled = True
- gajim.interface.systray.show_icon()
- gajim.interface.systray.set_img()
- else:
- gajim.config.set('trayicon', 'always')
- gajim.interface.systray_enabled = True
- gajim.interface.systray.show_icon()
- gajim.interface.systray.set_img()
-
- def on_advanced_notifications_button_clicked(self, widget):
- dialogs.AdvancedNotificationsWindow()
-
- def on_play_sounds_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sounds_on',
- [self.xml.get_object('manage_sounds_button')])
-
- def on_manage_sounds_button_clicked(self, widget):
- if self.sounds_preferences is None:
- self.sounds_preferences = ManageSoundsWindow()
- else:
- self.sounds_preferences.window.present()
-
- def update_text_tags(self):
- """
- Update color tags in opened chat windows
- """
- for ctrl in self._get_all_controls():
- ctrl.update_tags()
-
- def on_preference_widget_color_set(self, widget, text):
- color = widget.get_color()
- color_string = gtkgui_helpers.make_color_string(color)
- gajim.config.set(text, color_string)
- self.update_text_tags()
- gajim.interface.save_config()
-
- def on_preference_widget_font_set(self, widget, text):
- if widget:
- font = widget.get_font_name()
- else:
- font = ''
- gajim.config.set(text, font)
- self.update_text_font()
- gajim.interface.save_config()
-
- def update_text_font(self):
- """
- Update text font in opened chat windows
- """
- for ctrl in self._get_all_controls():
- ctrl.update_font()
-
- def on_incoming_nick_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'inmsgcolor')
-
- def on_outgoing_nick_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'outmsgcolor')
-
- def on_incoming_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'inmsgtxtcolor')
-
- def on_outgoing_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'outmsgtxtcolor')
-
- def on_url_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'urlmsgcolor')
-
- def on_status_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'statusmsgcolor')
-
- def on_conversation_fontbutton_font_set(self, widget):
- self.on_preference_widget_font_set(widget, 'conversation_font')
-
- def on_default_chat_font_toggled(self, widget):
- font_widget = self.xml.get_object('conversation_fontbutton')
- if widget.get_active():
- font_widget.set_sensitive(False)
- font_widget = None
- else:
- font_widget.set_sensitive(True)
- self.on_preference_widget_font_set(font_widget, 'conversation_font')
-
- def draw_color_widgets(self):
- col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton',
- 'outmsgcolor': 'outgoing_nick_colorbutton',
- 'inmsgtxtcolor': ['incoming_msg_colorbutton',
- 'incoming_msg_checkbutton'],
- 'outmsgtxtcolor': ['outgoing_msg_colorbutton',
- 'outgoing_msg_checkbutton'],
- 'statusmsgcolor': 'status_msg_colorbutton',
- 'urlmsgcolor': 'url_msg_colorbutton'}
- for c in col_to_widget:
- col = gajim.config.get(c)
- if col:
- if isinstance(col_to_widget[c], list):
- self.xml.get_object(col_to_widget[c][0]).set_color(
- gtk.gdk.color_parse(col))
- self.xml.get_object(col_to_widget[c][0]).set_sensitive(True)
- self.xml.get_object(col_to_widget[c][1]).set_active(True)
- else:
- self.xml.get_object(col_to_widget[c]).set_color(
- gtk.gdk.color_parse(col))
- else:
- if isinstance(col_to_widget[c], list):
- self.xml.get_object(col_to_widget[c][0]).set_color(
- gtk.gdk.color_parse('#000000'))
- self.xml.get_object(col_to_widget[c][0]).set_sensitive(False)
- self.xml.get_object(col_to_widget[c][1]).set_active(False)
- else:
- self.xml.get_object(col_to_widget[c]).set_color(
- gtk.gdk.color_parse('#000000'))
-
- def on_reset_colors_button_clicked(self, widget):
- col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton',
- 'outmsgcolor': 'outgoing_nick_colorbutton',
- 'inmsgtxtcolor': 'incoming_msg_colorbutton',
- 'outmsgtxtcolor': 'outgoing_msg_colorbutton',
- 'statusmsgcolor': 'status_msg_colorbutton',
- 'urlmsgcolor': 'url_msg_colorbutton'}
- for c in col_to_widget:
- gajim.config.set(c, gajim.interface.default_colors[c])
- self.draw_color_widgets()
-
- self.update_text_tags()
- gajim.interface.save_config()
-
- def _set_color(self, state, widget_name, option):
- """
- Set color value in prefs and update the UI
- """
- if state:
- color = self.xml.get_object(widget_name).get_color()
- color_string = gtkgui_helpers.make_color_string(color)
- else:
- color_string = ''
- gajim.config.set(option, color_string)
- gajim.interface.save_config()
-
- def on_incoming_msg_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.xml.get_object('incoming_msg_colorbutton').set_sensitive(state)
- self._set_color(state, 'incoming_msg_colorbutton', 'inmsgtxtcolor')
-
- def on_outgoing_msg_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.xml.get_object('outgoing_msg_colorbutton').set_sensitive(state)
- self._set_color(state, 'outgoing_msg_colorbutton', 'outmsgtxtcolor')
-
- def on_auto_away_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'autoaway',
- [self.auto_away_time_spinbutton, self.auto_away_message_entry])
-
- def on_auto_away_time_spinbutton_value_changed(self, widget):
- aat = widget.get_value_as_int()
- gajim.config.set('autoawaytime', aat)
- gajim.interface.sleeper = common.sleepy.Sleepy(
- gajim.config.get('autoawaytime') * 60,
- gajim.config.get('autoxatime') * 60)
- gajim.interface.save_config()
-
- def on_auto_away_message_entry_changed(self, widget):
- gajim.config.set('autoaway_message', widget.get_text().decode('utf-8'))
-
- def on_auto_xa_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'autoxa',
- [self.auto_xa_time_spinbutton, self.auto_xa_message_entry])
-
- def on_auto_xa_time_spinbutton_value_changed(self, widget):
- axt = widget.get_value_as_int()
- gajim.config.set('autoxatime', axt)
- gajim.interface.sleeper = common.sleepy.Sleepy(
- gajim.config.get('autoawaytime') * 60,
- gajim.config.get('autoxatime') * 60)
- gajim.interface.save_config()
-
- def on_auto_xa_message_entry_changed(self, widget):
- gajim.config.set('autoxa_message', widget.get_text().decode('utf-8'))
-
- def on_prompt_online_status_message_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'ask_online_status')
-
- def on_prompt_offline_status_message_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'ask_offline_status')
-
- def fill_default_msg_treeview(self):
- model = self.default_msg_tree.get_model()
- model.clear()
- status = []
- for status_ in gajim.config.get_per('defaultstatusmsg'):
- status.append(status_)
- status.sort()
- for status_ in status:
- msg = gajim.config.get_per('defaultstatusmsg', status_, 'message')
- msg = helpers.from_one_line(msg)
- enabled = gajim.config.get_per('defaultstatusmsg', status_, 'enabled')
- iter_ = model.append()
- uf_show = helpers.get_uf_show(status_)
- model.set(iter_, 0, status_, 1, uf_show, 2, msg, 3, enabled)
-
- def on_default_msg_cell_edited(self, cell, row, new_text):
- model = self.default_msg_tree.get_model()
- iter_ = model.get_iter_from_string(row)
- model.set_value(iter_, 2, new_text)
-
- def default_msg_toggled_cb(self, cell, path):
- model = self.default_msg_tree.get_model()
- model[path][3] = not model[path][3]
-
- def on_default_msg_treemodel_row_changed(self, model, path, iter_):
- status = model[iter_][0]
- message = model[iter_][2].decode('utf-8')
- message = helpers.to_one_line(message)
- gajim.config.set_per('defaultstatusmsg', status, 'enabled',
- model[iter_][3])
- gajim.config.set_per('defaultstatusmsg', status, 'message', message)
-
- def on_default_status_expander_activate(self, expander):
- eventbox = self.xml.get_object('default_status_eventbox')
- vbox = self.xml.get_object('status_vbox')
- vbox.set_child_packing(eventbox, not expander.get_expanded(), True, 0,
- gtk.PACK_START)
-
- def save_status_messages(self, model):
- for msg in gajim.config.get_per('statusmsg'):
- gajim.config.del_per('statusmsg', msg)
- iter_ = model.get_iter_first()
- while iter_:
- val = model[iter_][0].decode('utf-8')
- if model[iter_][1]: # we have a preset message
- if not val: # no title, use message text for title
- val = model[iter_][1]
- gajim.config.add_per('statusmsg', val)
- msg = helpers.to_one_line(model[iter_][1].decode('utf-8'))
- gajim.config.set_per('statusmsg', val, 'message', msg)
- iter_ = model.iter_next(iter_)
- gajim.interface.save_config()
-
- def on_msg_treemodel_row_changed(self, model, path, iter_):
- self.save_status_messages(model)
-
- def on_msg_treemodel_row_deleted(self, model, path):
- self.save_status_messages(model)
-
- def on_av_combobox_changed(self, combobox, opt_name):
- model = combobox.get_model()
- active = combobox.get_active()
- device = model[active][1].decode('utf-8')
- gajim.config.set(opt_name + '_device', device)
-
- def on_audio_input_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'audio_input')
-
- def on_audio_output_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'audio_output')
-
- def on_video_input_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'video_input')
-
- def on_video_output_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'video_output')
-
- def on_stun_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'use_stun_server',
- [self.xml.get_object('stun_server_entry')])
-
- def stun_server_entry_changed(self, widget):
- gajim.config.set('stun_server', widget.get_text().decode('utf-8'))
-
- def on_applications_combobox_changed(self, widget):
- gajim.config.set('autodetect_browser_mailer', False)
- if widget.get_active() == 4:
- self.xml.get_object('custom_apps_frame').show()
- gajim.config.set('openwith', 'custom')
- else:
- if widget.get_active() == 0:
- gajim.config.set('autodetect_browser_mailer', True)
- elif widget.get_active() == 1:
- gajim.config.set('openwith', 'gnome-open')
- elif widget.get_active() == 2:
- gajim.config.set('openwith', 'kfmclient exec')
- elif widget.get_active() == 3:
- gajim.config.set('openwith', 'exo-open')
- self.xml.get_object('custom_apps_frame').hide()
- gajim.interface.save_config()
-
- def on_custom_browser_entry_changed(self, widget):
- gajim.config.set('custombrowser', widget.get_text().decode('utf-8'))
- gajim.interface.save_config()
-
- def on_custom_mail_client_entry_changed(self, widget):
- gajim.config.set('custommailapp', widget.get_text().decode('utf-8'))
- gajim.interface.save_config()
-
- def on_custom_file_manager_entry_changed(self, widget):
- gajim.config.set('custom_file_manager', widget.get_text().decode('utf-8'))
- gajim.interface.save_config()
-
- def on_log_show_changes_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'log_contact_status_changes')
-
- def on_log_encrypted_chats_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'log_encrypted_sessions')
-
- def on_send_os_info_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'send_os_info')
-
- def on_send_idle_time_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'send_idle_time')
-
- def on_check_default_client_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'check_if_gajim_is_default')
-
- def on_notify_gmail_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email')
-
- def on_notify_gmail_extra_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email_extra')
-
- def fill_msg_treeview(self):
- self.xml.get_object('delete_msg_button').set_sensitive(False)
- model = self.msg_tree.get_model()
- model.clear()
- preset_status = []
- for msg_name in gajim.config.get_per('statusmsg'):
- if msg_name.startswith('_last_'):
- continue
- preset_status.append(msg_name)
- preset_status.sort()
- for msg_name in preset_status:
- msg_text = gajim.config.get_per('statusmsg', msg_name, 'message')
- msg_text = helpers.from_one_line(msg_text)
- iter_ = model.append()
- model.set(iter_, 0, msg_name, 1, msg_text)
-
- def on_msg_cell_edited(self, cell, row, new_text):
- model = self.msg_tree.get_model()
- iter_ = model.get_iter_from_string(row)
- model.set_value(iter_, 0, new_text)
-
- def on_msg_treeview_cursor_changed(self, widget, data = None):
- (model, iter_) = self.msg_tree.get_selection().get_selected()
- if not iter_:
- return
- self.xml.get_object('delete_msg_button').set_sensitive(True)
- buf = self.xml.get_object('msg_textview').get_buffer()
- msg = model[iter_][1]
- buf.set_text(msg)
-
- def on_new_msg_button_clicked(self, widget, data = None):
- model = self.msg_tree.get_model()
- iter_ = model.append()
- model.set(iter_, 0, _('status message title'), 1, _('status message text'))
- self.msg_tree.set_cursor(model.get_path(iter_))
-
- def on_delete_msg_button_clicked(self, widget, data = None):
- (model, iter_) = self.msg_tree.get_selection().get_selected()
- if not iter_:
- return
- buf = self.xml.get_object('msg_textview').get_buffer()
- model.remove(iter_)
- buf.set_text('')
- self.xml.get_object('delete_msg_button').set_sensitive(False)
-
- def on_msg_textview_changed(self, widget, data = None):
- (model, iter_) = self.msg_tree.get_selection().get_selected()
- if not iter_:
- return
- buf = self.xml.get_object('msg_textview').get_buffer()
- first_iter, end_iter = buf.get_bounds()
- model.set_value(iter_, 1, buf.get_text(first_iter, end_iter))
-
- def on_msg_treeview_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Delete:
- self.on_delete_msg_button_clicked(widget)
-
- def on_open_advanced_editor_button_clicked(self, widget, data = None):
- if 'advanced_config' in gajim.interface.instances:
- gajim.interface.instances['advanced_config'].window.present()
- else:
- gajim.interface.instances['advanced_config'] = \
- dialogs.AdvancedConfigurationWindow()
+ """
+ Class for Preferences window
+ """
+
+ def on_preferences_window_destroy(self, widget):
+ """
+ Close window
+ """
+ del gajim.interface.instances['preferences']
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def __init__(self):
+ """
+ Initialize Preferences window
+ """
+ self.xml = gtkgui_helpers.get_gtk_builder('preferences_window.ui')
+ self.window = self.xml.get_object('preferences_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ self.notebook = self.xml.get_object('preferences_notebook')
+ self.one_window_type_combobox =\
+ self.xml.get_object('one_window_type_combobox')
+ self.iconset_combobox = self.xml.get_object('iconset_combobox')
+ self.notify_on_signin_checkbutton = self.xml.get_object(
+ 'notify_on_signin_checkbutton')
+ self.notify_on_signout_checkbutton = self.xml.get_object(
+ 'notify_on_signout_checkbutton')
+ self.auto_popup_away_checkbutton = self.xml.get_object(
+ 'auto_popup_away_checkbutton')
+ self.sound_dnd_checkbutton = self.xml.get_object('sound_dnd_checkbutton')
+ self.auto_away_checkbutton = self.xml.get_object('auto_away_checkbutton')
+ self.auto_away_time_spinbutton = self.xml.get_object(
+ 'auto_away_time_spinbutton')
+ self.auto_away_message_entry = self.xml.get_object(
+ 'auto_away_message_entry')
+ self.auto_xa_checkbutton = self.xml.get_object('auto_xa_checkbutton')
+ self.auto_xa_time_spinbutton = self.xml.get_object(
+ 'auto_xa_time_spinbutton')
+ self.auto_xa_message_entry = self.xml.get_object('auto_xa_message_entry')
+
+ ### General tab ###
+ # Display avatars in roster
+ st = gajim.config.get('show_avatars_in_roster')
+ self.xml.get_object('show_avatars_in_roster_checkbutton'). \
+ set_active(st)
+
+ # Display status msg under contact name in roster
+ st = gajim.config.get('show_status_msgs_in_roster')
+ self.xml.get_object('show_status_msgs_in_roster_checkbutton'). \
+ set_active( st)
+
+ # Display mood in roster
+ st = gajim.config.get('show_mood_in_roster')
+ self.xml.get_object('show_mood_in_roster_checkbutton'). \
+ set_active(st)
+
+ # Display activity in roster
+ st = gajim.config.get('show_activity_in_roster')
+ self.xml.get_object('show_activity_in_roster_checkbutton'). \
+ set_active(st)
+
+ # Display tunes in roster
+ st = gajim.config.get('show_tunes_in_roster')
+ self.xml.get_object('show_tunes_in_roster_checkbutton'). \
+ set_active(st)
+
+ # Display location in roster
+ st = gajim.config.get('show_location_in_roster')
+ self.xml.get_object('show_location_in_roster_checkbutton'). \
+ set_active(st)
+
+ # Sort contacts by show
+ st = gajim.config.get('sort_by_show_in_roster')
+ self.xml.get_object('sort_by_show_in_roster_checkbutton').set_active(st)
+ st = gajim.config.get('sort_by_show_in_muc')
+ self.xml.get_object('sort_by_show_in_muc_checkbutton').set_active(st)
+
+ # emoticons
+ emoticons_combobox = self.xml.get_object('emoticons_combobox')
+ emoticons_list = os.listdir(os.path.join(gajim.DATA_DIR, 'emoticons'))
+ # user themes
+ if os.path.isdir(gajim.MY_EMOTS_PATH):
+ emoticons_list += os.listdir(gajim.MY_EMOTS_PATH)
+ renderer_text = gtk.CellRendererText()
+ emoticons_combobox.pack_start(renderer_text, True)
+ emoticons_combobox.add_attribute(renderer_text, 'text', 0)
+ model = gtk.ListStore(str)
+ emoticons_combobox.set_model(model)
+ l = []
+ for dir_ in emoticons_list:
+ if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'emoticons', dir_)) \
+ and not os.path.isdir(os.path.join(gajim.MY_EMOTS_PATH, dir_)) :
+ continue
+ if dir_ != '.svn':
+ l.append(dir_)
+ l.append(_('Disabled'))
+ for i in xrange(len(l)):
+ model.append([l[i]])
+ if gajim.config.get('emoticons_theme') == l[i]:
+ emoticons_combobox.set_active(i)
+ if not gajim.config.get('emoticons_theme'):
+ emoticons_combobox.set_active(len(l)-1)
+
+ # Set default for single window type
+ choices = common.config.opt_one_window_types
+ type_ = gajim.config.get('one_message_window')
+ if type_ in choices:
+ self.one_window_type_combobox.set_active(choices.index(type_))
+ else:
+ self.one_window_type_combobox.set_active(0)
+
+ # Compact View
+ st = gajim.config.get('compact_view')
+ self.xml.get_object('compact_view_checkbutton').set_active(st)
+
+ # Ignore XHTML
+ st = gajim.config.get('ignore_incoming_xhtml')
+ self.xml.get_object('xhtml_checkbutton').set_active(st)
+
+ # use speller
+ if HAS_GTK_SPELL:
+ st = gajim.config.get('use_speller')
+ self.xml.get_object('speller_checkbutton').set_active(st)
+ else:
+ self.xml.get_object('speller_checkbutton').set_sensitive(False)
+
+ ### Style tab ###
+ # Themes
+ theme_combobox = self.xml.get_object('theme_combobox')
+ cell = gtk.CellRendererText()
+ theme_combobox.pack_start(cell, True)
+ theme_combobox.add_attribute(cell, 'text', 0)
+ self.update_theme_list()
+
+ # iconset
+ iconsets_list = os.listdir(os.path.join(gajim.DATA_DIR, 'iconsets'))
+ if os.path.isdir(gajim.MY_ICONSETS_PATH):
+ iconsets_list += os.listdir(gajim.MY_ICONSETS_PATH)
+ # new model, image in 0, string in 1
+ model = gtk.ListStore(gtk.Image, str)
+ renderer_image = cell_renderer_image.CellRendererImage(0, 0)
+ renderer_text = gtk.CellRendererText()
+ renderer_text.set_property('xpad', 5)
+ self.iconset_combobox.pack_start(renderer_image, expand = False)
+ self.iconset_combobox.pack_start(renderer_text, expand = True)
+ self.iconset_combobox.set_attributes(renderer_text, text = 1)
+ self.iconset_combobox.add_attribute(renderer_image, 'image', 0)
+ self.iconset_combobox.set_model(model)
+ l = []
+ for dir in iconsets_list:
+ if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', dir)) \
+ and not os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, dir)):
+ continue
+ if dir != '.svn' and dir != 'transports':
+ l.append(dir)
+ if l.count == 0:
+ l.append(' ')
+ for i in xrange(len(l)):
+ preview = gtk.Image()
+ files = []
+ files.append(os.path.join(helpers.get_iconset_path(l[i]), '16x16',
+ 'online.png'))
+ files.append(os.path.join(helpers.get_iconset_path(l[i]), '16x16',
+ 'online.gif'))
+ for file_ in files:
+ if os.path.exists(file_):
+ preview.set_from_file(file_)
+ model.append([preview, l[i]])
+ if gajim.config.get('iconset') == l[i]:
+ self.iconset_combobox.set_active(i)
+
+ # Use transports iconsets
+ st = gajim.config.get('use_transports_iconsets')
+ self.xml.get_object('transports_iconsets_checkbutton').set_active(st)
+
+ # Color widgets
+ self.draw_color_widgets()
+
+ # Font for messages
+ font = gajim.config.get('conversation_font')
+ # try to set default font for the current desktop env
+ fontbutton = self.xml.get_object('conversation_fontbutton')
+ if font == '':
+ fontbutton.set_sensitive(False)
+ self.xml.get_object('default_chat_font').set_active(True)
+ else:
+ fontbutton.set_font_name(font)
+
+ ### Personal Events tab ###
+ # outgoing send chat state notifications
+ st = gajim.config.get('outgoing_chat_state_notifications')
+ combo = self.xml.get_object('outgoing_chat_states_combobox')
+ if st == 'all':
+ combo.set_active(0)
+ elif st == 'composing_only':
+ combo.set_active(1)
+ else: # disabled
+ combo.set_active(2)
+
+ # displayed send chat state notifications
+ st = gajim.config.get('displayed_chat_state_notifications')
+ combo = self.xml.get_object('displayed_chat_states_combobox')
+ if st == 'all':
+ combo.set_active(0)
+ elif st == 'composing_only':
+ combo.set_active(1)
+ else: # disabled
+ combo.set_active(2)
+
+
+ ### Notifications tab ###
+ # On new event
+ on_event_combobox = self.xml.get_object('on_event_combobox')
+ if gajim.config.get('autopopup'):
+ on_event_combobox.set_active(0)
+ elif gajim.config.get('notify_on_new_message'):
+ on_event_combobox.set_active(1)
+ else:
+ on_event_combobox.set_active(2)
+
+ # notify on online statuses
+ st = gajim.config.get('notify_on_signin')
+ self.notify_on_signin_checkbutton.set_active(st)
+
+ # notify on offline statuses
+ st = gajim.config.get('notify_on_signout')
+ self.notify_on_signout_checkbutton.set_active(st)
+
+ # autopopupaway
+ st = gajim.config.get('autopopupaway')
+ self.auto_popup_away_checkbutton.set_active(st)
+
+ # sounddnd
+ st = gajim.config.get('sounddnd')
+ self.sound_dnd_checkbutton.set_active(st)
+
+ # Systray
+ systray_combobox = self.xml.get_object('systray_combobox')
+ if gajim.config.get('trayicon') == 'never':
+ systray_combobox.set_active(0)
+ elif gajim.config.get('trayicon') == 'on_event':
+ systray_combobox.set_active(1)
+ else:
+ systray_combobox.set_active(2)
+
+ # sounds
+ if gajim.config.get('sounds_on'):
+ self.xml.get_object('play_sounds_checkbutton').set_active(True)
+ else:
+ self.xml.get_object('manage_sounds_button').set_sensitive(False)
+
+ # Notify user of new gmail e-mail messages,
+ # make checkbox sensitive if user has a gtalk account
+ frame_gmail = self.xml.get_object('frame_gmail')
+ notify_gmail_checkbutton = self.xml.get_object('notify_gmail_checkbutton')
+ notify_gmail_extra_checkbutton = self.xml.get_object(
+ 'notify_gmail_extra_checkbutton')
+
+ for account in gajim.config.get_per('accounts'):
+ jid = gajim.get_jid_from_account(account)
+ if gajim.get_server_from_jid(jid) in gajim.gmail_domains:
+ frame_gmail.set_sensitive(True)
+ st = gajim.config.get('notify_on_new_gmail_email')
+ notify_gmail_checkbutton.set_active(st)
+ st = gajim.config.get('notify_on_new_gmail_email_extra')
+ notify_gmail_extra_checkbutton.set_active(st)
+ break
+
+ #### Status tab ###
+ # Autoaway
+ st = gajim.config.get('autoaway')
+ self.auto_away_checkbutton.set_active(st)
+
+ # Autoawaytime
+ st = gajim.config.get('autoawaytime')
+ self.auto_away_time_spinbutton.set_value(st)
+ self.auto_away_time_spinbutton.set_sensitive(gajim.config.get('autoaway'))
+
+ # autoaway message
+ st = gajim.config.get('autoaway_message')
+ self.auto_away_message_entry.set_text(st)
+ self.auto_away_message_entry.set_sensitive(gajim.config.get('autoaway'))
+
+ # Autoxa
+ st = gajim.config.get('autoxa')
+ self.auto_xa_checkbutton.set_active(st)
+
+ # Autoxatime
+ st = gajim.config.get('autoxatime')
+ self.auto_xa_time_spinbutton.set_value(st)
+ self.auto_xa_time_spinbutton.set_sensitive(gajim.config.get('autoxa'))
+
+ # autoxa message
+ st = gajim.config.get('autoxa_message')
+ self.auto_xa_message_entry.set_text(st)
+ self.auto_xa_message_entry.set_sensitive(gajim.config.get('autoxa'))
+
+ from common import sleepy
+ if not sleepy.SUPPORTED:
+ self.xml.get_object('autoaway_table').set_sensitive(False)
+
+ # ask_status when online / offline
+ st = gajim.config.get('ask_online_status')
+ self.xml.get_object('prompt_online_status_message_checkbutton').\
+ set_active(st)
+ st = gajim.config.get('ask_offline_status')
+ self.xml.get_object('prompt_offline_status_message_checkbutton').\
+ set_active(st)
+
+ # Default Status messages
+ self.default_msg_tree = self.xml.get_object('default_msg_treeview')
+ col2 = self.default_msg_tree.rc_get_style().bg[gtk.STATE_ACTIVE].\
+ to_string()
+ # (status, translated_status, message, enabled)
+ model = gtk.ListStore(str, str, str, bool)
+ self.default_msg_tree.set_model(model)
+ col = gtk.TreeViewColumn(_('Status'))
+ col.set_resizable(True)
+ self.default_msg_tree.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, False)
+ col.set_attributes(renderer, text = 1)
+ col = gtk.TreeViewColumn(_('Default Message'))
+ col.set_resizable(True)
+ self.default_msg_tree.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text = 2)
+ renderer.connect('edited', self.on_default_msg_cell_edited)
+ renderer.set_property('editable', True)
+ renderer.set_property('cell-background', col2)
+ col = gtk.TreeViewColumn(_('Enabled'))
+ col.set_resizable(True)
+ self.default_msg_tree.append_column(col)
+ renderer = gtk.CellRendererToggle()
+ col.pack_start(renderer, False)
+ col.set_attributes(renderer, active = 3)
+ renderer.set_property('activatable', True)
+ renderer.connect('toggled', self.default_msg_toggled_cb)
+ self.fill_default_msg_treeview()
+
+ # Status messages
+ self.msg_tree = self.xml.get_object('msg_treeview')
+ model = gtk.ListStore(str, str)
+ self.msg_tree.set_model(model)
+ col = gtk.TreeViewColumn('name')
+ self.msg_tree.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text = 0)
+ renderer.connect('edited', self.on_msg_cell_edited)
+ renderer.set_property('editable', True)
+ self.fill_msg_treeview()
+ buf = self.xml.get_object('msg_textview').get_buffer()
+ buf.connect('changed', self.on_msg_textview_changed)
+
+ ### Audio / Video tab ###
+ def create_av_combobox(opt_name, device_dict):
+ combobox = self.xml.get_object(opt_name + '_combobox')
+ cell = gtk.CellRendererText()
+ combobox.pack_start(cell, True)
+ combobox.add_attribute(cell, 'text', 0)
+ model = gtk.ListStore(str, str)
+ combobox.set_model(model)
+
+ for index, (name, value) in enumerate(sorted(device_dict.iteritems())):
+ model.append((name, value))
+ if gajim.config.get(opt_name + '_device') == value:
+ combobox.set_active(index)
+
+ if HAS_GST:
+ create_av_combobox('audio_input', AudioInputManager().get_devices())
+ create_av_combobox('audio_output', AudioOutputManager().get_devices())
+ create_av_combobox('video_input', VideoInputManager().get_devices())
+ create_av_combobox('video_output', VideoOutputManager().get_devices())
+ else:
+ for opt_name in ('audio_input', 'audio_output', 'video_input',
+ 'video_output'):
+ combobox = self.xml.get_object(opt_name + '_combobox')
+ combobox.set_sensitive(False)
+
+ # STUN
+ cb = self.xml.get_object('stun_checkbutton')
+ st = gajim.config.get('use_stun_server')
+ cb.set_active(st)
+
+ entry = self.xml.get_object('stun_server_entry')
+ entry.set_text(gajim.config.get('stun_server'))
+ if not st:
+ entry.set_sensitive(False)
+
+ ### Advanced tab ###
+ # open links with
+ if os.name == 'nt':
+ applications_frame = self.xml.get_object('applications_frame')
+ applications_frame.set_no_show_all(True)
+ applications_frame.hide()
+ else:
+ self.applications_combobox = self.xml.get_object(
+ 'applications_combobox')
+ self.xml.get_object('custom_apps_frame').hide()
+ self.xml.get_object('custom_apps_frame').set_no_show_all(True)
+
+ if gajim.config.get('autodetect_browser_mailer'):
+ self.applications_combobox.set_active(0)
+ # else autodetect_browser_mailer is False.
+ # so user has 'Always Use GNOME/KDE/Xfce' or Custom
+ elif gajim.config.get('openwith') == 'gnome-open':
+ self.applications_combobox.set_active(1)
+ elif gajim.config.get('openwith') == 'kfmclient exec':
+ self.applications_combobox.set_active(2)
+ elif gajim.config.get('openwith') == 'exo-open':
+ self.applications_combobox.set_active(3)
+ elif gajim.config.get('openwith') == 'custom':
+ self.applications_combobox.set_active(4)
+ self.xml.get_object('custom_apps_frame').show()
+
+ self.xml.get_object('custom_browser_entry').set_text(
+ gajim.config.get('custombrowser'))
+ self.xml.get_object('custom_mail_client_entry').set_text(
+ gajim.config.get('custommailapp'))
+ self.xml.get_object('custom_file_manager_entry').set_text(
+ gajim.config.get('custom_file_manager'))
+
+ # log status changes of contacts
+ st = gajim.config.get('log_contact_status_changes')
+ self.xml.get_object('log_show_changes_checkbutton').set_active(st)
+
+ # log encrypted chat sessions
+ w = self.xml.get_object('log_encrypted_chats_checkbutton')
+ st = self.get_per_account_option('log_encrypted_sessions')
+ if st == 'mixed':
+ w.set_inconsistent(True)
+ else:
+ w.set_active(st)
+
+ # send os info
+ w = self.xml.get_object('send_os_info_checkbutton')
+ st = self.get_per_account_option('send_os_info')
+ if st == 'mixed':
+ w.set_inconsistent(True)
+ else:
+ w.set_active(st)
+
+ # send idle time
+ w = self.xml.get_object('send_idle_time_checkbutton')
+ st = self.get_per_account_option('send_idle_time')
+ if st == 'mixed':
+ w.set_inconsistent(True)
+ else:
+ w.set_active(st)
+
+ # check if gajm is default
+ st = gajim.config.get('check_if_gajim_is_default')
+ self.xml.get_object('check_default_client_checkbutton').set_active(st)
+
+ # Ignore messages from unknown contacts
+ w = self.xml.get_object('ignore_events_from_unknown_contacts_checkbutton')
+ st = self.get_per_account_option('ignore_unknown_contacts')
+ if st == 'mixed':
+ w.set_inconsistent(True)
+ else:
+ w.set_active(st)
+
+ self.xml.connect_signals(self)
+
+ self.msg_tree.get_model().connect('row-changed',
+ self.on_msg_treemodel_row_changed)
+ self.msg_tree.get_model().connect('row-deleted',
+ self.on_msg_treemodel_row_deleted)
+ self.default_msg_tree.get_model().connect('row-changed',
+ self.on_default_msg_treemodel_row_changed)
+
+ self.theme_preferences = None
+ self.sounds_preferences = None
+
+ self.notebook.set_current_page(0)
+
+ self.window.show_all()
+ gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
+
+ def on_preferences_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ 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"
+ """
+ if len(gajim.connections) == 0:
+ # a non existant key return default value
+ return gajim.config.get_per('accounts', '__default__', opt)
+ val = None
+ for account in gajim.connections:
+ v = gajim.config.get_per('accounts', account, opt)
+ if val is None:
+ val = v
+ elif val != v:
+ return 'mixed'
+ return val
+
+ def on_checkbutton_toggled(self, widget, config_name,
+ change_sensitivity_widgets=None):
+ gajim.config.set(config_name, widget.get_active())
+ if change_sensitivity_widgets:
+ for w in change_sensitivity_widgets:
+ w.set_sensitive(widget.get_active())
+ gajim.interface.save_config()
+
+ def on_per_account_checkbutton_toggled(self, widget, config_name,
+ change_sensitivity_widgets=None):
+ for account in gajim.connections:
+ gajim.config.set_per('accounts', account, config_name,
+ widget.get_active())
+ if change_sensitivity_widgets:
+ for w in change_sensitivity_widgets:
+ w.set_sensitive(widget.get_active())
+ gajim.interface.save_config()
+
+ def _get_all_controls(self):
+ for ctrl in gajim.interface.msg_win_mgr.get_controls():
+ yield ctrl
+ for account in gajim.connections:
+ for ctrl in gajim.interface.minimized_controls[account].values():
+ yield ctrl
+
+ def _get_all_muc_controls(self):
+ for ctrl in gajim.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC):
+ yield ctrl
+ for account in gajim.connections:
+ for ctrl in gajim.interface.minimized_controls[account].values():
+ yield ctrl
+
+ def on_sort_by_show_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'sort_by_show_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+
+ def on_sort_by_show_in_muc_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'sort_by_show_in_muc')
+ # Redraw groupchats
+ for ctrl in self._get_all_muc_controls():
+ ctrl.draw_roster()
+
+ def on_show_avatars_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'show_avatars_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+ # Redraw groupchats (in an ugly way)
+ for ctrl in self._get_all_muc_controls():
+ ctrl.draw_roster()
+
+ def on_show_status_msgs_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'show_status_msgs_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+ for ctrl in self._get_all_muc_controls():
+ ctrl.update_ui()
+
+ def on_show_mood_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'show_mood_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+
+ def on_show_activity_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'show_activity_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+
+ def on_show_tunes_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'show_tunes_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+
+ def on_show_location_in_roster_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'show_location_in_roster')
+ gajim.interface.roster.setup_and_draw_roster()
+
+ def on_emoticons_combobox_changed(self, widget):
+ active = widget.get_active()
+ model = widget.get_model()
+ emot_theme = model[active][0].decode('utf-8')
+ if emot_theme == _('Disabled'):
+ gajim.config.set('emoticons_theme', '')
+ else:
+ gajim.config.set('emoticons_theme', emot_theme)
+
+ gajim.interface.init_emoticons(need_reload = True)
+ gajim.interface.make_regexps()
+ self.toggle_emoticons()
+
+ def toggle_emoticons(self):
+ """
+ Update emoticons state in Opened Chat Windows
+ """
+ for ctrl in self._get_all_controls():
+ ctrl.toggle_emoticons()
+
+ def on_one_window_type_combo_changed(self, widget):
+ active = widget.get_active()
+ config_type = common.config.opt_one_window_types[active]
+ gajim.config.set('one_message_window', config_type)
+ gajim.interface.save_config()
+ gajim.interface.msg_win_mgr.reconfig()
+
+ def on_compact_view_checkbutton_toggled(self, widget):
+ active = widget.get_active()
+ for ctrl in self._get_all_controls():
+ ctrl.chat_buttons_set_visible(active)
+ gajim.config.set('compact_view', active)
+ gajim.interface.save_config()
+
+ def on_xhtml_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml')
+ helpers.update_optional_features()
+
+ def apply_speller(self):
+ for ctrl in self._get_all_controls():
+ if isinstance(ctrl, chat_control.ChatControlBase):
+ try:
+ spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
+ except (TypeError, RuntimeError, OSError):
+ spell_obj = None
+
+ if not spell_obj:
+ ctrl.set_speller()
+
+ def remove_speller(self):
+ for ctrl in self._get_all_controls():
+ if isinstance(ctrl, chat_control.ChatControlBase):
+ try:
+ spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
+ except (TypeError, RuntimeError):
+ spell_obj = None
+ if spell_obj:
+ spell_obj.detach()
+
+ def on_speller_checkbutton_toggled(self, widget):
+ active = widget.get_active()
+ gajim.config.set('use_speller', active)
+ gajim.interface.save_config()
+ if active:
+ lang = gajim.config.get('speller_language')
+ if not lang:
+ lang = gajim.LANG
+ tv = gtk.TextView()
+ try:
+ gtkspell.Spell(tv, lang)
+ except (TypeError, RuntimeError, OSError):
+ dialogs.ErrorDialog(
+ _('Dictionary for lang %s not available') % lang,
+ _('You have to install %s dictionary to use spellchecking, or '
+ 'choose another language by setting the speller_language option.'
+ ) % lang)
+ gajim.config.set('use_speller', False)
+ widget.set_active(False)
+ else:
+ gajim.config.set('speller_language', lang)
+ self.apply_speller()
+ else:
+ self.remove_speller()
+
+ def on_theme_combobox_changed(self, widget):
+ model = widget.get_model()
+ active = widget.get_active()
+ config_theme = model[active][0].decode('utf-8').replace(' ', '_')
+
+ gajim.config.set('roster_theme', config_theme)
+
+ # begin repainting themed widgets throughout
+ gajim.interface.roster.repaint_themed_widgets()
+ gajim.interface.roster.change_roster_style(None)
+ gajim.interface.save_config()
+
+ def update_theme_list(self):
+ theme_combobox = self.xml.get_object('theme_combobox')
+ model = gtk.ListStore(str)
+ theme_combobox.set_model(model)
+ i = 0
+ for config_theme in gajim.config.get_per('themes'):
+ theme = config_theme.replace('_', ' ')
+ model.append([theme])
+ if gajim.config.get('roster_theme') == config_theme:
+ theme_combobox.set_active(i)
+ i += 1
+
+ def on_manage_theme_button_clicked(self, widget):
+ if self.theme_preferences is None:
+ self.theme_preferences = dialogs.GajimThemesWindow()
+ else:
+ self.theme_preferences.window.present()
+ self.theme_preferences.select_active_theme()
+
+ def on_iconset_combobox_changed(self, widget):
+ model = widget.get_model()
+ active = widget.get_active()
+ icon_string = model[active][1].decode('utf-8')
+ gajim.config.set('iconset', icon_string)
+ gtkgui_helpers.reload_jabber_state_images()
+ gajim.interface.save_config()
+
+ def on_transports_iconsets_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'use_transports_iconsets')
+ gtkgui_helpers.reload_jabber_state_images()
+
+ def on_outgoing_chat_states_combobox_changed(self, widget):
+ active = widget.get_active()
+ old_value = gajim.config.get('outgoing_chat_state_notifications')
+ if active == 0: # all
+ gajim.config.set('outgoing_chat_state_notifications', 'all')
+ elif active == 1: # only composing
+ gajim.config.set('outgoing_chat_state_notifications', 'composing_only')
+ else: # disabled
+ gajim.config.set('outgoing_chat_state_notifications', 'disabled')
+ new_value = gajim.config.get('outgoing_chat_state_notifications')
+ if 'disabled' in (old_value, new_value):
+ # we changed from disabled to sth else or vice versa
+ helpers.update_optional_features()
+
+ def on_displayed_chat_states_combobox_changed(self, widget):
+ active = widget.get_active()
+ if active == 0: # all
+ gajim.config.set('displayed_chat_state_notifications', 'all')
+ elif active == 1: # only composing
+ gajim.config.set('displayed_chat_state_notifications',
+ 'composing_only')
+ else: # disabled
+ gajim.config.set('displayed_chat_state_notifications', 'disabled')
+
+ def on_ignore_events_from_unknown_contacts_checkbutton_toggled(self, widget):
+ widget.set_inconsistent(False)
+ self.on_per_account_checkbutton_toggled(widget, 'ignore_unknown_contacts')
+
+ def on_on_event_combobox_changed(self, widget):
+ active = widget.get_active()
+ if active == 0:
+ gajim.config.set('autopopup', True)
+ gajim.config.set('notify_on_new_message', False)
+ elif active == 1:
+ gajim.config.set('autopopup', False)
+ gajim.config.set('notify_on_new_message', True)
+ else:
+ gajim.config.set('autopopup', False)
+ gajim.config.set('notify_on_new_message', False)
+
+ def on_notify_on_signin_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'notify_on_signin')
+
+ def on_notify_on_signout_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'notify_on_signout')
+
+ def on_auto_popup_away_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'autopopupaway')
+
+ def on_sound_dnd_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'sounddnd')
+
+ def on_systray_combobox_changed(self, widget):
+ active = widget.get_active()
+ if active == 0:
+ gajim.config.set('trayicon', 'never')
+ gajim.interface.systray_enabled = False
+ gajim.interface.systray.hide_icon()
+ elif active == 1:
+ gajim.config.set('trayicon', 'on_event')
+ gajim.interface.systray_enabled = True
+ gajim.interface.systray.show_icon()
+ gajim.interface.systray.set_img()
+ else:
+ gajim.config.set('trayicon', 'always')
+ gajim.interface.systray_enabled = True
+ gajim.interface.systray.show_icon()
+ gajim.interface.systray.set_img()
+
+ def on_advanced_notifications_button_clicked(self, widget):
+ dialogs.AdvancedNotificationsWindow()
+
+ def on_play_sounds_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'sounds_on',
+ [self.xml.get_object('manage_sounds_button')])
+
+ def on_manage_sounds_button_clicked(self, widget):
+ if self.sounds_preferences is None:
+ self.sounds_preferences = ManageSoundsWindow()
+ else:
+ self.sounds_preferences.window.present()
+
+ def update_text_tags(self):
+ """
+ Update color tags in opened chat windows
+ """
+ for ctrl in self._get_all_controls():
+ ctrl.update_tags()
+
+ def on_preference_widget_color_set(self, widget, text):
+ color = widget.get_color()
+ color_string = gtkgui_helpers.make_color_string(color)
+ gajim.config.set(text, color_string)
+ self.update_text_tags()
+ gajim.interface.save_config()
+
+ def on_preference_widget_font_set(self, widget, text):
+ if widget:
+ font = widget.get_font_name()
+ else:
+ font = ''
+ gajim.config.set(text, font)
+ self.update_text_font()
+ gajim.interface.save_config()
+
+ def update_text_font(self):
+ """
+ Update text font in opened chat windows
+ """
+ for ctrl in self._get_all_controls():
+ ctrl.update_font()
+
+ def on_incoming_nick_colorbutton_color_set(self, widget):
+ self.on_preference_widget_color_set(widget, 'inmsgcolor')
+
+ def on_outgoing_nick_colorbutton_color_set(self, widget):
+ self.on_preference_widget_color_set(widget, 'outmsgcolor')
+
+ def on_incoming_msg_colorbutton_color_set(self, widget):
+ self.on_preference_widget_color_set(widget, 'inmsgtxtcolor')
+
+ def on_outgoing_msg_colorbutton_color_set(self, widget):
+ self.on_preference_widget_color_set(widget, 'outmsgtxtcolor')
+
+ def on_url_msg_colorbutton_color_set(self, widget):
+ self.on_preference_widget_color_set(widget, 'urlmsgcolor')
+
+ def on_status_msg_colorbutton_color_set(self, widget):
+ self.on_preference_widget_color_set(widget, 'statusmsgcolor')
+
+ def on_conversation_fontbutton_font_set(self, widget):
+ self.on_preference_widget_font_set(widget, 'conversation_font')
+
+ def on_default_chat_font_toggled(self, widget):
+ font_widget = self.xml.get_object('conversation_fontbutton')
+ if widget.get_active():
+ font_widget.set_sensitive(False)
+ font_widget = None
+ else:
+ font_widget.set_sensitive(True)
+ self.on_preference_widget_font_set(font_widget, 'conversation_font')
+
+ def draw_color_widgets(self):
+ col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton',
+ 'outmsgcolor': 'outgoing_nick_colorbutton',
+ 'inmsgtxtcolor': ['incoming_msg_colorbutton',
+ 'incoming_msg_checkbutton'],
+ 'outmsgtxtcolor': ['outgoing_msg_colorbutton',
+ 'outgoing_msg_checkbutton'],
+ 'statusmsgcolor': 'status_msg_colorbutton',
+ 'urlmsgcolor': 'url_msg_colorbutton'}
+ for c in col_to_widget:
+ col = gajim.config.get(c)
+ if col:
+ if isinstance(col_to_widget[c], list):
+ self.xml.get_object(col_to_widget[c][0]).set_color(
+ gtk.gdk.color_parse(col))
+ self.xml.get_object(col_to_widget[c][0]).set_sensitive(True)
+ self.xml.get_object(col_to_widget[c][1]).set_active(True)
+ else:
+ self.xml.get_object(col_to_widget[c]).set_color(
+ gtk.gdk.color_parse(col))
+ else:
+ if isinstance(col_to_widget[c], list):
+ self.xml.get_object(col_to_widget[c][0]).set_color(
+ gtk.gdk.color_parse('#000000'))
+ self.xml.get_object(col_to_widget[c][0]).set_sensitive(False)
+ self.xml.get_object(col_to_widget[c][1]).set_active(False)
+ else:
+ self.xml.get_object(col_to_widget[c]).set_color(
+ gtk.gdk.color_parse('#000000'))
+
+ def on_reset_colors_button_clicked(self, widget):
+ col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton',
+ 'outmsgcolor': 'outgoing_nick_colorbutton',
+ 'inmsgtxtcolor': 'incoming_msg_colorbutton',
+ 'outmsgtxtcolor': 'outgoing_msg_colorbutton',
+ 'statusmsgcolor': 'status_msg_colorbutton',
+ 'urlmsgcolor': 'url_msg_colorbutton'}
+ for c in col_to_widget:
+ gajim.config.set(c, gajim.interface.default_colors[c])
+ self.draw_color_widgets()
+
+ self.update_text_tags()
+ gajim.interface.save_config()
+
+ def _set_color(self, state, widget_name, option):
+ """
+ Set color value in prefs and update the UI
+ """
+ if state:
+ color = self.xml.get_object(widget_name).get_color()
+ color_string = gtkgui_helpers.make_color_string(color)
+ else:
+ color_string = ''
+ gajim.config.set(option, color_string)
+ gajim.interface.save_config()
+
+ def on_incoming_msg_checkbutton_toggled(self, widget):
+ state = widget.get_active()
+ self.xml.get_object('incoming_msg_colorbutton').set_sensitive(state)
+ self._set_color(state, 'incoming_msg_colorbutton', 'inmsgtxtcolor')
+
+ def on_outgoing_msg_checkbutton_toggled(self, widget):
+ state = widget.get_active()
+ self.xml.get_object('outgoing_msg_colorbutton').set_sensitive(state)
+ self._set_color(state, 'outgoing_msg_colorbutton', 'outmsgtxtcolor')
+
+ def on_auto_away_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'autoaway',
+ [self.auto_away_time_spinbutton, self.auto_away_message_entry])
+
+ def on_auto_away_time_spinbutton_value_changed(self, widget):
+ aat = widget.get_value_as_int()
+ gajim.config.set('autoawaytime', aat)
+ gajim.interface.sleeper = common.sleepy.Sleepy(
+ gajim.config.get('autoawaytime') * 60,
+ gajim.config.get('autoxatime') * 60)
+ gajim.interface.save_config()
+
+ def on_auto_away_message_entry_changed(self, widget):
+ gajim.config.set('autoaway_message', widget.get_text().decode('utf-8'))
+
+ def on_auto_xa_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'autoxa',
+ [self.auto_xa_time_spinbutton, self.auto_xa_message_entry])
+
+ def on_auto_xa_time_spinbutton_value_changed(self, widget):
+ axt = widget.get_value_as_int()
+ gajim.config.set('autoxatime', axt)
+ gajim.interface.sleeper = common.sleepy.Sleepy(
+ gajim.config.get('autoawaytime') * 60,
+ gajim.config.get('autoxatime') * 60)
+ gajim.interface.save_config()
+
+ def on_auto_xa_message_entry_changed(self, widget):
+ gajim.config.set('autoxa_message', widget.get_text().decode('utf-8'))
+
+ def on_prompt_online_status_message_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'ask_online_status')
+
+ def on_prompt_offline_status_message_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'ask_offline_status')
+
+ def fill_default_msg_treeview(self):
+ model = self.default_msg_tree.get_model()
+ model.clear()
+ status = []
+ for status_ in gajim.config.get_per('defaultstatusmsg'):
+ status.append(status_)
+ status.sort()
+ for status_ in status:
+ msg = gajim.config.get_per('defaultstatusmsg', status_, 'message')
+ msg = helpers.from_one_line(msg)
+ enabled = gajim.config.get_per('defaultstatusmsg', status_, 'enabled')
+ iter_ = model.append()
+ uf_show = helpers.get_uf_show(status_)
+ model.set(iter_, 0, status_, 1, uf_show, 2, msg, 3, enabled)
+
+ def on_default_msg_cell_edited(self, cell, row, new_text):
+ model = self.default_msg_tree.get_model()
+ iter_ = model.get_iter_from_string(row)
+ model.set_value(iter_, 2, new_text)
+
+ def default_msg_toggled_cb(self, cell, path):
+ model = self.default_msg_tree.get_model()
+ model[path][3] = not model[path][3]
+
+ def on_default_msg_treemodel_row_changed(self, model, path, iter_):
+ status = model[iter_][0]
+ message = model[iter_][2].decode('utf-8')
+ message = helpers.to_one_line(message)
+ gajim.config.set_per('defaultstatusmsg', status, 'enabled',
+ model[iter_][3])
+ gajim.config.set_per('defaultstatusmsg', status, 'message', message)
+
+ def on_default_status_expander_activate(self, expander):
+ eventbox = self.xml.get_object('default_status_eventbox')
+ vbox = self.xml.get_object('status_vbox')
+ vbox.set_child_packing(eventbox, not expander.get_expanded(), True, 0,
+ gtk.PACK_START)
+
+ def save_status_messages(self, model):
+ for msg in gajim.config.get_per('statusmsg'):
+ gajim.config.del_per('statusmsg', msg)
+ iter_ = model.get_iter_first()
+ while iter_:
+ val = model[iter_][0].decode('utf-8')
+ if model[iter_][1]: # we have a preset message
+ if not val: # no title, use message text for title
+ val = model[iter_][1]
+ gajim.config.add_per('statusmsg', val)
+ msg = helpers.to_one_line(model[iter_][1].decode('utf-8'))
+ gajim.config.set_per('statusmsg', val, 'message', msg)
+ iter_ = model.iter_next(iter_)
+ gajim.interface.save_config()
+
+ def on_msg_treemodel_row_changed(self, model, path, iter_):
+ self.save_status_messages(model)
+
+ def on_msg_treemodel_row_deleted(self, model, path):
+ self.save_status_messages(model)
+
+ def on_av_combobox_changed(self, combobox, opt_name):
+ model = combobox.get_model()
+ active = combobox.get_active()
+ device = model[active][1].decode('utf-8')
+ gajim.config.set(opt_name + '_device', device)
+
+ def on_audio_input_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'audio_input')
+
+ def on_audio_output_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'audio_output')
+
+ def on_video_input_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'video_input')
+
+ def on_video_output_combobox_changed(self, widget):
+ self.on_av_combobox_changed(widget, 'video_output')
+
+ def on_stun_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'use_stun_server',
+ [self.xml.get_object('stun_server_entry')])
+
+ def stun_server_entry_changed(self, widget):
+ gajim.config.set('stun_server', widget.get_text().decode('utf-8'))
+
+ def on_applications_combobox_changed(self, widget):
+ gajim.config.set('autodetect_browser_mailer', False)
+ if widget.get_active() == 4:
+ self.xml.get_object('custom_apps_frame').show()
+ gajim.config.set('openwith', 'custom')
+ else:
+ if widget.get_active() == 0:
+ gajim.config.set('autodetect_browser_mailer', True)
+ elif widget.get_active() == 1:
+ gajim.config.set('openwith', 'gnome-open')
+ elif widget.get_active() == 2:
+ gajim.config.set('openwith', 'kfmclient exec')
+ elif widget.get_active() == 3:
+ gajim.config.set('openwith', 'exo-open')
+ self.xml.get_object('custom_apps_frame').hide()
+ gajim.interface.save_config()
+
+ def on_custom_browser_entry_changed(self, widget):
+ gajim.config.set('custombrowser', widget.get_text().decode('utf-8'))
+ gajim.interface.save_config()
+
+ def on_custom_mail_client_entry_changed(self, widget):
+ gajim.config.set('custommailapp', widget.get_text().decode('utf-8'))
+ gajim.interface.save_config()
+
+ def on_custom_file_manager_entry_changed(self, widget):
+ gajim.config.set('custom_file_manager', widget.get_text().decode('utf-8'))
+ gajim.interface.save_config()
+
+ def on_log_show_changes_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'log_contact_status_changes')
+
+ def on_log_encrypted_chats_checkbutton_toggled(self, widget):
+ widget.set_inconsistent(False)
+ self.on_per_account_checkbutton_toggled(widget, 'log_encrypted_sessions')
+
+ def on_send_os_info_checkbutton_toggled(self, widget):
+ widget.set_inconsistent(False)
+ self.on_per_account_checkbutton_toggled(widget, 'send_os_info')
+
+ def on_send_idle_time_checkbutton_toggled(self, widget):
+ widget.set_inconsistent(False)
+ self.on_per_account_checkbutton_toggled(widget, 'send_idle_time')
+
+ def on_check_default_client_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'check_if_gajim_is_default')
+
+ def on_notify_gmail_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email')
+
+ def on_notify_gmail_extra_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email_extra')
+
+ def fill_msg_treeview(self):
+ self.xml.get_object('delete_msg_button').set_sensitive(False)
+ model = self.msg_tree.get_model()
+ model.clear()
+ preset_status = []
+ for msg_name in gajim.config.get_per('statusmsg'):
+ if msg_name.startswith('_last_'):
+ continue
+ preset_status.append(msg_name)
+ preset_status.sort()
+ for msg_name in preset_status:
+ msg_text = gajim.config.get_per('statusmsg', msg_name, 'message')
+ msg_text = helpers.from_one_line(msg_text)
+ iter_ = model.append()
+ model.set(iter_, 0, msg_name, 1, msg_text)
+
+ def on_msg_cell_edited(self, cell, row, new_text):
+ model = self.msg_tree.get_model()
+ iter_ = model.get_iter_from_string(row)
+ model.set_value(iter_, 0, new_text)
+
+ def on_msg_treeview_cursor_changed(self, widget, data = None):
+ (model, iter_) = self.msg_tree.get_selection().get_selected()
+ if not iter_:
+ return
+ self.xml.get_object('delete_msg_button').set_sensitive(True)
+ buf = self.xml.get_object('msg_textview').get_buffer()
+ msg = model[iter_][1]
+ buf.set_text(msg)
+
+ def on_new_msg_button_clicked(self, widget, data = None):
+ model = self.msg_tree.get_model()
+ iter_ = model.append()
+ model.set(iter_, 0, _('status message title'), 1, _('status message text'))
+ self.msg_tree.set_cursor(model.get_path(iter_))
+
+ def on_delete_msg_button_clicked(self, widget, data = None):
+ (model, iter_) = self.msg_tree.get_selection().get_selected()
+ if not iter_:
+ return
+ buf = self.xml.get_object('msg_textview').get_buffer()
+ model.remove(iter_)
+ buf.set_text('')
+ self.xml.get_object('delete_msg_button').set_sensitive(False)
+
+ def on_msg_textview_changed(self, widget, data = None):
+ (model, iter_) = self.msg_tree.get_selection().get_selected()
+ if not iter_:
+ return
+ buf = self.xml.get_object('msg_textview').get_buffer()
+ first_iter, end_iter = buf.get_bounds()
+ model.set_value(iter_, 1, buf.get_text(first_iter, end_iter))
+
+ def on_msg_treeview_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Delete:
+ self.on_delete_msg_button_clicked(widget)
+
+ def on_open_advanced_editor_button_clicked(self, widget, data = None):
+ if 'advanced_config' in gajim.interface.instances:
+ gajim.interface.instances['advanced_config'].window.present()
+ else:
+ gajim.interface.instances['advanced_config'] = \
+ dialogs.AdvancedConfigurationWindow()
#---------- ManageProxiesWindow class -------------#
class ManageProxiesWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_proxies_window.ui')
- self.window = self.xml.get_object('manage_proxies_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.proxies_treeview = self.xml.get_object('proxies_treeview')
- self.proxyname_entry = self.xml.get_object('proxyname_entry')
- self.proxytype_combobox = self.xml.get_object('proxytype_combobox')
-
- self.init_list()
- self.block_signal = False
- self.xml.connect_signals(self)
- self.window.show_all()
- # hide the BOSH fields by default
- self.show_bosh_fields()
-
- def show_bosh_fields(self, show=True):
- if show:
- self.xml.get_object('boshuri_entry').show()
- self.xml.get_object('boshuri_label').show()
- self.xml.get_object('boshuseproxy_checkbutton').show()
- else:
- cb = self.xml.get_object('boshuseproxy_checkbutton')
- cb.hide()
- cb.set_active(True)
- self.on_boshuseproxy_checkbutton_toggled(cb)
- self.xml.get_object('boshuri_entry').hide()
- self.xml.get_object('boshuri_label').hide()
-
-
- def fill_proxies_treeview(self):
- model = self.proxies_treeview.get_model()
- model.clear()
- iter_ = model.append()
- model.set(iter_, 0, _('None'))
- for p in gajim.config.get_per('proxies'):
- iter_ = model.append()
- model.set(iter_, 0, p)
-
- def init_list(self):
- self.xml.get_object('remove_proxy_button').set_sensitive(False)
- self.proxytype_combobox.set_sensitive(False)
- self.xml.get_object('proxy_table').set_sensitive(False)
- model = gtk.ListStore(str)
- self.proxies_treeview.set_model(model)
- col = gtk.TreeViewColumn('Proxies')
- self.proxies_treeview.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.set_attributes(renderer, text = 0)
- self.fill_proxies_treeview()
- self.xml.get_object('proxytype_combobox').set_active(0)
-
- def on_manage_proxies_window_destroy(self, widget):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].\
- update_proxy_list()
- del gajim.interface.instances['manage_proxies']
-
- def on_add_proxy_button_clicked(self, widget):
- model = self.proxies_treeview.get_model()
- proxies = gajim.config.get_per('proxies')
- i = 1
- while ('proxy' + unicode(i)) in proxies:
- i += 1
- iter_ = model.append()
- model.set(iter_, 0, 'proxy' + unicode(i))
- gajim.config.add_per('proxies', 'proxy' + unicode(i))
- self.proxies_treeview.set_cursor(model.get_path(iter_))
-
- def on_remove_proxy_button_clicked(self, widget):
- (model, iter_) = self.proxies_treeview.get_selection().get_selected()
- if not iter_:
- return
- proxy = model[iter_][0].decode('utf-8')
- model.remove(iter_)
- gajim.config.del_per('proxies', proxy)
- self.xml.get_object('remove_proxy_button').set_sensitive(False)
- self.block_signal = True
- self.on_proxies_treeview_cursor_changed(self.proxies_treeview)
- self.block_signal = False
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_useauth_checkbutton_toggled(self, widget):
- if self.block_signal:
- return
- act = widget.get_active()
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'useauth', act)
- self.xml.get_object('proxyuser_entry').set_sensitive(act)
- self.xml.get_object('proxypass_entry').set_sensitive(act)
-
- def on_boshuseproxy_checkbutton_toggled(self, widget):
- if self.block_signal:
- return
- act = widget.get_active()
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'bosh_useproxy', act)
- self.xml.get_object('proxyhost_entry').set_sensitive(act)
- self.xml.get_object('proxyport_entry').set_sensitive(act)
-
- def on_proxies_treeview_cursor_changed(self, widget):
- #FIXME: check if off proxy settings are correct (see
- # http://trac.gajim.org/changeset/1921#file2 line 1221
- proxyhost_entry = self.xml.get_object('proxyhost_entry')
- proxyport_entry = self.xml.get_object('proxyport_entry')
- proxyuser_entry = self.xml.get_object('proxyuser_entry')
- proxypass_entry = self.xml.get_object('proxypass_entry')
- boshuri_entry = self.xml.get_object('boshuri_entry')
- useauth_checkbutton = self.xml.get_object('useauth_checkbutton')
- boshuseproxy_checkbutton = self.xml.get_object('boshuseproxy_checkbutton')
- self.block_signal = True
- proxyhost_entry.set_text('')
- proxyport_entry.set_text('')
- proxyuser_entry.set_text('')
- proxypass_entry.set_text('')
- boshuri_entry.set_text('')
-
- #boshuseproxy_checkbutton.set_active(False)
- #self.on_boshuseproxy_checkbutton_toggled(boshuseproxy_checkbutton)
-
- #useauth_checkbutton.set_active(False)
- #self.on_useauth_checkbutton_toggled(useauth_checkbutton)
-
- (model, iter_) = widget.get_selection().get_selected()
- if not iter_:
- self.xml.get_object('proxyname_entry').set_text('')
- self.xml.get_object('proxytype_combobox').set_sensitive(False)
- self.xml.get_object('proxy_table').set_sensitive(False)
- self.block_signal = False
- return
-
- proxy = model[iter_][0]
- self.xml.get_object('proxyname_entry').set_text(proxy)
-
- if proxy == _('None'): # special proxy None
- self.show_bosh_fields(False)
- self.proxyname_entry.set_editable(False)
- self.xml.get_object('remove_proxy_button').set_sensitive(False)
- self.xml.get_object('proxytype_combobox').set_sensitive(False)
- self.xml.get_object('proxy_table').set_sensitive(False)
- else:
- proxytype = gajim.config.get_per('proxies', proxy, 'type')
-
- self.show_bosh_fields(proxytype=='bosh')
-
- self.proxyname_entry.set_editable(True)
- self.xml.get_object('remove_proxy_button').set_sensitive(True)
- self.xml.get_object('proxytype_combobox').set_sensitive(True)
- self.xml.get_object('proxy_table').set_sensitive(True)
- proxyhost_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'host'))
- proxyport_entry.set_text(unicode(gajim.config.get_per('proxies',
- proxy, 'port')))
- proxyuser_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'user'))
- proxypass_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'pass'))
- boshuri_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'bosh_uri'))
- types = ['http', 'socks5', 'bosh']
- self.proxytype_combobox.set_active(types.index(proxytype))
- boshuseproxy_checkbutton.set_active(
- gajim.config.get_per('proxies', proxy, 'bosh_useproxy'))
- useauth_checkbutton.set_active(
- gajim.config.get_per('proxies', proxy, 'useauth'))
- self.block_signal = False
-
- def on_proxies_treeview_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Delete:
- self.on_remove_proxy_button_clicked(widget)
-
- def on_proxyname_entry_changed(self, widget):
- if self.block_signal:
- return
- (model, iter_) = self.proxies_treeview.get_selection().get_selected()
- if not iter_:
- return
- old_name = model.get_value(iter_, 0).decode('utf-8')
- new_name = widget.get_text().decode('utf-8')
- if new_name == '':
- return
- if new_name == old_name:
- return
- config = gajim.config.get_per('proxies', old_name)
- gajim.config.del_per('proxies', old_name)
- gajim.config.add_per('proxies', new_name)
- for option in config:
- gajim.config.set_per('proxies', new_name, option,
- config[option][common.config.OPT_VAL])
- model.set_value(iter_, 0, new_name)
-
- def on_proxytype_combobox_changed(self, widget):
- if self.block_signal:
- return
- types = ['http', 'socks5', 'bosh']
- type_ = self.proxytype_combobox.get_active()
- self.show_bosh_fields(types[type_]=='bosh')
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'type', types[type_])
-
- def on_proxyhost_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text().decode('utf-8')
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'host', value)
-
- def on_proxyport_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text().decode('utf-8')
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'port', value)
-
- def on_proxyuser_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text().decode('utf-8')
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'user', value)
-
- def on_boshuri_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text().decode('utf-8')
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'bosh_uri', value)
-
- def on_proxypass_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text().decode('utf-8')
- proxy = self.proxyname_entry.get_text().decode('utf-8')
- gajim.config.set_per('proxies', proxy, 'pass', value)
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('manage_proxies_window.ui')
+ self.window = self.xml.get_object('manage_proxies_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ self.proxies_treeview = self.xml.get_object('proxies_treeview')
+ self.proxyname_entry = self.xml.get_object('proxyname_entry')
+ self.proxytype_combobox = self.xml.get_object('proxytype_combobox')
+
+ self.init_list()
+ self.block_signal = False
+ self.xml.connect_signals(self)
+ self.window.show_all()
+ # hide the BOSH fields by default
+ self.show_bosh_fields()
+
+ def show_bosh_fields(self, show=True):
+ if show:
+ self.xml.get_object('boshuri_entry').show()
+ self.xml.get_object('boshuri_label').show()
+ self.xml.get_object('boshuseproxy_checkbutton').show()
+ else:
+ cb = self.xml.get_object('boshuseproxy_checkbutton')
+ cb.hide()
+ cb.set_active(True)
+ self.on_boshuseproxy_checkbutton_toggled(cb)
+ self.xml.get_object('boshuri_entry').hide()
+ self.xml.get_object('boshuri_label').hide()
+
+
+ def fill_proxies_treeview(self):
+ model = self.proxies_treeview.get_model()
+ model.clear()
+ iter_ = model.append()
+ model.set(iter_, 0, _('None'))
+ for p in gajim.config.get_per('proxies'):
+ iter_ = model.append()
+ model.set(iter_, 0, p)
+
+ def init_list(self):
+ self.xml.get_object('remove_proxy_button').set_sensitive(False)
+ self.proxytype_combobox.set_sensitive(False)
+ self.xml.get_object('proxy_table').set_sensitive(False)
+ model = gtk.ListStore(str)
+ self.proxies_treeview.set_model(model)
+ col = gtk.TreeViewColumn('Proxies')
+ self.proxies_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text = 0)
+ self.fill_proxies_treeview()
+ self.xml.get_object('proxytype_combobox').set_active(0)
+
+ def on_manage_proxies_window_destroy(self, widget):
+ if 'accounts' in gajim.interface.instances:
+ gajim.interface.instances['accounts'].\
+ update_proxy_list()
+ del gajim.interface.instances['manage_proxies']
+
+ def on_add_proxy_button_clicked(self, widget):
+ model = self.proxies_treeview.get_model()
+ proxies = gajim.config.get_per('proxies')
+ i = 1
+ while ('proxy' + unicode(i)) in proxies:
+ i += 1
+ iter_ = model.append()
+ model.set(iter_, 0, 'proxy' + unicode(i))
+ gajim.config.add_per('proxies', 'proxy' + unicode(i))
+ self.proxies_treeview.set_cursor(model.get_path(iter_))
+
+ def on_remove_proxy_button_clicked(self, widget):
+ (model, iter_) = self.proxies_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ proxy = model[iter_][0].decode('utf-8')
+ model.remove(iter_)
+ gajim.config.del_per('proxies', proxy)
+ self.xml.get_object('remove_proxy_button').set_sensitive(False)
+ self.block_signal = True
+ self.on_proxies_treeview_cursor_changed(self.proxies_treeview)
+ self.block_signal = False
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_useauth_checkbutton_toggled(self, widget):
+ if self.block_signal:
+ return
+ act = widget.get_active()
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'useauth', act)
+ self.xml.get_object('proxyuser_entry').set_sensitive(act)
+ self.xml.get_object('proxypass_entry').set_sensitive(act)
+
+ def on_boshuseproxy_checkbutton_toggled(self, widget):
+ if self.block_signal:
+ return
+ act = widget.get_active()
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'bosh_useproxy', act)
+ self.xml.get_object('proxyhost_entry').set_sensitive(act)
+ self.xml.get_object('proxyport_entry').set_sensitive(act)
+
+ def on_proxies_treeview_cursor_changed(self, widget):
+ #FIXME: check if off proxy settings are correct (see
+ # http://trac.gajim.org/changeset/1921#file2 line 1221
+ proxyhost_entry = self.xml.get_object('proxyhost_entry')
+ proxyport_entry = self.xml.get_object('proxyport_entry')
+ proxyuser_entry = self.xml.get_object('proxyuser_entry')
+ proxypass_entry = self.xml.get_object('proxypass_entry')
+ boshuri_entry = self.xml.get_object('boshuri_entry')
+ useauth_checkbutton = self.xml.get_object('useauth_checkbutton')
+ boshuseproxy_checkbutton = self.xml.get_object('boshuseproxy_checkbutton')
+ self.block_signal = True
+ proxyhost_entry.set_text('')
+ proxyport_entry.set_text('')
+ proxyuser_entry.set_text('')
+ proxypass_entry.set_text('')
+ boshuri_entry.set_text('')
+
+ #boshuseproxy_checkbutton.set_active(False)
+ #self.on_boshuseproxy_checkbutton_toggled(boshuseproxy_checkbutton)
+
+ #useauth_checkbutton.set_active(False)
+ #self.on_useauth_checkbutton_toggled(useauth_checkbutton)
+
+ (model, iter_) = widget.get_selection().get_selected()
+ if not iter_:
+ self.xml.get_object('proxyname_entry').set_text('')
+ self.xml.get_object('proxytype_combobox').set_sensitive(False)
+ self.xml.get_object('proxy_table').set_sensitive(False)
+ self.block_signal = False
+ return
+
+ proxy = model[iter_][0]
+ self.xml.get_object('proxyname_entry').set_text(proxy)
+
+ if proxy == _('None'): # special proxy None
+ self.show_bosh_fields(False)
+ self.proxyname_entry.set_editable(False)
+ self.xml.get_object('remove_proxy_button').set_sensitive(False)
+ self.xml.get_object('proxytype_combobox').set_sensitive(False)
+ self.xml.get_object('proxy_table').set_sensitive(False)
+ else:
+ proxytype = gajim.config.get_per('proxies', proxy, 'type')
+
+ self.show_bosh_fields(proxytype=='bosh')
+
+ self.proxyname_entry.set_editable(True)
+ self.xml.get_object('remove_proxy_button').set_sensitive(True)
+ self.xml.get_object('proxytype_combobox').set_sensitive(True)
+ self.xml.get_object('proxy_table').set_sensitive(True)
+ proxyhost_entry.set_text(gajim.config.get_per('proxies', proxy,
+ 'host'))
+ proxyport_entry.set_text(unicode(gajim.config.get_per('proxies',
+ proxy, 'port')))
+ proxyuser_entry.set_text(gajim.config.get_per('proxies', proxy,
+ 'user'))
+ proxypass_entry.set_text(gajim.config.get_per('proxies', proxy,
+ 'pass'))
+ boshuri_entry.set_text(gajim.config.get_per('proxies', proxy,
+ 'bosh_uri'))
+ types = ['http', 'socks5', 'bosh']
+ self.proxytype_combobox.set_active(types.index(proxytype))
+ boshuseproxy_checkbutton.set_active(
+ gajim.config.get_per('proxies', proxy, 'bosh_useproxy'))
+ useauth_checkbutton.set_active(
+ gajim.config.get_per('proxies', proxy, 'useauth'))
+ self.block_signal = False
+
+ def on_proxies_treeview_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Delete:
+ self.on_remove_proxy_button_clicked(widget)
+
+ def on_proxyname_entry_changed(self, widget):
+ if self.block_signal:
+ return
+ (model, iter_) = self.proxies_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ old_name = model.get_value(iter_, 0).decode('utf-8')
+ new_name = widget.get_text().decode('utf-8')
+ if new_name == '':
+ return
+ if new_name == old_name:
+ return
+ config = gajim.config.get_per('proxies', old_name)
+ gajim.config.del_per('proxies', old_name)
+ gajim.config.add_per('proxies', new_name)
+ for option in config:
+ gajim.config.set_per('proxies', new_name, option,
+ config[option][common.config.OPT_VAL])
+ model.set_value(iter_, 0, new_name)
+
+ def on_proxytype_combobox_changed(self, widget):
+ if self.block_signal:
+ return
+ types = ['http', 'socks5', 'bosh']
+ type_ = self.proxytype_combobox.get_active()
+ self.show_bosh_fields(types[type_]=='bosh')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'type', types[type_])
+
+ def on_proxyhost_entry_changed(self, widget):
+ if self.block_signal:
+ return
+ value = widget.get_text().decode('utf-8')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'host', value)
+
+ def on_proxyport_entry_changed(self, widget):
+ if self.block_signal:
+ return
+ value = widget.get_text().decode('utf-8')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'port', value)
+
+ def on_proxyuser_entry_changed(self, widget):
+ if self.block_signal:
+ return
+ value = widget.get_text().decode('utf-8')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'user', value)
+
+ def on_boshuri_entry_changed(self, widget):
+ if self.block_signal:
+ return
+ value = widget.get_text().decode('utf-8')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'bosh_uri', value)
+
+ def on_proxypass_entry_changed(self, widget):
+ if self.block_signal:
+ return
+ value = widget.get_text().decode('utf-8')
+ proxy = self.proxyname_entry.get_text().decode('utf-8')
+ gajim.config.set_per('proxies', proxy, 'pass', value)
#---------- AccountsWindow class -------------#
class AccountsWindow:
- """
- Class for accounts window: list of accounts
- """
-
- def on_accounts_window_destroy(self, widget):
- del gajim.interface.instances['accounts']
-
- def on_close_button_clicked(self, widget):
- self.check_resend_relog()
- self.window.destroy()
-
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('accounts_window.ui')
- self.window = self.xml.get_object('accounts_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.accounts_treeview = self.xml.get_object('accounts_treeview')
- self.remove_button = self.xml.get_object('remove_button')
- self.rename_button = self.xml.get_object('rename_button')
- path_to_kbd_input_img = gtkgui_helpers.get_icon_path('gajim-kbd_input')
- img = self.xml.get_object('rename_image')
- img.set_from_file(path_to_kbd_input_img)
- self.notebook = self.xml.get_object('notebook')
- # Name
- model = gtk.ListStore(str)
- self.accounts_treeview.set_model(model)
- # column
- renderer = gtk.CellRendererText()
- self.accounts_treeview.insert_column_with_attributes(-1, _('Name'),
- renderer, text=0)
-
- self.current_account = None
- # When we fill info, we don't want to handle the changed signals
- self.ignore_events = False
- self.need_relogin = False
- self.resend_presence = False
-
- self.update_proxy_list()
- self.xml.connect_signals(self)
- self.init_accounts()
- self.window.show_all()
-
- # Merge accounts
- st = gajim.config.get('mergeaccounts')
- checkbutton = self.xml.get_object('merge_checkbutton')
- checkbutton.set_active(st)
- # prevent roster redraws by connecting the signal after button state is
- # set
- checkbutton.connect('toggled', self.on_merge_checkbutton_toggled)
-
- self.avahi_available = True
- try:
- import avahi
- except ImportError:
- self.avahi_available = False
-
- def on_accounts_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.check_resend_relog()
- self.window.destroy()
-
- def select_account(self, account):
- model = self.accounts_treeview.get_model()
- iter_ = model.get_iter_root()
- while iter_:
- acct = model[iter_][0].decode('utf-8')
- if account == acct:
- self.accounts_treeview.set_cursor(model.get_path(iter_))
- return
- iter_ = model.iter_next(iter_)
-
- def init_accounts(self):
- """
- Initialize listStore with existing accounts
- """
- self.remove_button.set_sensitive(False)
- self.rename_button.set_sensitive(False)
- self.current_account = None
- model = self.accounts_treeview.get_model()
- model.clear()
- for account in gajim.config.get_per('accounts'):
- iter_ = model.append()
- model.set(iter_, 0, account)
-
- def resend(self, account):
- if not account in gajim.connections:
- return
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- status = gajim.connections[account].status
- gajim.connections[account].change_status(show, status)
-
- def check_resend_relog(self):
- if self.need_relogin and self.current_account == gajim.ZEROCONF_ACC_NAME:
- if gajim.ZEROCONF_ACC_NAME in gajim.connections:
- gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details()
- return
-
- elif self.need_relogin and self.current_account and \
- gajim.connections[self.current_account].connected > 0:
- def login(account, show_before, status_before):
- """
- Login with previous status
- """
- # first make sure connection is really closed,
- # 0.5 may not be enough
- gajim.connections[account].disconnect(True)
- gajim.interface.roster.send_status(account, show_before,
- status_before)
-
- def relog(account):
- self.dialog.destroy()
- show_before = gajim.SHOW_LIST[gajim.connections[account].connected]
- status_before = gajim.connections[account].status
- gajim.interface.roster.send_status(account, 'offline',
- _('Be right back.'))
- gobject.timeout_add(500, login, account, show_before, status_before)
-
- def on_yes(checked, account):
- relog(account)
- def on_no(account):
- if self.resend_presence:
- self.resend(account)
- if self.current_account in gajim.connections:
- self.dialog = dialogs.YesNoDialog(_('Relogin now?'),
- _('If you want all the changes to apply instantly, '
- 'you must relogin.'), on_response_yes=(on_yes,
- self.current_account), on_response_no=(on_no,
- self.current_account))
- elif self.resend_presence:
- self.resend(self.current_account)
-
- self.need_relogin = False
- self.resend_presence = False
-
- def on_accounts_treeview_cursor_changed(self, widget):
- """
- Activate modify buttons when a row is selected, update accounts info
- """
- sel = self.accounts_treeview.get_selection()
- (model, iter_) = sel.get_selected()
- if iter_:
- account = model[iter_][0].decode('utf-8')
- else:
- account = None
- if self.current_account and self.current_account == account:
- # We're comming back to our current account, no need to update widgets
- return
- # Save config for previous account if needed cause focus_out event is
- # called after the changed event
- if self.current_account and self.window.get_focus():
- focused_widget = self.window.get_focus()
- focused_widget_name = focused_widget.get_name()
- if focused_widget_name in ('jid_entry1', 'resource_entry1',
- 'custom_port_entry'):
- if focused_widget_name == 'jid_entry1':
- func = self.on_jid_entry1_focus_out_event
- elif focused_widget_name == 'resource_entry1':
- func = self.on_resource_entry1_focus_out_event
- elif focused_widget_name == 'custom_port_entry':
- func = self.on_custom_port_entry_focus_out_event
- if func(focused_widget, None):
- # Error detected in entry, don't change account, re-put cursor on
- # previous row
- self.select_account(self.current_account)
- return True
- self.window.set_focus(widget)
-
- self.check_resend_relog()
-
- if account:
- self.remove_button.set_sensitive(True)
- self.rename_button.set_sensitive(True)
- else:
- self.remove_button.set_sensitive(False)
- self.rename_button.set_sensitive(False)
- if iter_:
- self.current_account = account
- if account == gajim.ZEROCONF_ACC_NAME:
- self.remove_button.set_sensitive(False)
- self.init_account()
- self.update_proxy_list()
-
- def update_proxy_list(self):
- if self.current_account:
- our_proxy = gajim.config.get_per('accounts', self.current_account,
- 'proxy')
- else:
- our_proxy = ''
-
- if not our_proxy:
- our_proxy = _('None')
- proxy_combobox = self.xml.get_object('proxies_combobox1')
- model = gtk.ListStore(str)
- proxy_combobox.set_model(model)
- l = gajim.config.get_per('proxies')
- l.insert(0, _('None'))
- for i in xrange(len(l)):
- model.append([l[i]])
- if our_proxy == l[i]:
- proxy_combobox.set_active(i)
-
- def init_account(self):
- if not self.current_account:
- self.notebook.set_current_page(0)
- return
- if gajim.config.get_per('accounts', self.current_account, 'is_zeroconf'):
- self.ignore_events = True
- self.init_zeroconf_account()
- self.ignore_events = False
- self.notebook.set_current_page(2)
- return
- self.ignore_events = True
- self.init_normal_account()
- self.ignore_events = False
- self.notebook.set_current_page(1)
-
- def init_zeroconf_account(self):
- active = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'active')
- self.xml.get_object('enable_zeroconf_checkbutton2').set_active(active)
- if not gajim.HAVE_ZEROCONF:
- self.xml.get_object('enable_zeroconf_checkbutton2').set_sensitive(
- False)
- self.xml.get_object('zeroconf_notebook').set_sensitive(active)
- # General tab
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'autoconnect')
- self.xml.get_object('autoconnect_checkbutton2').set_active(st)
-
- list_no_log_for = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'no_log_for').split()
- if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
- self.xml.get_object('log_history_checkbutton2').set_active(0)
- else:
- self.xml.get_object('log_history_checkbutton2').set_active(1)
-
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'sync_with_global_status')
- self.xml.get_object('sync_with_global_status_checkbutton2').set_active(st)
-
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'use_custom_host')
- self.xml.get_object('custom_port_checkbutton2').set_active(st)
- self.xml.get_object('custom_port_entry2').set_sensitive(st)
-
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- if not st:
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port', '5298')
- st = '5298'
- self.xml.get_object('custom_port_entry2').set_text(str(st))
-
- # Personal tab
- gpg_key_label = self.xml.get_object('gpg_key_label2')
- if gajim.ZEROCONF_ACC_NAME in gajim.connections and \
- gajim.connections[gajim.ZEROCONF_ACC_NAME].gpg:
- self.xml.get_object('gpg_choose_button2').set_sensitive(True)
- self.init_account_gpg()
- else:
- gpg_key_label.set_text(_('OpenPGP is not usable on this computer'))
- self.xml.get_object('gpg_choose_button2').set_sensitive(False)
-
- for opt in ('first_name', 'last_name', 'jabber_id', 'email'):
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_' + opt)
- self.xml.get_object(opt + '_entry2').set_text(st)
-
- def init_account_gpg(self):
- account = self.current_account
- keyid = gajim.config.get_per('accounts', account, 'keyid')
- keyname = gajim.config.get_per('accounts', account, 'keyname')
- use_gpg_agent = gajim.config.get('use_gpg_agent')
-
- if account == gajim.ZEROCONF_ACC_NAME:
- widget_name_add = '2'
- else:
- widget_name_add = '1'
-
- gpg_key_label = self.xml.get_object('gpg_key_label' + widget_name_add)
- gpg_name_label = self.xml.get_object('gpg_name_label' + widget_name_add)
- use_gpg_agent_checkbutton = self.xml.get_object(
- 'use_gpg_agent_checkbutton' + widget_name_add)
-
- if not keyid:
- use_gpg_agent_checkbutton.set_sensitive(False)
- gpg_key_label.set_text(_('No key selected'))
- gpg_name_label.set_text('')
- return
-
- gpg_key_label.set_text(keyid)
- gpg_name_label.set_text(keyname)
- use_gpg_agent_checkbutton.set_sensitive(True)
- use_gpg_agent_checkbutton.set_active(use_gpg_agent)
-
- def draw_normal_jid(self):
- account = self.current_account
- self.ignore_events = True
- active = gajim.config.get_per('accounts', account, 'active')
- self.xml.get_object('enable_checkbutton1').set_active(active)
- self.xml.get_object('normal_notebook1').set_sensitive(active)
- if gajim.config.get_per('accounts', account, 'anonymous_auth'):
- self.xml.get_object('anonymous_checkbutton1').set_active(True)
- self.xml.get_object('jid_label1').set_text(_('Server:'))
- save_password = self.xml.get_object('save_password_checkbutton1')
- save_password.set_active(False)
- save_password.set_sensitive(False)
- password_entry = self.xml.get_object('password_entry1')
- password_entry.set_text('')
- password_entry.set_sensitive(False)
- jid = gajim.config.get_per('accounts', account, 'hostname')
- else:
- self.xml.get_object('anonymous_checkbutton1').set_active(False)
- self.xml.get_object('jid_label1').set_text(_('Jabber ID:'))
- savepass = gajim.config.get_per('accounts', account, 'savepass')
- save_password = self.xml.get_object('save_password_checkbutton1')
- save_password.set_sensitive(True)
- save_password.set_active(savepass)
- password_entry = self.xml.get_object('password_entry1')
- if savepass:
- passstr = passwords.get_password(account) or ''
- password_entry.set_sensitive(True)
- else:
- passstr = ''
- password_entry.set_sensitive(False)
- password_entry.set_text(passstr)
-
- jid = gajim.config.get_per('accounts', account, 'name') \
- + '@' + gajim.config.get_per('accounts', account, 'hostname')
- self.xml.get_object('jid_entry1').set_text(jid)
- self.ignore_events = False
-
- def init_normal_account(self):
- account = self.current_account
- # Account tab
- self.draw_normal_jid()
- self.xml.get_object('resource_entry1').set_text(gajim.config.get_per(
- 'accounts', account, 'resource'))
- self.xml.get_object('adjust_priority_with_status_checkbutton1').\
- set_active(gajim.config.get_per('accounts', account,
- 'adjust_priority_with_status'))
- spinbutton = self.xml.get_object('priority_spinbutton1')
- if gajim.config.get('enable_negative_priority'):
- spinbutton.set_range(-128, 127)
- else:
- spinbutton.set_range(0, 127)
- spinbutton.set_value(gajim.config.get_per('accounts', account,
- 'priority'))
-
- # Connection tab
- use_env_http_proxy = gajim.config.get_per('accounts', account,
- 'use_env_http_proxy')
- self.xml.get_object('use_env_http_proxy_checkbutton1').set_active(
- use_env_http_proxy)
- self.xml.get_object('proxy_hbox1').set_sensitive(not use_env_http_proxy)
-
- warn_when_insecure_ssl = gajim.config.get_per('accounts', account,
- 'warn_when_insecure_ssl_connection')
- self.xml.get_object('warn_when_insecure_connection_checkbutton1').\
- set_active(warn_when_insecure_ssl)
-
- self.xml.get_object('send_keepalive_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'keep_alives_enabled'))
-
- use_custom_host = gajim.config.get_per('accounts', account,
- 'use_custom_host')
- self.xml.get_object('custom_host_port_checkbutton1').set_active(
- use_custom_host)
- custom_host = gajim.config.get_per('accounts', account, 'custom_host')
- if not custom_host:
- custom_host = gajim.config.get_per('accounts', account, 'hostname')
- gajim.config.set_per('accounts', account, 'custom_host', custom_host)
- self.xml.get_object('custom_host_entry1').set_text(custom_host)
- custom_port = gajim.config.get_per('accounts', account, 'custom_port')
- if not custom_port:
- custom_port = 5222
- gajim.config.set_per('accounts', account, 'custom_port', custom_port)
- self.xml.get_object('custom_port_entry1').set_text(unicode(custom_port))
-
- # Personal tab
- gpg_key_label = self.xml.get_object('gpg_key_label1')
- if gajim.HAVE_GPG:
- self.xml.get_object('gpg_choose_button1').set_sensitive(True)
- self.init_account_gpg()
- else:
- gpg_key_label.set_text(_('OpenPGP is not usable on this computer'))
- self.xml.get_object('gpg_choose_button1').set_sensitive(False)
-
- # General tab
- self.xml.get_object('autoconnect_checkbutton1').set_active(gajim.config.\
- get_per('accounts', account, 'autoconnect'))
- self.xml.get_object('autoreconnect_checkbutton1').set_active(gajim.
- config.get_per('accounts', account, 'autoreconnect'))
-
- list_no_log_for = gajim.config.get_per('accounts', account,
- 'no_log_for').split()
- if account in list_no_log_for:
- self.xml.get_object('log_history_checkbutton1').set_active(False)
- else:
- self.xml.get_object('log_history_checkbutton1').set_active(True)
-
- self.xml.get_object('sync_with_global_status_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'sync_with_global_status'))
- self.xml.get_object('use_ft_proxies_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'use_ft_proxies'))
-
- def on_add_button_clicked(self, widget):
- """
- When add button is clicked: open an account information window
- """
- if 'account_creation_wizard' in gajim.interface.instances:
- gajim.interface.instances['account_creation_wizard'].window.present()
- else:
- gajim.interface.instances['account_creation_wizard'] = \
- AccountCreationWizardWindow()
-
- def on_remove_button_clicked(self, widget):
- """
- When delete button is clicked: Remove an account from the listStore and
- from the config file
- """
- if not self.current_account:
- return
- account = self.current_account
- if len(gajim.events.get_events(account)):
- dialogs.ErrorDialog(_('Unread events'),
- _('Read all pending events before removing this account.'))
- return
-
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- # Should never happen as button is insensitive
- return
-
- win_opened = False
- if gajim.interface.msg_win_mgr.get_controls(acct = account):
- win_opened = True
- else:
- for key in gajim.interface.instances[account]:
- if gajim.interface.instances[account][key] and key != \
- 'remove_account':
- win_opened = True
- break
- # Detect if we have opened windows for this account
- def remove(account):
- if 'remove_account' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['remove_account'].window.\
- present()
- else:
- gajim.interface.instances[account]['remove_account'] = \
- RemoveAccountWindow(account)
- if win_opened:
- dialogs.ConfirmationDialog(
- _('You have opened chat in account %s') % account,
- _('All chat and groupchat windows will be closed. Do you want to '
- 'continue?'),
- on_response_ok = (remove, account))
- else:
- remove(account)
-
- def on_rename_button_clicked(self, widget):
- if not self.current_account:
- return
- active = gajim.config.get_per('accounts', self.current_account, 'active')
- if active and gajim.connections[self.current_account].connected != 0:
- dialogs.ErrorDialog(
- _('You are currently connected to the server'),
- _('To change the account name, you must be disconnected.'))
- return
- if len(gajim.events.get_events(self.current_account)):
- dialogs.ErrorDialog(_('Unread events'),
- _('To change the account name, you must read all pending '
- 'events.'))
- return
- # Get the new name
- def on_renamed(new_name, old_name):
- if new_name in gajim.connections:
- dialogs.ErrorDialog(_('Account Name Already Used'),
- _('This name is already used by another of your accounts. '
- 'Please choose another name.'))
- return
- if (new_name == ''):
- dialogs.ErrorDialog(_('Invalid account name'),
- _('Account name cannot be empty.'))
- return
- if new_name.find(' ') != -1:
- dialogs.ErrorDialog(_('Invalid account name'),
- _('Account name cannot contain spaces.'))
- return
- if active:
- # update variables
- gajim.interface.instances[new_name] = gajim.interface.instances[
- old_name]
- gajim.interface.minimized_controls[new_name] = \
- gajim.interface.minimized_controls[old_name]
- gajim.nicks[new_name] = gajim.nicks[old_name]
- gajim.block_signed_in_notifications[new_name] = \
- gajim.block_signed_in_notifications[old_name]
- gajim.groups[new_name] = gajim.groups[old_name]
- gajim.gc_connected[new_name] = gajim.gc_connected[old_name]
- gajim.automatic_rooms[new_name] = gajim.automatic_rooms[old_name]
- gajim.newly_added[new_name] = gajim.newly_added[old_name]
- gajim.to_be_removed[new_name] = gajim.to_be_removed[old_name]
- gajim.sleeper_state[new_name] = gajim.sleeper_state[old_name]
- gajim.encrypted_chats[new_name] = gajim.encrypted_chats[old_name]
- gajim.last_message_time[new_name] = \
- gajim.last_message_time[old_name]
- gajim.status_before_autoaway[new_name] = \
- gajim.status_before_autoaway[old_name]
- gajim.transport_avatar[new_name] = gajim.transport_avatar[old_name]
- gajim.gajim_optional_features[new_name] = \
- gajim.gajim_optional_features[old_name]
- gajim.caps_hash[new_name] = gajim.caps_hash[old_name]
-
- gajim.contacts.change_account_name(old_name, new_name)
- gajim.events.change_account_name(old_name, new_name)
-
- # change account variable for chat / gc controls
- gajim.interface.msg_win_mgr.change_account_name(old_name, new_name)
- # upgrade account variable in opened windows
- for kind in ('infos', 'disco', 'gc_config', 'search',
- 'online_dialog'):
- for j in gajim.interface.instances[new_name][kind]:
- gajim.interface.instances[new_name][kind][j].account = \
- new_name
-
- # ServiceCache object keep old property account
- if hasattr(gajim.connections[old_name], 'services_cache'):
- gajim.connections[old_name].services_cache.account = new_name
- del gajim.interface.instances[old_name]
- del gajim.interface.minimized_controls[old_name]
- del gajim.nicks[old_name]
- del gajim.block_signed_in_notifications[old_name]
- del gajim.groups[old_name]
- del gajim.gc_connected[old_name]
- del gajim.automatic_rooms[old_name]
- del gajim.newly_added[old_name]
- del gajim.to_be_removed[old_name]
- del gajim.sleeper_state[old_name]
- del gajim.encrypted_chats[old_name]
- del gajim.last_message_time[old_name]
- del gajim.status_before_autoaway[old_name]
- del gajim.transport_avatar[old_name]
- del gajim.gajim_optional_features[old_name]
- del gajim.caps_hash[old_name]
- gajim.connections[old_name].name = new_name
- gajim.connections[new_name] = gajim.connections[old_name]
- del gajim.connections[old_name]
- gajim.config.add_per('accounts', new_name)
- old_config = gajim.config.get_per('accounts', old_name)
- for opt in old_config:
- gajim.config.set_per('accounts', new_name, opt, old_config[opt][1])
- gajim.config.del_per('accounts', old_name)
- if self.current_account == old_name:
- self.current_account = new_name
- if old_name == gajim.ZEROCONF_ACC_NAME:
- gajim.ZEROCONF_ACC_NAME = new_name
- # refresh roster
- gajim.interface.roster.setup_and_draw_roster()
- self.init_accounts()
- self.select_account(new_name)
-
- title = _('Rename Account')
- message = _('Enter a new name for account %s') % self.current_account
- old_text = self.current_account
- dialogs.InputDialog(title, message, old_text, is_modal=False,
- ok_handler=(on_renamed, self.current_account))
-
- def option_changed(self, option, value):
- return gajim.config.get_per('accounts', self.current_account, option) != \
- value
-
- def on_jid_entry1_focus_out_event(self, widget, event):
- if self.ignore_events:
- return
- jid = widget.get_text()
- # check if jid is conform to RFC and stringprep it
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat, s:
- if not widget.is_focus():
- pritext = _('Invalid Jabber ID')
- dialogs.ErrorDialog(pritext, str(s))
- gobject.idle_add(lambda: widget.grab_focus())
- return True
-
- jid_splited = jid.split('@', 1)
- if len(jid_splited) != 2 and not gajim.config.get_per('accounts',
- self.current_account, 'anonymous_auth'):
- if not widget.is_focus():
- pritext = _('Invalid Jabber ID')
- sectext = _('A Jabber ID must be in the form "user@servername".')
- dialogs.ErrorDialog(pritext, sectext)
- gobject.idle_add(lambda: widget.grab_focus())
- return True
-
-
- if gajim.config.get_per('accounts', self.current_account,
- 'anonymous_auth'):
- gajim.config.set_per('accounts', self.current_account, 'hostname',
- jid_splited[0])
- if self.option_changed('hostname', jid_splited[0]):
- self.need_relogin = True
- else:
- if self.option_changed('name', jid_splited[0]) or \
- self.option_changed('hostname', jid_splited[1]):
- self.need_relogin = True
-
- gajim.config.set_per('accounts', self.current_account, 'name',
- jid_splited[0])
- gajim.config.set_per('accounts', self.current_account, 'hostname',
- jid_splited[1])
-
- def on_anonymous_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- active = widget.get_active()
- gajim.config.set_per('accounts', self.current_account, 'anonymous_auth',
- active)
- self.draw_normal_jid()
-
- def on_password_entry1_changed(self, widget):
- if self.ignore_events:
- return
- passwords.save_password(self.current_account, widget.get_text().decode(
- 'utf-8'))
-
- def on_save_password_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- active = widget.get_active()
- password_entry = self.xml.get_object('password_entry1')
- password_entry.set_sensitive(active)
- gajim.config.set_per('accounts', self.current_account, 'savepass', active)
- if active:
- password = password_entry.get_text()
- passwords.save_password(self.current_account, password)
- else:
- passwords.save_password(self.current_account, '')
-
- def on_resource_entry1_focus_out_event(self, widget, event):
- if self.ignore_events:
- return
- resource = self.xml.get_object('resource_entry1').get_text().decode(
- 'utf-8')
- try:
- resource = helpers.parse_resource(resource)
- except helpers.InvalidFormat, s:
- if not widget.is_focus():
- pritext = _('Invalid Jabber ID')
- dialogs.ErrorDialog(pritext, str(s))
- gobject.idle_add(lambda: widget.grab_focus())
- return True
-
- if self.option_changed('resource', resource):
- self.need_relogin = True
-
- gajim.config.set_per('accounts', self.current_account, 'resource',
- resource)
-
- def on_adjust_priority_with_status_checkbutton1_toggled(self, widget):
- self.xml.get_object('priority_spinbutton1').set_sensitive(
- not widget.get_active())
- self.on_checkbutton_toggled(widget, 'adjust_priority_with_status',
- account = self.current_account)
-
- def on_priority_spinbutton1_value_changed(self, widget):
- prio = widget.get_value_as_int()
-
- if self.option_changed('priority', prio):
- self.resend_presence = True
-
- gajim.config.set_per('accounts', self.current_account, 'priority', prio)
-
- def on_synchronise_contacts_button1_clicked(self, widget):
- try:
- dialogs.SynchroniseSelectAccountDialog(self.current_account)
- except GajimGeneralException:
- # If we showed ErrorDialog, there will not be dialog instance
- return
-
- def on_change_password_button1_clicked(self, widget):
- def on_changed(new_password):
- if new_password is not None:
- gajim.connections[self.current_account].change_password(
- new_password)
- if self.xml.get_object('save_password_checkbutton1').get_active():
- self.xml.get_object('password_entry1').set_text(new_password)
-
- try:
- dialogs.ChangePasswordDialog(self.current_account, on_changed)
- except GajimGeneralException:
- # if we showed ErrorDialog, there will not be dialog instance
- return
-
- def on_autoconnect_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'autoconnect',
- account=self.current_account)
-
- def on_autoreconnect_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'autoreconnect',
- account=self.current_account)
-
- def on_log_history_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- list_no_log_for = gajim.config.get_per('accounts', self.current_account,
- 'no_log_for').split()
- if self.current_account in list_no_log_for:
- list_no_log_for.remove(self.current_account)
-
- if not widget.get_active():
- list_no_log_for.append(self.current_account)
- gajim.config.set_per('accounts', self.current_account, 'no_log_for',
- ' '.join(list_no_log_for))
-
- def on_sync_with_global_status_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'sync_with_global_status',
- account=self.current_account)
- gajim.interface.roster.update_status_combobox()
-
- def on_use_ft_proxies_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'use_ft_proxies',
- account=self.current_account)
-
- def on_use_env_http_proxy_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'use_env_http_proxy',
- account=self.current_account)
- hbox = self.xml.get_object('proxy_hbox1')
- hbox.set_sensitive(not widget.get_active())
-
- def on_proxies_combobox1_changed(self, widget):
- active = widget.get_active()
- proxy = widget.get_model()[active][0].decode('utf-8')
- if proxy == _('None'):
- proxy = ''
-
- if self.option_changed('proxy', proxy):
- self.need_relogin = True
-
- gajim.config.set_per('accounts', self.current_account, 'proxy', proxy)
-
- def on_manage_proxies_button1_clicked(self, widget):
- if 'manage_proxies' in gajim.interface.instances:
- gajim.interface.instances['manage_proxies'].window.present()
- else:
- gajim.interface.instances['manage_proxies'] = ManageProxiesWindow()
-
- def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
-
- self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection',
- account=self.current_account)
-
- def on_send_keepalive_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'keep_alives_enabled',
- account=self.current_account)
- gajim.config.set_per('accounts', self.current_account,
- 'ping_alives_enabled', widget.get_active())
-
- def on_custom_host_port_checkbutton1_toggled(self, widget):
- if self.option_changed('use_custom_host', widget.get_active()):
- self.need_relogin = True
-
- self.on_checkbutton_toggled(widget, 'use_custom_host',
- account=self.current_account)
- active = widget.get_active()
- self.xml.get_object('custom_host_port_hbox1').set_sensitive(active)
-
- def on_custom_host_entry1_changed(self, widget):
- if self.ignore_events:
- return
- host = widget.get_text().decode('utf-8')
- if self.option_changed('custom_host', host):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account, 'custom_host',
- host)
-
- def on_custom_port_entry_focus_out_event(self, widget, event):
- if self.ignore_events:
- return
- custom_port = widget.get_text()
- try:
- custom_port = int(custom_port)
- except Exception:
- if not widget.is_focus():
- dialogs.ErrorDialog(_('Invalid entry'),
- _('Custom port must be a port number.'))
- gobject.idle_add(lambda: widget.grab_focus())
- return True
- if self.option_changed('custom_port', custom_port):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account, 'custom_port',
- custom_port)
-
- def on_gpg_choose_button_clicked(self, widget, data = None):
- if self.current_account in gajim.connections and \
- gajim.connections[self.current_account].gpg:
- secret_keys = gajim.connections[self.current_account].\
- ask_gpg_secrete_keys()
-
- # self.current_account is None and/or gajim.connections is {}
- else:
- if gajim.HAVE_GPG:
- secret_keys = GnuPG.GnuPG().get_secret_keys()
- else:
- secret_keys = []
- if not secret_keys:
- dialogs.ErrorDialog(_('Failed to get secret keys'),
- _('There is no OpenPGP secret key available.'))
- secret_keys[_('None')] = _('None')
-
- def on_key_selected(keyID):
- if keyID is None:
- return
- if self.current_account == gajim.ZEROCONF_ACC_NAME:
- wiget_name_ext = '2'
- else:
- wiget_name_ext = '1'
- gpg_key_label = self.xml.get_object('gpg_key_label' + wiget_name_ext)
- gpg_name_label = self.xml.get_object('gpg_name_label' + wiget_name_ext)
- use_gpg_agent_checkbutton = self.xml.get_object(
- 'use_gpg_agent_checkbutton' + wiget_name_ext)
- if keyID[0] == _('None'):
- gpg_key_label.set_text(_('No key selected'))
- gpg_name_label.set_text('')
- use_gpg_agent_checkbutton.set_sensitive(False)
- if self.option_changed('keyid', ''):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account, 'keyname',
- '')
- gajim.config.set_per('accounts', self.current_account, 'keyid', '')
- else:
- gpg_key_label.set_text(keyID[0])
- gpg_name_label.set_text(keyID[1])
- use_gpg_agent_checkbutton.set_sensitive(True)
- if self.option_changed('keyid', keyID[0]):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account, 'keyname',
- keyID[1])
- gajim.config.set_per('accounts', self.current_account, 'keyid',
- keyID[0])
-
- dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
- _('Choose your OpenPGP key'), secret_keys, on_key_selected)
-
- def on_use_gpg_agent_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'use_gpg_agent')
-
- def on_edit_details_button1_clicked(self, widget):
- if self.current_account not in gajim.interface.instances:
- dialogs.ErrorDialog(_('No such account available'),
- _('You must create your account before editing your personal '
- 'information.'))
- return
-
- # show error dialog if account is newly created (not in gajim.connections)
- if self.current_account not in gajim.connections or \
- gajim.connections[self.current_account].connected < 2:
- dialogs.ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not edit your personal information.'))
- return
-
- if not gajim.connections[self.current_account].vcard_supported:
- dialogs.ErrorDialog(_("Your server doesn't support Vcard"),
- _("Your server can't save your personal information."))
- return
-
- gajim.interface.edit_own_details(self.current_account)
-
- def on_checkbutton_toggled(self, widget, config_name,
- change_sensitivity_widgets = None, account = None):
- if account:
- gajim.config.set_per('accounts', account, config_name,
- widget.get_active())
- else:
- gajim.config.set(config_name, widget.get_active())
- if change_sensitivity_widgets:
- for w in change_sensitivity_widgets:
- w.set_sensitive(widget.get_active())
- gajim.interface.save_config()
-
- def on_merge_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'mergeaccounts')
- if len(gajim.connections) >= 2: # Do not merge accounts if only one active
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
-
- def _disable_account(self, account):
- gajim.interface.roster.close_all(account)
- if account == gajim.ZEROCONF_ACC_NAME:
- gajim.connections[account].disable_account()
- del gajim.connections[account]
- gajim.interface.save_config()
- del gajim.interface.instances[account]
- del gajim.interface.minimized_controls[account]
- del gajim.nicks[account]
- del gajim.block_signed_in_notifications[account]
- del gajim.groups[account]
- gajim.contacts.remove_account(account)
- del gajim.gc_connected[account]
- del gajim.automatic_rooms[account]
- del gajim.to_be_removed[account]
- del gajim.newly_added[account]
- del gajim.sleeper_state[account]
- del gajim.encrypted_chats[account]
- del gajim.last_message_time[account]
- del gajim.status_before_autoaway[account]
- del gajim.transport_avatar[account]
- del gajim.gajim_optional_features[account]
- del gajim.caps_hash[account]
- if len(gajim.connections) >= 2:
- # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.interface.roster.set_actions_menu_needs_rebuild()
-
- def _enable_account(self, account):
- if account == gajim.ZEROCONF_ACC_NAME:
- gajim.connections[account] = connection_zeroconf.ConnectionZeroconf(
- account)
- if gajim.connections[account].gpg:
- self.xml.get_object('gpg_choose_button2').set_sensitive(True)
- else:
- gajim.connections[account] = common.connection.Connection(account)
- if gajim.connections[account].gpg:
- self.xml.get_object('gpg_choose_button1').set_sensitive(True)
- self.init_account_gpg()
- # update variables
- gajim.interface.instances[account] = {'infos': {},
- 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}}
- gajim.interface.minimized_controls[account] = {}
- gajim.connections[account].connected = 0
- gajim.groups[account] = {}
- gajim.contacts.add_account(account)
- gajim.gc_connected[account] = {}
- gajim.automatic_rooms[account] = {}
- gajim.newly_added[account] = []
- gajim.to_be_removed[account] = []
- if account == gajim.ZEROCONF_ACC_NAME:
- gajim.nicks[account] = gajim.ZEROCONF_ACC_NAME
- else:
- gajim.nicks[account] = gajim.config.get_per('accounts', account,
- 'name')
- gajim.block_signed_in_notifications[account] = True
- gajim.sleeper_state[account] = 'off'
- gajim.encrypted_chats[account] = []
- gajim.last_message_time[account] = {}
- gajim.status_before_autoaway[account] = ''
- gajim.transport_avatar[account] = {}
- gajim.gajim_optional_features[account] = []
- gajim.caps_hash[account] = ''
- # refresh roster
- if len(gajim.connections) >= 2:
- # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.interface.roster.set_actions_menu_needs_rebuild()
- gajim.interface.save_config()
-
- def on_enable_zeroconf_checkbutton2_toggled(self, widget):
- # don't do anything if there is an account with the local name but is a
- # normal account
- if self.ignore_events:
- return
- if self.current_account in gajim.connections and \
- gajim.connections[self.current_account].connected > 0:
- self.ignore_events = True
- self.xml.get_object('enable_zeroconf_checkbutton2').set_active(True)
- self.ignore_events = False
- dialogs.ErrorDialog(
- _('You are currently connected to the server'),
- _('To disable the account, you must be disconnected.'))
- return
- if gajim.ZEROCONF_ACC_NAME in gajim.connections and not \
- gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf:
- gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR',
- (_('Account Local already exists.'),
- _('Please rename or remove it before enabling link-local messaging'
- '.')))
- return
-
- if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
- and not widget.get_active():
- self.xml.get_object('zeroconf_notebook').set_sensitive(False)
- # disable
- self._disable_account(gajim.ZEROCONF_ACC_NAME)
-
- elif not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'active') and widget.get_active():
- self.xml.get_object('zeroconf_notebook').set_sensitive(True)
- # enable (will create new account if not present)
- self._enable_account(gajim.ZEROCONF_ACC_NAME)
-
- self.on_checkbutton_toggled(widget, 'active',
- account=gajim.ZEROCONF_ACC_NAME)
-
- def on_enable_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- if self.current_account in gajim.connections and \
- gajim.connections[self.current_account].connected > 0:
- # connecting or connected
- self.ignore_events = True
- self.xml.get_object('enable_checkbutton1').set_active(True)
- self.ignore_events = False
- dialogs.ErrorDialog(
- _('You are currently connected to the server'),
- _('To disable the account, you must be disconnected.'))
- return
- # add/remove account in roster and all variables
- if widget.get_active():
- # enable
- self._enable_account(self.current_account)
- else:
- # disable
- self._disable_account(self.current_account)
- self.on_checkbutton_toggled(widget, 'active',
- account=self.current_account, change_sensitivity_widgets=[
- self.xml.get_object('normal_notebook1')])
-
- def on_custom_port_checkbutton2_toggled(self, widget):
- self.xml.get_object('custom_port_entry2').set_sensitive(
- widget.get_active())
- self.on_checkbutton_toggled(widget, 'use_custom_host',
- account = self.current_account)
- if not widget.get_active():
- self.xml.get_object('custom_port_entry2').set_text('5298')
-
- def on_first_name_entry2_changed(self, widget):
- if self.ignore_events:
- return
- name = widget.get_text().decode('utf-8')
- if self.option_changed('zeroconf_first_name', name):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_first_name', name)
-
- def on_last_name_entry2_changed(self, widget):
- if self.ignore_events:
- return
- name = widget.get_text().decode('utf-8')
- if self.option_changed('zeroconf_last_name', name):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_last_name', name)
-
- def on_jabber_id_entry2_changed(self, widget):
- if self.ignore_events:
- return
- id_ = widget.get_text().decode('utf-8')
- if self.option_changed('zeroconf_jabber_id', id_):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_jabber_id', id_)
-
- def on_email_entry2_changed(self, widget):
- if self.ignore_events:
- return
- email = widget.get_text().decode('utf-8')
- if self.option_changed('zeroconf_email', email):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_email', email)
+ """
+ Class for accounts window: list of accounts
+ """
+
+ def on_accounts_window_destroy(self, widget):
+ del gajim.interface.instances['accounts']
+
+ def on_close_button_clicked(self, widget):
+ self.check_resend_relog()
+ self.window.destroy()
+
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('accounts_window.ui')
+ self.window = self.xml.get_object('accounts_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ self.accounts_treeview = self.xml.get_object('accounts_treeview')
+ self.remove_button = self.xml.get_object('remove_button')
+ self.rename_button = self.xml.get_object('rename_button')
+ path_to_kbd_input_img = gtkgui_helpers.get_icon_path('gajim-kbd_input')
+ img = self.xml.get_object('rename_image')
+ img.set_from_file(path_to_kbd_input_img)
+ self.notebook = self.xml.get_object('notebook')
+ # Name
+ model = gtk.ListStore(str)
+ self.accounts_treeview.set_model(model)
+ # column
+ renderer = gtk.CellRendererText()
+ self.accounts_treeview.insert_column_with_attributes(-1, _('Name'),
+ renderer, text=0)
+
+ self.current_account = None
+ # When we fill info, we don't want to handle the changed signals
+ self.ignore_events = False
+ self.need_relogin = False
+ self.resend_presence = False
+
+ self.update_proxy_list()
+ self.xml.connect_signals(self)
+ self.init_accounts()
+ self.window.show_all()
+
+ # Merge accounts
+ st = gajim.config.get('mergeaccounts')
+ checkbutton = self.xml.get_object('merge_checkbutton')
+ checkbutton.set_active(st)
+ # prevent roster redraws by connecting the signal after button state is
+ # set
+ checkbutton.connect('toggled', self.on_merge_checkbutton_toggled)
+
+ self.avahi_available = True
+ try:
+ import avahi
+ except ImportError:
+ self.avahi_available = False
+
+ def on_accounts_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.check_resend_relog()
+ self.window.destroy()
+
+ def select_account(self, account):
+ model = self.accounts_treeview.get_model()
+ iter_ = model.get_iter_root()
+ while iter_:
+ acct = model[iter_][0].decode('utf-8')
+ if account == acct:
+ self.accounts_treeview.set_cursor(model.get_path(iter_))
+ return
+ iter_ = model.iter_next(iter_)
+
+ def init_accounts(self):
+ """
+ Initialize listStore with existing accounts
+ """
+ self.remove_button.set_sensitive(False)
+ self.rename_button.set_sensitive(False)
+ self.current_account = None
+ model = self.accounts_treeview.get_model()
+ model.clear()
+ for account in gajim.config.get_per('accounts'):
+ iter_ = model.append()
+ model.set(iter_, 0, account)
+
+ def resend(self, account):
+ if not account in gajim.connections:
+ return
+ show = gajim.SHOW_LIST[gajim.connections[account].connected]
+ status = gajim.connections[account].status
+ gajim.connections[account].change_status(show, status)
+
+ def check_resend_relog(self):
+ if self.need_relogin and self.current_account == gajim.ZEROCONF_ACC_NAME:
+ if gajim.ZEROCONF_ACC_NAME in gajim.connections:
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details()
+ return
+
+ elif self.need_relogin and self.current_account and \
+ gajim.connections[self.current_account].connected > 0:
+ def login(account, show_before, status_before):
+ """
+ Login with previous status
+ """
+ # first make sure connection is really closed,
+ # 0.5 may not be enough
+ gajim.connections[account].disconnect(True)
+ gajim.interface.roster.send_status(account, show_before,
+ status_before)
+
+ def relog(account):
+ self.dialog.destroy()
+ show_before = gajim.SHOW_LIST[gajim.connections[account].connected]
+ status_before = gajim.connections[account].status
+ gajim.interface.roster.send_status(account, 'offline',
+ _('Be right back.'))
+ gobject.timeout_add(500, login, account, show_before, status_before)
+
+ def on_yes(checked, account):
+ relog(account)
+ def on_no(account):
+ if self.resend_presence:
+ self.resend(account)
+ if self.current_account in gajim.connections:
+ self.dialog = dialogs.YesNoDialog(_('Relogin now?'),
+ _('If you want all the changes to apply instantly, '
+ 'you must relogin.'), on_response_yes=(on_yes,
+ self.current_account), on_response_no=(on_no,
+ self.current_account))
+ elif self.resend_presence:
+ self.resend(self.current_account)
+
+ self.need_relogin = False
+ self.resend_presence = False
+
+ def on_accounts_treeview_cursor_changed(self, widget):
+ """
+ Activate modify buttons when a row is selected, update accounts info
+ """
+ sel = self.accounts_treeview.get_selection()
+ (model, iter_) = sel.get_selected()
+ if iter_:
+ account = model[iter_][0].decode('utf-8')
+ else:
+ account = None
+ if self.current_account and self.current_account == account:
+ # We're comming back to our current account, no need to update widgets
+ return
+ # Save config for previous account if needed cause focus_out event is
+ # called after the changed event
+ if self.current_account and self.window.get_focus():
+ focused_widget = self.window.get_focus()
+ focused_widget_name = focused_widget.get_name()
+ if focused_widget_name in ('jid_entry1', 'resource_entry1',
+ 'custom_port_entry'):
+ if focused_widget_name == 'jid_entry1':
+ func = self.on_jid_entry1_focus_out_event
+ elif focused_widget_name == 'resource_entry1':
+ func = self.on_resource_entry1_focus_out_event
+ elif focused_widget_name == 'custom_port_entry':
+ func = self.on_custom_port_entry_focus_out_event
+ if func(focused_widget, None):
+ # Error detected in entry, don't change account, re-put cursor on
+ # previous row
+ self.select_account(self.current_account)
+ return True
+ self.window.set_focus(widget)
+
+ self.check_resend_relog()
+
+ if account:
+ self.remove_button.set_sensitive(True)
+ self.rename_button.set_sensitive(True)
+ else:
+ self.remove_button.set_sensitive(False)
+ self.rename_button.set_sensitive(False)
+ if iter_:
+ self.current_account = account
+ if account == gajim.ZEROCONF_ACC_NAME:
+ self.remove_button.set_sensitive(False)
+ self.init_account()
+ self.update_proxy_list()
+
+ def update_proxy_list(self):
+ if self.current_account:
+ our_proxy = gajim.config.get_per('accounts', self.current_account,
+ 'proxy')
+ else:
+ our_proxy = ''
+
+ if not our_proxy:
+ our_proxy = _('None')
+ proxy_combobox = self.xml.get_object('proxies_combobox1')
+ model = gtk.ListStore(str)
+ proxy_combobox.set_model(model)
+ l = gajim.config.get_per('proxies')
+ l.insert(0, _('None'))
+ for i in xrange(len(l)):
+ model.append([l[i]])
+ if our_proxy == l[i]:
+ proxy_combobox.set_active(i)
+
+ def init_account(self):
+ if not self.current_account:
+ self.notebook.set_current_page(0)
+ return
+ if gajim.config.get_per('accounts', self.current_account, 'is_zeroconf'):
+ self.ignore_events = True
+ self.init_zeroconf_account()
+ self.ignore_events = False
+ self.notebook.set_current_page(2)
+ return
+ self.ignore_events = True
+ self.init_normal_account()
+ self.ignore_events = False
+ self.notebook.set_current_page(1)
+
+ def init_zeroconf_account(self):
+ active = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'active')
+ self.xml.get_object('enable_zeroconf_checkbutton2').set_active(active)
+ if not gajim.HAVE_ZEROCONF:
+ self.xml.get_object('enable_zeroconf_checkbutton2').set_sensitive(
+ False)
+ self.xml.get_object('zeroconf_notebook').set_sensitive(active)
+ # General tab
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'autoconnect')
+ self.xml.get_object('autoconnect_checkbutton2').set_active(st)
+
+ list_no_log_for = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'no_log_for').split()
+ if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
+ self.xml.get_object('log_history_checkbutton2').set_active(0)
+ else:
+ self.xml.get_object('log_history_checkbutton2').set_active(1)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'sync_with_global_status')
+ self.xml.get_object('sync_with_global_status_checkbutton2').set_active(st)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'use_custom_host')
+ self.xml.get_object('custom_port_checkbutton2').set_active(st)
+ self.xml.get_object('custom_port_entry2').set_sensitive(st)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'custom_port')
+ if not st:
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'custom_port', '5298')
+ st = '5298'
+ self.xml.get_object('custom_port_entry2').set_text(str(st))
+
+ # Personal tab
+ gpg_key_label = self.xml.get_object('gpg_key_label2')
+ if gajim.ZEROCONF_ACC_NAME in gajim.connections and \
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].gpg:
+ self.xml.get_object('gpg_choose_button2').set_sensitive(True)
+ self.init_account_gpg()
+ else:
+ gpg_key_label.set_text(_('OpenPGP is not usable on this computer'))
+ self.xml.get_object('gpg_choose_button2').set_sensitive(False)
+
+ for opt in ('first_name', 'last_name', 'jabber_id', 'email'):
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'zeroconf_' + opt)
+ self.xml.get_object(opt + '_entry2').set_text(st)
+
+ def init_account_gpg(self):
+ account = self.current_account
+ keyid = gajim.config.get_per('accounts', account, 'keyid')
+ keyname = gajim.config.get_per('accounts', account, 'keyname')
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+
+ if account == gajim.ZEROCONF_ACC_NAME:
+ widget_name_add = '2'
+ else:
+ widget_name_add = '1'
+
+ gpg_key_label = self.xml.get_object('gpg_key_label' + widget_name_add)
+ gpg_name_label = self.xml.get_object('gpg_name_label' + widget_name_add)
+ use_gpg_agent_checkbutton = self.xml.get_object(
+ 'use_gpg_agent_checkbutton' + widget_name_add)
+
+ if not keyid:
+ use_gpg_agent_checkbutton.set_sensitive(False)
+ gpg_key_label.set_text(_('No key selected'))
+ gpg_name_label.set_text('')
+ return
+
+ gpg_key_label.set_text(keyid)
+ gpg_name_label.set_text(keyname)
+ use_gpg_agent_checkbutton.set_sensitive(True)
+ use_gpg_agent_checkbutton.set_active(use_gpg_agent)
+
+ def draw_normal_jid(self):
+ account = self.current_account
+ self.ignore_events = True
+ active = gajim.config.get_per('accounts', account, 'active')
+ self.xml.get_object('enable_checkbutton1').set_active(active)
+ self.xml.get_object('normal_notebook1').set_sensitive(active)
+ if gajim.config.get_per('accounts', account, 'anonymous_auth'):
+ self.xml.get_object('anonymous_checkbutton1').set_active(True)
+ self.xml.get_object('jid_label1').set_text(_('Server:'))
+ save_password = self.xml.get_object('save_password_checkbutton1')
+ save_password.set_active(False)
+ save_password.set_sensitive(False)
+ password_entry = self.xml.get_object('password_entry1')
+ password_entry.set_text('')
+ password_entry.set_sensitive(False)
+ jid = gajim.config.get_per('accounts', account, 'hostname')
+ else:
+ self.xml.get_object('anonymous_checkbutton1').set_active(False)
+ self.xml.get_object('jid_label1').set_text(_('Jabber ID:'))
+ savepass = gajim.config.get_per('accounts', account, 'savepass')
+ save_password = self.xml.get_object('save_password_checkbutton1')
+ save_password.set_sensitive(True)
+ save_password.set_active(savepass)
+ password_entry = self.xml.get_object('password_entry1')
+ if savepass:
+ passstr = passwords.get_password(account) or ''
+ password_entry.set_sensitive(True)
+ else:
+ passstr = ''
+ password_entry.set_sensitive(False)
+ password_entry.set_text(passstr)
+
+ jid = gajim.config.get_per('accounts', account, 'name') \
+ + '@' + gajim.config.get_per('accounts', account, 'hostname')
+ self.xml.get_object('jid_entry1').set_text(jid)
+ self.ignore_events = False
+
+ def init_normal_account(self):
+ account = self.current_account
+ # Account tab
+ self.draw_normal_jid()
+ self.xml.get_object('resource_entry1').set_text(gajim.config.get_per(
+ 'accounts', account, 'resource'))
+ self.xml.get_object('adjust_priority_with_status_checkbutton1').\
+ set_active(gajim.config.get_per('accounts', account,
+ 'adjust_priority_with_status'))
+ spinbutton = self.xml.get_object('priority_spinbutton1')
+ if gajim.config.get('enable_negative_priority'):
+ spinbutton.set_range(-128, 127)
+ else:
+ spinbutton.set_range(0, 127)
+ spinbutton.set_value(gajim.config.get_per('accounts', account,
+ 'priority'))
+
+ # Connection tab
+ use_env_http_proxy = gajim.config.get_per('accounts', account,
+ 'use_env_http_proxy')
+ self.xml.get_object('use_env_http_proxy_checkbutton1').set_active(
+ use_env_http_proxy)
+ self.xml.get_object('proxy_hbox1').set_sensitive(not use_env_http_proxy)
+
+ warn_when_insecure_ssl = gajim.config.get_per('accounts', account,
+ 'warn_when_insecure_ssl_connection')
+ self.xml.get_object('warn_when_insecure_connection_checkbutton1').\
+ set_active(warn_when_insecure_ssl)
+
+ self.xml.get_object('send_keepalive_checkbutton1').set_active(
+ gajim.config.get_per('accounts', account, 'keep_alives_enabled'))
+
+ use_custom_host = gajim.config.get_per('accounts', account,
+ 'use_custom_host')
+ self.xml.get_object('custom_host_port_checkbutton1').set_active(
+ use_custom_host)
+ custom_host = gajim.config.get_per('accounts', account, 'custom_host')
+ if not custom_host:
+ custom_host = gajim.config.get_per('accounts', account, 'hostname')
+ gajim.config.set_per('accounts', account, 'custom_host', custom_host)
+ self.xml.get_object('custom_host_entry1').set_text(custom_host)
+ custom_port = gajim.config.get_per('accounts', account, 'custom_port')
+ if not custom_port:
+ custom_port = 5222
+ gajim.config.set_per('accounts', account, 'custom_port', custom_port)
+ self.xml.get_object('custom_port_entry1').set_text(unicode(custom_port))
+
+ # Personal tab
+ gpg_key_label = self.xml.get_object('gpg_key_label1')
+ if gajim.HAVE_GPG:
+ self.xml.get_object('gpg_choose_button1').set_sensitive(True)
+ self.init_account_gpg()
+ else:
+ gpg_key_label.set_text(_('OpenPGP is not usable on this computer'))
+ self.xml.get_object('gpg_choose_button1').set_sensitive(False)
+
+ # General tab
+ self.xml.get_object('autoconnect_checkbutton1').set_active(gajim.config.\
+ get_per('accounts', account, 'autoconnect'))
+ self.xml.get_object('autoreconnect_checkbutton1').set_active(gajim.
+ config.get_per('accounts', account, 'autoreconnect'))
+
+ list_no_log_for = gajim.config.get_per('accounts', account,
+ 'no_log_for').split()
+ if account in list_no_log_for:
+ self.xml.get_object('log_history_checkbutton1').set_active(False)
+ else:
+ self.xml.get_object('log_history_checkbutton1').set_active(True)
+
+ self.xml.get_object('sync_with_global_status_checkbutton1').set_active(
+ gajim.config.get_per('accounts', account, 'sync_with_global_status'))
+ self.xml.get_object('use_ft_proxies_checkbutton1').set_active(
+ gajim.config.get_per('accounts', account, 'use_ft_proxies'))
+
+ def on_add_button_clicked(self, widget):
+ """
+ When add button is clicked: open an account information window
+ """
+ if 'account_creation_wizard' in gajim.interface.instances:
+ gajim.interface.instances['account_creation_wizard'].window.present()
+ else:
+ gajim.interface.instances['account_creation_wizard'] = \
+ AccountCreationWizardWindow()
+
+ def on_remove_button_clicked(self, widget):
+ """
+ When delete button is clicked: Remove an account from the listStore and
+ from the config file
+ """
+ if not self.current_account:
+ return
+ account = self.current_account
+ if len(gajim.events.get_events(account)):
+ dialogs.ErrorDialog(_('Unread events'),
+ _('Read all pending events before removing this account.'))
+ return
+
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ # Should never happen as button is insensitive
+ return
+
+ win_opened = False
+ if gajim.interface.msg_win_mgr.get_controls(acct = account):
+ win_opened = True
+ else:
+ for key in gajim.interface.instances[account]:
+ if gajim.interface.instances[account][key] and key != \
+ 'remove_account':
+ win_opened = True
+ break
+ # Detect if we have opened windows for this account
+ def remove(account):
+ if 'remove_account' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['remove_account'].window.\
+ present()
+ else:
+ gajim.interface.instances[account]['remove_account'] = \
+ RemoveAccountWindow(account)
+ if win_opened:
+ dialogs.ConfirmationDialog(
+ _('You have opened chat in account %s') % account,
+ _('All chat and groupchat windows will be closed. Do you want to '
+ 'continue?'),
+ on_response_ok = (remove, account))
+ else:
+ remove(account)
+
+ def on_rename_button_clicked(self, widget):
+ if not self.current_account:
+ return
+ active = gajim.config.get_per('accounts', self.current_account, 'active')
+ if active and gajim.connections[self.current_account].connected != 0:
+ dialogs.ErrorDialog(
+ _('You are currently connected to the server'),
+ _('To change the account name, you must be disconnected.'))
+ return
+ if len(gajim.events.get_events(self.current_account)):
+ dialogs.ErrorDialog(_('Unread events'),
+ _('To change the account name, you must read all pending '
+ 'events.'))
+ return
+ # Get the new name
+ def on_renamed(new_name, old_name):
+ if new_name in gajim.connections:
+ dialogs.ErrorDialog(_('Account Name Already Used'),
+ _('This name is already used by another of your accounts. '
+ 'Please choose another name.'))
+ return
+ if (new_name == ''):
+ dialogs.ErrorDialog(_('Invalid account name'),
+ _('Account name cannot be empty.'))
+ return
+ if new_name.find(' ') != -1:
+ dialogs.ErrorDialog(_('Invalid account name'),
+ _('Account name cannot contain spaces.'))
+ return
+ if active:
+ # update variables
+ gajim.interface.instances[new_name] = gajim.interface.instances[
+ old_name]
+ gajim.interface.minimized_controls[new_name] = \
+ gajim.interface.minimized_controls[old_name]
+ gajim.nicks[new_name] = gajim.nicks[old_name]
+ gajim.block_signed_in_notifications[new_name] = \
+ gajim.block_signed_in_notifications[old_name]
+ gajim.groups[new_name] = gajim.groups[old_name]
+ gajim.gc_connected[new_name] = gajim.gc_connected[old_name]
+ gajim.automatic_rooms[new_name] = gajim.automatic_rooms[old_name]
+ gajim.newly_added[new_name] = gajim.newly_added[old_name]
+ gajim.to_be_removed[new_name] = gajim.to_be_removed[old_name]
+ gajim.sleeper_state[new_name] = gajim.sleeper_state[old_name]
+ gajim.encrypted_chats[new_name] = gajim.encrypted_chats[old_name]
+ gajim.last_message_time[new_name] = \
+ gajim.last_message_time[old_name]
+ gajim.status_before_autoaway[new_name] = \
+ gajim.status_before_autoaway[old_name]
+ gajim.transport_avatar[new_name] = gajim.transport_avatar[old_name]
+ gajim.gajim_optional_features[new_name] = \
+ gajim.gajim_optional_features[old_name]
+ gajim.caps_hash[new_name] = gajim.caps_hash[old_name]
+
+ gajim.contacts.change_account_name(old_name, new_name)
+ gajim.events.change_account_name(old_name, new_name)
+
+ # change account variable for chat / gc controls
+ gajim.interface.msg_win_mgr.change_account_name(old_name, new_name)
+ # upgrade account variable in opened windows
+ for kind in ('infos', 'disco', 'gc_config', 'search',
+ 'online_dialog'):
+ for j in gajim.interface.instances[new_name][kind]:
+ gajim.interface.instances[new_name][kind][j].account = \
+ new_name
+
+ # ServiceCache object keep old property account
+ if hasattr(gajim.connections[old_name], 'services_cache'):
+ gajim.connections[old_name].services_cache.account = new_name
+ del gajim.interface.instances[old_name]
+ del gajim.interface.minimized_controls[old_name]
+ del gajim.nicks[old_name]
+ del gajim.block_signed_in_notifications[old_name]
+ del gajim.groups[old_name]
+ del gajim.gc_connected[old_name]
+ del gajim.automatic_rooms[old_name]
+ del gajim.newly_added[old_name]
+ del gajim.to_be_removed[old_name]
+ del gajim.sleeper_state[old_name]
+ del gajim.encrypted_chats[old_name]
+ del gajim.last_message_time[old_name]
+ del gajim.status_before_autoaway[old_name]
+ del gajim.transport_avatar[old_name]
+ del gajim.gajim_optional_features[old_name]
+ del gajim.caps_hash[old_name]
+ gajim.connections[old_name].name = new_name
+ gajim.connections[new_name] = gajim.connections[old_name]
+ del gajim.connections[old_name]
+ gajim.config.add_per('accounts', new_name)
+ old_config = gajim.config.get_per('accounts', old_name)
+ for opt in old_config:
+ gajim.config.set_per('accounts', new_name, opt, old_config[opt][1])
+ gajim.config.del_per('accounts', old_name)
+ if self.current_account == old_name:
+ self.current_account = new_name
+ if old_name == gajim.ZEROCONF_ACC_NAME:
+ gajim.ZEROCONF_ACC_NAME = new_name
+ # refresh roster
+ gajim.interface.roster.setup_and_draw_roster()
+ self.init_accounts()
+ self.select_account(new_name)
+
+ title = _('Rename Account')
+ message = _('Enter a new name for account %s') % self.current_account
+ old_text = self.current_account
+ dialogs.InputDialog(title, message, old_text, is_modal=False,
+ ok_handler=(on_renamed, self.current_account))
+
+ def option_changed(self, option, value):
+ return gajim.config.get_per('accounts', self.current_account, option) != \
+ value
+
+ def on_jid_entry1_focus_out_event(self, widget, event):
+ if self.ignore_events:
+ return
+ jid = widget.get_text()
+ # check if jid is conform to RFC and stringprep it
+ try:
+ jid = helpers.parse_jid(jid)
+ except helpers.InvalidFormat, s:
+ if not widget.is_focus():
+ pritext = _('Invalid Jabber ID')
+ dialogs.ErrorDialog(pritext, str(s))
+ gobject.idle_add(lambda: widget.grab_focus())
+ return True
+
+ jid_splited = jid.split('@', 1)
+ if len(jid_splited) != 2 and not gajim.config.get_per('accounts',
+ self.current_account, 'anonymous_auth'):
+ if not widget.is_focus():
+ pritext = _('Invalid Jabber ID')
+ sectext = _('A Jabber ID must be in the form "user@servername".')
+ dialogs.ErrorDialog(pritext, sectext)
+ gobject.idle_add(lambda: widget.grab_focus())
+ return True
+
+
+ if gajim.config.get_per('accounts', self.current_account,
+ 'anonymous_auth'):
+ gajim.config.set_per('accounts', self.current_account, 'hostname',
+ jid_splited[0])
+ if self.option_changed('hostname', jid_splited[0]):
+ self.need_relogin = True
+ else:
+ if self.option_changed('name', jid_splited[0]) or \
+ self.option_changed('hostname', jid_splited[1]):
+ self.need_relogin = True
+
+ gajim.config.set_per('accounts', self.current_account, 'name',
+ jid_splited[0])
+ gajim.config.set_per('accounts', self.current_account, 'hostname',
+ jid_splited[1])
+
+ def on_anonymous_checkbutton1_toggled(self, widget):
+ if self.ignore_events:
+ return
+ active = widget.get_active()
+ gajim.config.set_per('accounts', self.current_account, 'anonymous_auth',
+ active)
+ self.draw_normal_jid()
+
+ def on_password_entry1_changed(self, widget):
+ if self.ignore_events:
+ return
+ passwords.save_password(self.current_account, widget.get_text().decode(
+ 'utf-8'))
+
+ def on_save_password_checkbutton1_toggled(self, widget):
+ if self.ignore_events:
+ return
+ active = widget.get_active()
+ password_entry = self.xml.get_object('password_entry1')
+ password_entry.set_sensitive(active)
+ gajim.config.set_per('accounts', self.current_account, 'savepass', active)
+ if active:
+ password = password_entry.get_text()
+ passwords.save_password(self.current_account, password)
+ else:
+ passwords.save_password(self.current_account, '')
+
+ def on_resource_entry1_focus_out_event(self, widget, event):
+ if self.ignore_events:
+ return
+ resource = self.xml.get_object('resource_entry1').get_text().decode(
+ 'utf-8')
+ try:
+ resource = helpers.parse_resource(resource)
+ except helpers.InvalidFormat, s:
+ if not widget.is_focus():
+ pritext = _('Invalid Jabber ID')
+ dialogs.ErrorDialog(pritext, str(s))
+ gobject.idle_add(lambda: widget.grab_focus())
+ return True
+
+ if self.option_changed('resource', resource):
+ self.need_relogin = True
+
+ gajim.config.set_per('accounts', self.current_account, 'resource',
+ resource)
+
+ def on_adjust_priority_with_status_checkbutton1_toggled(self, widget):
+ self.xml.get_object('priority_spinbutton1').set_sensitive(
+ not widget.get_active())
+ self.on_checkbutton_toggled(widget, 'adjust_priority_with_status',
+ account = self.current_account)
+
+ def on_priority_spinbutton1_value_changed(self, widget):
+ prio = widget.get_value_as_int()
+
+ if self.option_changed('priority', prio):
+ self.resend_presence = True
+
+ gajim.config.set_per('accounts', self.current_account, 'priority', prio)
+
+ def on_synchronise_contacts_button1_clicked(self, widget):
+ try:
+ dialogs.SynchroniseSelectAccountDialog(self.current_account)
+ except GajimGeneralException:
+ # If we showed ErrorDialog, there will not be dialog instance
+ return
+
+ def on_change_password_button1_clicked(self, widget):
+ def on_changed(new_password):
+ if new_password is not None:
+ gajim.connections[self.current_account].change_password(
+ new_password)
+ if self.xml.get_object('save_password_checkbutton1').get_active():
+ self.xml.get_object('password_entry1').set_text(new_password)
+
+ try:
+ dialogs.ChangePasswordDialog(self.current_account, on_changed)
+ except GajimGeneralException:
+ # if we showed ErrorDialog, there will not be dialog instance
+ return
+
+ def on_autoconnect_checkbutton_toggled(self, widget):
+ if self.ignore_events:
+ return
+ self.on_checkbutton_toggled(widget, 'autoconnect',
+ account=self.current_account)
+
+ def on_autoreconnect_checkbutton_toggled(self, widget):
+ if self.ignore_events:
+ return
+ self.on_checkbutton_toggled(widget, 'autoreconnect',
+ account=self.current_account)
+
+ def on_log_history_checkbutton_toggled(self, widget):
+ if self.ignore_events:
+ return
+ list_no_log_for = gajim.config.get_per('accounts', self.current_account,
+ 'no_log_for').split()
+ if self.current_account in list_no_log_for:
+ list_no_log_for.remove(self.current_account)
+
+ if not widget.get_active():
+ list_no_log_for.append(self.current_account)
+ gajim.config.set_per('accounts', self.current_account, 'no_log_for',
+ ' '.join(list_no_log_for))
+
+ def on_sync_with_global_status_checkbutton_toggled(self, widget):
+ if self.ignore_events:
+ return
+ self.on_checkbutton_toggled(widget, 'sync_with_global_status',
+ account=self.current_account)
+ gajim.interface.roster.update_status_combobox()
+
+ def on_use_ft_proxies_checkbutton1_toggled(self, widget):
+ if self.ignore_events:
+ return
+ self.on_checkbutton_toggled(widget, 'use_ft_proxies',
+ account=self.current_account)
+
+ def on_use_env_http_proxy_checkbutton1_toggled(self, widget):
+ if self.ignore_events:
+ return
+ self.on_checkbutton_toggled(widget, 'use_env_http_proxy',
+ account=self.current_account)
+ hbox = self.xml.get_object('proxy_hbox1')
+ hbox.set_sensitive(not widget.get_active())
+
+ def on_proxies_combobox1_changed(self, widget):
+ active = widget.get_active()
+ proxy = widget.get_model()[active][0].decode('utf-8')
+ if proxy == _('None'):
+ proxy = ''
+
+ if self.option_changed('proxy', proxy):
+ self.need_relogin = True
+
+ gajim.config.set_per('accounts', self.current_account, 'proxy', proxy)
+
+ def on_manage_proxies_button1_clicked(self, widget):
+ if 'manage_proxies' in gajim.interface.instances:
+ gajim.interface.instances['manage_proxies'].window.present()
+ else:
+ gajim.interface.instances['manage_proxies'] = ManageProxiesWindow()
+
+ def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget):
+ if self.ignore_events:
+ return
+
+ self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection',
+ account=self.current_account)
+
+ def on_send_keepalive_checkbutton1_toggled(self, widget):
+ if self.ignore_events:
+ return
+ self.on_checkbutton_toggled(widget, 'keep_alives_enabled',
+ account=self.current_account)
+ gajim.config.set_per('accounts', self.current_account,
+ 'ping_alives_enabled', widget.get_active())
+
+ def on_custom_host_port_checkbutton1_toggled(self, widget):
+ if self.option_changed('use_custom_host', widget.get_active()):
+ self.need_relogin = True
+
+ self.on_checkbutton_toggled(widget, 'use_custom_host',
+ account=self.current_account)
+ active = widget.get_active()
+ self.xml.get_object('custom_host_port_hbox1').set_sensitive(active)
+
+ def on_custom_host_entry1_changed(self, widget):
+ if self.ignore_events:
+ return
+ host = widget.get_text().decode('utf-8')
+ if self.option_changed('custom_host', host):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account, 'custom_host',
+ host)
+
+ def on_custom_port_entry_focus_out_event(self, widget, event):
+ if self.ignore_events:
+ return
+ custom_port = widget.get_text()
+ try:
+ custom_port = int(custom_port)
+ except Exception:
+ if not widget.is_focus():
+ dialogs.ErrorDialog(_('Invalid entry'),
+ _('Custom port must be a port number.'))
+ gobject.idle_add(lambda: widget.grab_focus())
+ return True
+ if self.option_changed('custom_port', custom_port):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account, 'custom_port',
+ custom_port)
+
+ def on_gpg_choose_button_clicked(self, widget, data = None):
+ if self.current_account in gajim.connections and \
+ gajim.connections[self.current_account].gpg:
+ secret_keys = gajim.connections[self.current_account].\
+ ask_gpg_secrete_keys()
+
+ # self.current_account is None and/or gajim.connections is {}
+ else:
+ if gajim.HAVE_GPG:
+ secret_keys = GnuPG.GnuPG().get_secret_keys()
+ else:
+ secret_keys = []
+ if not secret_keys:
+ dialogs.ErrorDialog(_('Failed to get secret keys'),
+ _('There is no OpenPGP secret key available.'))
+ secret_keys[_('None')] = _('None')
+
+ def on_key_selected(keyID):
+ if keyID is None:
+ return
+ if self.current_account == gajim.ZEROCONF_ACC_NAME:
+ wiget_name_ext = '2'
+ else:
+ wiget_name_ext = '1'
+ gpg_key_label = self.xml.get_object('gpg_key_label' + wiget_name_ext)
+ gpg_name_label = self.xml.get_object('gpg_name_label' + wiget_name_ext)
+ use_gpg_agent_checkbutton = self.xml.get_object(
+ 'use_gpg_agent_checkbutton' + wiget_name_ext)
+ if keyID[0] == _('None'):
+ gpg_key_label.set_text(_('No key selected'))
+ gpg_name_label.set_text('')
+ use_gpg_agent_checkbutton.set_sensitive(False)
+ if self.option_changed('keyid', ''):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account, 'keyname',
+ '')
+ gajim.config.set_per('accounts', self.current_account, 'keyid', '')
+ else:
+ gpg_key_label.set_text(keyID[0])
+ gpg_name_label.set_text(keyID[1])
+ use_gpg_agent_checkbutton.set_sensitive(True)
+ if self.option_changed('keyid', keyID[0]):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account, 'keyname',
+ keyID[1])
+ gajim.config.set_per('accounts', self.current_account, 'keyid',
+ keyID[0])
+
+ dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
+ _('Choose your OpenPGP key'), secret_keys, on_key_selected)
+
+ def on_use_gpg_agent_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'use_gpg_agent')
+
+ def on_edit_details_button1_clicked(self, widget):
+ if self.current_account not in gajim.interface.instances:
+ dialogs.ErrorDialog(_('No such account available'),
+ _('You must create your account before editing your personal '
+ 'information.'))
+ return
+
+ # show error dialog if account is newly created (not in gajim.connections)
+ if self.current_account not in gajim.connections or \
+ gajim.connections[self.current_account].connected < 2:
+ dialogs.ErrorDialog(_('You are not connected to the server'),
+ _('Without a connection, you can not edit your personal information.'))
+ return
+
+ if not gajim.connections[self.current_account].vcard_supported:
+ dialogs.ErrorDialog(_("Your server doesn't support Vcard"),
+ _("Your server can't save your personal information."))
+ return
+
+ gajim.interface.edit_own_details(self.current_account)
+
+ def on_checkbutton_toggled(self, widget, config_name,
+ change_sensitivity_widgets = None, account = None):
+ if account:
+ gajim.config.set_per('accounts', account, config_name,
+ widget.get_active())
+ else:
+ gajim.config.set(config_name, widget.get_active())
+ if change_sensitivity_widgets:
+ for w in change_sensitivity_widgets:
+ w.set_sensitive(widget.get_active())
+ gajim.interface.save_config()
+
+ def on_merge_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'mergeaccounts')
+ if len(gajim.connections) >= 2: # Do not merge accounts if only one active
+ gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
+ else:
+ gajim.interface.roster.regroup = False
+ gajim.interface.roster.setup_and_draw_roster()
+
+ def _disable_account(self, account):
+ gajim.interface.roster.close_all(account)
+ if account == gajim.ZEROCONF_ACC_NAME:
+ gajim.connections[account].disable_account()
+ del gajim.connections[account]
+ gajim.interface.save_config()
+ del gajim.interface.instances[account]
+ del gajim.interface.minimized_controls[account]
+ del gajim.nicks[account]
+ del gajim.block_signed_in_notifications[account]
+ del gajim.groups[account]
+ gajim.contacts.remove_account(account)
+ del gajim.gc_connected[account]
+ del gajim.automatic_rooms[account]
+ del gajim.to_be_removed[account]
+ del gajim.newly_added[account]
+ del gajim.sleeper_state[account]
+ del gajim.encrypted_chats[account]
+ del gajim.last_message_time[account]
+ del gajim.status_before_autoaway[account]
+ del gajim.transport_avatar[account]
+ del gajim.gajim_optional_features[account]
+ del gajim.caps_hash[account]
+ if len(gajim.connections) >= 2:
+ # Do not merge accounts if only one exists
+ gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
+ else:
+ gajim.interface.roster.regroup = False
+ gajim.interface.roster.setup_and_draw_roster()
+ gajim.interface.roster.set_actions_menu_needs_rebuild()
+
+ def _enable_account(self, account):
+ if account == gajim.ZEROCONF_ACC_NAME:
+ gajim.connections[account] = connection_zeroconf.ConnectionZeroconf(
+ account)
+ if gajim.connections[account].gpg:
+ self.xml.get_object('gpg_choose_button2').set_sensitive(True)
+ else:
+ gajim.connections[account] = common.connection.Connection(account)
+ if gajim.connections[account].gpg:
+ self.xml.get_object('gpg_choose_button1').set_sensitive(True)
+ self.init_account_gpg()
+ # update variables
+ gajim.interface.instances[account] = {'infos': {},
+ 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}}
+ gajim.interface.minimized_controls[account] = {}
+ gajim.connections[account].connected = 0
+ gajim.groups[account] = {}
+ gajim.contacts.add_account(account)
+ gajim.gc_connected[account] = {}
+ gajim.automatic_rooms[account] = {}
+ gajim.newly_added[account] = []
+ gajim.to_be_removed[account] = []
+ if account == gajim.ZEROCONF_ACC_NAME:
+ gajim.nicks[account] = gajim.ZEROCONF_ACC_NAME
+ else:
+ gajim.nicks[account] = gajim.config.get_per('accounts', account,
+ 'name')
+ gajim.block_signed_in_notifications[account] = True
+ gajim.sleeper_state[account] = 'off'
+ gajim.encrypted_chats[account] = []
+ gajim.last_message_time[account] = {}
+ gajim.status_before_autoaway[account] = ''
+ gajim.transport_avatar[account] = {}
+ gajim.gajim_optional_features[account] = []
+ gajim.caps_hash[account] = ''
+ # refresh roster
+ if len(gajim.connections) >= 2:
+ # Do not merge accounts if only one exists
+ gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
+ else:
+ gajim.interface.roster.regroup = False
+ gajim.interface.roster.setup_and_draw_roster()
+ gajim.interface.roster.set_actions_menu_needs_rebuild()
+ gajim.interface.save_config()
+
+ def on_enable_zeroconf_checkbutton2_toggled(self, widget):
+ # don't do anything if there is an account with the local name but is a
+ # normal account
+ if self.ignore_events:
+ return
+ if self.current_account in gajim.connections and \
+ gajim.connections[self.current_account].connected > 0:
+ self.ignore_events = True
+ self.xml.get_object('enable_zeroconf_checkbutton2').set_active(True)
+ self.ignore_events = False
+ dialogs.ErrorDialog(
+ _('You are currently connected to the server'),
+ _('To disable the account, you must be disconnected.'))
+ return
+ if gajim.ZEROCONF_ACC_NAME in gajim.connections and not \
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf:
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR',
+ (_('Account Local already exists.'),
+ _('Please rename or remove it before enabling link-local messaging'
+ '.')))
+ return
+
+ if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
+ and not widget.get_active():
+ self.xml.get_object('zeroconf_notebook').set_sensitive(False)
+ # disable
+ self._disable_account(gajim.ZEROCONF_ACC_NAME)
+
+ elif not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
+ 'active') and widget.get_active():
+ self.xml.get_object('zeroconf_notebook').set_sensitive(True)
+ # enable (will create new account if not present)
+ self._enable_account(gajim.ZEROCONF_ACC_NAME)
+
+ self.on_checkbutton_toggled(widget, 'active',
+ account=gajim.ZEROCONF_ACC_NAME)
+
+ def on_enable_checkbutton1_toggled(self, widget):
+ if self.ignore_events:
+ return
+ if self.current_account in gajim.connections and \
+ gajim.connections[self.current_account].connected > 0:
+ # connecting or connected
+ self.ignore_events = True
+ self.xml.get_object('enable_checkbutton1').set_active(True)
+ self.ignore_events = False
+ dialogs.ErrorDialog(
+ _('You are currently connected to the server'),
+ _('To disable the account, you must be disconnected.'))
+ return
+ # add/remove account in roster and all variables
+ if widget.get_active():
+ # enable
+ self._enable_account(self.current_account)
+ else:
+ # disable
+ self._disable_account(self.current_account)
+ self.on_checkbutton_toggled(widget, 'active',
+ account=self.current_account, change_sensitivity_widgets=[
+ self.xml.get_object('normal_notebook1')])
+
+ def on_custom_port_checkbutton2_toggled(self, widget):
+ self.xml.get_object('custom_port_entry2').set_sensitive(
+ widget.get_active())
+ self.on_checkbutton_toggled(widget, 'use_custom_host',
+ account = self.current_account)
+ if not widget.get_active():
+ self.xml.get_object('custom_port_entry2').set_text('5298')
+
+ def on_first_name_entry2_changed(self, widget):
+ if self.ignore_events:
+ return
+ name = widget.get_text().decode('utf-8')
+ if self.option_changed('zeroconf_first_name', name):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account,
+ 'zeroconf_first_name', name)
+
+ def on_last_name_entry2_changed(self, widget):
+ if self.ignore_events:
+ return
+ name = widget.get_text().decode('utf-8')
+ if self.option_changed('zeroconf_last_name', name):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account,
+ 'zeroconf_last_name', name)
+
+ def on_jabber_id_entry2_changed(self, widget):
+ if self.ignore_events:
+ return
+ id_ = widget.get_text().decode('utf-8')
+ if self.option_changed('zeroconf_jabber_id', id_):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account,
+ 'zeroconf_jabber_id', id_)
+
+ def on_email_entry2_changed(self, widget):
+ if self.ignore_events:
+ return
+ email = widget.get_text().decode('utf-8')
+ if self.option_changed('zeroconf_email', email):
+ self.need_relogin = True
+ gajim.config.set_per('accounts', self.current_account,
+ 'zeroconf_email', email)
class FakeDataForm(gtk.Table, object):
- """
- Class for forms that are in XML format <entry1>value1</entry1> infos in a
- table {entry1: value1}
- """
-
- def __init__(self, infos):
- gtk.Table.__init__(self)
- self.infos = infos
- self.entries = {}
- self._draw_table()
-
- def _draw_table(self):
- """
- Draw the table
- """
- nbrow = 0
- if 'instructions' in self.infos:
- nbrow = 1
- self.resize(rows = nbrow, columns = 2)
- label = gtk.Label(self.infos['instructions'])
- self.attach(label, 0, 2, 0, 1, 0, 0, 0, 0)
- for name in self.infos.keys():
- if name in ('key', 'instructions', 'x', 'registered'):
- continue
- if not name:
- continue
-
- nbrow = nbrow + 1
- self.resize(rows = nbrow, columns = 2)
- label = gtk.Label(name.capitalize() + ':')
- self.attach(label, 0, 1, nbrow - 1, nbrow, 0, 0, 0, 0)
- entry = gtk.Entry()
- entry.set_activates_default(True)
- if self.infos[name]:
- entry.set_text(self.infos[name])
- if name == 'password':
- entry.set_visibility(False)
- self.attach(entry, 1, 2, nbrow - 1, nbrow, 0, 0, 0, 0)
- self.entries[name] = entry
- if nbrow == 1:
- entry.grab_focus()
-
- def get_infos(self):
- for name in self.entries.keys():
- self.infos[name] = self.entries[name].get_text().decode('utf-8')
- return self.infos
+ """
+ Class for forms that are in XML format <entry1>value1</entry1> infos in a
+ table {entry1: value1}
+ """
+
+ def __init__(self, infos):
+ gtk.Table.__init__(self)
+ self.infos = infos
+ self.entries = {}
+ self._draw_table()
+
+ def _draw_table(self):
+ """
+ Draw the table
+ """
+ nbrow = 0
+ if 'instructions' in self.infos:
+ nbrow = 1
+ self.resize(rows = nbrow, columns = 2)
+ label = gtk.Label(self.infos['instructions'])
+ self.attach(label, 0, 2, 0, 1, 0, 0, 0, 0)
+ for name in self.infos.keys():
+ if name in ('key', 'instructions', 'x', 'registered'):
+ continue
+ if not name:
+ continue
+
+ nbrow = nbrow + 1
+ self.resize(rows = nbrow, columns = 2)
+ label = gtk.Label(name.capitalize() + ':')
+ self.attach(label, 0, 1, nbrow - 1, nbrow, 0, 0, 0, 0)
+ entry = gtk.Entry()
+ entry.set_activates_default(True)
+ if self.infos[name]:
+ entry.set_text(self.infos[name])
+ if name == 'password':
+ entry.set_visibility(False)
+ self.attach(entry, 1, 2, nbrow - 1, nbrow, 0, 0, 0, 0)
+ self.entries[name] = entry
+ if nbrow == 1:
+ entry.grab_focus()
+
+ def get_infos(self):
+ for name in self.entries.keys():
+ self.infos[name] = self.entries[name].get_text().decode('utf-8')
+ return self.infos
class ServiceRegistrationWindow:
- """
- Class for Service registration window. Window that appears when we want to
- subscribe to a service if is_form we use dataforms_widget else we use
- service_registarion_window
- """
- def __init__(self, service, infos, account, is_form):
- self.service = service
- self.account = account
- self.is_form = is_form
- self.xml = gtkgui_helpers.get_gtk_builder('service_registration_window.ui')
- self.window = self.xml.get_object('service_registration_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- if self.is_form:
- dataform = dataforms.ExtendForm(node = infos)
- self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
- if self.data_form_widget.title:
- self.window.set_title('%s - Gajim' % self.data_form_widget.title)
- table = self.xml.get_object('table')
- table.attach(self.data_form_widget, 0, 2, 0, 1)
- else:
- if 'registered' in infos:
- self.window.set_title(_('Edit %s') % service)
- else:
- self.window.set_title(_('Register to %s') % service)
- self.data_form_widget = FakeDataForm(infos)
- table = self.xml.get_object('table')
- table.attach(self.data_form_widget, 0, 2, 0, 1)
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_ok_button_clicked(self, widget):
- # send registration info to the core
- if self.is_form:
- form = self.data_form_widget.data_form
- gajim.connections[self.account].register_agent(self.service,
- form, True) # True is for is_form
- else:
- infos = self.data_form_widget.get_infos()
- if 'instructions' in infos:
- del infos['instructions']
- if 'registered' in infos:
- del infos['registered']
- gajim.connections[self.account].register_agent(self.service, infos)
-
- self.window.destroy()
+ """
+ Class for Service registration window. Window that appears when we want to
+ subscribe to a service if is_form we use dataforms_widget else we use
+ service_registarion_window
+ """
+ def __init__(self, service, infos, account, is_form):
+ self.service = service
+ self.account = account
+ self.is_form = is_form
+ self.xml = gtkgui_helpers.get_gtk_builder('service_registration_window.ui')
+ self.window = self.xml.get_object('service_registration_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ if self.is_form:
+ dataform = dataforms.ExtendForm(node = infos)
+ self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
+ if self.data_form_widget.title:
+ self.window.set_title('%s - Gajim' % self.data_form_widget.title)
+ table = self.xml.get_object('table')
+ table.attach(self.data_form_widget, 0, 2, 0, 1)
+ else:
+ if 'registered' in infos:
+ self.window.set_title(_('Edit %s') % service)
+ else:
+ self.window.set_title(_('Register to %s') % service)
+ self.data_form_widget = FakeDataForm(infos)
+ table = self.xml.get_object('table')
+ table.attach(self.data_form_widget, 0, 2, 0, 1)
+
+ self.xml.connect_signals(self)
+ self.window.show_all()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_ok_button_clicked(self, widget):
+ # send registration info to the core
+ if self.is_form:
+ form = self.data_form_widget.data_form
+ gajim.connections[self.account].register_agent(self.service,
+ form, True) # True is for is_form
+ else:
+ infos = self.data_form_widget.get_infos()
+ if 'instructions' in infos:
+ del infos['instructions']
+ if 'registered' in infos:
+ del infos['registered']
+ gajim.connections[self.account].register_agent(self.service, infos)
+
+ self.window.destroy()
class GroupchatConfigWindow:
- def __init__(self, account, room_jid, form = None):
- self.account = account
- self.room_jid = room_jid
- self.form = form
- self.remove_button = {}
- self.affiliation_treeview = {}
- self.start_users_dict = {} # list at the beginning
- self.affiliation_labels = {'outcast': _('Ban List'),
- 'member': _('Member List'),
- 'owner': _('Owner List'),
- 'admin':_('Administrator List')}
-
- self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window')
- self.window = self.xml.get_object('data_form_window')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- if self.form:
- config_vbox = self.xml.get_object('config_vbox')
- dataform = dataforms.ExtendForm(node = self.form)
- self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
- # hide scrollbar of this data_form_widget, we already have in this
- # widget
- sw = self.data_form_widget.xml.get_object('single_form_scrolledwindow')
- sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
-
- self.data_form_widget.show()
- config_vbox.pack_start(self.data_form_widget)
-
- # Draw the edit affiliation list things
- add_on_vbox = self.xml.get_object('add_on_vbox')
-
- for affiliation in self.affiliation_labels.keys():
- self.start_users_dict[affiliation] = {}
- hbox = gtk.HBox(spacing = 5)
- add_on_vbox.pack_start(hbox, False)
-
- label = gtk.Label(self.affiliation_labels[affiliation])
- hbox.pack_start(label, False)
-
- bb = gtk.HButtonBox()
- bb.set_layout(gtk.BUTTONBOX_END)
- bb.set_spacing(5)
- hbox.pack_start(bb)
- add_button = gtk.Button(stock = gtk.STOCK_ADD)
- add_button.connect('clicked', self.on_add_button_clicked, affiliation)
- bb.pack_start(add_button)
- self.remove_button[affiliation] = gtk.Button(stock = gtk.STOCK_REMOVE)
- self.remove_button[affiliation].set_sensitive(False)
- self.remove_button[affiliation].connect('clicked',
- self.on_remove_button_clicked, affiliation)
- bb.pack_start(self.remove_button[affiliation])
-
- liststore = gtk.ListStore(str, str, str, str) # Jid, reason, nick, role
- self.affiliation_treeview[affiliation] = gtk.TreeView(liststore)
- self.affiliation_treeview[affiliation].get_selection().set_mode(
- gtk.SELECTION_MULTIPLE)
- self.affiliation_treeview[affiliation].connect('cursor-changed',
- self.on_affiliation_treeview_cursor_changed, affiliation)
- renderer = gtk.CellRendererText()
- col = gtk.TreeViewColumn(_('JID'), renderer)
- col.add_attribute(renderer, 'text', 0)
- col.set_resizable(True)
- col.set_sort_column_id(0)
- self.affiliation_treeview[affiliation].append_column(col)
-
- if affiliation == 'outcast':
- renderer = gtk.CellRendererText()
- renderer.set_property('editable', True)
- renderer.connect('edited', self.on_cell_edited)
- col = gtk.TreeViewColumn(_('Reason'), renderer)
- col.add_attribute(renderer, 'text', 1)
- col.set_resizable(True)
- col.set_sort_column_id(1)
- self.affiliation_treeview[affiliation].append_column(col)
- elif affiliation == 'member':
- renderer = gtk.CellRendererText()
- col = gtk.TreeViewColumn(_('Nick'), renderer)
- col.add_attribute(renderer, 'text', 2)
- col.set_resizable(True)
- col.set_sort_column_id(2)
- self.affiliation_treeview[affiliation].append_column(col)
- renderer = gtk.CellRendererText()
- col = gtk.TreeViewColumn(_('Role'), renderer)
- col.add_attribute(renderer, 'text', 3)
- col.set_resizable(True)
- col.set_sort_column_id(3)
- self.affiliation_treeview[affiliation].append_column(col)
-
- sw = gtk.ScrolledWindow()
- sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
- sw.add(self.affiliation_treeview[affiliation])
- add_on_vbox.pack_start(sw)
- gajim.connections[self.account].get_affiliation_list(self.room_jid,
- affiliation)
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_cell_edited(self, cell, path, new_text):
- model = self.affiliation_treeview['outcast'].get_model()
- new_text = new_text.decode('utf-8')
- iter_ = model.get_iter(path)
- model[iter_][1] = new_text
-
- def on_add_button_clicked(self, widget, affiliation):
- if affiliation == 'outcast':
- title = _('Banning...')
- #You can move '\n' before user@domain if that line is TOO BIG
- prompt = _('<b>Whom do you want to ban?</b>\n\n')
- elif affiliation == 'member':
- title = _('Adding Member...')
- prompt = _('<b>Whom do you want to make a member?</b>\n\n')
- elif affiliation == 'owner':
- title = _('Adding Owner...')
- prompt = _('<b>Whom do you want to make an owner?</b>\n\n')
- else:
- title = _('Adding Administrator...')
- prompt = _('<b>Whom do you want to make an administrator?</b>\n\n')
- prompt += _('Can be one of the following:\n'
- '1. user@domain/resource (only that resource matches).\n'
- '2. user@domain (any resource matches).\n'
- '3. domain/resource (only that resource matches).\n'
- '4. domain (the domain itself matches, as does any user@domain,\n'
- 'domain/resource, or address containing a subdomain).')
-
- def on_ok(jid):
- if not jid:
- return
- model = self.affiliation_treeview[affiliation].get_model()
- model.append((jid,'', '', ''))
- dialogs.InputDialog(title, prompt, ok_handler=on_ok)
-
- def on_remove_button_clicked(self, widget, affiliation):
- selection = self.affiliation_treeview[affiliation].get_selection()
- model, paths = selection.get_selected_rows()
- row_refs = []
- for path in paths:
- row_refs.append(gtk.TreeRowReference(model, path))
- for row_ref in row_refs:
- path = row_ref.get_path()
- iter_ = model.get_iter(path)
- jid = model[iter_][0]
- model.remove(iter_)
- self.remove_button[affiliation].set_sensitive(False)
-
- def on_affiliation_treeview_cursor_changed(self, widget, affiliation):
- self.remove_button[affiliation].set_sensitive(True)
-
- def affiliation_list_received(self, users_dict):
- """
- Fill the affiliation treeview
- """
- for jid in users_dict:
- affiliation = users_dict[jid]['affiliation']
- if affiliation not in self.affiliation_labels.keys():
- # Unknown affiliation or 'none' affiliation, do not show it
- continue
- self.start_users_dict[affiliation][jid] = users_dict[jid]
- tv = self.affiliation_treeview[affiliation]
- model = tv.get_model()
- reason = users_dict[jid].get('reason', '')
- nick = users_dict[jid].get('nick', '')
- role = users_dict[jid].get('role', '')
- model.append((jid, reason, nick, role))
-
- def on_data_form_window_destroy(self, widget):
- del gajim.interface.instances[self.account]['gc_config'][self.room_jid]
-
- def on_ok_button_clicked(self, widget):
- if self.form:
- form = self.data_form_widget.data_form
- gajim.connections[self.account].send_gc_config(self.room_jid, form)
- for affiliation in self.affiliation_labels.keys():
- users_dict = {}
- actual_jid_list = []
- model = self.affiliation_treeview[affiliation].get_model()
- iter_ = model.get_iter_first()
- # add new jid
- while iter_:
- jid = model[iter_][0].decode('utf-8')
- actual_jid_list.append(jid)
- if jid not in self.start_users_dict[affiliation] or \
- (affiliation == 'outcast' and 'reason' in self.start_users_dict[affiliation]\
- [jid] and self.start_users_dict[affiliation][jid]\
- ['reason'] != model[iter_][1].decode('utf-8')):
- users_dict[jid] = {'affiliation': affiliation}
- if affiliation == 'outcast':
- users_dict[jid]['reason'] = model[iter_][1].decode('utf-8')
- iter_ = model.iter_next(iter_)
- # remove removed one
- for jid in self.start_users_dict[affiliation]:
- if jid not in actual_jid_list:
- users_dict[jid] = {'affiliation': 'none'}
- if users_dict:
- gajim.connections[self.account].send_gc_affiliation_list(
- self.room_jid, users_dict)
- self.window.destroy()
+ def __init__(self, account, room_jid, form = None):
+ self.account = account
+ self.room_jid = room_jid
+ self.form = form
+ self.remove_button = {}
+ self.affiliation_treeview = {}
+ self.start_users_dict = {} # list at the beginning
+ self.affiliation_labels = {'outcast': _('Ban List'),
+ 'member': _('Member List'),
+ 'owner': _('Owner List'),
+ 'admin':_('Administrator List')}
+
+ self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window')
+ self.window = self.xml.get_object('data_form_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+
+ if self.form:
+ config_vbox = self.xml.get_object('config_vbox')
+ dataform = dataforms.ExtendForm(node = self.form)
+ self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
+ # hide scrollbar of this data_form_widget, we already have in this
+ # widget
+ sw = self.data_form_widget.xml.get_object('single_form_scrolledwindow')
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
+
+ self.data_form_widget.show()
+ config_vbox.pack_start(self.data_form_widget)
+
+ # Draw the edit affiliation list things
+ add_on_vbox = self.xml.get_object('add_on_vbox')
+
+ for affiliation in self.affiliation_labels.keys():
+ self.start_users_dict[affiliation] = {}
+ hbox = gtk.HBox(spacing = 5)
+ add_on_vbox.pack_start(hbox, False)
+
+ label = gtk.Label(self.affiliation_labels[affiliation])
+ hbox.pack_start(label, False)
+
+ bb = gtk.HButtonBox()
+ bb.set_layout(gtk.BUTTONBOX_END)
+ bb.set_spacing(5)
+ hbox.pack_start(bb)
+ add_button = gtk.Button(stock = gtk.STOCK_ADD)
+ add_button.connect('clicked', self.on_add_button_clicked, affiliation)
+ bb.pack_start(add_button)
+ self.remove_button[affiliation] = gtk.Button(stock = gtk.STOCK_REMOVE)
+ self.remove_button[affiliation].set_sensitive(False)
+ self.remove_button[affiliation].connect('clicked',
+ self.on_remove_button_clicked, affiliation)
+ bb.pack_start(self.remove_button[affiliation])
+
+ liststore = gtk.ListStore(str, str, str, str) # Jid, reason, nick, role
+ self.affiliation_treeview[affiliation] = gtk.TreeView(liststore)
+ self.affiliation_treeview[affiliation].get_selection().set_mode(
+ gtk.SELECTION_MULTIPLE)
+ self.affiliation_treeview[affiliation].connect('cursor-changed',
+ self.on_affiliation_treeview_cursor_changed, affiliation)
+ renderer = gtk.CellRendererText()
+ col = gtk.TreeViewColumn(_('JID'), renderer)
+ col.add_attribute(renderer, 'text', 0)
+ col.set_resizable(True)
+ col.set_sort_column_id(0)
+ self.affiliation_treeview[affiliation].append_column(col)
+
+ if affiliation == 'outcast':
+ renderer = gtk.CellRendererText()
+ renderer.set_property('editable', True)
+ renderer.connect('edited', self.on_cell_edited)
+ col = gtk.TreeViewColumn(_('Reason'), renderer)
+ col.add_attribute(renderer, 'text', 1)
+ col.set_resizable(True)
+ col.set_sort_column_id(1)
+ self.affiliation_treeview[affiliation].append_column(col)
+ elif affiliation == 'member':
+ renderer = gtk.CellRendererText()
+ col = gtk.TreeViewColumn(_('Nick'), renderer)
+ col.add_attribute(renderer, 'text', 2)
+ col.set_resizable(True)
+ col.set_sort_column_id(2)
+ self.affiliation_treeview[affiliation].append_column(col)
+ renderer = gtk.CellRendererText()
+ col = gtk.TreeViewColumn(_('Role'), renderer)
+ col.add_attribute(renderer, 'text', 3)
+ col.set_resizable(True)
+ col.set_sort_column_id(3)
+ self.affiliation_treeview[affiliation].append_column(col)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
+ sw.add(self.affiliation_treeview[affiliation])
+ add_on_vbox.pack_start(sw)
+ gajim.connections[self.account].get_affiliation_list(self.room_jid,
+ affiliation)
+
+ self.xml.connect_signals(self)
+ self.window.show_all()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_cell_edited(self, cell, path, new_text):
+ model = self.affiliation_treeview['outcast'].get_model()
+ new_text = new_text.decode('utf-8')
+ iter_ = model.get_iter(path)
+ model[iter_][1] = new_text
+
+ def on_add_button_clicked(self, widget, affiliation):
+ if affiliation == 'outcast':
+ title = _('Banning...')
+ #You can move '\n' before user@domain if that line is TOO BIG
+ prompt = _('<b>Whom do you want to ban?</b>\n\n')
+ elif affiliation == 'member':
+ title = _('Adding Member...')
+ prompt = _('<b>Whom do you want to make a member?</b>\n\n')
+ elif affiliation == 'owner':
+ title = _('Adding Owner...')
+ prompt = _('<b>Whom do you want to make an owner?</b>\n\n')
+ else:
+ title = _('Adding Administrator...')
+ prompt = _('<b>Whom do you want to make an administrator?</b>\n\n')
+ prompt += _('Can be one of the following:\n'
+ '1. user@domain/resource (only that resource matches).\n'
+ '2. user@domain (any resource matches).\n'
+ '3. domain/resource (only that resource matches).\n'
+ '4. domain (the domain itself matches, as does any user@domain,\n'
+ 'domain/resource, or address containing a subdomain).')
+
+ def on_ok(jid):
+ if not jid:
+ return
+ model = self.affiliation_treeview[affiliation].get_model()
+ model.append((jid,'', '', ''))
+ dialogs.InputDialog(title, prompt, ok_handler=on_ok)
+
+ def on_remove_button_clicked(self, widget, affiliation):
+ selection = self.affiliation_treeview[affiliation].get_selection()
+ model, paths = selection.get_selected_rows()
+ row_refs = []
+ for path in paths:
+ row_refs.append(gtk.TreeRowReference(model, path))
+ for row_ref in row_refs:
+ path = row_ref.get_path()
+ iter_ = model.get_iter(path)
+ jid = model[iter_][0]
+ model.remove(iter_)
+ self.remove_button[affiliation].set_sensitive(False)
+
+ def on_affiliation_treeview_cursor_changed(self, widget, affiliation):
+ self.remove_button[affiliation].set_sensitive(True)
+
+ def affiliation_list_received(self, users_dict):
+ """
+ Fill the affiliation treeview
+ """
+ for jid in users_dict:
+ affiliation = users_dict[jid]['affiliation']
+ if affiliation not in self.affiliation_labels.keys():
+ # Unknown affiliation or 'none' affiliation, do not show it
+ continue
+ self.start_users_dict[affiliation][jid] = users_dict[jid]
+ tv = self.affiliation_treeview[affiliation]
+ model = tv.get_model()
+ reason = users_dict[jid].get('reason', '')
+ nick = users_dict[jid].get('nick', '')
+ role = users_dict[jid].get('role', '')
+ model.append((jid, reason, nick, role))
+
+ def on_data_form_window_destroy(self, widget):
+ del gajim.interface.instances[self.account]['gc_config'][self.room_jid]
+
+ def on_ok_button_clicked(self, widget):
+ if self.form:
+ form = self.data_form_widget.data_form
+ gajim.connections[self.account].send_gc_config(self.room_jid, form)
+ for affiliation in self.affiliation_labels.keys():
+ users_dict = {}
+ actual_jid_list = []
+ model = self.affiliation_treeview[affiliation].get_model()
+ iter_ = model.get_iter_first()
+ # add new jid
+ while iter_:
+ jid = model[iter_][0].decode('utf-8')
+ actual_jid_list.append(jid)
+ if jid not in self.start_users_dict[affiliation] or \
+ (affiliation == 'outcast' and 'reason' in self.start_users_dict[affiliation]\
+ [jid] and self.start_users_dict[affiliation][jid]\
+ ['reason'] != model[iter_][1].decode('utf-8')):
+ users_dict[jid] = {'affiliation': affiliation}
+ if affiliation == 'outcast':
+ users_dict[jid]['reason'] = model[iter_][1].decode('utf-8')
+ iter_ = model.iter_next(iter_)
+ # remove removed one
+ for jid in self.start_users_dict[affiliation]:
+ if jid not in actual_jid_list:
+ users_dict[jid] = {'affiliation': 'none'}
+ if users_dict:
+ gajim.connections[self.account].send_gc_affiliation_list(
+ self.room_jid, users_dict)
+ self.window.destroy()
#---------- RemoveAccountWindow class -------------#
class RemoveAccountWindow:
- """
- Ask for removing from gajim only or from gajim and server too and do
- removing of the account given
- """
-
- def on_remove_account_window_destroy(self, widget):
- if self.account in gajim.interface.instances:
- del gajim.interface.instances[self.account]['remove_account']
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def __init__(self, account):
- self.account = account
- xml = gtkgui_helpers.get_gtk_builder('remove_account_window.ui')
- self.window = xml.get_object('remove_account_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.remove_and_unregister_radiobutton = xml.get_object(
- 'remove_and_unregister_radiobutton')
- self.window.set_title(_('Removing %s account') % self.account)
- xml.connect_signals(self)
- self.window.show_all()
-
- def on_remove_button_clicked(self, widget):
- def remove():
- if gajim.connections[self.account].connected and \
- not self.remove_and_unregister_radiobutton.get_active():
- # change status to offline only if we will not remove this JID from
- # server
- gajim.connections[self.account].change_status('offline', 'offline')
- if self.remove_and_unregister_radiobutton.get_active():
- if not gajim.connections[self.account].password:
- def on_ok(passphrase, checked):
- if passphrase == -1:
- # We don't remove account cause we canceled pw window
- return
- gajim.connections[self.account].password = passphrase
- gajim.connections[self.account].unregister_account(
- self._on_remove_success)
-
- dialogs.PassphraseDialog(
- _('Password Required'),
- _('Enter your password for account %s') % self.account,
- _('Save password'), ok_handler=on_ok)
- return
- gajim.connections[self.account].unregister_account(
- self._on_remove_success)
- else:
- self._on_remove_success(True)
-
- if gajim.connections[self.account].connected:
- dialogs.ConfirmationDialog(
- _('Account "%s" is connected to the server') % self.account,
- _('If you remove it, the connection will be lost.'),
- on_response_ok=remove)
- else:
- remove()
-
- def on_remove_responce_ok(self, is_checked):
- if is_checked[0]:
- self._on_remove_success(True)
-
- def _on_remove_success(self, res):
- # action of unregistration has failed, we don't remove the account
- # Error message is send by connect_and_auth()
- if not res:
- confirmation_check = dialogs.ConfirmationDialogDoubleRadio(
- _('Connection to server %s failed') % self.account,
- _('What would you like to do?'),
- _('Remove only from Gajim'),
- _('Don\'t remove anything. I\'ll try again later'),
- on_response_ok=self.on_remove_responce_ok, is_modal=False)
- return
- # Close all opened windows
- gajim.interface.roster.close_all(self.account, force = True)
- gajim.connections[self.account].disconnect(on_purpose = True)
- del gajim.connections[self.account]
- gajim.logger.remove_roster(gajim.get_jid_from_account(self.account))
- gajim.config.del_per('accounts', self.account)
- gajim.interface.save_config()
- del gajim.interface.instances[self.account]
- del gajim.interface.minimized_controls[self.account]
- del gajim.nicks[self.account]
- del gajim.block_signed_in_notifications[self.account]
- del gajim.groups[self.account]
- gajim.contacts.remove_account(self.account)
- del gajim.gc_connected[self.account]
- del gajim.automatic_rooms[self.account]
- del gajim.to_be_removed[self.account]
- del gajim.newly_added[self.account]
- del gajim.sleeper_state[self.account]
- del gajim.encrypted_chats[self.account]
- del gajim.last_message_time[self.account]
- del gajim.status_before_autoaway[self.account]
- del gajim.transport_avatar[self.account]
- del gajim.gajim_optional_features[self.account]
- del gajim.caps_hash[self.account]
- if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.interface.roster.set_actions_menu_needs_rebuild()
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].init_accounts()
- gajim.interface.instances['accounts'].init_account()
- self.window.destroy()
+ """
+ Ask for removing from gajim only or from gajim and server too and do
+ removing of the account given
+ """
+
+ def on_remove_account_window_destroy(self, widget):
+ if self.account in gajim.interface.instances:
+ del gajim.interface.instances[self.account]['remove_account']
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def __init__(self, account):
+ self.account = account
+ xml = gtkgui_helpers.get_gtk_builder('remove_account_window.ui')
+ self.window = xml.get_object('remove_account_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ self.remove_and_unregister_radiobutton = xml.get_object(
+ 'remove_and_unregister_radiobutton')
+ self.window.set_title(_('Removing %s account') % self.account)
+ xml.connect_signals(self)
+ self.window.show_all()
+
+ def on_remove_button_clicked(self, widget):
+ def remove():
+ if gajim.connections[self.account].connected and \
+ not self.remove_and_unregister_radiobutton.get_active():
+ # change status to offline only if we will not remove this JID from
+ # server
+ gajim.connections[self.account].change_status('offline', 'offline')
+ if self.remove_and_unregister_radiobutton.get_active():
+ if not gajim.connections[self.account].password:
+ def on_ok(passphrase, checked):
+ if passphrase == -1:
+ # We don't remove account cause we canceled pw window
+ return
+ gajim.connections[self.account].password = passphrase
+ gajim.connections[self.account].unregister_account(
+ self._on_remove_success)
+
+ dialogs.PassphraseDialog(
+ _('Password Required'),
+ _('Enter your password for account %s') % self.account,
+ _('Save password'), ok_handler=on_ok)
+ return
+ gajim.connections[self.account].unregister_account(
+ self._on_remove_success)
+ else:
+ self._on_remove_success(True)
+
+ if gajim.connections[self.account].connected:
+ dialogs.ConfirmationDialog(
+ _('Account "%s" is connected to the server') % self.account,
+ _('If you remove it, the connection will be lost.'),
+ on_response_ok=remove)
+ else:
+ remove()
+
+ def on_remove_responce_ok(self, is_checked):
+ if is_checked[0]:
+ self._on_remove_success(True)
+
+ def _on_remove_success(self, res):
+ # action of unregistration has failed, we don't remove the account
+ # Error message is send by connect_and_auth()
+ if not res:
+ confirmation_check = dialogs.ConfirmationDialogDoubleRadio(
+ _('Connection to server %s failed') % self.account,
+ _('What would you like to do?'),
+ _('Remove only from Gajim'),
+ _('Don\'t remove anything. I\'ll try again later'),
+ on_response_ok=self.on_remove_responce_ok, is_modal=False)
+ return
+ # Close all opened windows
+ gajim.interface.roster.close_all(self.account, force = True)
+ gajim.connections[self.account].disconnect(on_purpose = True)
+ del gajim.connections[self.account]
+ gajim.logger.remove_roster(gajim.get_jid_from_account(self.account))
+ gajim.config.del_per('accounts', self.account)
+ gajim.interface.save_config()
+ del gajim.interface.instances[self.account]
+ del gajim.interface.minimized_controls[self.account]
+ del gajim.nicks[self.account]
+ del gajim.block_signed_in_notifications[self.account]
+ del gajim.groups[self.account]
+ gajim.contacts.remove_account(self.account)
+ del gajim.gc_connected[self.account]
+ del gajim.automatic_rooms[self.account]
+ del gajim.to_be_removed[self.account]
+ del gajim.newly_added[self.account]
+ del gajim.sleeper_state[self.account]
+ del gajim.encrypted_chats[self.account]
+ del gajim.last_message_time[self.account]
+ del gajim.status_before_autoaway[self.account]
+ del gajim.transport_avatar[self.account]
+ del gajim.gajim_optional_features[self.account]
+ del gajim.caps_hash[self.account]
+ if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
+ gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
+ else:
+ gajim.interface.roster.regroup = False
+ gajim.interface.roster.setup_and_draw_roster()
+ gajim.interface.roster.set_actions_menu_needs_rebuild()
+ if 'accounts' in gajim.interface.instances:
+ gajim.interface.instances['accounts'].init_accounts()
+ gajim.interface.instances['accounts'].init_account()
+ self.window.destroy()
#---------- ManageBookmarksWindow class -------------#
class ManageBookmarksWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_bookmarks_window.ui')
- self.window = self.xml.get_object('manage_bookmarks_window')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick,
- # Show_Status
- self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str)
- self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING)
-
- # Store bookmarks in treeview.
- for account in gajim.connections:
- if gajim.connections[account].connected <= 1:
- continue
- if gajim.connections[account].is_zeroconf:
- continue
- if not gajim.connections[account].private_storage_supported:
- continue
- iter_ = self.treestore.append(None, [None, account, None, None,
- None, None, None, None])
-
- for bookmark in gajim.connections[account].bookmarks:
- if bookmark['name'] == '':
- # No name was given for this bookmark.
- # Use the first part of JID instead...
- name = bookmark['jid'].split("@")[0]
- bookmark['name'] = name
-
- # make '1', '0', 'true', 'false' (or other) to True/False
- autojoin = helpers.from_xs_boolean_to_python_boolean(
- bookmark['autojoin'])
-
- minimize = helpers.from_xs_boolean_to_python_boolean(
- bookmark['minimize'])
-
- print_status = bookmark.get('print_status', '')
- if print_status not in ('', 'all', 'in_and_out', 'none'):
- print_status = ''
- self.treestore.append(iter_, [
- account,
- bookmark['name'],
- bookmark['jid'],
- autojoin,
- minimize,
- bookmark['password'],
- bookmark['nick'],
- print_status ])
-
- self.print_status_combobox = self.xml.get_object('print_status_combobox')
- model = gtk.ListStore(str, str)
-
- self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'),
- 'in_and_out': _('Enter and leave only'),
- 'none': Q_('?print_status:None')}
- opts = sorted(self.option_list.keys())
- for opt in opts:
- model.append([self.option_list[opt], opt])
-
- self.print_status_combobox.set_model(model)
- self.print_status_combobox.set_active(1)
-
- self.view = self.xml.get_object('bookmarks_treeview')
- self.view.set_model(self.treestore)
- self.view.expand_all()
-
- renderer = gtk.CellRendererText()
- column = gtk.TreeViewColumn('Bookmarks', renderer, text=1)
- self.view.append_column(column)
-
- self.selection = self.view.get_selection()
- self.selection.connect('changed', self.bookmark_selected)
-
- #Prepare input fields
- self.title_entry = self.xml.get_object('title_entry')
- self.title_entry.connect('changed', self.on_title_entry_changed)
- self.nick_entry = self.xml.get_object('nick_entry')
- self.nick_entry.connect('changed', self.on_nick_entry_changed)
- self.server_entry = self.xml.get_object('server_entry')
- self.server_entry.connect('changed', self.on_server_entry_changed)
- self.room_entry = self.xml.get_object('room_entry')
- self.room_entry.connect('changed', self.on_room_entry_changed)
- self.pass_entry = self.xml.get_object('pass_entry')
- self.pass_entry.connect('changed', self.on_pass_entry_changed)
- self.autojoin_checkbutton = self.xml.get_object('autojoin_checkbutton')
- self.minimize_checkbutton = self.xml.get_object('minimize_checkbutton')
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_bookmarks_treeview_button_press_event(self, widget, event):
- (model, iter_) = self.selection.get_selected()
- if not iter_:
- # Removed a bookmark before
- return
-
- if model.iter_parent(iter_):
- # The currently selected node is a bookmark
- return not self.check_valid_bookmark()
-
- def on_manage_bookmarks_window_destroy(self, widget, event):
- del gajim.interface.instances['manage_bookmarks']
-
- def on_add_bookmark_button_clicked(self, widget):
- """
- Add a new bookmark
- """
- # Get the account that is currently used
- # (the parent of the currently selected item)
- (model, iter_) = self.selection.get_selected()
- if not iter_: # Nothing selected, do nothing
- return
-
- parent = model.iter_parent(iter_)
-
- if parent:
- # We got a bookmark selected, so we add_to the parent
- add_to = parent
- else:
- # No parent, so we got an account -> add to this.
- add_to = iter_
-
- account = model[add_to][1].decode('utf-8')
- nick = gajim.nicks[account]
- iter_ = self.treestore.append(add_to, [account, _('New Group Chat'), '',
- False, False, '', nick, 'in_and_out'])
-
- self.view.expand_row(model.get_path(add_to), True)
- self.view.set_cursor(model.get_path(iter_))
-
- def on_remove_bookmark_button_clicked(self, widget):
- """
- Remove selected bookmark
- """
- (model, iter_) = self.selection.get_selected()
- if not iter_: # Nothing selected
- return
-
- if not model.iter_parent(iter_):
- # Don't remove account iters
- return
-
- model.remove(iter_)
- self.clear_fields()
-
- def check_valid_bookmark(self):
- """
- Check if all neccessary fields are entered correctly
- """
- (model, iter_) = self.selection.get_selected()
-
- if not model.iter_parent(iter_):
- #Account data can't be changed
- return
-
- if self.server_entry.get_text().decode('utf-8') == '' or \
- self.room_entry.get_text().decode('utf-8') == '':
- dialogs.ErrorDialog(_('This bookmark has invalid data'),
- _('Please be sure to fill out server and room fields or remove this'
- ' bookmark.'))
- return False
-
- return True
-
- def on_ok_button_clicked(self, widget):
- """
- Parse the treestore data into our new bookmarks array, then send the new
- bookmarks to the server.
- """
- (model, iter_) = self.selection.get_selected()
- if iter_ and model.iter_parent(iter_):
- #bookmark selected, check it
- if not self.check_valid_bookmark():
- return
-
- for account in self.treestore:
- account_unicode = account[1].decode('utf-8')
- gajim.connections[account_unicode].bookmarks = []
-
- for bm in account.iterchildren():
- #Convert True/False/None to '1' or '0'
- autojoin = unicode(int(bm[3]))
- minimize = unicode(int(bm[4]))
-
- #create the bookmark-dict
- bmdict = { 'name': bm[1], 'jid': bm[2], 'autojoin': autojoin,
- 'minimize': minimize, 'password': bm[5], 'nick': bm[6],
- 'print_status': bm[7]}
-
- gajim.connections[account_unicode].bookmarks.append(bmdict)
-
- gajim.connections[account_unicode].store_bookmarks()
- gajim.interface.roster.set_actions_menu_needs_rebuild()
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def bookmark_selected(self, selection):
- """
- Fill in the bookmark's data into the fields.
- """
- (model, iter_) = selection.get_selected()
-
- if not iter_:
- # After removing the last bookmark for one account
- # this will be None, so we will just:
- return
-
- widgets = [ self.title_entry, self.nick_entry, self.room_entry,
- self.server_entry, self.pass_entry, self.autojoin_checkbutton,
- self.minimize_checkbutton, self.print_status_combobox]
-
- if model.iter_parent(iter_):
- # make the fields sensitive
- for field in widgets:
- field.set_sensitive(True)
- else:
- # Top-level has no data (it's the account fields)
- # clear fields & make them insensitive
- self.clear_fields()
- for field in widgets:
- field.set_sensitive(False)
- return
-
- # Fill in the data for childs
- self.title_entry.set_text(model[iter_][1])
- room_jid = model[iter_][2].decode('utf-8')
- try:
- (room, server) = room_jid.split('@')
- except ValueError:
- # We just added this one
- room = ''
- server = ''
- self.room_entry.set_text(room)
- self.server_entry.set_text(server)
-
- self.autojoin_checkbutton.set_active(model[iter_][3])
- self.minimize_checkbutton.set_active(model[iter_][4])
- # sensitive only if auto join is checked
- self.minimize_checkbutton.set_sensitive(model[iter_][3])
-
- if model[iter_][5] is not None:
- password = model[iter_][5].decode('utf-8')
- else:
- password = None
-
- if password:
- self.pass_entry.set_text(password)
- else:
- self.pass_entry.set_text('')
- nick = model[iter_][6]
- if nick:
- nick = nick.decode('utf-8')
- self.nick_entry.set_text(nick)
- else:
- self.nick_entry.set_text('')
-
- print_status = model[iter_][7]
- opts = sorted(self.option_list.keys())
- self.print_status_combobox.set_active(opts.index(print_status))
-
- def on_title_entry_changed(self, widget):
- (model, iter_) = self.selection.get_selected()
- if iter_: # After removing a bookmark, we got nothing selected
- if model.iter_parent(iter_):
- # Don't clear the title field for account nodes
- model[iter_][1] = self.title_entry.get_text()
-
- def on_nick_entry_changed(self, widget):
- (model, iter_) = self.selection.get_selected()
- if iter_:
- nick = self.nick_entry.get_text().decode('utf-8')
- try:
- nick = helpers.parse_resource(nick)
- except helpers.InvalidFormat, e:
- dialogs.ErrorDialog(_('Invalid nickname'),
- _('Character not allowed'))
- self.nick_entry.set_text(model[iter_][6])
- return True
- model[iter_][6] = nick
-
- def on_server_entry_changed(self, widget):
- (model, iter_) = self.selection.get_selected()
- if iter_:
- room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \
- self.server_entry.get_text().decode('utf-8').strip()
- try:
- room_jid = helpers.parse_resource(room_jid)
- except helpers.InvalidFormat, e:
- dialogs.ErrorDialog(_('Invalid server'),
- _('Character not allowed'))
- self.server_entry.set_text(model[iter_][2].split('@')[1])
- return True
- model[iter_][2] = room_jid
-
- def on_room_entry_changed(self, widget):
- (model, iter_) = self.selection.get_selected()
- if iter_:
- room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \
- self.server_entry.get_text().decode('utf-8').strip()
- try:
- room_jid = helpers.parse_resource(room_jid)
- except helpers.InvalidFormat, e:
- dialogs.ErrorDialog(_('Invalid room'),
- _('Character not allowed'))
- self.room_entry.set_text(model[iter_][2].split('@')[0])
- return True
- model[iter_][2] = room_jid
-
- def on_pass_entry_changed(self, widget):
- (model, iter_) = self.selection.get_selected()
- if iter_:
- model[iter_][5] = self.pass_entry.get_text()
-
- def on_autojoin_checkbutton_toggled(self, widget):
- (model, iter_) = self.selection.get_selected()
- if iter_:
- model[iter_][3] = self.autojoin_checkbutton.get_active()
- self.minimize_checkbutton.set_sensitive(model[iter_][3])
-
- def on_minimize_checkbutton_toggled(self, widget):
- (model, iter_) = self.selection.get_selected()
- if iter_:
- model[iter_][4] = self.minimize_checkbutton.get_active()
-
- def on_print_status_combobox_changed(self, widget):
- active = widget.get_active()
- model = widget.get_model()
- print_status = model[active][1]
- (model2, iter_) = self.selection.get_selected()
- if iter_:
- model2[iter_][7] = print_status
-
- def clear_fields(self):
- widgets = [ self.title_entry, self.nick_entry, self.room_entry,
- self.server_entry, self.pass_entry ]
- for field in widgets:
- field.set_text('')
- self.autojoin_checkbutton.set_active(False)
- self.minimize_checkbutton.set_active(False)
- self.print_status_combobox.set_active(1)
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('manage_bookmarks_window.ui')
+ self.window = self.xml.get_object('manage_bookmarks_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+
+ # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick,
+ # Show_Status
+ self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str)
+ self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING)
+
+ # Store bookmarks in treeview.
+ for account in gajim.connections:
+ if gajim.connections[account].connected <= 1:
+ continue
+ if gajim.connections[account].is_zeroconf:
+ continue
+ if not gajim.connections[account].private_storage_supported:
+ continue
+ iter_ = self.treestore.append(None, [None, account, None, None,
+ None, None, None, None])
+
+ for bookmark in gajim.connections[account].bookmarks:
+ if bookmark['name'] == '':
+ # No name was given for this bookmark.
+ # Use the first part of JID instead...
+ name = bookmark['jid'].split("@")[0]
+ bookmark['name'] = name
+
+ # make '1', '0', 'true', 'false' (or other) to True/False
+ autojoin = helpers.from_xs_boolean_to_python_boolean(
+ bookmark['autojoin'])
+
+ minimize = helpers.from_xs_boolean_to_python_boolean(
+ bookmark['minimize'])
+
+ print_status = bookmark.get('print_status', '')
+ if print_status not in ('', 'all', 'in_and_out', 'none'):
+ print_status = ''
+ self.treestore.append(iter_, [
+ account,
+ bookmark['name'],
+ bookmark['jid'],
+ autojoin,
+ minimize,
+ bookmark['password'],
+ bookmark['nick'],
+ print_status ])
+
+ self.print_status_combobox = self.xml.get_object('print_status_combobox')
+ model = gtk.ListStore(str, str)
+
+ self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'),
+ 'in_and_out': _('Enter and leave only'),
+ 'none': Q_('?print_status:None')}
+ opts = sorted(self.option_list.keys())
+ for opt in opts:
+ model.append([self.option_list[opt], opt])
+
+ self.print_status_combobox.set_model(model)
+ self.print_status_combobox.set_active(1)
+
+ self.view = self.xml.get_object('bookmarks_treeview')
+ self.view.set_model(self.treestore)
+ self.view.expand_all()
+
+ renderer = gtk.CellRendererText()
+ column = gtk.TreeViewColumn('Bookmarks', renderer, text=1)
+ self.view.append_column(column)
+
+ self.selection = self.view.get_selection()
+ self.selection.connect('changed', self.bookmark_selected)
+
+ #Prepare input fields
+ self.title_entry = self.xml.get_object('title_entry')
+ self.title_entry.connect('changed', self.on_title_entry_changed)
+ self.nick_entry = self.xml.get_object('nick_entry')
+ self.nick_entry.connect('changed', self.on_nick_entry_changed)
+ self.server_entry = self.xml.get_object('server_entry')
+ self.server_entry.connect('changed', self.on_server_entry_changed)
+ self.room_entry = self.xml.get_object('room_entry')
+ self.room_entry.connect('changed', self.on_room_entry_changed)
+ self.pass_entry = self.xml.get_object('pass_entry')
+ self.pass_entry.connect('changed', self.on_pass_entry_changed)
+ self.autojoin_checkbutton = self.xml.get_object('autojoin_checkbutton')
+ self.minimize_checkbutton = self.xml.get_object('minimize_checkbutton')
+
+ self.xml.connect_signals(self)
+ self.window.show_all()
+
+ def on_bookmarks_treeview_button_press_event(self, widget, event):
+ (model, iter_) = self.selection.get_selected()
+ if not iter_:
+ # Removed a bookmark before
+ return
+
+ if model.iter_parent(iter_):
+ # The currently selected node is a bookmark
+ return not self.check_valid_bookmark()
+
+ def on_manage_bookmarks_window_destroy(self, widget, event):
+ del gajim.interface.instances['manage_bookmarks']
+
+ def on_add_bookmark_button_clicked(self, widget):
+ """
+ Add a new bookmark
+ """
+ # Get the account that is currently used
+ # (the parent of the currently selected item)
+ (model, iter_) = self.selection.get_selected()
+ if not iter_: # Nothing selected, do nothing
+ return
+
+ parent = model.iter_parent(iter_)
+
+ if parent:
+ # We got a bookmark selected, so we add_to the parent
+ add_to = parent
+ else:
+ # No parent, so we got an account -> add to this.
+ add_to = iter_
+
+ account = model[add_to][1].decode('utf-8')
+ nick = gajim.nicks[account]
+ iter_ = self.treestore.append(add_to, [account, _('New Group Chat'), '',
+ False, False, '', nick, 'in_and_out'])
+
+ self.view.expand_row(model.get_path(add_to), True)
+ self.view.set_cursor(model.get_path(iter_))
+
+ def on_remove_bookmark_button_clicked(self, widget):
+ """
+ Remove selected bookmark
+ """
+ (model, iter_) = self.selection.get_selected()
+ if not iter_: # Nothing selected
+ return
+
+ if not model.iter_parent(iter_):
+ # Don't remove account iters
+ return
+
+ model.remove(iter_)
+ self.clear_fields()
+
+ def check_valid_bookmark(self):
+ """
+ Check if all neccessary fields are entered correctly
+ """
+ (model, iter_) = self.selection.get_selected()
+
+ if not model.iter_parent(iter_):
+ #Account data can't be changed
+ return
+
+ if self.server_entry.get_text().decode('utf-8') == '' or \
+ self.room_entry.get_text().decode('utf-8') == '':
+ dialogs.ErrorDialog(_('This bookmark has invalid data'),
+ _('Please be sure to fill out server and room fields or remove this'
+ ' bookmark.'))
+ return False
+
+ return True
+
+ def on_ok_button_clicked(self, widget):
+ """
+ Parse the treestore data into our new bookmarks array, then send the new
+ bookmarks to the server.
+ """
+ (model, iter_) = self.selection.get_selected()
+ if iter_ and model.iter_parent(iter_):
+ #bookmark selected, check it
+ if not self.check_valid_bookmark():
+ return
+
+ for account in self.treestore:
+ account_unicode = account[1].decode('utf-8')
+ gajim.connections[account_unicode].bookmarks = []
+
+ for bm in account.iterchildren():
+ #Convert True/False/None to '1' or '0'
+ autojoin = unicode(int(bm[3]))
+ minimize = unicode(int(bm[4]))
+
+ #create the bookmark-dict
+ bmdict = { 'name': bm[1], 'jid': bm[2], 'autojoin': autojoin,
+ 'minimize': minimize, 'password': bm[5], 'nick': bm[6],
+ 'print_status': bm[7]}
+
+ gajim.connections[account_unicode].bookmarks.append(bmdict)
+
+ gajim.connections[account_unicode].store_bookmarks()
+ gajim.interface.roster.set_actions_menu_needs_rebuild()
+ self.window.destroy()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def bookmark_selected(self, selection):
+ """
+ Fill in the bookmark's data into the fields.
+ """
+ (model, iter_) = selection.get_selected()
+
+ if not iter_:
+ # After removing the last bookmark for one account
+ # this will be None, so we will just:
+ return
+
+ widgets = [ self.title_entry, self.nick_entry, self.room_entry,
+ self.server_entry, self.pass_entry, self.autojoin_checkbutton,
+ self.minimize_checkbutton, self.print_status_combobox]
+
+ if model.iter_parent(iter_):
+ # make the fields sensitive
+ for field in widgets:
+ field.set_sensitive(True)
+ else:
+ # Top-level has no data (it's the account fields)
+ # clear fields & make them insensitive
+ self.clear_fields()
+ for field in widgets:
+ field.set_sensitive(False)
+ return
+
+ # Fill in the data for childs
+ self.title_entry.set_text(model[iter_][1])
+ room_jid = model[iter_][2].decode('utf-8')
+ try:
+ (room, server) = room_jid.split('@')
+ except ValueError:
+ # We just added this one
+ room = ''
+ server = ''
+ self.room_entry.set_text(room)
+ self.server_entry.set_text(server)
+
+ self.autojoin_checkbutton.set_active(model[iter_][3])
+ self.minimize_checkbutton.set_active(model[iter_][4])
+ # sensitive only if auto join is checked
+ self.minimize_checkbutton.set_sensitive(model[iter_][3])
+
+ if model[iter_][5] is not None:
+ password = model[iter_][5].decode('utf-8')
+ else:
+ password = None
+
+ if password:
+ self.pass_entry.set_text(password)
+ else:
+ self.pass_entry.set_text('')
+ nick = model[iter_][6]
+ if nick:
+ nick = nick.decode('utf-8')
+ self.nick_entry.set_text(nick)
+ else:
+ self.nick_entry.set_text('')
+
+ print_status = model[iter_][7]
+ opts = sorted(self.option_list.keys())
+ self.print_status_combobox.set_active(opts.index(print_status))
+
+ def on_title_entry_changed(self, widget):
+ (model, iter_) = self.selection.get_selected()
+ if iter_: # After removing a bookmark, we got nothing selected
+ if model.iter_parent(iter_):
+ # Don't clear the title field for account nodes
+ model[iter_][1] = self.title_entry.get_text()
+
+ def on_nick_entry_changed(self, widget):
+ (model, iter_) = self.selection.get_selected()
+ if iter_:
+ nick = self.nick_entry.get_text().decode('utf-8')
+ try:
+ nick = helpers.parse_resource(nick)
+ except helpers.InvalidFormat, e:
+ dialogs.ErrorDialog(_('Invalid nickname'),
+ _('Character not allowed'))
+ self.nick_entry.set_text(model[iter_][6])
+ return True
+ model[iter_][6] = nick
+
+ def on_server_entry_changed(self, widget):
+ (model, iter_) = self.selection.get_selected()
+ if iter_:
+ room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \
+ self.server_entry.get_text().decode('utf-8').strip()
+ try:
+ room_jid = helpers.parse_resource(room_jid)
+ except helpers.InvalidFormat, e:
+ dialogs.ErrorDialog(_('Invalid server'),
+ _('Character not allowed'))
+ self.server_entry.set_text(model[iter_][2].split('@')[1])
+ return True
+ model[iter_][2] = room_jid
+
+ def on_room_entry_changed(self, widget):
+ (model, iter_) = self.selection.get_selected()
+ if iter_:
+ room_jid = self.room_entry.get_text().decode('utf-8').strip() + '@' + \
+ self.server_entry.get_text().decode('utf-8').strip()
+ try:
+ room_jid = helpers.parse_resource(room_jid)
+ except helpers.InvalidFormat, e:
+ dialogs.ErrorDialog(_('Invalid room'),
+ _('Character not allowed'))
+ self.room_entry.set_text(model[iter_][2].split('@')[0])
+ return True
+ model[iter_][2] = room_jid
+
+ def on_pass_entry_changed(self, widget):
+ (model, iter_) = self.selection.get_selected()
+ if iter_:
+ model[iter_][5] = self.pass_entry.get_text()
+
+ def on_autojoin_checkbutton_toggled(self, widget):
+ (model, iter_) = self.selection.get_selected()
+ if iter_:
+ model[iter_][3] = self.autojoin_checkbutton.get_active()
+ self.minimize_checkbutton.set_sensitive(model[iter_][3])
+
+ def on_minimize_checkbutton_toggled(self, widget):
+ (model, iter_) = self.selection.get_selected()
+ if iter_:
+ model[iter_][4] = self.minimize_checkbutton.get_active()
+
+ def on_print_status_combobox_changed(self, widget):
+ active = widget.get_active()
+ model = widget.get_model()
+ print_status = model[active][1]
+ (model2, iter_) = self.selection.get_selected()
+ if iter_:
+ model2[iter_][7] = print_status
+
+ def clear_fields(self):
+ widgets = [ self.title_entry, self.nick_entry, self.room_entry,
+ self.server_entry, self.pass_entry ]
+ for field in widgets:
+ field.set_text('')
+ self.autojoin_checkbutton.set_active(False)
+ self.minimize_checkbutton.set_active(False)
+ self.print_status_combobox.set_active(1)
class AccountCreationWizardWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'account_creation_wizard_window.ui')
- self.window = self.xml.get_object('account_creation_wizard_window')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- completion = gtk.EntryCompletion()
- # Connect events from comboboxentry.child
- server_comboboxentry = self.xml.get_object('server_comboboxentry')
- entry = server_comboboxentry.child
- entry.connect('key_press_event',
- self.on_server_comboboxentry_key_press_event, server_comboboxentry)
- entry.set_completion(completion)
- # Do the same for the other server comboboxentry
- server_comboboxentry1 = self.xml.get_object('server_comboboxentry1')
- entry = server_comboboxentry1.child
- entry.connect('key_press_event',
- self.on_server_comboboxentry_key_press_event, server_comboboxentry1)
- entry.set_completion(completion)
-
- self.update_proxy_list()
-
- # parse servers.xml
- servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml')
- servers = gtkgui_helpers.parse_server_xml(servers_xml)
- servers_model = gtk.ListStore(str, int)
- for server in servers:
- if not server[2]['hidden']:
- servers_model.append((str(server[0]), int(server[1])))
-
- completion.set_model(servers_model)
- completion.set_text_column(0)
-
- # Put servers into comboboxentries
- server_comboboxentry.set_model(servers_model)
- server_comboboxentry.set_text_column(0)
- server_comboboxentry1.set_model(servers_model)
- server_comboboxentry1.set_text_column(0)
-
- # Generic widgets
- self.notebook = self.xml.get_object('notebook')
- self.cancel_button = self.xml.get_object('cancel_button')
- self.back_button = self.xml.get_object('back_button')
- self.forward_button = self.xml.get_object('forward_button')
- self.finish_button = self.xml.get_object('finish_button')
- self.advanced_button = self.xml.get_object('advanced_button')
- self.finish_label = self.xml.get_object('finish_label')
- self.go_online_checkbutton = self.xml.get_object(
- 'go_online_checkbutton')
- self.show_vcard_checkbutton = self.xml.get_object(
- 'show_vcard_checkbutton')
- self.progressbar = self.xml.get_object('progressbar')
-
- # some vars
- self.update_progressbar_timeout_id = None
-
- self.notebook.set_current_page(0)
- self.xml.connect_signals(self)
- self.window.show_all()
- gajim.ged.register_event_handler('NEW_ACC_CONNECTED', ged.CORE,
- self.new_acc_connected)
- gajim.ged.register_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE,
- self.new_acc_not_connected)
- gajim.ged.register_event_handler('ACC_OK', ged.CORE, self.acc_is_ok)
- gajim.ged.register_event_handler('ACC_NOT_OK', ged.CORE,
- self.acc_is_not_ok)
-
- def on_wizard_window_destroy(self, widget):
- page = self.notebook.get_current_page()
- if page in (4, 5) and self.account in gajim.connections:
- # connection instance is saved in gajim.connections and we canceled the
- # addition of the account
- del gajim.connections[self.account]
- if self.account in gajim.config.get_per('accounts'):
- gajim.config.del_per('accounts', self.account)
- gajim.ged.remove_event_handler('NEW_ACC_CONNECTED', ged.CORE,
- self.new_acc_connected)
- gajim.ged.remove_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE,
- self.new_acc_not_connected)
- gajim.ged.remove_event_handler('ACC_OK', ged.CORE, self.acc_is_ok)
- gajim.ged.remove_event_handler('ACC_NOT_OK', ged.CORE,
- self.acc_is_not_ok)
- del gajim.interface.instances['account_creation_wizard']
-
- def on_register_server_features_button_clicked(self, widget):
- helpers.launch_browser_mailer('url',
- 'http://www.jabber.org/network/oldnetwork.shtml')
-
- def on_save_password_checkbutton_toggled(self, widget):
- self.xml.get_object('password_entry').grab_focus()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_back_button_clicked(self, widget):
- cur_page = self.notebook.get_current_page()
- if cur_page in (1, 2):
- self.notebook.set_current_page(0)
- self.back_button.set_sensitive(False)
- elif cur_page == 3:
- self.xml.get_object('form_vbox').remove(self.data_form_widget)
- self.notebook.set_current_page(2) # show server page
- elif cur_page == 4:
- if self.account in gajim.connections:
- del gajim.connections[self.account]
- self.notebook.set_current_page(2)
- self.xml.get_object('form_vbox').remove(self.data_form_widget)
- elif cur_page == 6: # finish page
- self.forward_button.show()
- if self.modify:
- self.notebook.set_current_page(1) # Go to parameters page
- else:
- self.notebook.set_current_page(2) # Go to server page
-
- def on_anonymous_checkbutton1_toggled(self, widget):
- active = widget.get_active()
- self.xml.get_object('username_entry').set_sensitive(not active)
- self.xml.get_object('password_entry').set_sensitive(not active)
- self.xml.get_object('save_password_checkbutton').set_sensitive(not active)
-
- def show_finish_page(self):
- self.cancel_button.hide()
- self.back_button.hide()
- self.forward_button.hide()
- if self.modify:
- finish_text = '<big><b>%s</b></big>\n\n%s' % (
- _('Account has been added successfully'),
- _('You can set advanced account options by pressing the '
- 'Advanced button, or later by choosing the Accounts menu item '
- 'under the Edit menu from the main window.'))
- else:
- finish_text = '<big><b>%s</b></big>\n\n%s' % (
- _('Your new account has been created successfully'),
- _('You can set advanced account options by pressing the Advanced '
- 'button, or later by choosing the Accounts menu item under the Edit'
- ' menu from the main window.'))
- self.finish_label.set_markup(finish_text)
- self.finish_button.show()
- self.finish_button.set_property('has-default', True)
- self.advanced_button.show()
- self.go_online_checkbutton.show()
- img = self.xml.get_object('finish_image')
- if self.modify:
- img.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_DIALOG)
- else:
- path_to_file = gtkgui_helpers.get_icon_path('gajim', 48)
- img.set_from_file(path_to_file)
- self.show_vcard_checkbutton.set_active(not self.modify)
- self.notebook.set_current_page(6) # show finish page
-
- def on_forward_button_clicked(self, widget):
- cur_page = self.notebook.get_current_page()
-
- if cur_page == 0:
- widget = self.xml.get_object('use_existing_account_radiobutton')
- if widget.get_active():
- self.modify = True
- self.notebook.set_current_page(1)
- else:
- self.modify = False
- self.notebook.set_current_page(2)
- self.back_button.set_sensitive(True)
- return
-
- elif cur_page == 1:
- # We are adding an existing account
- anonymous = self.xml.get_object('anonymous_checkbutton1').get_active()
- username = self.xml.get_object('username_entry').get_text().decode(
- 'utf-8').strip()
- if not username and not anonymous:
- pritext = _('Invalid username')
- sectext = _(
- 'You must provide a username to configure this account.')
- dialogs.ErrorDialog(pritext, sectext)
- return
- server = self.xml.get_object('server_comboboxentry').child.get_text().\
- decode('utf-8').strip()
- savepass = self.xml.get_object('save_password_checkbutton').\
- get_active()
- password = self.xml.get_object('password_entry').get_text().decode(
- 'utf-8')
-
- jid = username + '@' + server
- # check if jid is conform to RFC and stringprep it
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat, s:
- pritext = _('Invalid Jabber ID')
- dialogs.ErrorDialog(pritext, str(s))
- return
-
- self.account = server
- i = 1
- while self.account in gajim.connections:
- self.account = server + str(i)
- i += 1
-
- username, server = gajim.get_name_and_server_from_jid(jid)
- if self.xml.get_object('anonymous_checkbutton1').get_active():
- self.save_account('', server, False, '', anonymous=True)
- else:
- self.save_account(username, server, savepass, password)
- self.show_finish_page()
- elif cur_page == 2:
- # We are creating a new account
- server = self.xml.get_object('server_comboboxentry1').child.get_text()\
- .decode('utf-8')
-
- if not server:
- dialogs.ErrorDialog(_('Invalid server'),
- _('Please provide a server on which you want to register.'))
- return
- self.account = server
- i = 1
- while self.account in gajim.connections:
- self.account = server + str(i)
- i += 1
-
- config = self.get_config('', server, '', '')
- # Get advanced options
- proxies_combobox = self.xml.get_object('proxies_combobox')
- active = proxies_combobox.get_active()
- proxy = proxies_combobox.get_model()[active][0].decode('utf-8')
- if proxy == _('None'):
- proxy = ''
- config['proxy'] = proxy
-
- config['use_custom_host'] = self.xml.get_object(
- 'custom_host_port_checkbutton').get_active()
- custom_port = self.xml.get_object('custom_port_entry').get_text()
- try:
- custom_port = int(custom_port)
- except Exception:
- dialogs.ErrorDialog(_('Invalid entry'),
- _('Custom port must be a port number.'))
- return
- config['custom_port'] = custom_port
- config['custom_host'] = self.xml.get_object(
- 'custom_host_entry').get_text().decode('utf-8')
-
- if self.xml.get_object('anonymous_checkbutton2').get_active():
- self.modify = True
- self.save_account('', server, False, '', anonymous=True)
- self.show_finish_page()
- else:
- self.notebook.set_current_page(5) # show creating page
- self.back_button.hide()
- self.forward_button.hide()
- self.update_progressbar_timeout_id = gobject.timeout_add(100,
- self.update_progressbar)
- # Get form from serveur
- con = connection.Connection(self.account)
- gajim.connections[self.account] = con
- con.new_account(self.account, config)
- elif cur_page == 3:
- checked = self.xml.get_object('ssl_checkbutton').get_active()
- if checked:
- hostname = gajim.connections[self.account].new_account_info[
- 'hostname']
- # Check if cert is already in file
- certs = ''
- if os.path.isfile(gajim.MY_CACERTS):
- f = open(gajim.MY_CACERTS)
- certs = f.read()
- f.close()
- if self.ssl_cert in certs:
- dialogs.ErrorDialog(_('Certificate Already in File'),
- _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS)
- else:
- f = open(gajim.MY_CACERTS, 'a')
- f.write(hostname + '\n')
- f.write(self.ssl_cert + '\n\n')
- f.close()
- gajim.connections[self.account].new_account_info[
- 'ssl_fingerprint_sha1'] = self.ssl_fingerprint
- self.notebook.set_current_page(4) # show fom page
- elif cur_page == 4:
- if self.is_form:
- form = self.data_form_widget.data_form
- else:
- form = self.data_form_widget.get_infos()
- gajim.connections[self.account].send_new_account_infos(form,
- self.is_form)
- self.xml.get_object('form_vbox').remove(self.data_form_widget)
- self.xml.get_object('progressbar_label').set_markup('<b>Account is being created</b>\n\nPlease wait...')
- self.notebook.set_current_page(5) # show creating page
- self.back_button.hide()
- self.forward_button.hide()
- self.update_progressbar_timeout_id = gobject.timeout_add(100,
- self.update_progressbar)
-
- def update_proxy_list(self):
- proxies_combobox = self.xml.get_object('proxies_combobox')
- model = gtk.ListStore(str)
- proxies_combobox.set_model(model)
- l = gajim.config.get_per('proxies')
- l.insert(0, _('None'))
- for i in xrange(len(l)):
- model.append([l[i]])
- proxies_combobox.set_active(0)
-
- def on_manage_proxies_button_clicked(self, widget):
- if 'manage_proxies' in gajim.interface.instances:
- gajim.interface.instances['manage_proxies'].window.present()
- else:
- gajim.interface.instances['manage_proxies'] = \
- ManageProxiesWindow()
-
- def on_custom_host_port_checkbutton_toggled(self, widget):
- self.xml.get_object('custom_host_hbox').set_sensitive(widget.get_active())
-
- def update_progressbar(self):
- self.progressbar.pulse()
- return True # loop forever
-
- def new_acc_connected(self, account, array):
- """
- Connection to server succeded, present the form to the user
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- form, is_form, ssl_msg, ssl_err, ssl_cert, ssl_fingerprint = array
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
- self.back_button.show()
- self.forward_button.show()
- self.is_form = is_form
- if is_form:
- dataform = dataforms.ExtendForm(node = form)
- self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
- else:
- self.data_form_widget = FakeDataForm(form)
- self.data_form_widget.show_all()
- self.xml.get_object('form_vbox').pack_start(self.data_form_widget)
- self.ssl_fingerprint = ssl_fingerprint
- self.ssl_cert = ssl_cert
- if ssl_msg:
- # An SSL warning occured, show it
- hostname = gajim.connections[self.account].new_account_info['hostname']
- self.xml.get_object('ssl_label').set_markup(_('<b>Security Warning</b>'
- '\n\nThe authenticity of the %(hostname)s SSL certificate could be '
- 'invalid.\nSSL Error: %(error)s\n'
- 'Do you still want to connect to this server?') % {
- 'hostname': hostname, 'error': ssl_msg})
- if ssl_err in (18, 27):
- text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint
- self.xml.get_object('ssl_checkbutton').set_label(text)
- else:
- self.xml.get_object('ssl_checkbutton').set_no_show_all(True)
- self.xml.get_object('ssl_checkbutton').hide()
- self.notebook.set_current_page(3) # show SSL page
- else:
- self.notebook.set_current_page(4) # show form page
-
- def new_acc_not_connected(self, account, reason):
- """
- Account creation failed: connection to server failed
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- if self.account not in gajim.connections:
- return
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
- del gajim.connections[self.account]
- if self.account in gajim.config.get_per('accounts'):
- gajim.config.del_per('accounts', self.account)
- self.back_button.show()
- self.cancel_button.show()
- self.go_online_checkbutton.hide()
- self.show_vcard_checkbutton.hide()
- img = self.xml.get_object('finish_image')
- img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)
- finish_text = '<big><b>%s</b></big>\n\n%s' % (
- _('An error occurred during account creation') , reason)
- self.finish_label.set_markup(finish_text)
- self.notebook.set_current_page(6) # show finish page
-
- def acc_is_ok(self, account, config):
- """
- Account creation succeeded
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- self.create_vars(config)
- self.show_finish_page()
-
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
-
- def acc_is_not_ok(self, account, reason):
- """
- Account creation failed
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- self.back_button.show()
- self.cancel_button.show()
- self.go_online_checkbutton.hide()
- self.show_vcard_checkbutton.hide()
- del gajim.connections[self.account]
- if self.account in gajim.config.get_per('accounts'):
- gajim.config.del_per('accounts', self.account)
- img = self.xml.get_object('finish_image')
- img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)
- finish_text = '<big><b>%s</b></big>\n\n%s' % (_('An error occurred during '
- 'account creation') , reason)
- self.finish_label.set_markup(finish_text)
- self.notebook.set_current_page(6) # show finish page
-
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
-
- def on_advanced_button_clicked(self, widget):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].window.present()
- else:
- gajim.interface.instances['accounts'] = AccountsWindow()
- gajim.interface.instances['accounts'].select_account(
- self.account)
- self.window.destroy()
-
- def on_finish_button_clicked(self, widget):
- go_online = self.xml.get_object('go_online_checkbutton').get_active()
- show_vcard = self.xml.get_object('show_vcard_checkbutton').get_active()
- self.window.destroy()
- if show_vcard:
- gajim.interface.show_vcard_when_connect.append(self.account)
- if go_online:
- gajim.interface.roster.send_status(self.account, 'online', '')
-
- def on_username_entry_key_press_event(self, widget, event):
- # Check for pressed @ and jump to combobox if found
- if event.keyval == gtk.keysyms.at:
- combobox = self.xml.get_object('server_comboboxentry')
- combobox.grab_focus()
- combobox.child.set_position(-1)
- return True
-
- def on_server_comboboxentry_key_press_event(self, widget, event, combobox):
- # If backspace is pressed in empty field, return to the nick entry field
- backspace = event.keyval == gtk.keysyms.BackSpace
- empty = len(combobox.get_active_text()) == 0
- if backspace and empty and self.modify:
- username_entry = self.xml.get_object('username_entry')
- username_entry.grab_focus()
- username_entry.set_position(-1)
- return True
-
- def get_config(self, login, server, savepass, password, anonymous=False):
- config = {}
- config['name'] = login
- config['hostname'] = server
- config['savepass'] = savepass
- config['password'] = password
- config['resource'] = 'Gajim'
- config['anonymous_auth'] = anonymous
- config['priority'] = 5
- config['autoconnect'] = True
- config['no_log_for'] = ''
- config['sync_with_global_status'] = True
- config['proxy'] = ''
- config['usessl'] = False
- config['use_custom_host'] = False
- config['custom_port'] = 0
- config['custom_host'] = ''
- config['keyname'] = ''
- config['keyid'] = ''
- return config
-
- def save_account(self, login, server, savepass, password, anonymous=False):
- if self.account in gajim.connections:
- dialogs.ErrorDialog(_('Account name is in use'),
- _('You already have an account using this name.'))
- return
- con = connection.Connection(self.account)
- con.password = password
-
- config = self.get_config(login, server, savepass, password, anonymous)
-
- if not self.modify:
- con.new_account(self.account, config)
- return
- gajim.connections[self.account] = con
- self.create_vars(config)
-
- def create_vars(self, config):
- gajim.config.add_per('accounts', self.account)
-
- if not config['savepass']:
- config['password'] = ''
-
- for opt in config:
- gajim.config.set_per('accounts', self.account, opt, config[opt])
-
- # update variables
- gajim.interface.instances[self.account] = {'infos': {}, 'disco': {},
- 'gc_config': {}, 'search': {}, 'online_dialog': {}}
- gajim.interface.minimized_controls[self.account] = {}
- gajim.connections[self.account].connected = 0
- gajim.connections[self.account].keepalives = gajim.config.get_per(
- 'accounts', self.account, 'keep_alive_every_foo_secs')
- gajim.groups[self.account] = {}
- gajim.contacts.add_account(self.account)
- gajim.gc_connected[self.account] = {}
- gajim.automatic_rooms[self.account] = {}
- gajim.newly_added[self.account] = []
- gajim.to_be_removed[self.account] = []
- gajim.nicks[self.account] = config['name']
- gajim.block_signed_in_notifications[self.account] = True
- gajim.sleeper_state[self.account] = 'off'
- gajim.encrypted_chats[self.account] = []
- gajim.last_message_time[self.account] = {}
- gajim.status_before_autoaway[self.account] = ''
- gajim.transport_avatar[self.account] = {}
- gajim.gajim_optional_features[self.account] = []
- gajim.caps_hash[self.account] = ''
- # refresh accounts window
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].init_accounts()
- # refresh roster
- if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.interface.roster.set_actions_menu_needs_rebuild()
- gajim.interface.save_config()
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder(
+ 'account_creation_wizard_window.ui')
+ self.window = self.xml.get_object('account_creation_wizard_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+
+ completion = gtk.EntryCompletion()
+ # Connect events from comboboxentry.child
+ server_comboboxentry = self.xml.get_object('server_comboboxentry')
+ entry = server_comboboxentry.child
+ entry.connect('key_press_event',
+ self.on_server_comboboxentry_key_press_event, server_comboboxentry)
+ entry.set_completion(completion)
+ # Do the same for the other server comboboxentry
+ server_comboboxentry1 = self.xml.get_object('server_comboboxentry1')
+ entry = server_comboboxentry1.child
+ entry.connect('key_press_event',
+ self.on_server_comboboxentry_key_press_event, server_comboboxentry1)
+ entry.set_completion(completion)
+
+ self.update_proxy_list()
+
+ # parse servers.xml
+ servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml')
+ servers = gtkgui_helpers.parse_server_xml(servers_xml)
+ servers_model = gtk.ListStore(str, int)
+ for server in servers:
+ if not server[2]['hidden']:
+ servers_model.append((str(server[0]), int(server[1])))
+
+ completion.set_model(servers_model)
+ completion.set_text_column(0)
+
+ # Put servers into comboboxentries
+ server_comboboxentry.set_model(servers_model)
+ server_comboboxentry.set_text_column(0)
+ server_comboboxentry1.set_model(servers_model)
+ server_comboboxentry1.set_text_column(0)
+
+ # Generic widgets
+ self.notebook = self.xml.get_object('notebook')
+ self.cancel_button = self.xml.get_object('cancel_button')
+ self.back_button = self.xml.get_object('back_button')
+ self.forward_button = self.xml.get_object('forward_button')
+ self.finish_button = self.xml.get_object('finish_button')
+ self.advanced_button = self.xml.get_object('advanced_button')
+ self.finish_label = self.xml.get_object('finish_label')
+ self.go_online_checkbutton = self.xml.get_object(
+ 'go_online_checkbutton')
+ self.show_vcard_checkbutton = self.xml.get_object(
+ 'show_vcard_checkbutton')
+ self.progressbar = self.xml.get_object('progressbar')
+
+ # some vars
+ self.update_progressbar_timeout_id = None
+
+ self.notebook.set_current_page(0)
+ self.xml.connect_signals(self)
+ self.window.show_all()
+ gajim.ged.register_event_handler('NEW_ACC_CONNECTED', ged.CORE,
+ self.new_acc_connected)
+ gajim.ged.register_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE,
+ self.new_acc_not_connected)
+ gajim.ged.register_event_handler('ACC_OK', ged.CORE, self.acc_is_ok)
+ gajim.ged.register_event_handler('ACC_NOT_OK', ged.CORE,
+ self.acc_is_not_ok)
+
+ def on_wizard_window_destroy(self, widget):
+ page = self.notebook.get_current_page()
+ if page in (4, 5) and self.account in gajim.connections:
+ # connection instance is saved in gajim.connections and we canceled the
+ # addition of the account
+ del gajim.connections[self.account]
+ if self.account in gajim.config.get_per('accounts'):
+ gajim.config.del_per('accounts', self.account)
+ gajim.ged.remove_event_handler('NEW_ACC_CONNECTED', ged.CORE,
+ self.new_acc_connected)
+ gajim.ged.remove_event_handler('NEW_ACC_NOT_CONNECTED', ged.CORE,
+ self.new_acc_not_connected)
+ gajim.ged.remove_event_handler('ACC_OK', ged.CORE, self.acc_is_ok)
+ gajim.ged.remove_event_handler('ACC_NOT_OK', ged.CORE,
+ self.acc_is_not_ok)
+ del gajim.interface.instances['account_creation_wizard']
+
+ def on_register_server_features_button_clicked(self, widget):
+ helpers.launch_browser_mailer('url',
+ 'http://www.jabber.org/network/oldnetwork.shtml')
+
+ def on_save_password_checkbutton_toggled(self, widget):
+ self.xml.get_object('password_entry').grab_focus()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_back_button_clicked(self, widget):
+ cur_page = self.notebook.get_current_page()
+ if cur_page in (1, 2):
+ self.notebook.set_current_page(0)
+ self.back_button.set_sensitive(False)
+ elif cur_page == 3:
+ self.xml.get_object('form_vbox').remove(self.data_form_widget)
+ self.notebook.set_current_page(2) # show server page
+ elif cur_page == 4:
+ if self.account in gajim.connections:
+ del gajim.connections[self.account]
+ self.notebook.set_current_page(2)
+ self.xml.get_object('form_vbox').remove(self.data_form_widget)
+ elif cur_page == 6: # finish page
+ self.forward_button.show()
+ if self.modify:
+ self.notebook.set_current_page(1) # Go to parameters page
+ else:
+ self.notebook.set_current_page(2) # Go to server page
+
+ def on_anonymous_checkbutton1_toggled(self, widget):
+ active = widget.get_active()
+ self.xml.get_object('username_entry').set_sensitive(not active)
+ self.xml.get_object('password_entry').set_sensitive(not active)
+ self.xml.get_object('save_password_checkbutton').set_sensitive(not active)
+
+ def show_finish_page(self):
+ self.cancel_button.hide()
+ self.back_button.hide()
+ self.forward_button.hide()
+ if self.modify:
+ finish_text = '<big><b>%s</b></big>\n\n%s' % (
+ _('Account has been added successfully'),
+ _('You can set advanced account options by pressing the '
+ 'Advanced button, or later by choosing the Accounts menu item '
+ 'under the Edit menu from the main window.'))
+ else:
+ finish_text = '<big><b>%s</b></big>\n\n%s' % (
+ _('Your new account has been created successfully'),
+ _('You can set advanced account options by pressing the Advanced '
+ 'button, or later by choosing the Accounts menu item under the Edit'
+ ' menu from the main window.'))
+ self.finish_label.set_markup(finish_text)
+ self.finish_button.show()
+ self.finish_button.set_property('has-default', True)
+ self.advanced_button.show()
+ self.go_online_checkbutton.show()
+ img = self.xml.get_object('finish_image')
+ if self.modify:
+ img.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_DIALOG)
+ else:
+ path_to_file = gtkgui_helpers.get_icon_path('gajim', 48)
+ img.set_from_file(path_to_file)
+ self.show_vcard_checkbutton.set_active(not self.modify)
+ self.notebook.set_current_page(6) # show finish page
+
+ def on_forward_button_clicked(self, widget):
+ cur_page = self.notebook.get_current_page()
+
+ if cur_page == 0:
+ widget = self.xml.get_object('use_existing_account_radiobutton')
+ if widget.get_active():
+ self.modify = True
+ self.notebook.set_current_page(1)
+ else:
+ self.modify = False
+ self.notebook.set_current_page(2)
+ self.back_button.set_sensitive(True)
+ return
+
+ elif cur_page == 1:
+ # We are adding an existing account
+ anonymous = self.xml.get_object('anonymous_checkbutton1').get_active()
+ username = self.xml.get_object('username_entry').get_text().decode(
+ 'utf-8').strip()
+ if not username and not anonymous:
+ pritext = _('Invalid username')
+ sectext = _(
+ 'You must provide a username to configure this account.')
+ dialogs.ErrorDialog(pritext, sectext)
+ return
+ server = self.xml.get_object('server_comboboxentry').child.get_text().\
+ decode('utf-8').strip()
+ savepass = self.xml.get_object('save_password_checkbutton').\
+ get_active()
+ password = self.xml.get_object('password_entry').get_text().decode(
+ 'utf-8')
+
+ jid = username + '@' + server
+ # check if jid is conform to RFC and stringprep it
+ try:
+ jid = helpers.parse_jid(jid)
+ except helpers.InvalidFormat, s:
+ pritext = _('Invalid Jabber ID')
+ dialogs.ErrorDialog(pritext, str(s))
+ return
+
+ self.account = server
+ i = 1
+ while self.account in gajim.connections:
+ self.account = server + str(i)
+ i += 1
+
+ username, server = gajim.get_name_and_server_from_jid(jid)
+ if self.xml.get_object('anonymous_checkbutton1').get_active():
+ self.save_account('', server, False, '', anonymous=True)
+ else:
+ self.save_account(username, server, savepass, password)
+ self.show_finish_page()
+ elif cur_page == 2:
+ # We are creating a new account
+ server = self.xml.get_object('server_comboboxentry1').child.get_text()\
+ .decode('utf-8')
+
+ if not server:
+ dialogs.ErrorDialog(_('Invalid server'),
+ _('Please provide a server on which you want to register.'))
+ return
+ self.account = server
+ i = 1
+ while self.account in gajim.connections:
+ self.account = server + str(i)
+ i += 1
+
+ config = self.get_config('', server, '', '')
+ # Get advanced options
+ proxies_combobox = self.xml.get_object('proxies_combobox')
+ active = proxies_combobox.get_active()
+ proxy = proxies_combobox.get_model()[active][0].decode('utf-8')
+ if proxy == _('None'):
+ proxy = ''
+ config['proxy'] = proxy
+
+ config['use_custom_host'] = self.xml.get_object(
+ 'custom_host_port_checkbutton').get_active()
+ custom_port = self.xml.get_object('custom_port_entry').get_text()
+ try:
+ custom_port = int(custom_port)
+ except Exception:
+ dialogs.ErrorDialog(_('Invalid entry'),
+ _('Custom port must be a port number.'))
+ return
+ config['custom_port'] = custom_port
+ config['custom_host'] = self.xml.get_object(
+ 'custom_host_entry').get_text().decode('utf-8')
+
+ if self.xml.get_object('anonymous_checkbutton2').get_active():
+ self.modify = True
+ self.save_account('', server, False, '', anonymous=True)
+ self.show_finish_page()
+ else:
+ self.notebook.set_current_page(5) # show creating page
+ self.back_button.hide()
+ self.forward_button.hide()
+ self.update_progressbar_timeout_id = gobject.timeout_add(100,
+ self.update_progressbar)
+ # Get form from serveur
+ con = connection.Connection(self.account)
+ gajim.connections[self.account] = con
+ con.new_account(self.account, config)
+ elif cur_page == 3:
+ checked = self.xml.get_object('ssl_checkbutton').get_active()
+ if checked:
+ hostname = gajim.connections[self.account].new_account_info[
+ 'hostname']
+ # Check if cert is already in file
+ certs = ''
+ if os.path.isfile(gajim.MY_CACERTS):
+ f = open(gajim.MY_CACERTS)
+ certs = f.read()
+ f.close()
+ if self.ssl_cert in certs:
+ dialogs.ErrorDialog(_('Certificate Already in File'),
+ _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS)
+ else:
+ f = open(gajim.MY_CACERTS, 'a')
+ f.write(hostname + '\n')
+ f.write(self.ssl_cert + '\n\n')
+ f.close()
+ gajim.connections[self.account].new_account_info[
+ 'ssl_fingerprint_sha1'] = self.ssl_fingerprint
+ self.notebook.set_current_page(4) # show fom page
+ elif cur_page == 4:
+ if self.is_form:
+ form = self.data_form_widget.data_form
+ else:
+ form = self.data_form_widget.get_infos()
+ gajim.connections[self.account].send_new_account_infos(form,
+ self.is_form)
+ self.xml.get_object('form_vbox').remove(self.data_form_widget)
+ self.xml.get_object('progressbar_label').set_markup('<b>Account is being created</b>\n\nPlease wait...')
+ self.notebook.set_current_page(5) # show creating page
+ self.back_button.hide()
+ self.forward_button.hide()
+ self.update_progressbar_timeout_id = gobject.timeout_add(100,
+ self.update_progressbar)
+
+ def update_proxy_list(self):
+ proxies_combobox = self.xml.get_object('proxies_combobox')
+ model = gtk.ListStore(str)
+ proxies_combobox.set_model(model)
+ l = gajim.config.get_per('proxies')
+ l.insert(0, _('None'))
+ for i in xrange(len(l)):
+ model.append([l[i]])
+ proxies_combobox.set_active(0)
+
+ def on_manage_proxies_button_clicked(self, widget):
+ if 'manage_proxies' in gajim.interface.instances:
+ gajim.interface.instances['manage_proxies'].window.present()
+ else:
+ gajim.interface.instances['manage_proxies'] = \
+ ManageProxiesWindow()
+
+ def on_custom_host_port_checkbutton_toggled(self, widget):
+ self.xml.get_object('custom_host_hbox').set_sensitive(widget.get_active())
+
+ def update_progressbar(self):
+ self.progressbar.pulse()
+ return True # loop forever
+
+ def new_acc_connected(self, account, array):
+ """
+ Connection to server succeded, present the form to the user
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ form, is_form, ssl_msg, ssl_err, ssl_cert, ssl_fingerprint = array
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ self.back_button.show()
+ self.forward_button.show()
+ self.is_form = is_form
+ if is_form:
+ dataform = dataforms.ExtendForm(node = form)
+ self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
+ else:
+ self.data_form_widget = FakeDataForm(form)
+ self.data_form_widget.show_all()
+ self.xml.get_object('form_vbox').pack_start(self.data_form_widget)
+ self.ssl_fingerprint = ssl_fingerprint
+ self.ssl_cert = ssl_cert
+ if ssl_msg:
+ # An SSL warning occured, show it
+ hostname = gajim.connections[self.account].new_account_info['hostname']
+ self.xml.get_object('ssl_label').set_markup(_('<b>Security Warning</b>'
+ '\n\nThe authenticity of the %(hostname)s SSL certificate could be '
+ 'invalid.\nSSL Error: %(error)s\n'
+ 'Do you still want to connect to this server?') % {
+ 'hostname': hostname, 'error': ssl_msg})
+ if ssl_err in (18, 27):
+ text = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % ssl_fingerprint
+ self.xml.get_object('ssl_checkbutton').set_label(text)
+ else:
+ self.xml.get_object('ssl_checkbutton').set_no_show_all(True)
+ self.xml.get_object('ssl_checkbutton').hide()
+ self.notebook.set_current_page(3) # show SSL page
+ else:
+ self.notebook.set_current_page(4) # show form page
+
+ def new_acc_not_connected(self, account, reason):
+ """
+ Account creation failed: connection to server failed
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ if self.account not in gajim.connections:
+ return
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ del gajim.connections[self.account]
+ if self.account in gajim.config.get_per('accounts'):
+ gajim.config.del_per('accounts', self.account)
+ self.back_button.show()
+ self.cancel_button.show()
+ self.go_online_checkbutton.hide()
+ self.show_vcard_checkbutton.hide()
+ img = self.xml.get_object('finish_image')
+ img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)
+ finish_text = '<big><b>%s</b></big>\n\n%s' % (
+ _('An error occurred during account creation') , reason)
+ self.finish_label.set_markup(finish_text)
+ self.notebook.set_current_page(6) # show finish page
+
+ def acc_is_ok(self, account, config):
+ """
+ Account creation succeeded
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ self.create_vars(config)
+ self.show_finish_page()
+
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+
+ def acc_is_not_ok(self, account, reason):
+ """
+ Account creation failed
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ self.back_button.show()
+ self.cancel_button.show()
+ self.go_online_checkbutton.hide()
+ self.show_vcard_checkbutton.hide()
+ del gajim.connections[self.account]
+ if self.account in gajim.config.get_per('accounts'):
+ gajim.config.del_per('accounts', self.account)
+ img = self.xml.get_object('finish_image')
+ img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)
+ finish_text = '<big><b>%s</b></big>\n\n%s' % (_('An error occurred during '
+ 'account creation') , reason)
+ self.finish_label.set_markup(finish_text)
+ self.notebook.set_current_page(6) # show finish page
+
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+
+ def on_advanced_button_clicked(self, widget):
+ if 'accounts' in gajim.interface.instances:
+ gajim.interface.instances['accounts'].window.present()
+ else:
+ gajim.interface.instances['accounts'] = AccountsWindow()
+ gajim.interface.instances['accounts'].select_account(
+ self.account)
+ self.window.destroy()
+
+ def on_finish_button_clicked(self, widget):
+ go_online = self.xml.get_object('go_online_checkbutton').get_active()
+ show_vcard = self.xml.get_object('show_vcard_checkbutton').get_active()
+ self.window.destroy()
+ if show_vcard:
+ gajim.interface.show_vcard_when_connect.append(self.account)
+ if go_online:
+ gajim.interface.roster.send_status(self.account, 'online', '')
+
+ def on_username_entry_key_press_event(self, widget, event):
+ # Check for pressed @ and jump to combobox if found
+ if event.keyval == gtk.keysyms.at:
+ combobox = self.xml.get_object('server_comboboxentry')
+ combobox.grab_focus()
+ combobox.child.set_position(-1)
+ return True
+
+ def on_server_comboboxentry_key_press_event(self, widget, event, combobox):
+ # If backspace is pressed in empty field, return to the nick entry field
+ backspace = event.keyval == gtk.keysyms.BackSpace
+ empty = len(combobox.get_active_text()) == 0
+ if backspace and empty and self.modify:
+ username_entry = self.xml.get_object('username_entry')
+ username_entry.grab_focus()
+ username_entry.set_position(-1)
+ return True
+
+ def get_config(self, login, server, savepass, password, anonymous=False):
+ config = {}
+ config['name'] = login
+ config['hostname'] = server
+ config['savepass'] = savepass
+ config['password'] = password
+ config['resource'] = 'Gajim'
+ config['anonymous_auth'] = anonymous
+ config['priority'] = 5
+ config['autoconnect'] = True
+ config['no_log_for'] = ''
+ config['sync_with_global_status'] = True
+ config['proxy'] = ''
+ config['usessl'] = False
+ config['use_custom_host'] = False
+ config['custom_port'] = 0
+ config['custom_host'] = ''
+ config['keyname'] = ''
+ config['keyid'] = ''
+ return config
+
+ def save_account(self, login, server, savepass, password, anonymous=False):
+ if self.account in gajim.connections:
+ dialogs.ErrorDialog(_('Account name is in use'),
+ _('You already have an account using this name.'))
+ return
+ con = connection.Connection(self.account)
+ con.password = password
+
+ config = self.get_config(login, server, savepass, password, anonymous)
+
+ if not self.modify:
+ con.new_account(self.account, config)
+ return
+ gajim.connections[self.account] = con
+ self.create_vars(config)
+
+ def create_vars(self, config):
+ gajim.config.add_per('accounts', self.account)
+
+ if not config['savepass']:
+ config['password'] = ''
+
+ for opt in config:
+ gajim.config.set_per('accounts', self.account, opt, config[opt])
+
+ # update variables
+ gajim.interface.instances[self.account] = {'infos': {}, 'disco': {},
+ 'gc_config': {}, 'search': {}, 'online_dialog': {}}
+ gajim.interface.minimized_controls[self.account] = {}
+ gajim.connections[self.account].connected = 0
+ gajim.connections[self.account].keepalives = gajim.config.get_per(
+ 'accounts', self.account, 'keep_alive_every_foo_secs')
+ gajim.groups[self.account] = {}
+ gajim.contacts.add_account(self.account)
+ gajim.gc_connected[self.account] = {}
+ gajim.automatic_rooms[self.account] = {}
+ gajim.newly_added[self.account] = []
+ gajim.to_be_removed[self.account] = []
+ gajim.nicks[self.account] = config['name']
+ gajim.block_signed_in_notifications[self.account] = True
+ gajim.sleeper_state[self.account] = 'off'
+ gajim.encrypted_chats[self.account] = []
+ gajim.last_message_time[self.account] = {}
+ gajim.status_before_autoaway[self.account] = ''
+ gajim.transport_avatar[self.account] = {}
+ gajim.gajim_optional_features[self.account] = []
+ gajim.caps_hash[self.account] = ''
+ # refresh accounts window
+ if 'accounts' in gajim.interface.instances:
+ gajim.interface.instances['accounts'].init_accounts()
+ # refresh roster
+ if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
+ gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
+ else:
+ gajim.interface.roster.regroup = False
+ gajim.interface.roster.setup_and_draw_roster()
+ gajim.interface.roster.set_actions_menu_needs_rebuild()
+ gajim.interface.save_config()
class ManagePEPServicesWindow:
- def __init__(self, account):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_pep_services_window.ui')
- self.window = self.xml.get_object('manage_pep_services_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.xml.get_object('configure_button').set_sensitive(False)
- self.xml.get_object('delete_button').set_sensitive(False)
- self.xml.connect_signals(self)
- self.account = account
-
- self.init_services()
- self.xml.get_object('services_treeview').get_selection().connect(
- 'changed', self.on_services_selection_changed)
- self.window.show_all()
-
- def on_manage_pep_services_window_destroy(self, widget):
- '''close window'''
- del gajim.interface.instances[self.account]['pep_services']
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_services_selection_changed(self, sel):
- self.xml.get_object('configure_button').set_sensitive(True)
- self.xml.get_object('delete_button').set_sensitive(True)
-
- def init_services(self):
- self.treeview = self.xml.get_object('services_treeview')
- # service, access_model, group
- self.treestore = gtk.ListStore(str)
- self.treeview.set_model(self.treestore)
-
- col = gtk.TreeViewColumn('Service')
- self.treeview.append_column(col)
-
- cellrenderer_text = gtk.CellRendererText()
- col.pack_start(cellrenderer_text)
- col.add_attribute(cellrenderer_text, 'text', 0)
-
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].discoverItems(our_jid)
-
- def items_received(self, items):
- our_jid = gajim.get_jid_from_account(self.account)
- for item in items:
- if 'jid' in item and item['jid'] == our_jid and 'node' in item:
- self.treestore.append([item['node']])
-
- def node_removed(self, node):
- model = self.treeview.get_model()
- iter_ = model.get_iter_root()
- while iter_:
- if model[iter_][0] == node:
- model.remove(iter_)
- break
- iter_ = model.get_iter_next(iter_)
-
- def on_delete_button_clicked(self, widget):
- selection = self.treeview.get_selection()
- if not selection:
- return
- model, iter_ = selection.get_selected()
- node = model[iter_][0]
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].send_pb_delete(our_jid, node)
-
- def on_configure_button_clicked(self, widget):
- selection = self.treeview.get_selection()
- if not selection:
- return
- model, iter_ = selection.get_selected()
- node = model[iter_][0]
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].request_pb_configuration(our_jid, node)
-
- def config(self, node, form):
- def on_ok(form, node):
- form.type = 'submit'
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].send_pb_configure(our_jid, node, form)
- window = dialogs.DataFormWindow(form, (on_ok, node))
- title = "Configure %s" % node
- window.set_title(title)
- window.show_all()
+ def __init__(self, account):
+ self.xml = gtkgui_helpers.get_gtk_builder('manage_pep_services_window.ui')
+ self.window = self.xml.get_object('manage_pep_services_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ self.xml.get_object('configure_button').set_sensitive(False)
+ self.xml.get_object('delete_button').set_sensitive(False)
+ self.xml.connect_signals(self)
+ self.account = account
+
+ self.init_services()
+ self.xml.get_object('services_treeview').get_selection().connect(
+ 'changed', self.on_services_selection_changed)
+ self.window.show_all()
+
+ def on_manage_pep_services_window_destroy(self, widget):
+ '''close window'''
+ del gajim.interface.instances[self.account]['pep_services']
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_services_selection_changed(self, sel):
+ self.xml.get_object('configure_button').set_sensitive(True)
+ self.xml.get_object('delete_button').set_sensitive(True)
+
+ def init_services(self):
+ self.treeview = self.xml.get_object('services_treeview')
+ # service, access_model, group
+ self.treestore = gtk.ListStore(str)
+ self.treeview.set_model(self.treestore)
+
+ col = gtk.TreeViewColumn('Service')
+ self.treeview.append_column(col)
+
+ cellrenderer_text = gtk.CellRendererText()
+ col.pack_start(cellrenderer_text)
+ col.add_attribute(cellrenderer_text, 'text', 0)
+
+ our_jid = gajim.get_jid_from_account(self.account)
+ gajim.connections[self.account].discoverItems(our_jid)
+
+ def items_received(self, items):
+ our_jid = gajim.get_jid_from_account(self.account)
+ for item in items:
+ if 'jid' in item and item['jid'] == our_jid and 'node' in item:
+ self.treestore.append([item['node']])
+
+ def node_removed(self, node):
+ model = self.treeview.get_model()
+ iter_ = model.get_iter_root()
+ while iter_:
+ if model[iter_][0] == node:
+ model.remove(iter_)
+ break
+ iter_ = model.get_iter_next(iter_)
+
+ def on_delete_button_clicked(self, widget):
+ selection = self.treeview.get_selection()
+ if not selection:
+ return
+ model, iter_ = selection.get_selected()
+ node = model[iter_][0]
+ our_jid = gajim.get_jid_from_account(self.account)
+ gajim.connections[self.account].send_pb_delete(our_jid, node)
+
+ def on_configure_button_clicked(self, widget):
+ selection = self.treeview.get_selection()
+ if not selection:
+ return
+ model, iter_ = selection.get_selected()
+ node = model[iter_][0]
+ our_jid = gajim.get_jid_from_account(self.account)
+ gajim.connections[self.account].request_pb_configuration(our_jid, node)
+
+ def config(self, node, form):
+ def on_ok(form, node):
+ form.type = 'submit'
+ our_jid = gajim.get_jid_from_account(self.account)
+ gajim.connections[self.account].send_pb_configure(our_jid, node, form)
+ window = dialogs.DataFormWindow(form, (on_ok, node))
+ title = "Configure %s" % node
+ window.set_title(title)
+ window.show_all()
class ManageSoundsWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_sounds_window.ui')
- self.window = self.xml.get_object('manage_sounds_window')
-
- # sounds treeview
- self.sound_tree = self.xml.get_object('sounds_treeview')
-
- # active, event ui name, path to sound file, event_config_name
- model = gtk.ListStore(bool, str, str, str)
- self.sound_tree.set_model(model)
-
- col = gtk.TreeViewColumn(_('Active'))
- self.sound_tree.append_column(col)
- renderer = gtk.CellRendererToggle()
- renderer.set_property('activatable', True)
- renderer.connect('toggled', self.sound_toggled_cb)
- col.pack_start(renderer)
- col.set_attributes(renderer, active = 0)
-
- col = gtk.TreeViewColumn(_('Event'))
- self.sound_tree.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = 1)
-
- self.fill_sound_treeview()
-
- self.xml.connect_signals(self)
-
- self.sound_tree.get_model().connect('row-changed',
- self.on_sounds_treemodel_row_changed)
-
- self.window.show_all()
-
- def on_sounds_treemodel_row_changed(self, model, path, iter_):
- sound_event = model[iter_][3].decode('utf-8')
- gajim.config.set_per('soundevents', sound_event, 'enabled',
- bool(model[path][0]))
- gajim.config.set_per('soundevents', sound_event, 'path',
- model[iter_][2].decode('utf-8'))
- gajim.interface.save_config()
-
- def sound_toggled_cb(self, cell, path):
- model = self.sound_tree.get_model()
- model[path][0] = not model[path][0]
-
- def fill_sound_treeview(self):
- model = self.sound_tree.get_model()
- model.clear()
- model.set_sort_column_id(1, gtk.SORT_ASCENDING)
-
- # NOTE: sounds_ui_names MUST have all items of
- # sounds = gajim.config.get_per('soundevents') as keys
- sounds_dict = {
- 'first_message_received': _('First Message Received'),
- 'next_message_received_focused': _('Next Message Received Focused'),
- 'next_message_received_unfocused':
- _('Next Message Received Unfocused'),
- 'contact_connected': _('Contact Connected'),
- 'contact_disconnected': _('Contact Disconnected'),
- 'message_sent': _('Message Sent'),
- 'muc_message_highlight': _('Group Chat Message Highlight'),
- 'muc_message_received': _('Group Chat Message Received'),
- 'gmail_received': _('GMail Email Received')
- }
-
- for sound_event_config_name, sound_ui_name in sounds_dict.items():
- enabled = gajim.config.get_per('soundevents',
- sound_event_config_name, 'enabled')
- path = gajim.config.get_per('soundevents',
- sound_event_config_name, 'path')
- model.append((enabled, sound_ui_name, path, sound_event_config_name))
-
- def on_treeview_sounds_cursor_changed(self, widget, data = None):
- (model, iter_) = self.sound_tree.get_selection().get_selected()
- sounds_entry = self.xml.get_object('sounds_entry')
- if not iter_:
- sounds_entry.set_text('')
- return
- path_to_snd_file = model[iter_][2]
- sounds_entry.set_text(path_to_snd_file)
-
- def on_browse_for_sounds_button_clicked(self, widget, data = None):
- (model, iter_) = self.sound_tree.get_selection().get_selected()
- if not iter_:
- return
- def on_ok(widget, path_to_snd_file):
- self.dialog.destroy()
- model, iter_ = self.sound_tree.get_selection().get_selected()
- if not path_to_snd_file:
- model[iter_][2] = ''
- self.xml.get_object('sounds_entry').set_text('')
- model[iter_][0] = False
- return
- directory = os.path.dirname(path_to_snd_file)
- gajim.config.set('last_sounds_dir', directory)
- path_to_snd_file = helpers.strip_soundfile_path(path_to_snd_file)
- self.xml.get_object('sounds_entry').set_text(path_to_snd_file)
-
- model[iter_][2] = path_to_snd_file # set new path to sounds_model
- model[iter_][0] = True # set the sound to enabled
-
- def on_cancel(widget):
- self.dialog.destroy()
-
- path_to_snd_file = model[iter_][2].decode('utf-8')
- self.dialog = dialogs.SoundChooserDialog(path_to_snd_file, on_ok,
- on_cancel)
-
- def on_sounds_entry_changed(self, widget):
- path_to_snd_file = widget.get_text()
- model, iter_ = self.sound_tree.get_selection().get_selected()
- model[iter_][2] = path_to_snd_file # set new path to sounds_model
-
- def on_play_button_clicked(self, widget):
- model, iter_ = self.sound_tree.get_selection().get_selected()
- if not iter_:
- return
- snd_event_config_name = model[iter_][3]
- helpers.play_sound(snd_event_config_name)
-
- def on_close_button_clicked(self, widget):
- self.window.hide()
-
- def on_manage_sounds_window_delete_event(self, widget, event):
- self.window.hide()
- return True # do NOT destroy the window
-# vim: se ts=3:
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('manage_sounds_window.ui')
+ self.window = self.xml.get_object('manage_sounds_window')
+
+ # sounds treeview
+ self.sound_tree = self.xml.get_object('sounds_treeview')
+
+ # active, event ui name, path to sound file, event_config_name
+ model = gtk.ListStore(bool, str, str, str)
+ self.sound_tree.set_model(model)
+
+ col = gtk.TreeViewColumn(_('Active'))
+ self.sound_tree.append_column(col)
+ renderer = gtk.CellRendererToggle()
+ renderer.set_property('activatable', True)
+ renderer.connect('toggled', self.sound_toggled_cb)
+ col.pack_start(renderer)
+ col.set_attributes(renderer, active = 0)
+
+ col = gtk.TreeViewColumn(_('Event'))
+ self.sound_tree.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = 1)
+
+ self.fill_sound_treeview()
+
+ self.xml.connect_signals(self)
+
+ self.sound_tree.get_model().connect('row-changed',
+ self.on_sounds_treemodel_row_changed)
+
+ self.window.show_all()
+
+ def on_sounds_treemodel_row_changed(self, model, path, iter_):
+ sound_event = model[iter_][3].decode('utf-8')
+ gajim.config.set_per('soundevents', sound_event, 'enabled',
+ bool(model[path][0]))
+ gajim.config.set_per('soundevents', sound_event, 'path',
+ model[iter_][2].decode('utf-8'))
+ gajim.interface.save_config()
+
+ def sound_toggled_cb(self, cell, path):
+ model = self.sound_tree.get_model()
+ model[path][0] = not model[path][0]
+
+ def fill_sound_treeview(self):
+ model = self.sound_tree.get_model()
+ model.clear()
+ model.set_sort_column_id(1, gtk.SORT_ASCENDING)
+
+ # NOTE: sounds_ui_names MUST have all items of
+ # sounds = gajim.config.get_per('soundevents') as keys
+ sounds_dict = {
+ 'first_message_received': _('First Message Received'),
+ 'next_message_received_focused': _('Next Message Received Focused'),
+ 'next_message_received_unfocused':
+ _('Next Message Received Unfocused'),
+ 'contact_connected': _('Contact Connected'),
+ 'contact_disconnected': _('Contact Disconnected'),
+ 'message_sent': _('Message Sent'),
+ 'muc_message_highlight': _('Group Chat Message Highlight'),
+ 'muc_message_received': _('Group Chat Message Received'),
+ 'gmail_received': _('GMail Email Received')
+ }
+
+ for sound_event_config_name, sound_ui_name in sounds_dict.items():
+ enabled = gajim.config.get_per('soundevents',
+ sound_event_config_name, 'enabled')
+ path = gajim.config.get_per('soundevents',
+ sound_event_config_name, 'path')
+ model.append((enabled, sound_ui_name, path, sound_event_config_name))
+
+ def on_treeview_sounds_cursor_changed(self, widget, data = None):
+ (model, iter_) = self.sound_tree.get_selection().get_selected()
+ sounds_entry = self.xml.get_object('sounds_entry')
+ if not iter_:
+ sounds_entry.set_text('')
+ return
+ path_to_snd_file = model[iter_][2]
+ sounds_entry.set_text(path_to_snd_file)
+
+ def on_browse_for_sounds_button_clicked(self, widget, data = None):
+ (model, iter_) = self.sound_tree.get_selection().get_selected()
+ if not iter_:
+ return
+ def on_ok(widget, path_to_snd_file):
+ self.dialog.destroy()
+ model, iter_ = self.sound_tree.get_selection().get_selected()
+ if not path_to_snd_file:
+ model[iter_][2] = ''
+ self.xml.get_object('sounds_entry').set_text('')
+ model[iter_][0] = False
+ return
+ directory = os.path.dirname(path_to_snd_file)
+ gajim.config.set('last_sounds_dir', directory)
+ path_to_snd_file = helpers.strip_soundfile_path(path_to_snd_file)
+ self.xml.get_object('sounds_entry').set_text(path_to_snd_file)
+
+ model[iter_][2] = path_to_snd_file # set new path to sounds_model
+ model[iter_][0] = True # set the sound to enabled
+
+ def on_cancel(widget):
+ self.dialog.destroy()
+
+ path_to_snd_file = model[iter_][2].decode('utf-8')
+ self.dialog = dialogs.SoundChooserDialog(path_to_snd_file, on_ok,
+ on_cancel)
+
+ def on_sounds_entry_changed(self, widget):
+ path_to_snd_file = widget.get_text()
+ model, iter_ = self.sound_tree.get_selection().get_selected()
+ model[iter_][2] = path_to_snd_file # set new path to sounds_model
+
+ def on_play_button_clicked(self, widget):
+ model, iter_ = self.sound_tree.get_selection().get_selected()
+ if not iter_:
+ return
+ snd_event_config_name = model[iter_][3]
+ helpers.play_sound(snd_event_config_name)
+
+ def on_close_button_clicked(self, widget):
+ self.window.hide()
+
+ def on_manage_sounds_window_delete_event(self, widget, event):
+ self.window.hide()
+ return True # do NOT destroy the window
diff --git a/src/conversation_textview.py b/src/conversation_textview.py
index c94602615..4f80fd6bb 100644
--- a/src/conversation_textview.py
+++ b/src/conversation_textview.py
@@ -56,1297 +56,1295 @@ ALREADY_RECEIVED = 1
SHOWN = 2
def is_selection_modified(mark):
- name = mark.get_name()
- if name and name in ('selection_bound', 'insert'):
- return True
- else:
- return False
+ name = mark.get_name()
+ if name and name in ('selection_bound', 'insert'):
+ return True
+ else:
+ return False
def has_focus(widget):
- return widget.flags() & gtk.HAS_FOCUS == gtk.HAS_FOCUS
+ return widget.flags() & gtk.HAS_FOCUS == gtk.HAS_FOCUS
class TextViewImage(gtk.Image):
- def __init__(self, anchor, text):
- super(TextViewImage, self).__init__()
- self.anchor = anchor
- self._selected = False
- self._disconnect_funcs = []
- self.connect('parent-set', self.on_parent_set)
- self.connect('expose-event', self.on_expose)
- self.set_tooltip_text(text)
- self.anchor.set_data('plaintext', text)
-
- def _get_selected(self):
- parent = self.get_parent()
- if not parent or not self.anchor: return False
- buffer_ = parent.get_buffer()
- position = buffer_.get_iter_at_child_anchor(self.anchor)
- bounds = buffer_.get_selection_bounds()
- if bounds and position.in_range(*bounds):
- return True
- else:
- return False
-
- def get_state(self):
- parent = self.get_parent()
- if not parent:
- return gtk.STATE_NORMAL
- if self._selected:
- if has_focus(parent):
- return gtk.STATE_SELECTED
- else:
- return gtk.STATE_ACTIVE
- else:
- return gtk.STATE_NORMAL
-
- def _update_selected(self):
- selected = self._get_selected()
- if self._selected != selected:
- self._selected = selected
- self.queue_draw()
-
- def _do_connect(self, widget, signal, callback):
- id_ = widget.connect(signal, callback)
- def disconnect():
- widget.disconnect(id_)
- self._disconnect_funcs.append(disconnect)
-
- def _disconnect_signals(self):
- for func in self._disconnect_funcs:
- func()
- self._disconnect_funcs = []
-
- def on_parent_set(self, widget, old_parent):
- parent = self.get_parent()
- if not parent:
- self._disconnect_signals()
- return
-
- self._do_connect(parent, 'style-set', self.do_queue_draw)
- self._do_connect(parent, 'focus-in-event', self.do_queue_draw)
- self._do_connect(parent, 'focus-out-event', self.do_queue_draw)
-
- textbuf = parent.get_buffer()
- self._do_connect(textbuf, 'mark-set', self.on_mark_set)
- self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted)
-
- def do_queue_draw(self, *args):
- self.queue_draw()
- return False
-
- def on_mark_set(self, buf, iterat, mark):
- self.on_mark_modified(mark)
- return False
-
- def on_mark_deleted(self, buf, mark):
- self.on_mark_modified(mark)
- return False
-
- def on_mark_modified(self, mark):
- if is_selection_modified(mark):
- self._update_selected()
-
- def on_expose(self, widget, event):
- state = self.get_state()
- if state != gtk.STATE_NORMAL:
- gc = widget.get_style().base_gc[state]
- area = widget.allocation
- widget.window.draw_rectangle(gc, True, area.x, area.y,
- area.width, area.height)
- return False
+ def __init__(self, anchor, text):
+ super(TextViewImage, self).__init__()
+ self.anchor = anchor
+ self._selected = False
+ self._disconnect_funcs = []
+ self.connect('parent-set', self.on_parent_set)
+ self.connect('expose-event', self.on_expose)
+ self.set_tooltip_text(text)
+ self.anchor.set_data('plaintext', text)
+
+ def _get_selected(self):
+ parent = self.get_parent()
+ if not parent or not self.anchor: return False
+ buffer_ = parent.get_buffer()
+ position = buffer_.get_iter_at_child_anchor(self.anchor)
+ bounds = buffer_.get_selection_bounds()
+ if bounds and position.in_range(*bounds):
+ return True
+ else:
+ return False
+
+ def get_state(self):
+ parent = self.get_parent()
+ if not parent:
+ return gtk.STATE_NORMAL
+ if self._selected:
+ if has_focus(parent):
+ return gtk.STATE_SELECTED
+ else:
+ return gtk.STATE_ACTIVE
+ else:
+ return gtk.STATE_NORMAL
+
+ def _update_selected(self):
+ selected = self._get_selected()
+ if self._selected != selected:
+ self._selected = selected
+ self.queue_draw()
+
+ def _do_connect(self, widget, signal, callback):
+ id_ = widget.connect(signal, callback)
+ def disconnect():
+ widget.disconnect(id_)
+ self._disconnect_funcs.append(disconnect)
+
+ def _disconnect_signals(self):
+ for func in self._disconnect_funcs:
+ func()
+ self._disconnect_funcs = []
+
+ def on_parent_set(self, widget, old_parent):
+ parent = self.get_parent()
+ if not parent:
+ self._disconnect_signals()
+ return
+
+ self._do_connect(parent, 'style-set', self.do_queue_draw)
+ self._do_connect(parent, 'focus-in-event', self.do_queue_draw)
+ self._do_connect(parent, 'focus-out-event', self.do_queue_draw)
+
+ textbuf = parent.get_buffer()
+ self._do_connect(textbuf, 'mark-set', self.on_mark_set)
+ self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted)
+
+ def do_queue_draw(self, *args):
+ self.queue_draw()
+ return False
+
+ def on_mark_set(self, buf, iterat, mark):
+ self.on_mark_modified(mark)
+ return False
+
+ def on_mark_deleted(self, buf, mark):
+ self.on_mark_modified(mark)
+ return False
+
+ def on_mark_modified(self, mark):
+ if is_selection_modified(mark):
+ self._update_selected()
+
+ def on_expose(self, widget, event):
+ state = self.get_state()
+ if state != gtk.STATE_NORMAL:
+ gc = widget.get_style().base_gc[state]
+ area = widget.allocation
+ widget.window.draw_rectangle(gc, True, area.x, area.y,
+ area.width, area.height)
+ return False
class ConversationTextview(gobject.GObject):
- """
- Class for the conversation textview (where user reads already said messages)
- for chat/groupchat windows
- """
- __gsignals__ = dict(
- quote = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
- None, # return value
- (str, ) # arguments
- )
- )
-
- FOCUS_OUT_LINE_PIXBUF = gtkgui_helpers.get_icon_pixmap('gajim-muc_separator')
- XEP0184_WARNING_PIXBUF = gtkgui_helpers.get_icon_pixmap(
- 'gajim-receipt_missing')
-
- # smooth scroll constants
- MAX_SCROLL_TIME = 0.4 # seconds
- SCROLL_DELAY = 33 # milliseconds
-
- def __init__(self, account, used_in_history_window = False):
- """
- If used_in_history_window is True, then we do not show Clear menuitem in
- context menu
- """
- gobject.GObject.__init__(self)
- self.used_in_history_window = used_in_history_window
-
- self.fc = FuzzyClock()
-
-
- # no need to inherit TextView, use it as atrribute is safer
- self.tv = HtmlTextView()
- self.tv.html_hyperlink_handler = self.html_hyperlink_handler
-
- # set properties
- self.tv.set_border_width(1)
- self.tv.set_accepts_tab(True)
- self.tv.set_editable(False)
- self.tv.set_cursor_visible(False)
- self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
- self.tv.set_left_margin(2)
- self.tv.set_right_margin(2)
- self.handlers = {}
- self.images = []
- self.image_cache = {}
- self.xep0184_marks = {}
- self.xep0184_shown = {}
-
- # It's True when we scroll in the code, so we can detect scroll from user
- self.auto_scrolling = False
-
- # connect signals
- id_ = self.tv.connect('motion_notify_event',
- self.on_textview_motion_notify_event)
- self.handlers[id_] = self.tv
- id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup)
- self.handlers[id_] = self.tv
- id_ = self.tv.connect('button_press_event',
- self.on_textview_button_press_event)
- self.handlers[id_] = self.tv
-
- id_ = self.tv.connect('expose-event',
- self.on_textview_expose_event)
- self.handlers[id_] = self.tv
-
-
- self.account = account
- self.change_cursor = False
- self.last_time_printout = 0
-
- font = pango.FontDescription(gajim.config.get('conversation_font'))
- self.tv.modify_font(font)
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- buffer_.create_mark('end', end_iter, False)
-
- self.tagIn = buffer_.create_tag('incoming')
- color = gajim.config.get('inmsgcolor')
- font = pango.FontDescription(gajim.config.get('inmsgfont'))
- self.tagIn.set_property('foreground', color)
- self.tagIn.set_property('font-desc', font)
-
- self.tagOut = buffer_.create_tag('outgoing')
- color = gajim.config.get('outmsgcolor')
- font = pango.FontDescription(gajim.config.get('outmsgfont'))
- self.tagOut.set_property('foreground', color)
- self.tagOut.set_property('font-desc', font)
-
- self.tagStatus = buffer_.create_tag('status')
- color = gajim.config.get('statusmsgcolor')
- font = pango.FontDescription(gajim.config.get('satusmsgfont'))
- self.tagStatus.set_property('foreground', color)
- self.tagStatus.set_property('font-desc', font)
-
- self.tagInText = buffer_.create_tag('incomingtxt')
- color = gajim.config.get('inmsgtxtcolor')
- font = pango.FontDescription(gajim.config.get('inmsgtxtfont'))
- if color:
- self.tagInText.set_property('foreground', color)
- self.tagInText.set_property('font-desc', font)
-
- self.tagOutText = buffer_.create_tag('outgoingtxt')
- color = gajim.config.get('outmsgtxtcolor')
- if color:
- font = pango.FontDescription(gajim.config.get('outmsgtxtfont'))
- self.tagOutText.set_property('foreground', color)
- self.tagOutText.set_property('font-desc', font)
-
- colors = gajim.config.get('gc_nicknames_colors')
- colors = colors.split(':')
- for i,color in enumerate(colors):
- tagname = 'gc_nickname_color_' + str(i)
- tag = buffer_.create_tag(tagname)
- tag.set_property('foreground', color)
-
- tag = buffer_.create_tag('marked')
- color = gajim.config.get('markedmsgcolor')
- tag.set_property('foreground', color)
- tag.set_property('weight', pango.WEIGHT_BOLD)
-
- tag = buffer_.create_tag('time_sometimes')
- tag.set_property('foreground', 'darkgrey')
- tag.set_property('scale', pango.SCALE_SMALL)
- tag.set_property('justification', gtk.JUSTIFY_CENTER)
-
- tag = buffer_.create_tag('small')
- tag.set_property('scale', pango.SCALE_SMALL)
-
- tag = buffer_.create_tag('restored_message')
- color = gajim.config.get('restored_messages_color')
- tag.set_property('foreground', color)
-
- self.tagURL = buffer_.create_tag('url')
- color = gajim.config.get('urlmsgcolor')
- self.tagURL.set_property('foreground', color)
- self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE)
- id_ = self.tagURL.connect('event', self.hyperlink_handler, 'url')
- self.handlers[id_] = self.tagURL
-
- self.tagMail = buffer_.create_tag('mail')
- self.tagMail.set_property('foreground', color)
- self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE)
- id_ = self.tagMail.connect('event', self.hyperlink_handler, 'mail')
- self.handlers[id_] = self.tagMail
-
- self.tagXMPP = buffer_.create_tag('xmpp')
- self.tagXMPP.set_property('foreground', color)
- self.tagXMPP.set_property('underline', pango.UNDERLINE_SINGLE)
- id_ = self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp')
- self.handlers[id_] = self.tagXMPP
-
- self.tagSthAtSth = buffer_.create_tag('sth_at_sth')
- self.tagSthAtSth.set_property('foreground', color)
- self.tagSthAtSth.set_property('underline', pango.UNDERLINE_SINGLE)
- id_ = self.tagSthAtSth.connect('event', self.hyperlink_handler,
- 'sth_at_sth')
- self.handlers[id_] = self.tagSthAtSth
-
- tag = buffer_.create_tag('bold')
- tag.set_property('weight', pango.WEIGHT_BOLD)
-
- tag = buffer_.create_tag('italic')
- tag.set_property('style', pango.STYLE_ITALIC)
-
- tag = buffer_.create_tag('underline')
- tag.set_property('underline', pango.UNDERLINE_SINGLE)
-
- buffer_.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER)
-
- tag = buffer_.create_tag('xep0184-warning')
-
- # One mark at the begining then 2 marks between each lines
- size = gajim.config.get('max_conversation_lines')
- size = 2 * size - 1
- self.marks_queue = Queue.Queue(size)
-
- self.allow_focus_out_line = True
- # holds a mark at the end of --- line
- self.focus_out_end_mark = None
-
- self.xep0184_warning_tooltip = tooltips.BaseTooltip()
-
- self.line_tooltip = tooltips.BaseTooltip()
- # use it for hr too
- self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF
- self.smooth_id = None
-
- def del_handlers(self):
- for i in self.handlers.keys():
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers
- self.tv.destroy()
- #FIXME:
- # self.line_tooltip.destroy()
-
- def update_tags(self):
- self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
- self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor'))
- self.tagStatus.set_property('foreground',
- gajim.config.get('statusmsgcolor'))
- self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor'))
- self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor'))
-
- def at_the_end(self):
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- end_rect = self.tv.get_iter_location(end_iter)
- visible_rect = self.tv.get_visible_rect()
- if end_rect.y <= (visible_rect.y + visible_rect.height):
- return True
- return False
-
- # Smooth scrolling inspired by Pidgin code
- def smooth_scroll(self):
- parent = self.tv.get_parent()
- if not parent:
- return False
- vadj = parent.get_vadjustment()
- max_val = vadj.upper - vadj.page_size + 1
- cur_val = vadj.get_value()
- # scroll by 1/3rd of remaining distance
- onethird = cur_val + ((max_val - cur_val) / 3.0)
- self.auto_scrolling = True
- vadj.set_value(onethird)
- self.auto_scrolling = False
- if max_val - onethird < 0.01:
- self.smooth_id = None
- self.smooth_scroll_timer.cancel()
- return False
- return True
-
- def smooth_scroll_timeout(self):
- gobject.idle_add(self.do_smooth_scroll_timeout)
- return
-
- def do_smooth_scroll_timeout(self):
- if not self.smooth_id:
- # we finished scrolling
- return
- gobject.source_remove(self.smooth_id)
- self.smooth_id = None
- parent = self.tv.get_parent()
- if parent:
- vadj = parent.get_vadjustment()
- self.auto_scrolling = True
- vadj.set_value(vadj.upper - vadj.page_size + 1)
- self.auto_scrolling = False
-
- def smooth_scroll_to_end(self):
- if None != self.smooth_id: # already scrolling
- return False
- self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY,
- self.smooth_scroll)
- self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME,
- self.smooth_scroll_timeout)
- self.smooth_scroll_timer.start()
- return False
-
- def scroll_to_end(self):
- parent = self.tv.get_parent()
- buffer_ = self.tv.get_buffer()
- end_mark = buffer_.get_mark('end')
- if not end_mark:
- return False
- self.auto_scrolling = True
- self.tv.scroll_to_mark(end_mark, 0, True, 0, 1)
- adjustment = parent.get_hadjustment()
- adjustment.set_value(0)
- self.auto_scrolling = False
- return False # when called in an idle_add, just do it once
-
- def bring_scroll_to_end(self, diff_y = 0,
- use_smooth=gajim.config.get('use_smooth_scrolling')):
- ''' scrolls to the end of textview if end is not visible '''
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- end_rect = self.tv.get_iter_location(end_iter)
- visible_rect = self.tv.get_visible_rect()
- # scroll only if expected end is not visible
- if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
- if use_smooth:
- gobject.idle_add(self.smooth_scroll_to_end)
- else:
- gobject.idle_add(self.scroll_to_end_iter)
-
- def scroll_to_end_iter(self):
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- if not end_iter:
- return False
- self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
- return False # when called in an idle_add, just do it once
-
- def stop_scrolling(self):
- if self.smooth_id:
- gobject.source_remove(self.smooth_id)
- self.smooth_id = None
- self.smooth_scroll_timer.cancel()
-
- def show_xep0184_warning(self, id_):
- if id_ in self.xep0184_marks:
- return
-
- buffer_ = self.tv.get_buffer()
- buffer_.begin_user_action()
-
- self.xep0184_marks[id_] = buffer_.create_mark(None,
- buffer_.get_end_iter(), left_gravity=True)
- self.xep0184_shown[id_] = NOT_SHOWN
-
- def show_it():
- if (not id_ in self.xep0184_shown) or \
- self.xep0184_shown[id_] == ALREADY_RECEIVED:
- return False
-
- end_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_])
- buffer_.insert(end_iter, ' ')
- anchor = buffer_.create_child_anchor(end_iter)
- img = TextViewImage(anchor, '')
- img.set_from_pixbuf(ConversationTextview.XEP0184_WARNING_PIXBUF)
- img.show()
- self.tv.add_child_at_anchor(img, anchor)
- before_img_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_])
- before_img_iter.forward_char()
- post_img_iter = before_img_iter.copy()
- post_img_iter.forward_char()
- buffer_.apply_tag_by_name('xep0184-warning', before_img_iter,
- post_img_iter)
-
- self.xep0184_shown[id_] = SHOWN
- return False
- gobject.timeout_add_seconds(3, show_it)
-
- buffer_.end_user_action()
-
- def hide_xep0184_warning(self, id_):
- if id_ not in self.xep0184_marks:
- return
-
- if self.xep0184_shown[id_] == NOT_SHOWN:
- self.xep0184_shown[id_] = ALREADY_RECEIVED
- return
-
- buffer_ = self.tv.get_buffer()
- buffer_.begin_user_action()
-
- begin_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_])
-
- end_iter = begin_iter.copy()
- # XXX: Is there a nicer way?
- end_iter.forward_char()
- end_iter.forward_char()
-
- buffer_.delete(begin_iter, end_iter)
- buffer_.delete_mark(self.xep0184_marks[id_])
-
- buffer_.end_user_action()
-
- del self.xep0184_marks[id_]
- del self.xep0184_shown[id_]
-
- def show_focus_out_line(self):
- if not self.allow_focus_out_line:
- # if room did not receive focus-in from the last time we added
- # --- line then do not readd
- return
-
- print_focus_out_line = False
- buffer_ = self.tv.get_buffer()
-
- if self.focus_out_end_mark is None:
- # this happens only first time we focus out on this room
- print_focus_out_line = True
-
- else:
- focus_out_end_iter = buffer_.get_iter_at_mark(self.focus_out_end_mark)
- focus_out_end_iter_offset = focus_out_end_iter.get_offset()
- if focus_out_end_iter_offset != buffer_.get_end_iter().get_offset():
- # this means after last-focus something was printed
- # (else end_iter's offset is the same as before)
- # only then print ---- line (eg. we avoid printing many following
- # ---- lines)
- print_focus_out_line = True
-
- if print_focus_out_line and buffer_.get_char_count() > 0:
- buffer_.begin_user_action()
-
- # remove previous focus out line if such focus out line exists
- if self.focus_out_end_mark is not None:
- end_iter_for_previous_line = buffer_.get_iter_at_mark(
- self.focus_out_end_mark)
- begin_iter_for_previous_line = end_iter_for_previous_line.copy()
- # img_char+1 (the '\n')
- begin_iter_for_previous_line.backward_chars(2)
-
- # remove focus out line
- buffer_.delete(begin_iter_for_previous_line,
- end_iter_for_previous_line)
- buffer_.delete_mark(self.focus_out_end_mark)
-
- # add the new focus out line
- end_iter = buffer_.get_end_iter()
- buffer_.insert(end_iter, '\n')
- buffer_.insert_pixbuf(end_iter,
- ConversationTextview.FOCUS_OUT_LINE_PIXBUF)
-
- end_iter = buffer_.get_end_iter()
- before_img_iter = end_iter.copy()
- # one char back (an image also takes one char)
- before_img_iter.backward_char()
- buffer_.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
-
- self.allow_focus_out_line = False
-
- # update the iter we hold to make comparison the next time
- self.focus_out_end_mark = buffer_.create_mark(None,
- buffer_.get_end_iter(), left_gravity=True)
-
- buffer_.end_user_action()
-
- # scroll to the end (via idle in case the scrollbar has appeared)
- gobject.idle_add(self.scroll_to_end)
-
- def show_xep0184_warning_tooltip(self):
- pointer = self.tv.get_pointer()
- x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
- pointer[0], pointer[1])
- tags = self.tv.get_iter_at_location(x, y).get_tags()
- tag_table = self.tv.get_buffer().get_tag_table()
- xep0184_warning = False
- for tag in tags:
- if tag == tag_table.lookup('xep0184-warning'):
- xep0184_warning = True
- break
- if xep0184_warning and not self.xep0184_warning_tooltip.win:
- # check if the current pointer is still over the line
- position = self.tv.window.get_origin()
- self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that '
- 'this message has not yet\nbeen received by the remote end. '
- "If this icon stays\nfor a long time, it's likely the message got "
- 'lost.'), 8, position[1] + pointer[1])
-
- def show_line_tooltip(self):
- pointer = self.tv.get_pointer()
- x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
- pointer[0], pointer[1])
- tags = self.tv.get_iter_at_location(x, y).get_tags()
- tag_table = self.tv.get_buffer().get_tag_table()
- over_line = False
- for tag in tags:
- if tag == tag_table.lookup('focus-out-line'):
- over_line = True
- break
- if over_line and not self.line_tooltip.win:
- # check if the current pointer is still over the line
- position = self.tv.window.get_origin()
- self.line_tooltip.show_tooltip(_('Text below this line is what has '
- 'been said since the\nlast time you paid attention to this group '
- 'chat'), 8, position[1] + pointer[1])
-
- def on_textview_expose_event(self, widget, event):
- expalloc = event.area
- exp_x0 = expalloc.x
- exp_y0 = expalloc.y
- exp_x1 = exp_x0 + expalloc.width
- exp_y1 = exp_y0 + expalloc.height
-
- try:
- tryfirst = [self.image_cache[(exp_x0, exp_y0)]]
- except KeyError:
- tryfirst = []
-
- for image in tryfirst + self.images:
- imgalloc = image.allocation
- img_x0 = imgalloc.x
- img_y0 = imgalloc.y
- img_x1 = img_x0 + imgalloc.width
- img_y1 = img_y0 + imgalloc.height
-
- if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \
- exp_x1 <= img_x1 and exp_y1 <= img_y1:
- self.image_cache[(img_x0, img_y0)] = image
- widget.propagate_expose(image, event)
- return True
- return False
-
- def on_textview_motion_notify_event(self, widget, event):
- """
- Change the cursor to a hand when we are over a mail or an url
- """
- pointer_x, pointer_y = self.tv.window.get_pointer()[0:2]
- x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
- pointer_x, pointer_y)
- tags = self.tv.get_iter_at_location(x, y).get_tags()
- if self.change_cursor:
- self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
- gtk.gdk.Cursor(gtk.gdk.XTERM))
- self.change_cursor = False
- tag_table = self.tv.get_buffer().get_tag_table()
- over_line = False
- xep0184_warning = False
- for tag in tags:
- if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \
- tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')):
- self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
- gtk.gdk.Cursor(gtk.gdk.HAND2))
- self.change_cursor = True
- elif tag == tag_table.lookup('focus-out-line'):
- over_line = True
- elif tag == tag_table.lookup('xep0184-warning'):
- xep0184_warning = True
-
- if self.line_tooltip.timeout != 0:
- # Check if we should hide the line tooltip
- if not over_line:
- self.line_tooltip.hide_tooltip()
- if self.xep0184_warning_tooltip.timeout != 0:
- # Check if we should hide the XEP-184 warning tooltip
- if not xep0184_warning:
- self.xep0184_warning_tooltip.hide_tooltip()
- if over_line and not self.line_tooltip.win:
- self.line_tooltip.timeout = gobject.timeout_add(500,
- self.show_line_tooltip)
- self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
- gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
- self.change_cursor = True
- if xep0184_warning and not self.xep0184_warning_tooltip.win:
- self.xep0184_warning_tooltip.timeout = gobject.timeout_add(500,
- self.show_xep0184_warning_tooltip)
- self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
- gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
- self.change_cursor = True
-
- def clear(self, tv = None):
- """
- Clear text in the textview
- """
- buffer_ = self.tv.get_buffer()
- start, end = buffer_.get_bounds()
- buffer_.delete(start, end)
- size = gajim.config.get('max_conversation_lines')
- size = 2 * size - 1
- self.marks_queue = Queue.Queue(size)
- self.focus_out_end_mark = None
-
- def visit_url_from_menuitem(self, widget, link):
- """
- Basically it filters out the widget instance
- """
- helpers.launch_browser_mailer('url', link)
-
- def on_textview_populate_popup(self, textview, menu):
- """
- Override the default context menu and we prepend Clear (only if
- used_in_history_window is False) and if we have sth selected we show a
- submenu with actions on the phrase (see
- on_conversation_textview_button_press_event)
- """
- separator_menuitem_was_added = False
- if not self.used_in_history_window:
- item = gtk.SeparatorMenuItem()
- menu.prepend(item)
- separator_menuitem_was_added = True
-
- item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
- menu.prepend(item)
- id_ = item.connect('activate', self.clear)
- self.handlers[id_] = item
-
- if self.selected_phrase:
- if not separator_menuitem_was_added:
- item = gtk.SeparatorMenuItem()
- menu.prepend(item)
-
- if not self.used_in_history_window:
- item = gtk.MenuItem(_('_Quote'))
- id_ = item.connect('activate', self.on_quote)
- self.handlers[id_] = item
- menu.prepend(item)
-
- _selected_phrase = helpers.reduce_chars_newlines(
- self.selected_phrase, 25, 2)
- item = gtk.MenuItem(_('_Actions for "%s"') % _selected_phrase)
- menu.prepend(item)
- submenu = gtk.Menu()
- item.set_submenu(submenu)
-
- always_use_en = gajim.config.get('always_english_wikipedia')
- if always_use_en:
- link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\
- % self.selected_phrase
- else:
- link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
- % (gajim.LANG, self.selected_phrase)
- item = gtk.MenuItem(_('Read _Wikipedia Article'))
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- submenu.append(item)
-
- item = gtk.MenuItem(_('Look it up in _Dictionary'))
- dict_link = gajim.config.get('dictionary_url')
- if dict_link == 'WIKTIONARY':
- # special link (yeah undocumented but default)
- always_use_en = gajim.config.get('always_english_wiktionary')
- if always_use_en:
- link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\
- % self.selected_phrase
- else:
- link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
- % (gajim.LANG, self.selected_phrase)
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- else:
- if dict_link.find('%s') == -1:
- # we must have %s in the url if not WIKTIONARY
- item = gtk.MenuItem(_(
- 'Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
- item.set_property('sensitive', False)
- else:
- link = dict_link % self.selected_phrase
- id_ = item.connect('activate', self.visit_url_from_menuitem,
- link)
- self.handlers[id_] = item
- submenu.append(item)
-
-
- search_link = gajim.config.get('search_engine')
- if search_link.find('%s') == -1:
- # we must have %s in the url
- item = gtk.MenuItem(_('Web Search URL is missing an "%s"'))
- item.set_property('sensitive', False)
- else:
- item = gtk.MenuItem(_('Web _Search for it'))
- link = search_link % self.selected_phrase
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- submenu.append(item)
-
- item = gtk.MenuItem(_('Open as _Link'))
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- submenu.append(item)
-
- menu.show_all()
-
- def on_quote(self, widget):
- self.emit('quote', self.selected_phrase)
-
- def on_textview_button_press_event(self, widget, event):
- # If we clicked on a taged text do NOT open the standard popup menu
- # if normal text check if we have sth selected
- self.selected_phrase = '' # do not move belove event button check!
-
- if event.button != 3: # if not right click
- return False
-
- x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
- int(event.x), int(event.y))
- iter_ = self.tv.get_iter_at_location(x, y)
- tags = iter_.get_tags()
-
-
- if tags: # we clicked on sth special (it can be status message too)
- for tag in tags:
- tag_name = tag.get_property('name')
- if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'):
- return True # we block normal context menu
-
- # we check if sth was selected and if it was we assign
- # selected_phrase variable
- # so on_conversation_textview_populate_popup can use it
- buffer_ = self.tv.get_buffer()
- return_val = buffer_.get_selection_bounds()
- if return_val: # if sth was selected when we right-clicked
- # get the selected text
- start_sel, finish_sel = return_val[0], return_val[1]
- self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode(
- 'utf-8')
- elif ord(iter_.get_char()) > 31:
- # we clicked on a word, do as if it's selected for context menu
- start_sel = iter_.copy()
- if not start_sel.starts_word():
- start_sel.backward_word_start()
- finish_sel = iter_.copy()
- if not finish_sel.ends_word():
- finish_sel.forward_word_end()
- self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode(
- 'utf-8')
-
- def on_open_link_activate(self, widget, kind, text):
- helpers.launch_browser_mailer(kind, text)
-
- def on_copy_link_activate(self, widget, text):
- clip = gtk.clipboard_get()
- clip.set_text(text)
-
- def on_start_chat_activate(self, widget, jid):
- gajim.interface.new_chat_from_jid(self.account, jid)
-
- def on_join_group_chat_menuitem_activate(self, widget, room_jid):
- if 'join_gc' in gajim.interface.instances[self.account]:
- instance = gajim.interface.instances[self.account]['join_gc']
- instance.xml.get_object('room_jid_entry').set_text(room_jid)
- gajim.interface.instances[self.account]['join_gc'].window.present()
- else:
- try:
- dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid)
- except GajimGeneralException:
- pass
-
- def on_add_to_roster_activate(self, widget, jid):
- dialogs.AddNewContactWindow(self.account, jid)
-
- def make_link_menu(self, event, kind, text):
- xml = gtkgui_helpers.get_gtk_builder('chat_context_menu.ui')
- menu = xml.get_object('chat_context_menu')
- childs = menu.get_children()
- if kind == 'url':
- id_ = childs[0].connect('activate', self.on_copy_link_activate, text)
- self.handlers[id_] = childs[0]
- id_ = childs[1].connect('activate', self.on_open_link_activate, kind,
- text)
- self.handlers[id_] = childs[1]
- childs[2].hide() # copy mail address
- childs[3].hide() # open mail composer
- childs[4].hide() # jid section separator
- childs[5].hide() # start chat
- childs[6].hide() # join group chat
- childs[7].hide() # add to roster
- else: # It's a mail or a JID
- # load muc icon
- join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem')
- muc_icon = gtkgui_helpers.load_icon('muc_active')
- if muc_icon:
- join_group_chat_menuitem.set_image(muc_icon)
-
- text = text.lower()
- if text.startswith('xmpp:'):
- text = text[5:]
- id_ = childs[2].connect('activate', self.on_copy_link_activate, text)
- self.handlers[id_] = childs[2]
- id_ = childs[3].connect('activate', self.on_open_link_activate, kind,
- text)
- self.handlers[id_] = childs[3]
- id_ = childs[5].connect('activate', self.on_start_chat_activate, text)
- self.handlers[id_] = childs[5]
- id_ = childs[6].connect('activate',
- self.on_join_group_chat_menuitem_activate, text)
- self.handlers[id_] = childs[6]
-
- if self.account:
- id_ = childs[7].connect('activate', self.on_add_to_roster_activate,
- text)
- self.handlers[id_] = childs[7]
- childs[7].show() # show add to roster menuitem
- else:
- childs[7].hide() # hide add to roster menuitem
-
- if kind == 'xmpp':
- childs[2].hide() # copy mail address
- childs[3].hide() # open mail composer
- childs[4].hide() # jid section separator
- elif kind == 'mail':
- childs[4].hide() # jid section separator
- childs[5].hide() # start chat
- childs[6].hide() # join group chat
- childs[7].hide() # add to roster
-
- childs[0].hide() # copy link location
- childs[1].hide() # open link in browser
-
- menu.popup(None, None, None, event.button, event.time)
-
- def hyperlink_handler(self, texttag, widget, event, iter_, kind):
- if event.type == gtk.gdk.BUTTON_PRESS:
- begin_iter = iter_.copy()
- # we get the begining of the tag
- while not begin_iter.begins_tag(texttag):
- begin_iter.backward_char()
- end_iter = iter_.copy()
- # we get the end of the tag
- while not end_iter.ends_tag(texttag):
- end_iter.forward_char()
- word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode(
- 'utf-8')
- if event.button == 3: # right click
- self.make_link_menu(event, kind, word)
- else:
- # we launch the correct application
- if kind == 'xmpp':
- word = word[5:]
- if '?' in word:
- (jid, action) = word.split('?')
- if action == 'join':
- self.on_join_group_chat_menuitem_activate(None, jid)
- else:
- self.on_start_chat_activate(None, jid)
- else:
- self.on_start_chat_activate(None, word)
- else:
- helpers.launch_browser_mailer(kind, word)
-
- def html_hyperlink_handler(self, texttag, widget, event, iter_, kind, href):
- if event.type == gtk.gdk.BUTTON_PRESS:
- if event.button == 3: # right click
- self.make_link_menu(event, kind, href)
- return True
- else:
- # we launch the correct application
- helpers.launch_browser_mailer(kind, href)
-
-
- def detect_and_print_special_text(self, otext, other_tags, graphics=True):
- """
- Detect special text (emots & links & formatting), print normal text
- before any special text it founds, then print special text (that happens
- many times until last special text is printed) and then return the index
- after *last* special text, so we can print it in
- print_conversation_line()
- """
- buffer_ = self.tv.get_buffer()
-
- insert_tags_func = buffer_.insert_with_tags_by_name
- # detect_and_print_special_text() is also used by
- # HtmlHandler.handle_specials() and there tags is gtk.TextTag objects,
- # not strings
- if other_tags and isinstance(other_tags[0], gtk.TextTag):
- insert_tags_func = buffer_.insert_with_tags
-
- index = 0
-
- # Too many special elements (emoticons, LaTeX formulas, etc)
- # may cause Gajim to freeze (see #5129).
- # We impose an arbitrary limit of 100 specials per message.
- specials_limit = 100
-
- # basic: links + mail + formatting is always checked (we like that)
- if gajim.config.get('emoticons_theme') and graphics:
- # search for emoticons & urls
- iterator = gajim.interface.emot_and_basic_re.finditer(otext)
- else: # search for just urls + mail + formatting
- iterator = gajim.interface.basic_pattern_re.finditer(otext)
- for match in iterator:
- start, end = match.span()
- special_text = otext[start:end]
- if start > index:
- text_before_special_text = otext[index:start]
- end_iter = buffer_.get_end_iter()
- # we insert normal text
- insert_tags_func(end_iter, text_before_special_text, *other_tags)
- index = end # update index
-
- # now print it
- self.print_special_text(special_text, other_tags, graphics=graphics)
- specials_limit -= 1
- if specials_limit <= 0:
- break
-
- # add the rest of text located in the index and after
- end_iter = buffer_.get_end_iter()
- insert_tags_func(end_iter, otext[index:], *other_tags)
-
- return buffer_.get_end_iter()
-
- def print_special_text(self, special_text, other_tags, graphics=True):
- """
- Is called by detect_and_print_special_text and prints special text
- (emots, links, formatting)
- """
- tags = []
- use_other_tags = True
- text_is_valid_uri = False
- show_ascii_formatting_chars = \
- gajim.config.get('show_ascii_formatting_chars')
- buffer_ = self.tv.get_buffer()
-
- # Check if we accept this as an uri
- schemes = gajim.config.get('uri_schemes').split()
- for scheme in schemes:
- if special_text.startswith(scheme + ':'):
- text_is_valid_uri = True
-
- possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
- if gajim.config.get('emoticons_theme') and \
- possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics:
- # it's an emoticon
- emot_ascii = possible_emot_ascii_caps
- end_iter = buffer_.get_end_iter()
- anchor = buffer_.create_child_anchor(end_iter)
- img = TextViewImage(anchor, special_text)
- animations = gajim.interface.emoticons_animations
- if not emot_ascii in animations:
- animations[emot_ascii] = gtk.gdk.PixbufAnimation(
- gajim.interface.emoticons[emot_ascii])
- img.set_from_animation(animations[emot_ascii])
- img.show()
- self.images.append(img)
- # add with possible animation
- self.tv.add_child_at_anchor(img, anchor)
- elif special_text.startswith('www.') or \
- special_text.startswith('ftp.') or \
- text_is_valid_uri:
- tags.append('url')
- use_other_tags = False
- elif special_text.startswith('mailto:'):
- tags.append('mail')
- use_other_tags = False
- elif special_text.startswith('xmpp:'):
- tags.append('xmpp')
- use_other_tags = False
- elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text):
- # it's a JID or mail
- tags.append('sth_at_sth')
- use_other_tags = False
- elif special_text.startswith('*'): # it's a bold text
- tags.append('bold')
- if special_text[1] == '/' and special_text[-2] == '/' and\
- len(special_text) > 4: # it's also italic
- tags.append('italic')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove */ /*
- elif special_text[1] == '_' and special_text[-2] == '_' and \
- len(special_text) > 4: # it's also underlined
- tags.append('underline')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove *_ _*
- else:
- if not show_ascii_formatting_chars:
- special_text = special_text[1:-1] # remove * *
- elif special_text.startswith('/'): # it's an italic text
- tags.append('italic')
- if special_text[1] == '*' and special_text[-2] == '*' and \
- len(special_text) > 4: # it's also bold
- tags.append('bold')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove /* */
- elif special_text[1] == '_' and special_text[-2] == '_' and \
- len(special_text) > 4: # it's also underlined
- tags.append('underline')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove /_ _/
- else:
- if not show_ascii_formatting_chars:
- special_text = special_text[1:-1] # remove / /
- elif special_text.startswith('_'): # it's an underlined text
- tags.append('underline')
- if special_text[1] == '*' and special_text[-2] == '*' and \
- len(special_text) > 4: # it's also bold
- tags.append('bold')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove _* *_
- elif special_text[1] == '/' and special_text[-2] == '/' and \
- len(special_text) > 4: # it's also italic
- tags.append('italic')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove _/ /_
- else:
- if not show_ascii_formatting_chars:
- special_text = special_text[1:-1] # remove _ _
- elif gajim.HAVE_LATEX and special_text.startswith('$$') and \
- special_text.endswith('$$') and graphics:
- try:
- imagepath = latex.latex_to_image(special_text[2:-2])
- except LatexError, e:
- # print the error after the line has been written
- gobject.idle_add(self.print_conversation_line, str(e), '', 'info',
- '', None)
- imagepath = None
- end_iter = buffer_.get_end_iter()
- if imagepath is not None:
- anchor = buffer_.create_child_anchor(end_iter)
- img = TextViewImage(anchor, special_text)
- img.set_from_file(imagepath)
- img.show()
- # add
- self.tv.add_child_at_anchor(img, anchor)
- # delete old file
- try:
- os.remove(imagepath)
- except Exception:
- pass
- else:
- buffer_.insert(end_iter, special_text)
- use_other_tags = False
- else:
- # It's nothing special
- if use_other_tags:
- end_iter = buffer_.get_end_iter()
- insert_tags_func = buffer_.insert_with_tags_by_name
- if other_tags and isinstance(other_tags[0], gtk.TextTag):
- insert_tags_func = buffer_.insert_with_tags
-
- insert_tags_func(end_iter, special_text, *other_tags)
-
- if tags:
- end_iter = buffer_.get_end_iter()
- all_tags = tags[:]
- if use_other_tags:
- all_tags += other_tags
- # convert all names to TextTag
- ttt = buffer_.get_tag_table()
- all_tags = [(ttt.lookup(t) if isinstance(t, str) else t) for t in all_tags]
- buffer_.insert_with_tags(end_iter, special_text, *all_tags)
-
- def print_empty_line(self):
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol')
-
- def print_conversation_line(self, text, jid, kind, name, tim,
- other_tags_for_name=[], other_tags_for_time=[],
- other_tags_for_text=[], subject=None, old_kind=None, xhtml=None,
- simple=False, graphics=True):
- """
- Print 'chat' type messages
- """
- buffer_ = self.tv.get_buffer()
- buffer_.begin_user_action()
- if self.marks_queue.full():
- # remove oldest line
- m1 = self.marks_queue.get()
- m2 = self.marks_queue.get()
- i1 = buffer_.get_iter_at_mark(m1)
- i2 = buffer_.get_iter_at_mark(m2)
- buffer_.delete(i1, i2)
- buffer_.delete_mark(m1)
- end_iter = buffer_.get_end_iter()
- end_offset = end_iter.get_offset()
- at_the_end = self.at_the_end()
- move_selection = False
- if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\
- get_offset() == end_offset:
- move_selection = True
-
- # Create one mark and add it to queue once if it's the first line
- # else twice (one for end bound, one for start bound)
- mark = None
- if buffer_.get_char_count() > 0:
- if not simple:
- buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol')
- if move_selection:
- sel_start, sel_end = buffer_.get_selection_bounds()
- sel_end.backward_char()
- buffer_.select_range(sel_start, sel_end)
- mark = buffer_.create_mark(None, end_iter, left_gravity=True)
- self.marks_queue.put(mark)
- if not mark:
- mark = buffer_.create_mark(None, end_iter, left_gravity=True)
- self.marks_queue.put(mark)
- if kind == 'incoming_queue':
- kind = 'incoming'
- if old_kind == 'incoming_queue':
- old_kind = 'incoming'
- # print the time stamp
- if not tim:
- # We don't have tim for outgoing messages...
- tim = time.localtime()
- current_print_time = gajim.config.get('print_time')
- if current_print_time == 'always' and kind != 'info' and not simple:
- timestamp_str = self.get_time_to_show(tim)
- timestamp = time.strftime(timestamp_str, tim)
- buffer_.insert_with_tags_by_name(end_iter, timestamp,
- *other_tags_for_time)
- elif current_print_time == 'sometimes' and kind != 'info' and not simple:
- every_foo_seconds = 60 * gajim.config.get(
- 'print_ichat_every_foo_minutes')
- seconds_passed = time.mktime(tim) - self.last_time_printout
- if seconds_passed > every_foo_seconds:
- self.last_time_printout = time.mktime(tim)
- end_iter = buffer_.get_end_iter()
- if gajim.config.get('print_time_fuzzy') > 0:
- ft = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim)
- tim_format = ft.decode(locale.getpreferredencoding())
- else:
- tim_format = self.get_time_to_show(tim)
- buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n',
- 'time_sometimes')
- # kind = info, we print things as if it was a status: same color, ...
- if kind in ('error', 'info'):
- kind = 'status'
- other_text_tag = self.detect_other_text_tag(text, kind)
- text_tags = other_tags_for_text[:] # create a new list
- if other_text_tag:
- # note that color of /me may be overwritten in gc_control
- text_tags.append(other_text_tag)
- else: # not status nor /me
- if gajim.config.get('chat_merge_consecutive_nickname'):
- if kind != old_kind:
- self.print_name(name, kind, other_tags_for_name)
- else:
- self.print_real_text(gajim.config.get(
- 'chat_merge_consecutive_nickname_indent'))
- else:
- self.print_name(name, kind, other_tags_for_name)
- if kind == 'incoming':
- text_tags.append('incomingtxt')
- elif kind == 'outgoing':
- text_tags.append('outgoingtxt')
- self.print_subject(subject)
- self.print_real_text(text, text_tags, name, xhtml, graphics=graphics)
-
- # scroll to the end of the textview
- if at_the_end or kind == 'outgoing':
- # we are at the end or we are sending something
- # scroll to the end (via idle in case the scrollbar has appeared)
- if gajim.config.get('use_smooth_scrolling'):
- gobject.idle_add(self.smooth_scroll_to_end)
- else:
- gobject.idle_add(self.scroll_to_end)
-
- buffer_.end_user_action()
-
- def get_time_to_show(self, tim):
- """
- Get the time, with the day before if needed and return it. It DOESN'T
- format a fuzzy time
- """
- format = ''
- # get difference in days since epoch (86400 = 24*3600)
- # number of days since epoch for current time (in GMT) -
- # number of days since epoch for message (in GMT)
- diff_day = int(timegm(time.localtime())) / 86400 -\
- int(timegm(tim)) / 86400
- if diff_day == 0:
- day_str = ''
- else:
- #%i is day in year (1-365)
- day_str = i18n.ngettext('Yesterday', '%i days ago', diff_day,
- replace_plural=diff_day)
- if day_str:
- format += day_str + ' '
- timestamp_str = gajim.config.get('time_stamp')
- timestamp_str = helpers.from_one_line(timestamp_str)
- format += timestamp_str
- tim_format = time.strftime(format, tim)
- if locale.getpreferredencoding() != 'KOI8-R':
- # if tim_format comes as unicode because of day_str.
- # we convert it to the encoding that we want (and that is utf-8)
- tim_format = helpers.ensure_utf8_string(tim_format)
- return tim_format
-
- def detect_other_text_tag(self, text, kind):
- if kind == 'status':
- return kind
- elif text.startswith('/me ') or text.startswith('/me\n'):
- return kind
-
- def print_name(self, name, kind, other_tags_for_name):
- if name:
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- name_tags = other_tags_for_name[:] # create a new list
- name_tags.append(kind)
- before_str = gajim.config.get('before_nickname')
- before_str = helpers.from_one_line(before_str)
- after_str = gajim.config.get('after_nickname')
- after_str = helpers.from_one_line(after_str)
- format = before_str + name + after_str + ' '
- buffer_.insert_with_tags_by_name(end_iter, format, *name_tags)
-
- def print_subject(self, subject):
- if subject: # if we have subject, show it too!
- subject = _('Subject: %s\n') % subject
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- buffer_.insert(end_iter, subject)
- self.print_empty_line()
-
- def print_real_text(self, text, text_tags=[], name=None, xhtml=None,
- graphics=True):
- """
- Add normal and special text. call this to add text
- """
- if xhtml:
- try:
- if name and (text.startswith('/me ') or text.startswith('/me\n')):
- xhtml = xhtml.replace('/me', '<i>* %s</i>' % (name,), 1)
- self.tv.display_html(xhtml.encode('utf-8'), self)
- return
- except Exception, e:
- gajim.log.debug('Error processing xhtml' + str(e))
- gajim.log.debug('with |' + xhtml + '|')
-
- # /me is replaced by name if name is given
- if name and (text.startswith('/me ') or text.startswith('/me\n')):
- text = '* ' + name + text[3:]
- text_tags.append('italic')
- # detect urls formatting and if the user has it on emoticons
- self.detect_and_print_special_text(text, text_tags, graphics=graphics)
-
-# vim: se ts=3:
+ """
+ Class for the conversation textview (where user reads already said messages)
+ for chat/groupchat windows
+ """
+ __gsignals__ = dict(
+ quote = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+ None, # return value
+ (str, ) # arguments
+ )
+ )
+
+ FOCUS_OUT_LINE_PIXBUF = gtkgui_helpers.get_icon_pixmap('gajim-muc_separator')
+ XEP0184_WARNING_PIXBUF = gtkgui_helpers.get_icon_pixmap(
+ 'gajim-receipt_missing')
+
+ # smooth scroll constants
+ MAX_SCROLL_TIME = 0.4 # seconds
+ SCROLL_DELAY = 33 # milliseconds
+
+ def __init__(self, account, used_in_history_window = False):
+ """
+ If used_in_history_window is True, then we do not show Clear menuitem in
+ context menu
+ """
+ gobject.GObject.__init__(self)
+ self.used_in_history_window = used_in_history_window
+
+ self.fc = FuzzyClock()
+
+
+ # no need to inherit TextView, use it as atrribute is safer
+ self.tv = HtmlTextView()
+ self.tv.html_hyperlink_handler = self.html_hyperlink_handler
+
+ # set properties
+ self.tv.set_border_width(1)
+ self.tv.set_accepts_tab(True)
+ self.tv.set_editable(False)
+ self.tv.set_cursor_visible(False)
+ self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+ self.tv.set_left_margin(2)
+ self.tv.set_right_margin(2)
+ self.handlers = {}
+ self.images = []
+ self.image_cache = {}
+ self.xep0184_marks = {}
+ self.xep0184_shown = {}
+
+ # It's True when we scroll in the code, so we can detect scroll from user
+ self.auto_scrolling = False
+
+ # connect signals
+ id_ = self.tv.connect('motion_notify_event',
+ self.on_textview_motion_notify_event)
+ self.handlers[id_] = self.tv
+ id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup)
+ self.handlers[id_] = self.tv
+ id_ = self.tv.connect('button_press_event',
+ self.on_textview_button_press_event)
+ self.handlers[id_] = self.tv
+
+ id_ = self.tv.connect('expose-event',
+ self.on_textview_expose_event)
+ self.handlers[id_] = self.tv
+
+
+ self.account = account
+ self.change_cursor = False
+ self.last_time_printout = 0
+
+ font = pango.FontDescription(gajim.config.get('conversation_font'))
+ self.tv.modify_font(font)
+ buffer_ = self.tv.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ buffer_.create_mark('end', end_iter, False)
+
+ self.tagIn = buffer_.create_tag('incoming')
+ color = gajim.config.get('inmsgcolor')
+ font = pango.FontDescription(gajim.config.get('inmsgfont'))
+ self.tagIn.set_property('foreground', color)
+ self.tagIn.set_property('font-desc', font)
+
+ self.tagOut = buffer_.create_tag('outgoing')
+ color = gajim.config.get('outmsgcolor')
+ font = pango.FontDescription(gajim.config.get('outmsgfont'))
+ self.tagOut.set_property('foreground', color)
+ self.tagOut.set_property('font-desc', font)
+
+ self.tagStatus = buffer_.create_tag('status')
+ color = gajim.config.get('statusmsgcolor')
+ font = pango.FontDescription(gajim.config.get('satusmsgfont'))
+ self.tagStatus.set_property('foreground', color)
+ self.tagStatus.set_property('font-desc', font)
+
+ self.tagInText = buffer_.create_tag('incomingtxt')
+ color = gajim.config.get('inmsgtxtcolor')
+ font = pango.FontDescription(gajim.config.get('inmsgtxtfont'))
+ if color:
+ self.tagInText.set_property('foreground', color)
+ self.tagInText.set_property('font-desc', font)
+
+ self.tagOutText = buffer_.create_tag('outgoingtxt')
+ color = gajim.config.get('outmsgtxtcolor')
+ if color:
+ font = pango.FontDescription(gajim.config.get('outmsgtxtfont'))
+ self.tagOutText.set_property('foreground', color)
+ self.tagOutText.set_property('font-desc', font)
+
+ colors = gajim.config.get('gc_nicknames_colors')
+ colors = colors.split(':')
+ for i,color in enumerate(colors):
+ tagname = 'gc_nickname_color_' + str(i)
+ tag = buffer_.create_tag(tagname)
+ tag.set_property('foreground', color)
+
+ tag = buffer_.create_tag('marked')
+ color = gajim.config.get('markedmsgcolor')
+ tag.set_property('foreground', color)
+ tag.set_property('weight', pango.WEIGHT_BOLD)
+
+ tag = buffer_.create_tag('time_sometimes')
+ tag.set_property('foreground', 'darkgrey')
+ tag.set_property('scale', pango.SCALE_SMALL)
+ tag.set_property('justification', gtk.JUSTIFY_CENTER)
+
+ tag = buffer_.create_tag('small')
+ tag.set_property('scale', pango.SCALE_SMALL)
+
+ tag = buffer_.create_tag('restored_message')
+ color = gajim.config.get('restored_messages_color')
+ tag.set_property('foreground', color)
+
+ self.tagURL = buffer_.create_tag('url')
+ color = gajim.config.get('urlmsgcolor')
+ self.tagURL.set_property('foreground', color)
+ self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE)
+ id_ = self.tagURL.connect('event', self.hyperlink_handler, 'url')
+ self.handlers[id_] = self.tagURL
+
+ self.tagMail = buffer_.create_tag('mail')
+ self.tagMail.set_property('foreground', color)
+ self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE)
+ id_ = self.tagMail.connect('event', self.hyperlink_handler, 'mail')
+ self.handlers[id_] = self.tagMail
+
+ self.tagXMPP = buffer_.create_tag('xmpp')
+ self.tagXMPP.set_property('foreground', color)
+ self.tagXMPP.set_property('underline', pango.UNDERLINE_SINGLE)
+ id_ = self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp')
+ self.handlers[id_] = self.tagXMPP
+
+ self.tagSthAtSth = buffer_.create_tag('sth_at_sth')
+ self.tagSthAtSth.set_property('foreground', color)
+ self.tagSthAtSth.set_property('underline', pango.UNDERLINE_SINGLE)
+ id_ = self.tagSthAtSth.connect('event', self.hyperlink_handler,
+ 'sth_at_sth')
+ self.handlers[id_] = self.tagSthAtSth
+
+ tag = buffer_.create_tag('bold')
+ tag.set_property('weight', pango.WEIGHT_BOLD)
+
+ tag = buffer_.create_tag('italic')
+ tag.set_property('style', pango.STYLE_ITALIC)
+
+ tag = buffer_.create_tag('underline')
+ tag.set_property('underline', pango.UNDERLINE_SINGLE)
+
+ buffer_.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER)
+
+ tag = buffer_.create_tag('xep0184-warning')
+
+ # One mark at the begining then 2 marks between each lines
+ size = gajim.config.get('max_conversation_lines')
+ size = 2 * size - 1
+ self.marks_queue = Queue.Queue(size)
+
+ self.allow_focus_out_line = True
+ # holds a mark at the end of --- line
+ self.focus_out_end_mark = None
+
+ self.xep0184_warning_tooltip = tooltips.BaseTooltip()
+
+ self.line_tooltip = tooltips.BaseTooltip()
+ # use it for hr too
+ self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF
+ self.smooth_id = None
+
+ def del_handlers(self):
+ for i in self.handlers.keys():
+ if self.handlers[i].handler_is_connected(i):
+ self.handlers[i].disconnect(i)
+ del self.handlers
+ self.tv.destroy()
+ #FIXME:
+ # self.line_tooltip.destroy()
+
+ def update_tags(self):
+ self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
+ self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor'))
+ self.tagStatus.set_property('foreground',
+ gajim.config.get('statusmsgcolor'))
+ self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor'))
+ self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor'))
+
+ def at_the_end(self):
+ buffer_ = self.tv.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ end_rect = self.tv.get_iter_location(end_iter)
+ visible_rect = self.tv.get_visible_rect()
+ if end_rect.y <= (visible_rect.y + visible_rect.height):
+ return True
+ return False
+
+ # Smooth scrolling inspired by Pidgin code
+ def smooth_scroll(self):
+ parent = self.tv.get_parent()
+ if not parent:
+ return False
+ vadj = parent.get_vadjustment()
+ max_val = vadj.upper - vadj.page_size + 1
+ cur_val = vadj.get_value()
+ # scroll by 1/3rd of remaining distance
+ onethird = cur_val + ((max_val - cur_val) / 3.0)
+ self.auto_scrolling = True
+ vadj.set_value(onethird)
+ self.auto_scrolling = False
+ if max_val - onethird < 0.01:
+ self.smooth_id = None
+ self.smooth_scroll_timer.cancel()
+ return False
+ return True
+
+ def smooth_scroll_timeout(self):
+ gobject.idle_add(self.do_smooth_scroll_timeout)
+ return
+
+ def do_smooth_scroll_timeout(self):
+ if not self.smooth_id:
+ # we finished scrolling
+ return
+ gobject.source_remove(self.smooth_id)
+ self.smooth_id = None
+ parent = self.tv.get_parent()
+ if parent:
+ vadj = parent.get_vadjustment()
+ self.auto_scrolling = True
+ vadj.set_value(vadj.upper - vadj.page_size + 1)
+ self.auto_scrolling = False
+
+ def smooth_scroll_to_end(self):
+ if None != self.smooth_id: # already scrolling
+ return False
+ self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY,
+ self.smooth_scroll)
+ self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME,
+ self.smooth_scroll_timeout)
+ self.smooth_scroll_timer.start()
+ return False
+
+ def scroll_to_end(self):
+ parent = self.tv.get_parent()
+ buffer_ = self.tv.get_buffer()
+ end_mark = buffer_.get_mark('end')
+ if not end_mark:
+ return False
+ self.auto_scrolling = True
+ self.tv.scroll_to_mark(end_mark, 0, True, 0, 1)
+ adjustment = parent.get_hadjustment()
+ adjustment.set_value(0)
+ self.auto_scrolling = False
+ return False # when called in an idle_add, just do it once
+
+ def bring_scroll_to_end(self, diff_y = 0,
+ use_smooth=gajim.config.get('use_smooth_scrolling')):
+ ''' scrolls to the end of textview if end is not visible '''
+ buffer_ = self.tv.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ end_rect = self.tv.get_iter_location(end_iter)
+ visible_rect = self.tv.get_visible_rect()
+ # scroll only if expected end is not visible
+ if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
+ if use_smooth:
+ gobject.idle_add(self.smooth_scroll_to_end)
+ else:
+ gobject.idle_add(self.scroll_to_end_iter)
+
+ def scroll_to_end_iter(self):
+ buffer_ = self.tv.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ if not end_iter:
+ return False
+ self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
+ return False # when called in an idle_add, just do it once
+
+ def stop_scrolling(self):
+ if self.smooth_id:
+ gobject.source_remove(self.smooth_id)
+ self.smooth_id = None
+ self.smooth_scroll_timer.cancel()
+
+ def show_xep0184_warning(self, id_):
+ if id_ in self.xep0184_marks:
+ return
+
+ buffer_ = self.tv.get_buffer()
+ buffer_.begin_user_action()
+
+ self.xep0184_marks[id_] = buffer_.create_mark(None,
+ buffer_.get_end_iter(), left_gravity=True)
+ self.xep0184_shown[id_] = NOT_SHOWN
+
+ def show_it():
+ if (not id_ in self.xep0184_shown) or \
+ self.xep0184_shown[id_] == ALREADY_RECEIVED:
+ return False
+
+ end_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_])
+ buffer_.insert(end_iter, ' ')
+ anchor = buffer_.create_child_anchor(end_iter)
+ img = TextViewImage(anchor, '')
+ img.set_from_pixbuf(ConversationTextview.XEP0184_WARNING_PIXBUF)
+ img.show()
+ self.tv.add_child_at_anchor(img, anchor)
+ before_img_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_])
+ before_img_iter.forward_char()
+ post_img_iter = before_img_iter.copy()
+ post_img_iter.forward_char()
+ buffer_.apply_tag_by_name('xep0184-warning', before_img_iter,
+ post_img_iter)
+
+ self.xep0184_shown[id_] = SHOWN
+ return False
+ gobject.timeout_add_seconds(3, show_it)
+
+ buffer_.end_user_action()
+
+ def hide_xep0184_warning(self, id_):
+ if id_ not in self.xep0184_marks:
+ return
+
+ if self.xep0184_shown[id_] == NOT_SHOWN:
+ self.xep0184_shown[id_] = ALREADY_RECEIVED
+ return
+
+ buffer_ = self.tv.get_buffer()
+ buffer_.begin_user_action()
+
+ begin_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_])
+
+ end_iter = begin_iter.copy()
+ # XXX: Is there a nicer way?
+ end_iter.forward_char()
+ end_iter.forward_char()
+
+ buffer_.delete(begin_iter, end_iter)
+ buffer_.delete_mark(self.xep0184_marks[id_])
+
+ buffer_.end_user_action()
+
+ del self.xep0184_marks[id_]
+ del self.xep0184_shown[id_]
+
+ def show_focus_out_line(self):
+ if not self.allow_focus_out_line:
+ # if room did not receive focus-in from the last time we added
+ # --- line then do not readd
+ return
+
+ print_focus_out_line = False
+ buffer_ = self.tv.get_buffer()
+
+ if self.focus_out_end_mark is None:
+ # this happens only first time we focus out on this room
+ print_focus_out_line = True
+
+ else:
+ focus_out_end_iter = buffer_.get_iter_at_mark(self.focus_out_end_mark)
+ focus_out_end_iter_offset = focus_out_end_iter.get_offset()
+ if focus_out_end_iter_offset != buffer_.get_end_iter().get_offset():
+ # this means after last-focus something was printed
+ # (else end_iter's offset is the same as before)
+ # only then print ---- line (eg. we avoid printing many following
+ # ---- lines)
+ print_focus_out_line = True
+
+ if print_focus_out_line and buffer_.get_char_count() > 0:
+ buffer_.begin_user_action()
+
+ # remove previous focus out line if such focus out line exists
+ if self.focus_out_end_mark is not None:
+ end_iter_for_previous_line = buffer_.get_iter_at_mark(
+ self.focus_out_end_mark)
+ begin_iter_for_previous_line = end_iter_for_previous_line.copy()
+ # img_char+1 (the '\n')
+ begin_iter_for_previous_line.backward_chars(2)
+
+ # remove focus out line
+ buffer_.delete(begin_iter_for_previous_line,
+ end_iter_for_previous_line)
+ buffer_.delete_mark(self.focus_out_end_mark)
+
+ # add the new focus out line
+ end_iter = buffer_.get_end_iter()
+ buffer_.insert(end_iter, '\n')
+ buffer_.insert_pixbuf(end_iter,
+ ConversationTextview.FOCUS_OUT_LINE_PIXBUF)
+
+ end_iter = buffer_.get_end_iter()
+ before_img_iter = end_iter.copy()
+ # one char back (an image also takes one char)
+ before_img_iter.backward_char()
+ buffer_.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
+
+ self.allow_focus_out_line = False
+
+ # update the iter we hold to make comparison the next time
+ self.focus_out_end_mark = buffer_.create_mark(None,
+ buffer_.get_end_iter(), left_gravity=True)
+
+ buffer_.end_user_action()
+
+ # scroll to the end (via idle in case the scrollbar has appeared)
+ gobject.idle_add(self.scroll_to_end)
+
+ def show_xep0184_warning_tooltip(self):
+ pointer = self.tv.get_pointer()
+ x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
+ pointer[0], pointer[1])
+ tags = self.tv.get_iter_at_location(x, y).get_tags()
+ tag_table = self.tv.get_buffer().get_tag_table()
+ xep0184_warning = False
+ for tag in tags:
+ if tag == tag_table.lookup('xep0184-warning'):
+ xep0184_warning = True
+ break
+ if xep0184_warning and not self.xep0184_warning_tooltip.win:
+ # check if the current pointer is still over the line
+ position = self.tv.window.get_origin()
+ self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that '
+ 'this message has not yet\nbeen received by the remote end. '
+ "If this icon stays\nfor a long time, it's likely the message got "
+ 'lost.'), 8, position[1] + pointer[1])
+
+ def show_line_tooltip(self):
+ pointer = self.tv.get_pointer()
+ x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
+ pointer[0], pointer[1])
+ tags = self.tv.get_iter_at_location(x, y).get_tags()
+ tag_table = self.tv.get_buffer().get_tag_table()
+ over_line = False
+ for tag in tags:
+ if tag == tag_table.lookup('focus-out-line'):
+ over_line = True
+ break
+ if over_line and not self.line_tooltip.win:
+ # check if the current pointer is still over the line
+ position = self.tv.window.get_origin()
+ self.line_tooltip.show_tooltip(_('Text below this line is what has '
+ 'been said since the\nlast time you paid attention to this group '
+ 'chat'), 8, position[1] + pointer[1])
+
+ def on_textview_expose_event(self, widget, event):
+ expalloc = event.area
+ exp_x0 = expalloc.x
+ exp_y0 = expalloc.y
+ exp_x1 = exp_x0 + expalloc.width
+ exp_y1 = exp_y0 + expalloc.height
+
+ try:
+ tryfirst = [self.image_cache[(exp_x0, exp_y0)]]
+ except KeyError:
+ tryfirst = []
+
+ for image in tryfirst + self.images:
+ imgalloc = image.allocation
+ img_x0 = imgalloc.x
+ img_y0 = imgalloc.y
+ img_x1 = img_x0 + imgalloc.width
+ img_y1 = img_y0 + imgalloc.height
+
+ if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \
+ exp_x1 <= img_x1 and exp_y1 <= img_y1:
+ self.image_cache[(img_x0, img_y0)] = image
+ widget.propagate_expose(image, event)
+ return True
+ return False
+
+ def on_textview_motion_notify_event(self, widget, event):
+ """
+ Change the cursor to a hand when we are over a mail or an url
+ """
+ pointer_x, pointer_y = self.tv.window.get_pointer()[0:2]
+ x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
+ pointer_x, pointer_y)
+ tags = self.tv.get_iter_at_location(x, y).get_tags()
+ if self.change_cursor:
+ self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.XTERM))
+ self.change_cursor = False
+ tag_table = self.tv.get_buffer().get_tag_table()
+ over_line = False
+ xep0184_warning = False
+ for tag in tags:
+ if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \
+ tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')):
+ self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.HAND2))
+ self.change_cursor = True
+ elif tag == tag_table.lookup('focus-out-line'):
+ over_line = True
+ elif tag == tag_table.lookup('xep0184-warning'):
+ xep0184_warning = True
+
+ if self.line_tooltip.timeout != 0:
+ # Check if we should hide the line tooltip
+ if not over_line:
+ self.line_tooltip.hide_tooltip()
+ if self.xep0184_warning_tooltip.timeout != 0:
+ # Check if we should hide the XEP-184 warning tooltip
+ if not xep0184_warning:
+ self.xep0184_warning_tooltip.hide_tooltip()
+ if over_line and not self.line_tooltip.win:
+ self.line_tooltip.timeout = gobject.timeout_add(500,
+ self.show_line_tooltip)
+ self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
+ self.change_cursor = True
+ if xep0184_warning and not self.xep0184_warning_tooltip.win:
+ self.xep0184_warning_tooltip.timeout = gobject.timeout_add(500,
+ self.show_xep0184_warning_tooltip)
+ self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
+ self.change_cursor = True
+
+ def clear(self, tv = None):
+ """
+ Clear text in the textview
+ """
+ buffer_ = self.tv.get_buffer()
+ start, end = buffer_.get_bounds()
+ buffer_.delete(start, end)
+ size = gajim.config.get('max_conversation_lines')
+ size = 2 * size - 1
+ self.marks_queue = Queue.Queue(size)
+ self.focus_out_end_mark = None
+
+ def visit_url_from_menuitem(self, widget, link):
+ """
+ Basically it filters out the widget instance
+ """
+ helpers.launch_browser_mailer('url', link)
+
+ def on_textview_populate_popup(self, textview, menu):
+ """
+ Override the default context menu and we prepend Clear (only if
+ used_in_history_window is False) and if we have sth selected we show a
+ submenu with actions on the phrase (see
+ on_conversation_textview_button_press_event)
+ """
+ separator_menuitem_was_added = False
+ if not self.used_in_history_window:
+ item = gtk.SeparatorMenuItem()
+ menu.prepend(item)
+ separator_menuitem_was_added = True
+
+ item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+ menu.prepend(item)
+ id_ = item.connect('activate', self.clear)
+ self.handlers[id_] = item
+
+ if self.selected_phrase:
+ if not separator_menuitem_was_added:
+ item = gtk.SeparatorMenuItem()
+ menu.prepend(item)
+
+ if not self.used_in_history_window:
+ item = gtk.MenuItem(_('_Quote'))
+ id_ = item.connect('activate', self.on_quote)
+ self.handlers[id_] = item
+ menu.prepend(item)
+
+ _selected_phrase = helpers.reduce_chars_newlines(
+ self.selected_phrase, 25, 2)
+ item = gtk.MenuItem(_('_Actions for "%s"') % _selected_phrase)
+ menu.prepend(item)
+ submenu = gtk.Menu()
+ item.set_submenu(submenu)
+
+ always_use_en = gajim.config.get('always_english_wikipedia')
+ if always_use_en:
+ link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\
+ % self.selected_phrase
+ else:
+ link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
+ % (gajim.LANG, self.selected_phrase)
+ item = gtk.MenuItem(_('Read _Wikipedia Article'))
+ id_ = item.connect('activate', self.visit_url_from_menuitem, link)
+ self.handlers[id_] = item
+ submenu.append(item)
+
+ item = gtk.MenuItem(_('Look it up in _Dictionary'))
+ dict_link = gajim.config.get('dictionary_url')
+ if dict_link == 'WIKTIONARY':
+ # special link (yeah undocumented but default)
+ always_use_en = gajim.config.get('always_english_wiktionary')
+ if always_use_en:
+ link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\
+ % self.selected_phrase
+ else:
+ link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
+ % (gajim.LANG, self.selected_phrase)
+ id_ = item.connect('activate', self.visit_url_from_menuitem, link)
+ self.handlers[id_] = item
+ else:
+ if dict_link.find('%s') == -1:
+ # we must have %s in the url if not WIKTIONARY
+ item = gtk.MenuItem(_(
+ 'Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
+ item.set_property('sensitive', False)
+ else:
+ link = dict_link % self.selected_phrase
+ id_ = item.connect('activate', self.visit_url_from_menuitem,
+ link)
+ self.handlers[id_] = item
+ submenu.append(item)
+
+
+ search_link = gajim.config.get('search_engine')
+ if search_link.find('%s') == -1:
+ # we must have %s in the url
+ item = gtk.MenuItem(_('Web Search URL is missing an "%s"'))
+ item.set_property('sensitive', False)
+ else:
+ item = gtk.MenuItem(_('Web _Search for it'))
+ link = search_link % self.selected_phrase
+ id_ = item.connect('activate', self.visit_url_from_menuitem, link)
+ self.handlers[id_] = item
+ submenu.append(item)
+
+ item = gtk.MenuItem(_('Open as _Link'))
+ id_ = item.connect('activate', self.visit_url_from_menuitem, link)
+ self.handlers[id_] = item
+ submenu.append(item)
+
+ menu.show_all()
+
+ def on_quote(self, widget):
+ self.emit('quote', self.selected_phrase)
+
+ def on_textview_button_press_event(self, widget, event):
+ # If we clicked on a taged text do NOT open the standard popup menu
+ # if normal text check if we have sth selected
+ self.selected_phrase = '' # do not move belove event button check!
+
+ if event.button != 3: # if not right click
+ return False
+
+ x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
+ int(event.x), int(event.y))
+ iter_ = self.tv.get_iter_at_location(x, y)
+ tags = iter_.get_tags()
+
+
+ if tags: # we clicked on sth special (it can be status message too)
+ for tag in tags:
+ tag_name = tag.get_property('name')
+ if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'):
+ return True # we block normal context menu
+
+ # we check if sth was selected and if it was we assign
+ # selected_phrase variable
+ # so on_conversation_textview_populate_popup can use it
+ buffer_ = self.tv.get_buffer()
+ return_val = buffer_.get_selection_bounds()
+ if return_val: # if sth was selected when we right-clicked
+ # get the selected text
+ start_sel, finish_sel = return_val[0], return_val[1]
+ self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode(
+ 'utf-8')
+ elif ord(iter_.get_char()) > 31:
+ # we clicked on a word, do as if it's selected for context menu
+ start_sel = iter_.copy()
+ if not start_sel.starts_word():
+ start_sel.backward_word_start()
+ finish_sel = iter_.copy()
+ if not finish_sel.ends_word():
+ finish_sel.forward_word_end()
+ self.selected_phrase = buffer_.get_text(start_sel, finish_sel).decode(
+ 'utf-8')
+
+ def on_open_link_activate(self, widget, kind, text):
+ helpers.launch_browser_mailer(kind, text)
+
+ def on_copy_link_activate(self, widget, text):
+ clip = gtk.clipboard_get()
+ clip.set_text(text)
+
+ def on_start_chat_activate(self, widget, jid):
+ gajim.interface.new_chat_from_jid(self.account, jid)
+
+ def on_join_group_chat_menuitem_activate(self, widget, room_jid):
+ if 'join_gc' in gajim.interface.instances[self.account]:
+ instance = gajim.interface.instances[self.account]['join_gc']
+ instance.xml.get_object('room_jid_entry').set_text(room_jid)
+ gajim.interface.instances[self.account]['join_gc'].window.present()
+ else:
+ try:
+ dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid)
+ except GajimGeneralException:
+ pass
+
+ def on_add_to_roster_activate(self, widget, jid):
+ dialogs.AddNewContactWindow(self.account, jid)
+
+ def make_link_menu(self, event, kind, text):
+ xml = gtkgui_helpers.get_gtk_builder('chat_context_menu.ui')
+ menu = xml.get_object('chat_context_menu')
+ childs = menu.get_children()
+ if kind == 'url':
+ id_ = childs[0].connect('activate', self.on_copy_link_activate, text)
+ self.handlers[id_] = childs[0]
+ id_ = childs[1].connect('activate', self.on_open_link_activate, kind,
+ text)
+ self.handlers[id_] = childs[1]
+ childs[2].hide() # copy mail address
+ childs[3].hide() # open mail composer
+ childs[4].hide() # jid section separator
+ childs[5].hide() # start chat
+ childs[6].hide() # join group chat
+ childs[7].hide() # add to roster
+ else: # It's a mail or a JID
+ # load muc icon
+ join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem')
+ muc_icon = gtkgui_helpers.load_icon('muc_active')
+ if muc_icon:
+ join_group_chat_menuitem.set_image(muc_icon)
+
+ text = text.lower()
+ if text.startswith('xmpp:'):
+ text = text[5:]
+ id_ = childs[2].connect('activate', self.on_copy_link_activate, text)
+ self.handlers[id_] = childs[2]
+ id_ = childs[3].connect('activate', self.on_open_link_activate, kind,
+ text)
+ self.handlers[id_] = childs[3]
+ id_ = childs[5].connect('activate', self.on_start_chat_activate, text)
+ self.handlers[id_] = childs[5]
+ id_ = childs[6].connect('activate',
+ self.on_join_group_chat_menuitem_activate, text)
+ self.handlers[id_] = childs[6]
+
+ if self.account:
+ id_ = childs[7].connect('activate', self.on_add_to_roster_activate,
+ text)
+ self.handlers[id_] = childs[7]
+ childs[7].show() # show add to roster menuitem
+ else:
+ childs[7].hide() # hide add to roster menuitem
+
+ if kind == 'xmpp':
+ childs[2].hide() # copy mail address
+ childs[3].hide() # open mail composer
+ childs[4].hide() # jid section separator
+ elif kind == 'mail':
+ childs[4].hide() # jid section separator
+ childs[5].hide() # start chat
+ childs[6].hide() # join group chat
+ childs[7].hide() # add to roster
+
+ childs[0].hide() # copy link location
+ childs[1].hide() # open link in browser
+
+ menu.popup(None, None, None, event.button, event.time)
+
+ def hyperlink_handler(self, texttag, widget, event, iter_, kind):
+ if event.type == gtk.gdk.BUTTON_PRESS:
+ begin_iter = iter_.copy()
+ # we get the begining of the tag
+ while not begin_iter.begins_tag(texttag):
+ begin_iter.backward_char()
+ end_iter = iter_.copy()
+ # we get the end of the tag
+ while not end_iter.ends_tag(texttag):
+ end_iter.forward_char()
+ word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode(
+ 'utf-8')
+ if event.button == 3: # right click
+ self.make_link_menu(event, kind, word)
+ else:
+ # we launch the correct application
+ if kind == 'xmpp':
+ word = word[5:]
+ if '?' in word:
+ (jid, action) = word.split('?')
+ if action == 'join':
+ self.on_join_group_chat_menuitem_activate(None, jid)
+ else:
+ self.on_start_chat_activate(None, jid)
+ else:
+ self.on_start_chat_activate(None, word)
+ else:
+ helpers.launch_browser_mailer(kind, word)
+
+ def html_hyperlink_handler(self, texttag, widget, event, iter_, kind, href):
+ if event.type == gtk.gdk.BUTTON_PRESS:
+ if event.button == 3: # right click
+ self.make_link_menu(event, kind, href)
+ return True
+ else:
+ # we launch the correct application
+ helpers.launch_browser_mailer(kind, href)
+
+
+ def detect_and_print_special_text(self, otext, other_tags, graphics=True):
+ """
+ Detect special text (emots & links & formatting), print normal text
+ before any special text it founds, then print special text (that happens
+ many times until last special text is printed) and then return the index
+ after *last* special text, so we can print it in
+ print_conversation_line()
+ """
+ buffer_ = self.tv.get_buffer()
+
+ insert_tags_func = buffer_.insert_with_tags_by_name
+ # detect_and_print_special_text() is also used by
+ # HtmlHandler.handle_specials() and there tags is gtk.TextTag objects,
+ # not strings
+ if other_tags and isinstance(other_tags[0], gtk.TextTag):
+ insert_tags_func = buffer_.insert_with_tags
+
+ index = 0
+
+ # Too many special elements (emoticons, LaTeX formulas, etc)
+ # may cause Gajim to freeze (see #5129).
+ # We impose an arbitrary limit of 100 specials per message.
+ specials_limit = 100
+
+ # basic: links + mail + formatting is always checked (we like that)
+ if gajim.config.get('emoticons_theme') and graphics:
+ # search for emoticons & urls
+ iterator = gajim.interface.emot_and_basic_re.finditer(otext)
+ else: # search for just urls + mail + formatting
+ iterator = gajim.interface.basic_pattern_re.finditer(otext)
+ for match in iterator:
+ start, end = match.span()
+ special_text = otext[start:end]
+ if start > index:
+ text_before_special_text = otext[index:start]
+ end_iter = buffer_.get_end_iter()
+ # we insert normal text
+ insert_tags_func(end_iter, text_before_special_text, *other_tags)
+ index = end # update index
+
+ # now print it
+ self.print_special_text(special_text, other_tags, graphics=graphics)
+ specials_limit -= 1
+ if specials_limit <= 0:
+ break
+
+ # add the rest of text located in the index and after
+ end_iter = buffer_.get_end_iter()
+ insert_tags_func(end_iter, otext[index:], *other_tags)
+
+ return buffer_.get_end_iter()
+
+ def print_special_text(self, special_text, other_tags, graphics=True):
+ """
+ Is called by detect_and_print_special_text and prints special text
+ (emots, links, formatting)
+ """
+ tags = []
+ use_other_tags = True
+ text_is_valid_uri = False
+ show_ascii_formatting_chars = \
+ gajim.config.get('show_ascii_formatting_chars')
+ buffer_ = self.tv.get_buffer()
+
+ # Check if we accept this as an uri
+ schemes = gajim.config.get('uri_schemes').split()
+ for scheme in schemes:
+ if special_text.startswith(scheme + ':'):
+ text_is_valid_uri = True
+
+ possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
+ if gajim.config.get('emoticons_theme') and \
+ possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics:
+ # it's an emoticon
+ emot_ascii = possible_emot_ascii_caps
+ end_iter = buffer_.get_end_iter()
+ anchor = buffer_.create_child_anchor(end_iter)
+ img = TextViewImage(anchor, special_text)
+ animations = gajim.interface.emoticons_animations
+ if not emot_ascii in animations:
+ animations[emot_ascii] = gtk.gdk.PixbufAnimation(
+ gajim.interface.emoticons[emot_ascii])
+ img.set_from_animation(animations[emot_ascii])
+ img.show()
+ self.images.append(img)
+ # add with possible animation
+ self.tv.add_child_at_anchor(img, anchor)
+ elif special_text.startswith('www.') or \
+ special_text.startswith('ftp.') or \
+ text_is_valid_uri:
+ tags.append('url')
+ use_other_tags = False
+ elif special_text.startswith('mailto:'):
+ tags.append('mail')
+ use_other_tags = False
+ elif special_text.startswith('xmpp:'):
+ tags.append('xmpp')
+ use_other_tags = False
+ elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text):
+ # it's a JID or mail
+ tags.append('sth_at_sth')
+ use_other_tags = False
+ elif special_text.startswith('*'): # it's a bold text
+ tags.append('bold')
+ if special_text[1] == '/' and special_text[-2] == '/' and\
+ len(special_text) > 4: # it's also italic
+ tags.append('italic')
+ if not show_ascii_formatting_chars:
+ special_text = special_text[2:-2] # remove */ /*
+ elif special_text[1] == '_' and special_text[-2] == '_' and \
+ len(special_text) > 4: # it's also underlined
+ tags.append('underline')
+ if not show_ascii_formatting_chars:
+ special_text = special_text[2:-2] # remove *_ _*
+ else:
+ if not show_ascii_formatting_chars:
+ special_text = special_text[1:-1] # remove * *
+ elif special_text.startswith('/'): # it's an italic text
+ tags.append('italic')
+ if special_text[1] == '*' and special_text[-2] == '*' and \
+ len(special_text) > 4: # it's also bold
+ tags.append('bold')
+ if not show_ascii_formatting_chars:
+ special_text = special_text[2:-2] # remove /* */
+ elif special_text[1] == '_' and special_text[-2] == '_' and \
+ len(special_text) > 4: # it's also underlined
+ tags.append('underline')
+ if not show_ascii_formatting_chars:
+ special_text = special_text[2:-2] # remove /_ _/
+ else:
+ if not show_ascii_formatting_chars:
+ special_text = special_text[1:-1] # remove / /
+ elif special_text.startswith('_'): # it's an underlined text
+ tags.append('underline')
+ if special_text[1] == '*' and special_text[-2] == '*' and \
+ len(special_text) > 4: # it's also bold
+ tags.append('bold')
+ if not show_ascii_formatting_chars:
+ special_text = special_text[2:-2] # remove _* *_
+ elif special_text[1] == '/' and special_text[-2] == '/' and \
+ len(special_text) > 4: # it's also italic
+ tags.append('italic')
+ if not show_ascii_formatting_chars:
+ special_text = special_text[2:-2] # remove _/ /_
+ else:
+ if not show_ascii_formatting_chars:
+ special_text = special_text[1:-1] # remove _ _
+ elif gajim.HAVE_LATEX and special_text.startswith('$$') and \
+ special_text.endswith('$$') and graphics:
+ try:
+ imagepath = latex.latex_to_image(special_text[2:-2])
+ except LatexError, e:
+ # print the error after the line has been written
+ gobject.idle_add(self.print_conversation_line, str(e), '', 'info',
+ '', None)
+ imagepath = None
+ end_iter = buffer_.get_end_iter()
+ if imagepath is not None:
+ anchor = buffer_.create_child_anchor(end_iter)
+ img = TextViewImage(anchor, special_text)
+ img.set_from_file(imagepath)
+ img.show()
+ # add
+ self.tv.add_child_at_anchor(img, anchor)
+ # delete old file
+ try:
+ os.remove(imagepath)
+ except Exception:
+ pass
+ else:
+ buffer_.insert(end_iter, special_text)
+ use_other_tags = False
+ else:
+ # It's nothing special
+ if use_other_tags:
+ end_iter = buffer_.get_end_iter()
+ insert_tags_func = buffer_.insert_with_tags_by_name
+ if other_tags and isinstance(other_tags[0], gtk.TextTag):
+ insert_tags_func = buffer_.insert_with_tags
+
+ insert_tags_func(end_iter, special_text, *other_tags)
+
+ if tags:
+ end_iter = buffer_.get_end_iter()
+ all_tags = tags[:]
+ if use_other_tags:
+ all_tags += other_tags
+ # convert all names to TextTag
+ ttt = buffer_.get_tag_table()
+ all_tags = [(ttt.lookup(t) if isinstance(t, str) else t) for t in all_tags]
+ buffer_.insert_with_tags(end_iter, special_text, *all_tags)
+
+ def print_empty_line(self):
+ buffer_ = self.tv.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol')
+
+ def print_conversation_line(self, text, jid, kind, name, tim,
+ other_tags_for_name=[], other_tags_for_time=[],
+ other_tags_for_text=[], subject=None, old_kind=None, xhtml=None,
+ simple=False, graphics=True):
+ """
+ Print 'chat' type messages
+ """
+ buffer_ = self.tv.get_buffer()
+ buffer_.begin_user_action()
+ if self.marks_queue.full():
+ # remove oldest line
+ m1 = self.marks_queue.get()
+ m2 = self.marks_queue.get()
+ i1 = buffer_.get_iter_at_mark(m1)
+ i2 = buffer_.get_iter_at_mark(m2)
+ buffer_.delete(i1, i2)
+ buffer_.delete_mark(m1)
+ end_iter = buffer_.get_end_iter()
+ end_offset = end_iter.get_offset()
+ at_the_end = self.at_the_end()
+ move_selection = False
+ if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\
+ get_offset() == end_offset:
+ move_selection = True
+
+ # Create one mark and add it to queue once if it's the first line
+ # else twice (one for end bound, one for start bound)
+ mark = None
+ if buffer_.get_char_count() > 0:
+ if not simple:
+ buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol')
+ if move_selection:
+ sel_start, sel_end = buffer_.get_selection_bounds()
+ sel_end.backward_char()
+ buffer_.select_range(sel_start, sel_end)
+ mark = buffer_.create_mark(None, end_iter, left_gravity=True)
+ self.marks_queue.put(mark)
+ if not mark:
+ mark = buffer_.create_mark(None, end_iter, left_gravity=True)
+ self.marks_queue.put(mark)
+ if kind == 'incoming_queue':
+ kind = 'incoming'
+ if old_kind == 'incoming_queue':
+ old_kind = 'incoming'
+ # print the time stamp
+ if not tim:
+ # We don't have tim for outgoing messages...
+ tim = time.localtime()
+ current_print_time = gajim.config.get('print_time')
+ if current_print_time == 'always' and kind != 'info' and not simple:
+ timestamp_str = self.get_time_to_show(tim)
+ timestamp = time.strftime(timestamp_str, tim)
+ buffer_.insert_with_tags_by_name(end_iter, timestamp,
+ *other_tags_for_time)
+ elif current_print_time == 'sometimes' and kind != 'info' and not simple:
+ every_foo_seconds = 60 * gajim.config.get(
+ 'print_ichat_every_foo_minutes')
+ seconds_passed = time.mktime(tim) - self.last_time_printout
+ if seconds_passed > every_foo_seconds:
+ self.last_time_printout = time.mktime(tim)
+ end_iter = buffer_.get_end_iter()
+ if gajim.config.get('print_time_fuzzy') > 0:
+ ft = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim)
+ tim_format = ft.decode(locale.getpreferredencoding())
+ else:
+ tim_format = self.get_time_to_show(tim)
+ buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n',
+ 'time_sometimes')
+ # kind = info, we print things as if it was a status: same color, ...
+ if kind in ('error', 'info'):
+ kind = 'status'
+ other_text_tag = self.detect_other_text_tag(text, kind)
+ text_tags = other_tags_for_text[:] # create a new list
+ if other_text_tag:
+ # note that color of /me may be overwritten in gc_control
+ text_tags.append(other_text_tag)
+ else: # not status nor /me
+ if gajim.config.get('chat_merge_consecutive_nickname'):
+ if kind != old_kind:
+ self.print_name(name, kind, other_tags_for_name)
+ else:
+ self.print_real_text(gajim.config.get(
+ 'chat_merge_consecutive_nickname_indent'))
+ else:
+ self.print_name(name, kind, other_tags_for_name)
+ if kind == 'incoming':
+ text_tags.append('incomingtxt')
+ elif kind == 'outgoing':
+ text_tags.append('outgoingtxt')
+ self.print_subject(subject)
+ self.print_real_text(text, text_tags, name, xhtml, graphics=graphics)
+
+ # scroll to the end of the textview
+ if at_the_end or kind == 'outgoing':
+ # we are at the end or we are sending something
+ # scroll to the end (via idle in case the scrollbar has appeared)
+ if gajim.config.get('use_smooth_scrolling'):
+ gobject.idle_add(self.smooth_scroll_to_end)
+ else:
+ gobject.idle_add(self.scroll_to_end)
+
+ buffer_.end_user_action()
+
+ def get_time_to_show(self, tim):
+ """
+ Get the time, with the day before if needed and return it. It DOESN'T
+ format a fuzzy time
+ """
+ format = ''
+ # get difference in days since epoch (86400 = 24*3600)
+ # number of days since epoch for current time (in GMT) -
+ # number of days since epoch for message (in GMT)
+ diff_day = int(timegm(time.localtime())) / 86400 -\
+ int(timegm(tim)) / 86400
+ if diff_day == 0:
+ day_str = ''
+ else:
+ #%i is day in year (1-365)
+ day_str = i18n.ngettext('Yesterday', '%i days ago', diff_day,
+ replace_plural=diff_day)
+ if day_str:
+ format += day_str + ' '
+ timestamp_str = gajim.config.get('time_stamp')
+ timestamp_str = helpers.from_one_line(timestamp_str)
+ format += timestamp_str
+ tim_format = time.strftime(format, tim)
+ if locale.getpreferredencoding() != 'KOI8-R':
+ # if tim_format comes as unicode because of day_str.
+ # we convert it to the encoding that we want (and that is utf-8)
+ tim_format = helpers.ensure_utf8_string(tim_format)
+ return tim_format
+
+ def detect_other_text_tag(self, text, kind):
+ if kind == 'status':
+ return kind
+ elif text.startswith('/me ') or text.startswith('/me\n'):
+ return kind
+
+ def print_name(self, name, kind, other_tags_for_name):
+ if name:
+ buffer_ = self.tv.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ name_tags = other_tags_for_name[:] # create a new list
+ name_tags.append(kind)
+ before_str = gajim.config.get('before_nickname')
+ before_str = helpers.from_one_line(before_str)
+ after_str = gajim.config.get('after_nickname')
+ after_str = helpers.from_one_line(after_str)
+ format = before_str + name + after_str + ' '
+ buffer_.insert_with_tags_by_name(end_iter, format, *name_tags)
+
+ def print_subject(self, subject):
+ if subject: # if we have subject, show it too!
+ subject = _('Subject: %s\n') % subject
+ buffer_ = self.tv.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ buffer_.insert(end_iter, subject)
+ self.print_empty_line()
+
+ def print_real_text(self, text, text_tags=[], name=None, xhtml=None,
+ graphics=True):
+ """
+ Add normal and special text. call this to add text
+ """
+ if xhtml:
+ try:
+ if name and (text.startswith('/me ') or text.startswith('/me\n')):
+ xhtml = xhtml.replace('/me', '<i>* %s</i>' % (name,), 1)
+ self.tv.display_html(xhtml.encode('utf-8'), self)
+ return
+ except Exception, e:
+ gajim.log.debug('Error processing xhtml' + str(e))
+ gajim.log.debug('with |' + xhtml + '|')
+
+ # /me is replaced by name if name is given
+ if name and (text.startswith('/me ') or text.startswith('/me\n')):
+ text = '* ' + name + text[3:]
+ text_tags.append('italic')
+ # detect urls formatting and if the user has it on emoticons
+ self.detect_and_print_special_text(text, text_tags, graphics=graphics)
diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py
index 34ae77a68..5fa9a2397 100644
--- a/src/dataforms_widget.py
+++ b/src/dataforms_widget.py
@@ -38,587 +38,585 @@ import itertools
class DataFormWidget(gtk.Alignment, object):
# "public" interface
- """
- Data Form widget. Use like any other widget
- """
-
- def __init__(self, dataformnode=None):
- ''' Create a widget. '''
- gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0)
-
- self._data_form = None
-
- self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui',
- 'data_form_vbox')
- self.xml.connect_signals(self)
- for name in ('instructions_label', 'instructions_hseparator',
- 'single_form_viewport', 'data_form_types_notebook',
- 'single_form_scrolledwindow', 'multiple_form_hbox',
- 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button',
- 'edit_button', 'up_button', 'down_button', 'clear_button'):
- self.__dict__[name] = self.xml.get_object(name)
-
- self.add(self.xml.get_object('data_form_vbox'))
-
- if dataformnode is not None:
- self.set_data_form(dataformnode)
-
- selection = self.records_treeview.get_selection()
- selection.connect('changed', self.on_records_selection_changed)
- selection.set_mode(gtk.SELECTION_MULTIPLE)
-
- def set_data_form(self, dataform):
- """
- Set the data form (xmpp.DataForm) displayed in widget
- """
- assert isinstance(dataform, dataforms.DataForm)
-
- self.del_data_form()
- self._data_form = dataform
- if isinstance(dataform, dataforms.SimpleDataForm):
- self.build_single_data_form()
- else:
- self.build_multiple_data_form()
-
- # create appropriate description for instructions field if there isn't any
- if dataform.instructions == '':
- self.instructions_label.set_no_show_all(True)
- self.instructions_label.hide()
- else:
- self.instructions_label.set_text(dataform.instructions)
- gtkgui_helpers.label_set_autowrap(self.instructions_label)
-
- def get_data_form(self):
- """
- Data form displayed in the widget or None if no form
- """
- return self._data_form
-
- def del_data_form(self):
- self.clean_data_form()
- self._data_form = None
-
- data_form = property(get_data_form, set_data_form, del_data_form,
- 'Data form presented in a widget')
-
- def get_title(self):
- """
- Get the title of data form, as a unicode object. If no title or no form,
- returns u''. Useful for setting window title
- """
- if self._data_form is not None:
- if self._data_form.title is not None:
- return self._data_form.title
- return u''
-
- title = property(get_title, None, None, 'Data form title')
-
- def show(self):
- ''' Treat 'us' as one widget. '''
- self.show_all()
+ """
+ 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)
+
+ self._data_form = None
+
+ self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui',
+ 'data_form_vbox')
+ self.xml.connect_signals(self)
+ for name in ('instructions_label', 'instructions_hseparator',
+ 'single_form_viewport', 'data_form_types_notebook',
+ 'single_form_scrolledwindow', 'multiple_form_hbox',
+ 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button',
+ 'edit_button', 'up_button', 'down_button', 'clear_button'):
+ self.__dict__[name] = self.xml.get_object(name)
+
+ self.add(self.xml.get_object('data_form_vbox'))
+
+ if dataformnode is not None:
+ self.set_data_form(dataformnode)
+
+ selection = self.records_treeview.get_selection()
+ selection.connect('changed', self.on_records_selection_changed)
+ selection.set_mode(gtk.SELECTION_MULTIPLE)
+
+ def set_data_form(self, dataform):
+ """
+ Set the data form (xmpp.DataForm) displayed in widget
+ """
+ assert isinstance(dataform, dataforms.DataForm)
+
+ self.del_data_form()
+ self._data_form = dataform
+ if isinstance(dataform, dataforms.SimpleDataForm):
+ self.build_single_data_form()
+ else:
+ self.build_multiple_data_form()
+
+ # create appropriate description for instructions field if there isn't any
+ if dataform.instructions == '':
+ self.instructions_label.set_no_show_all(True)
+ self.instructions_label.hide()
+ else:
+ self.instructions_label.set_text(dataform.instructions)
+ gtkgui_helpers.label_set_autowrap(self.instructions_label)
+
+ def get_data_form(self):
+ """
+ Data form displayed in the widget or None if no form
+ """
+ return self._data_form
+
+ def del_data_form(self):
+ self.clean_data_form()
+ self._data_form = None
+
+ data_form = property(get_data_form, set_data_form, del_data_form,
+ 'Data form presented in a widget')
+
+ def get_title(self):
+ """
+ Get the title of data form, as a unicode object. If no title or no form,
+ returns u''. Useful for setting window title
+ """
+ if self._data_form is not None:
+ if self._data_form.title is not None:
+ return self._data_form.title
+ return u''
+
+ title = property(get_title, None, None, 'Data form title')
+
+ def show(self):
+ ''' Treat 'us' as one widget. '''
+ self.show_all()
# "private" methods
# we have actually two different kinds of data forms: one is a simple form to fill,
# second is a table with several records;
- def empty_method(self):
- pass
-
- def clean_data_form(self):
- """
- Remove data about existing form. This metod is empty, because it is
- rewritten by build_*_data_form, according to type of form which is
- actually displayed
- """
- pass
-
- def build_single_data_form(self):
- '''Invoked when new single form is to be created.'''
- assert isinstance(self._data_form, dataforms.SimpleDataForm)
-
- self.clean_data_form()
-
- self.singleform = SingleForm(self._data_form)
- self.singleform.show()
- self.single_form_viewport.add(self.singleform)
- self.data_form_types_notebook.set_current_page(
- self.data_form_types_notebook.page_num(
- self.single_form_scrolledwindow))
-
- self.clean_data_form = self.clean_single_data_form
-
- def clean_single_data_form(self):
- """
- Called as clean_data_form, read the docs of clean_data_form(). Remove
- form from widget
- """
- self.singleform.destroy()
- self.clean_data_form = self.empty_method # we won't call it twice
- del self.singleform
-
- def build_multiple_data_form(self):
- """
- Invoked when new multiple form is to be created
- """
- assert isinstance(self._data_form, dataforms.MultipleDataForm)
-
- self.clean_data_form()
-
- # creating model for form...
- fieldtypes = []
- fieldvars = []
- for field in self._data_form.reported.iter_fields():
- # note: we store also text-private and hidden fields,
- # we just do not display them.
- # TODO: boolean fields
- #elif field.type=='boolean': fieldtypes.append(bool)
- fieldtypes.append(str)
- fieldvars.append(field.var)
-
- self.multiplemodel = gtk.ListStore(*fieldtypes)
-
- # moving all data to model
- for item in self._data_form.iter_records():
- iter_ = self.multiplemodel.append()
- for field in item.iter_fields():
- self.multiplemodel.set_value(iter_, fieldvars.index(field.var),
- field.value)
-
- # constructing columns...
- for field, counter in zip(self._data_form.reported.iter_fields(),
- itertools.count()):
- self.records_treeview.append_column(
- gtk.TreeViewColumn(field.label, gtk.CellRendererText(),
- text=counter))
-
- self.records_treeview.set_model(self.multiplemodel)
- self.records_treeview.show_all()
-
- self.data_form_types_notebook.set_current_page(
- self.data_form_types_notebook.page_num(
- self.multiple_form_hbox))
-
- self.clean_data_form = self.clean_multiple_data_form
-
- readwrite = self._data_form.type != 'result'
- if not readwrite:
- self.buttons_vbox.set_no_show_all(True)
- self.buttons_vbox.hide()
- else:
- self.buttons_vbox.set_no_show_all(False)
- # refresh list look
- self.refresh_multiple_buttons()
-
- def clean_multiple_data_form(self):
- """
- Called as clean_data_form, read the docs of clean_data_form(). Remove
- form from widget
- """
- self.clean_data_form = self.empty_method # we won't call it twice
- del self.multiplemodel
-
- def refresh_multiple_buttons(self):
- """
- Checks for treeview state and makes control buttons sensitive
- """
- selection = self.records_treeview.get_selection()
- model = self.records_treeview.get_model()
- count = selection.count_selected_rows()
- if count == 0:
- self.remove_button.set_sensitive(False)
- self.edit_button.set_sensitive(False)
- self.up_button.set_sensitive(False)
- self.down_button.set_sensitive(False)
- elif count == 1:
- self.remove_button.set_sensitive(True)
- self.edit_button.set_sensitive(True)
- _, (path,) = selection.get_selected_rows()
- iter_ = model.get_iter(path)
- if model.iter_next(iter_) is None:
- self.up_button.set_sensitive(True)
- self.down_button.set_sensitive(False)
- elif path == (0, ):
- self.up_button.set_sensitive(False)
- self.down_button.set_sensitive(True)
- else:
- self.up_button.set_sensitive(True)
- self.down_button.set_sensitive(True)
- else:
- self.remove_button.set_sensitive(True)
- self.edit_button.set_sensitive(True)
- self.up_button.set_sensitive(False)
- self.down_button.set_sensitive(False)
-
- if len(model) == 0:
- self.clear_button.set_sensitive(False)
- else:
- self.clear_button.set_sensitive(True)
-
- def on_clear_button_clicked(self, widget):
- self.records_treeview.get_model().clear()
-
- def on_remove_button_clicked(self, widget):
- selection = self.records_treeview.get_selection()
- model, rowrefs = selection.get_selected_rows()
- # rowref is a list of paths
- for i in xrange(len(rowrefs)):
- rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i])
- # rowref is a list of row references; need to convert because we will
- # modify the model, paths would change
- for rowref in rowrefs:
- del model[rowref.get_path()]
-
- def on_up_button_clicked(self, widget):
- selection = self.records_treeview.get_selection()
- model, (path,) = selection.get_selected_rows()
- iter_ = model.get_iter(path)
- # constructing path for previous iter
- previter = model.get_iter((path[0]-1,))
- model.swap(iter_, previter)
-
- self.refresh_multiple_buttons()
-
- def on_down_button_clicked(self, widget):
- selection = self.records_treeview.get_selection()
- model, (path,) = selection.get_selected_rows()
- iter_ = model.get_iter(path)
- nextiter = model.iter_next(iter_)
- model.swap(iter_, nextiter)
-
- self.refresh_multiple_buttons()
-
- def on_records_selection_changed(self, widget):
- self.refresh_multiple_buttons()
+ def empty_method(self):
+ pass
+
+ def clean_data_form(self):
+ """
+ Remove data about existing form. This metod is empty, because it is
+ rewritten by build_*_data_form, according to type of form which is
+ actually displayed
+ """
+ pass
+
+ def build_single_data_form(self):
+ '''Invoked when new single form is to be created.'''
+ assert isinstance(self._data_form, dataforms.SimpleDataForm)
+
+ self.clean_data_form()
+
+ self.singleform = SingleForm(self._data_form)
+ self.singleform.show()
+ self.single_form_viewport.add(self.singleform)
+ self.data_form_types_notebook.set_current_page(
+ self.data_form_types_notebook.page_num(
+ self.single_form_scrolledwindow))
+
+ self.clean_data_form = self.clean_single_data_form
+
+ def clean_single_data_form(self):
+ """
+ Called as clean_data_form, read the docs of clean_data_form(). Remove
+ form from widget
+ """
+ self.singleform.destroy()
+ self.clean_data_form = self.empty_method # we won't call it twice
+ del self.singleform
+
+ def build_multiple_data_form(self):
+ """
+ Invoked when new multiple form is to be created
+ """
+ assert isinstance(self._data_form, dataforms.MultipleDataForm)
+
+ self.clean_data_form()
+
+ # creating model for form...
+ fieldtypes = []
+ fieldvars = []
+ for field in self._data_form.reported.iter_fields():
+ # note: we store also text-private and hidden fields,
+ # we just do not display them.
+ # TODO: boolean fields
+ #elif field.type=='boolean': fieldtypes.append(bool)
+ fieldtypes.append(str)
+ fieldvars.append(field.var)
+
+ self.multiplemodel = gtk.ListStore(*fieldtypes)
+
+ # moving all data to model
+ for item in self._data_form.iter_records():
+ iter_ = self.multiplemodel.append()
+ for field in item.iter_fields():
+ self.multiplemodel.set_value(iter_, fieldvars.index(field.var),
+ field.value)
+
+ # constructing columns...
+ for field, counter in zip(self._data_form.reported.iter_fields(),
+ itertools.count()):
+ self.records_treeview.append_column(
+ gtk.TreeViewColumn(field.label, gtk.CellRendererText(),
+ text=counter))
+
+ self.records_treeview.set_model(self.multiplemodel)
+ self.records_treeview.show_all()
+
+ self.data_form_types_notebook.set_current_page(
+ self.data_form_types_notebook.page_num(
+ self.multiple_form_hbox))
+
+ self.clean_data_form = self.clean_multiple_data_form
+
+ readwrite = self._data_form.type != 'result'
+ if not readwrite:
+ self.buttons_vbox.set_no_show_all(True)
+ self.buttons_vbox.hide()
+ else:
+ self.buttons_vbox.set_no_show_all(False)
+ # refresh list look
+ self.refresh_multiple_buttons()
+
+ def clean_multiple_data_form(self):
+ """
+ Called as clean_data_form, read the docs of clean_data_form(). Remove
+ form from widget
+ """
+ self.clean_data_form = self.empty_method # we won't call it twice
+ del self.multiplemodel
+
+ def refresh_multiple_buttons(self):
+ """
+ Checks for treeview state and makes control buttons sensitive
+ """
+ selection = self.records_treeview.get_selection()
+ model = self.records_treeview.get_model()
+ count = selection.count_selected_rows()
+ if count == 0:
+ self.remove_button.set_sensitive(False)
+ self.edit_button.set_sensitive(False)
+ self.up_button.set_sensitive(False)
+ self.down_button.set_sensitive(False)
+ elif count == 1:
+ self.remove_button.set_sensitive(True)
+ self.edit_button.set_sensitive(True)
+ _, (path,) = selection.get_selected_rows()
+ iter_ = model.get_iter(path)
+ if model.iter_next(iter_) is None:
+ self.up_button.set_sensitive(True)
+ self.down_button.set_sensitive(False)
+ elif path == (0, ):
+ self.up_button.set_sensitive(False)
+ self.down_button.set_sensitive(True)
+ else:
+ self.up_button.set_sensitive(True)
+ self.down_button.set_sensitive(True)
+ else:
+ self.remove_button.set_sensitive(True)
+ self.edit_button.set_sensitive(True)
+ self.up_button.set_sensitive(False)
+ self.down_button.set_sensitive(False)
+
+ if len(model) == 0:
+ self.clear_button.set_sensitive(False)
+ else:
+ self.clear_button.set_sensitive(True)
+
+ def on_clear_button_clicked(self, widget):
+ self.records_treeview.get_model().clear()
+
+ def on_remove_button_clicked(self, widget):
+ selection = self.records_treeview.get_selection()
+ model, rowrefs = selection.get_selected_rows()
+ # rowref is a list of paths
+ for i in xrange(len(rowrefs)):
+ rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i])
+ # rowref is a list of row references; need to convert because we will
+ # modify the model, paths would change
+ for rowref in rowrefs:
+ del model[rowref.get_path()]
+
+ def on_up_button_clicked(self, widget):
+ selection = self.records_treeview.get_selection()
+ model, (path,) = selection.get_selected_rows()
+ iter_ = model.get_iter(path)
+ # constructing path for previous iter
+ previter = model.get_iter((path[0]-1,))
+ model.swap(iter_, previter)
+
+ self.refresh_multiple_buttons()
+
+ def on_down_button_clicked(self, widget):
+ selection = self.records_treeview.get_selection()
+ model, (path,) = selection.get_selected_rows()
+ iter_ = model.get_iter(path)
+ nextiter = model.iter_next(iter_)
+ model.swap(iter_, nextiter)
+
+ self.refresh_multiple_buttons()
+
+ def on_records_selection_changed(self, widget):
+ self.refresh_multiple_buttons()
class SingleForm(gtk.Table, object):
- """
- Widget that represent DATAFORM_SINGLE mode form. Because this is used not
- only to display single forms, but to form input windows of multiple-type
- forms, it is in another class
- """
-
- def __init__(self, dataform):
- assert isinstance(dataform, dataforms.SimpleDataForm)
-
- gtk.Table.__init__(self)
- self.set_col_spacings(12)
- self.set_row_spacings(6)
-
- def decorate_with_tooltip(widget, field):
- """
- Adds a tooltip containing field's description to a widget. Creates
- EventBox if widget doesn't have its own gdk window. Returns decorated
- widget
- """
- if field.description != '':
- if widget.flags() & gtk.NO_WINDOW:
- evbox = gtk.EventBox()
- evbox.add(widget)
- widget = evbox
- widget.set_tooltip_text(field.description)
- return widget
-
- self._data_form = dataform
-
- # building widget
- linecounter = 0
-
- # is the form changeable?
- readwrite = dataform.type != 'result'
-
- # for each field...
- for field in self._data_form.iter_fields():
- if field.type == 'hidden': continue
-
- commonlabel = True
- commonlabelcenter = False
- commonwidget = True
- widget = None
-
- if field.type == 'boolean':
- commonlabelcenter = True
- widget = gtk.CheckButton()
- widget.connect('toggled', self.on_boolean_checkbutton_toggled,
- field)
- widget.set_active(field.value)
-
- elif field.type == 'fixed':
- leftattach = 1
- rightattach = 2
- if field.label is None:
- commonlabel = False
- leftattach = 0
-
- commonwidget = False
- widget = gtk.Label(field.value)
- widget.set_line_wrap(True)
- self.attach(widget, leftattach, rightattach, linecounter,
- linecounter+1, xoptions=gtk.FILL, yoptions=gtk.FILL)
-
- elif field.type == 'list-single':
- # TODO: What if we have radio buttons and non-required field?
- # TODO: We cannot deactivate them all...
- if len(field.options) < 6:
- # 5 option max: show radiobutton
- widget = gtk.VBox()
- first_radio = None
- for value, label in field.iter_options():
- if not label:
- label = value
- radio = gtk.RadioButton(first_radio, label=label)
- radio.connect('toggled',
- self.on_list_single_radiobutton_toggled, field, value)
- if first_radio is None:
- first_radio = radio
- if field.value == '': # TODO: is None when done
- field.value = value
- if value == field.value:
- radio.set_active(True)
- widget.pack_start(radio, expand=False)
- else:
- # more than 5 options: show combobox
- def on_list_single_combobox_changed(combobox, f):
- iter_ = combobox.get_active_iter()
- if iter_:
- model = combobox.get_model()
- f.value = model[iter_][1]
- else:
- f.value = ''
- widget = gtkgui_helpers.create_combobox(field.options,
- field.value)
- widget.connect('changed', on_list_single_combobox_changed, field)
- widget.set_sensitive(readwrite)
-
- elif field.type == 'list-multi':
- # TODO: When more than few choices, make a list
- if len(field.options) < 6:
- # 5 option max: show checkbutton
- widget = gtk.VBox()
- for value, label in field.iter_options():
- check = gtk.CheckButton(label, use_underline=False)
- check.set_active(value in field.values)
- check.connect('toggled',
- self.on_list_multi_checkbutton_toggled, field, value)
- widget.pack_start(check, expand=False)
- else:
- # more than 5 options: show combobox
- def on_list_multi_treeview_changed(selection, f):
- def for_selected(treemodel, path, iter):
- vals.append(treemodel[iter][1])
- vals = []
- selection.selected_foreach(for_selected)
- field.values = vals[:]
- widget = gtk.ScrolledWindow()
- widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- tv = gtkgui_helpers.create_list_multi(field.options,
- field.values)
- widget.add(tv)
- widget.set_size_request(-1, 120)
- tv.get_selection().connect('changed',
- on_list_multi_treeview_changed, field)
- widget.set_sensitive(readwrite)
-
- elif field.type == 'jid-single':
- widget = gtk.Entry()
- widget.connect('changed', self.on_text_single_entry_changed, field)
- widget.set_text(field.value)
-
- elif field.type == 'jid-multi':
- commonwidget = False
-
- xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui',
- 'item_list_table')
- widget = xml.get_object('item_list_table')
- treeview = xml.get_object('item_treeview')
-
- listmodel = gtk.ListStore(str)
- for value in field.iter_values():
- # nobody will create several megabytes long stanza
- listmodel.insert(999999, (value,))
-
- treeview.set_model(listmodel)
-
- renderer = gtk.CellRendererText()
- renderer.set_property('editable', True)
- renderer.connect('edited',
- self.on_jid_multi_cellrenderertext_edited, treeview, listmodel,
- field)
-
- treeview.append_column(gtk.TreeViewColumn(None, renderer,
- text=0))
-
- decorate_with_tooltip(treeview, field)
-
- add_button=xml.get_object('add_button')
- add_button.connect('clicked',
- self.on_jid_multi_add_button_clicked, treeview, listmodel, field)
- edit_button=xml.get_object('edit_button')
- edit_button.connect('clicked',
- self.on_jid_multi_edit_button_clicked, treeview)
- remove_button=xml.get_object('remove_button')
- remove_button.connect('clicked',
- self.on_jid_multi_remove_button_clicked, treeview, field)
- clear_button=xml.get_object('clear_button')
- clear_button.connect('clicked',
- self.on_jid_multi_clean_button_clicked, listmodel, field)
- if not readwrite:
- add_button.set_no_show_all(True)
- edit_button.set_no_show_all(True)
- remove_button.set_no_show_all(True)
- clear_button.set_no_show_all(True)
-
- widget.set_sensitive(readwrite)
- self.attach(widget, 1, 2, linecounter, linecounter+1)
-
- del xml
-
- elif field.type == 'text-private':
- commonlabelcenter = True
- widget = gtk.Entry()
- widget.connect('changed', self.on_text_single_entry_changed, field)
- widget.set_visibility(False)
- widget.set_text(field.value)
-
- elif field.type == 'text-multi':
- # TODO: bigger text view
- commonwidget = False
-
- textwidget = gtk.TextView()
- textwidget.set_wrap_mode(gtk.WRAP_WORD)
- textwidget.get_buffer().connect('changed',
- self.on_text_multi_textbuffer_changed, field)
- textwidget.get_buffer().set_text(field.value)
-
- widget = gtk.ScrolledWindow()
- widget.add(textwidget)
-
- widget.set_sensitive(readwrite)
- widget=decorate_with_tooltip(widget, field)
- self.attach(widget, 1, 2, linecounter, linecounter+1)
-
- else:
- # field.type == 'text-single' or field.type is nonstandard:
- # JEP says that if we don't understand some type, we
- # should handle it as text-single
- commonlabelcenter = True
- if readwrite:
- widget = gtk.Entry()
- widget.connect('changed', self.on_text_single_entry_changed,
- field)
- widget.set_sensitive(readwrite)
- if field.value is None:
- field.value = u''
- widget.set_text(field.value)
- else:
- commonwidget=False
- widget = gtk.Label(field.value)
- widget.set_sensitive(True)
- widget.set_alignment(0.0, 0.5)
- widget=decorate_with_tooltip(widget, field)
- self.attach(widget, 1, 2, linecounter, linecounter+1,
- yoptions=gtk.FILL)
-
- if commonlabel and field.label is not None:
- label = gtk.Label(field.label)
- if commonlabelcenter:
- label.set_alignment(0.0, 0.5)
- else:
- label.set_alignment(0.0, 0.0)
- label = decorate_with_tooltip(label, field)
- self.attach(label, 0, 1, linecounter, linecounter+1,
- xoptions=gtk.FILL, yoptions=gtk.FILL)
-
- if commonwidget:
- assert widget is not None
- widget.set_sensitive(readwrite)
- widget = decorate_with_tooltip(widget, field)
- self.attach(widget, 1, 2, linecounter, linecounter+1,
- yoptions=gtk.FILL)
- widget.show_all()
-
- linecounter+=1
- if self.get_property('visible'):
- self.show_all()
-
- def show(self):
- # simulate that we are one widget
- self.show_all()
-
- def on_boolean_checkbutton_toggled(self, widget, field):
- field.value = widget.get_active()
-
- def on_list_single_radiobutton_toggled(self, widget, field, value):
- field.value = value
-
- def on_list_multi_checkbutton_toggled(self, widget, field, value):
- # TODO: make some methods like add_value and remove_value
- if widget.get_active() and value not in field.values:
- field.values += [value]
- elif not widget.get_active() and value in field.values:
- field.values = [v for v in field.values if v!=value]
-
- def on_text_single_entry_changed(self, widget, field):
- field.value = widget.get_text()
-
- def on_text_multi_textbuffer_changed(self, widget, field):
- field.value = widget.get_text(
- widget.get_start_iter(),
- widget.get_end_iter())
-
- def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, treeview,
- model, field):
- old = model[path][0]
- if old == newtext:
- return
- try:
- newtext = helpers.parse_jid(newtext)
- except helpers.InvalidFormat, s:
- dialogs.ErrorDialog(_('Invalid Jabber ID'), str(s))
- return
- if newtext in field.values:
- dialogs.ErrorDialog(
- _('Jabber ID already in list'),
- _('The Jabber ID you entered is already in the list. Choose another one.'))
- gobject.idle_add(treeview.set_cursor, path)
- return
- model[path][0]=newtext
-
- values = field.values
- values[values.index(old)]=newtext
- field.values = values
-
- def on_jid_multi_add_button_clicked(self, widget, treeview, model, field):
- #Default jid
- jid = _('new@jabber.id')
- if jid in field.values:
- i = 1
- while _('new%d@jabber.id') % i in field.values:
- i += 1
- jid = _('new%d@jabber.id') % i
- iter_ = model.insert(999999, (jid,))
- treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True)
- field.values = field.values + [jid]
-
- def on_jid_multi_edit_button_clicked(self, widget, treeview):
- model, iter_ = treeview.get_selection().get_selected()
- assert iter_ is not None
-
- treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True)
-
- def on_jid_multi_remove_button_clicked(self, widget, treeview, field):
- selection = treeview.get_selection()
- deleted = []
-
- def remove(model, path, iter_, deleted):
- deleted+=model[iter_]
- model.remove(iter_)
-
- selection.selected_foreach(remove, deleted)
- field.values = (v for v in field.values if v not in deleted)
-
- def on_jid_multi_clean_button_clicked(self, widget, model, field):
- model.clear()
- del field.values
-
-# vim: se ts=3:
+ """
+ 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)
+
+ gtk.Table.__init__(self)
+ self.set_col_spacings(12)
+ self.set_row_spacings(6)
+
+ def decorate_with_tooltip(widget, field):
+ """
+ Adds a tooltip containing field's description to a widget. Creates
+ EventBox if widget doesn't have its own gdk window. Returns decorated
+ widget
+ """
+ if field.description != '':
+ if widget.flags() & gtk.NO_WINDOW:
+ evbox = gtk.EventBox()
+ evbox.add(widget)
+ widget = evbox
+ widget.set_tooltip_text(field.description)
+ return widget
+
+ self._data_form = dataform
+
+ # building widget
+ linecounter = 0
+
+ # is the form changeable?
+ readwrite = dataform.type != 'result'
+
+ # for each field...
+ for field in self._data_form.iter_fields():
+ if field.type == 'hidden': continue
+
+ commonlabel = True
+ commonlabelcenter = False
+ commonwidget = True
+ widget = None
+
+ if field.type == 'boolean':
+ commonlabelcenter = True
+ widget = gtk.CheckButton()
+ widget.connect('toggled', self.on_boolean_checkbutton_toggled,
+ field)
+ widget.set_active(field.value)
+
+ elif field.type == 'fixed':
+ leftattach = 1
+ rightattach = 2
+ if field.label is None:
+ commonlabel = False
+ leftattach = 0
+
+ commonwidget = False
+ widget = gtk.Label(field.value)
+ widget.set_line_wrap(True)
+ self.attach(widget, leftattach, rightattach, linecounter,
+ linecounter+1, xoptions=gtk.FILL, yoptions=gtk.FILL)
+
+ elif field.type == 'list-single':
+ # TODO: What if we have radio buttons and non-required field?
+ # TODO: We cannot deactivate them all...
+ if len(field.options) < 6:
+ # 5 option max: show radiobutton
+ widget = gtk.VBox()
+ first_radio = None
+ for value, label in field.iter_options():
+ if not label:
+ label = value
+ radio = gtk.RadioButton(first_radio, label=label)
+ radio.connect('toggled',
+ self.on_list_single_radiobutton_toggled, field, value)
+ if first_radio is None:
+ first_radio = radio
+ if field.value == '': # TODO: is None when done
+ field.value = value
+ if value == field.value:
+ radio.set_active(True)
+ widget.pack_start(radio, expand=False)
+ else:
+ # more than 5 options: show combobox
+ def on_list_single_combobox_changed(combobox, f):
+ iter_ = combobox.get_active_iter()
+ if iter_:
+ model = combobox.get_model()
+ f.value = model[iter_][1]
+ else:
+ f.value = ''
+ widget = gtkgui_helpers.create_combobox(field.options,
+ field.value)
+ widget.connect('changed', on_list_single_combobox_changed, field)
+ widget.set_sensitive(readwrite)
+
+ elif field.type == 'list-multi':
+ # TODO: When more than few choices, make a list
+ if len(field.options) < 6:
+ # 5 option max: show checkbutton
+ widget = gtk.VBox()
+ for value, label in field.iter_options():
+ check = gtk.CheckButton(label, use_underline=False)
+ check.set_active(value in field.values)
+ check.connect('toggled',
+ self.on_list_multi_checkbutton_toggled, field, value)
+ widget.pack_start(check, expand=False)
+ else:
+ # more than 5 options: show combobox
+ def on_list_multi_treeview_changed(selection, f):
+ def for_selected(treemodel, path, iter):
+ vals.append(treemodel[iter][1])
+ vals = []
+ selection.selected_foreach(for_selected)
+ field.values = vals[:]
+ widget = gtk.ScrolledWindow()
+ widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ tv = gtkgui_helpers.create_list_multi(field.options,
+ field.values)
+ widget.add(tv)
+ widget.set_size_request(-1, 120)
+ tv.get_selection().connect('changed',
+ on_list_multi_treeview_changed, field)
+ widget.set_sensitive(readwrite)
+
+ elif field.type == 'jid-single':
+ widget = gtk.Entry()
+ widget.connect('changed', self.on_text_single_entry_changed, field)
+ widget.set_text(field.value)
+
+ elif field.type == 'jid-multi':
+ commonwidget = False
+
+ xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui',
+ 'item_list_table')
+ widget = xml.get_object('item_list_table')
+ treeview = xml.get_object('item_treeview')
+
+ listmodel = gtk.ListStore(str)
+ for value in field.iter_values():
+ # nobody will create several megabytes long stanza
+ listmodel.insert(999999, (value,))
+
+ treeview.set_model(listmodel)
+
+ renderer = gtk.CellRendererText()
+ renderer.set_property('editable', True)
+ renderer.connect('edited',
+ self.on_jid_multi_cellrenderertext_edited, treeview, listmodel,
+ field)
+
+ treeview.append_column(gtk.TreeViewColumn(None, renderer,
+ text=0))
+
+ decorate_with_tooltip(treeview, field)
+
+ add_button=xml.get_object('add_button')
+ add_button.connect('clicked',
+ self.on_jid_multi_add_button_clicked, treeview, listmodel, field)
+ edit_button=xml.get_object('edit_button')
+ edit_button.connect('clicked',
+ self.on_jid_multi_edit_button_clicked, treeview)
+ remove_button=xml.get_object('remove_button')
+ remove_button.connect('clicked',
+ self.on_jid_multi_remove_button_clicked, treeview, field)
+ clear_button=xml.get_object('clear_button')
+ clear_button.connect('clicked',
+ self.on_jid_multi_clean_button_clicked, listmodel, field)
+ if not readwrite:
+ add_button.set_no_show_all(True)
+ edit_button.set_no_show_all(True)
+ remove_button.set_no_show_all(True)
+ clear_button.set_no_show_all(True)
+
+ widget.set_sensitive(readwrite)
+ self.attach(widget, 1, 2, linecounter, linecounter+1)
+
+ del xml
+
+ elif field.type == 'text-private':
+ commonlabelcenter = True
+ widget = gtk.Entry()
+ widget.connect('changed', self.on_text_single_entry_changed, field)
+ widget.set_visibility(False)
+ widget.set_text(field.value)
+
+ elif field.type == 'text-multi':
+ # TODO: bigger text view
+ commonwidget = False
+
+ textwidget = gtk.TextView()
+ textwidget.set_wrap_mode(gtk.WRAP_WORD)
+ textwidget.get_buffer().connect('changed',
+ self.on_text_multi_textbuffer_changed, field)
+ textwidget.get_buffer().set_text(field.value)
+
+ widget = gtk.ScrolledWindow()
+ widget.add(textwidget)
+
+ widget.set_sensitive(readwrite)
+ widget=decorate_with_tooltip(widget, field)
+ self.attach(widget, 1, 2, linecounter, linecounter+1)
+
+ else:
+ # field.type == 'text-single' or field.type is nonstandard:
+ # JEP says that if we don't understand some type, we
+ # should handle it as text-single
+ commonlabelcenter = True
+ if readwrite:
+ widget = gtk.Entry()
+ widget.connect('changed', self.on_text_single_entry_changed,
+ field)
+ widget.set_sensitive(readwrite)
+ if field.value is None:
+ field.value = u''
+ widget.set_text(field.value)
+ else:
+ commonwidget=False
+ widget = gtk.Label(field.value)
+ widget.set_sensitive(True)
+ widget.set_alignment(0.0, 0.5)
+ widget=decorate_with_tooltip(widget, field)
+ self.attach(widget, 1, 2, linecounter, linecounter+1,
+ yoptions=gtk.FILL)
+
+ if commonlabel and field.label is not None:
+ label = gtk.Label(field.label)
+ if commonlabelcenter:
+ label.set_alignment(0.0, 0.5)
+ else:
+ label.set_alignment(0.0, 0.0)
+ label = decorate_with_tooltip(label, field)
+ self.attach(label, 0, 1, linecounter, linecounter+1,
+ xoptions=gtk.FILL, yoptions=gtk.FILL)
+
+ if commonwidget:
+ assert widget is not None
+ widget.set_sensitive(readwrite)
+ widget = decorate_with_tooltip(widget, field)
+ self.attach(widget, 1, 2, linecounter, linecounter+1,
+ yoptions=gtk.FILL)
+ widget.show_all()
+
+ linecounter+=1
+ if self.get_property('visible'):
+ self.show_all()
+
+ def show(self):
+ # simulate that we are one widget
+ self.show_all()
+
+ def on_boolean_checkbutton_toggled(self, widget, field):
+ field.value = widget.get_active()
+
+ def on_list_single_radiobutton_toggled(self, widget, field, value):
+ field.value = value
+
+ def on_list_multi_checkbutton_toggled(self, widget, field, value):
+ # TODO: make some methods like add_value and remove_value
+ if widget.get_active() and value not in field.values:
+ field.values += [value]
+ elif not widget.get_active() and value in field.values:
+ field.values = [v for v in field.values if v!=value]
+
+ def on_text_single_entry_changed(self, widget, field):
+ field.value = widget.get_text()
+
+ def on_text_multi_textbuffer_changed(self, widget, field):
+ field.value = widget.get_text(
+ widget.get_start_iter(),
+ widget.get_end_iter())
+
+ def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, treeview,
+ model, field):
+ old = model[path][0]
+ if old == newtext:
+ return
+ try:
+ newtext = helpers.parse_jid(newtext)
+ except helpers.InvalidFormat, s:
+ dialogs.ErrorDialog(_('Invalid Jabber ID'), str(s))
+ return
+ if newtext in field.values:
+ dialogs.ErrorDialog(
+ _('Jabber ID already in list'),
+ _('The Jabber ID you entered is already in the list. Choose another one.'))
+ gobject.idle_add(treeview.set_cursor, path)
+ return
+ model[path][0]=newtext
+
+ values = field.values
+ values[values.index(old)]=newtext
+ field.values = values
+
+ def on_jid_multi_add_button_clicked(self, widget, treeview, model, field):
+ #Default jid
+ jid = _('new@jabber.id')
+ if jid in field.values:
+ i = 1
+ while _('new%d@jabber.id') % i in field.values:
+ i += 1
+ jid = _('new%d@jabber.id') % i
+ iter_ = model.insert(999999, (jid,))
+ treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True)
+ field.values = field.values + [jid]
+
+ def on_jid_multi_edit_button_clicked(self, widget, treeview):
+ model, iter_ = treeview.get_selection().get_selected()
+ assert iter_ is not None
+
+ treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True)
+
+ def on_jid_multi_remove_button_clicked(self, widget, treeview, field):
+ selection = treeview.get_selection()
+ deleted = []
+
+ def remove(model, path, iter_, deleted):
+ deleted+=model[iter_]
+ model.remove(iter_)
+
+ selection.selected_foreach(remove, deleted)
+ field.values = (v for v in field.values if v not in deleted)
+
+ def on_jid_multi_clean_button_clicked(self, widget, model, field):
+ model.clear()
+ del field.values
diff --git a/src/dialogs.py b/src/dialogs.py
index 1f2a93fc4..40a6c57ca 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -43,10 +43,10 @@ from random import randrange
from common import pep
try:
- import gtkspell
- HAS_GTK_SPELL = True
+ import gtkspell
+ HAS_GTK_SPELL = True
except ImportError:
- HAS_GTK_SPELL = False
+ HAS_GTK_SPELL = False
# those imports are not used in this file, but in files that 'import dialogs'
# so they can do dialog.GajimThemesWindow() for example
@@ -60,4857 +60,4855 @@ from common import dataforms
from common.exceptions import GajimGeneralException
class EditGroupsDialog:
- """
- Class for the edit group dialog window
- """
-
- def __init__(self, list_):
- """
- list_ is a list of (contact, account) tuples
- """
- self.xml = gtkgui_helpers.get_gtk_builder('edit_groups_dialog.ui')
- self.dialog = self.xml.get_object('edit_groups_dialog')
- self.dialog.set_transient_for(gajim.interface.roster.window)
- self.list_ = list_
- self.changes_made = False
- self.treeview = self.xml.get_object('groups_treeview')
- if len(list_) == 1:
- contact = list_[0][0]
- self.xml.get_object('nickname_label').set_markup(
- _('Contact name: <i>%s</i>') % contact.get_shown_name())
- self.xml.get_object('jid_label').set_markup(
- _('Jabber ID: <i>%s</i>') % contact.jid)
- else:
- self.xml.get_object('nickname_label').set_no_show_all(True)
- self.xml.get_object('nickname_label').hide()
- self.xml.get_object('jid_label').set_no_show_all(True)
- self.xml.get_object('jid_label').hide()
-
- self.xml.connect_signals(self)
- self.init_list()
-
- self.dialog.show_all()
- if self.changes_made:
- for (contact, account) in self.list_:
- gajim.connections[account].update_contact(contact.jid, contact.name,
- contact.groups)
-
- def on_edit_groups_dialog_response(self, widget, response_id):
- if response_id == gtk.RESPONSE_CLOSE:
- self.dialog.destroy()
-
- def remove_group(self, group):
- """
- Remove group group from all contacts and all their brothers
- """
- for (contact, account) in self.list_:
- gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group])
-
- # FIXME: Ugly workaround.
- gajim.interface.roster.draw_group(_('General'), account)
-
- def add_group(self, group):
- """
- Add group group to all contacts and all their brothers
- """
- for (contact, account) in self.list_:
- gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group])
-
- # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General)
- gajim.interface.roster.draw_group(_('General'), account)
-
- def on_add_button_clicked(self, widget):
- group = self.xml.get_object('group_entry').get_text().decode('utf-8')
- if not group:
- return
- # Do not allow special groups
- if group in helpers.special_groups:
- return
- # check if it already exists
- model = self.treeview.get_model()
- iter_ = model.get_iter_root()
- while iter_:
- if model.get_value(iter_, 0).decode('utf-8') == group:
- return
- iter_ = model.iter_next(iter_)
- self.changes_made = True
- model.append((group, True, False))
- self.add_group(group)
- self.init_list() # Re-draw list to sort new item
-
- def group_toggled_cb(self, cell, path):
- self.changes_made = True
- model = self.treeview.get_model()
- if model[path][2]:
- model[path][2] = False
- model[path][1] = True
- else:
- model[path][1] = not model[path][1]
- group = model[path][0].decode('utf-8')
- if model[path][1]:
- self.add_group(group)
- else:
- self.remove_group(group)
-
- def init_list(self):
- store = gtk.ListStore(str, bool, bool)
- self.treeview.set_model(store)
- for column in self.treeview.get_columns():
- # Clear treeview when re-drawing
- self.treeview.remove_column(column)
- accounts = []
- # Store groups in a list so we can sort them and the number of contacts in
- # it
- groups = {}
- for (contact, account) in self.list_:
- if account not in accounts:
- accounts.append(account)
- for g in gajim.groups[account].keys():
- if g in groups:
- continue
- groups[g] = 0
- c_groups = contact.groups
- for g in c_groups:
- groups[g] += 1
- group_list = []
- # Remove special groups if they are empty
- for group in groups:
- if group not in helpers.special_groups or groups[group] > 0:
- group_list.append(group)
- group_list.sort()
- for group in group_list:
- iter_ = store.append()
- store.set(iter_, 0, group) # Group name
- if groups[group] == 0:
- store.set(iter_, 1, False)
- else:
- store.set(iter_, 1, True)
- if groups[group] == len(self.list_):
- # all contacts are in this group
- store.set(iter_, 2, False)
- else:
- store.set(iter_, 2, True)
- column = gtk.TreeViewColumn(_('Group'))
- column.set_expand(True)
- self.treeview.append_column(column)
- renderer = gtk.CellRendererText()
- column.pack_start(renderer)
- column.set_attributes(renderer, text=0)
-
- column = gtk.TreeViewColumn(_('In the group'))
- column.set_expand(False)
- self.treeview.append_column(column)
- renderer = gtk.CellRendererToggle()
- column.pack_start(renderer)
- renderer.set_property('activatable', True)
- renderer.connect('toggled', self.group_toggled_cb)
- column.set_attributes(renderer, active=1, inconsistent=2)
+ """
+ Class for the edit group dialog window
+ """
+
+ def __init__(self, list_):
+ """
+ list_ is a list of (contact, account) tuples
+ """
+ self.xml = gtkgui_helpers.get_gtk_builder('edit_groups_dialog.ui')
+ self.dialog = self.xml.get_object('edit_groups_dialog')
+ self.dialog.set_transient_for(gajim.interface.roster.window)
+ self.list_ = list_
+ self.changes_made = False
+ self.treeview = self.xml.get_object('groups_treeview')
+ if len(list_) == 1:
+ contact = list_[0][0]
+ self.xml.get_object('nickname_label').set_markup(
+ _('Contact name: <i>%s</i>') % contact.get_shown_name())
+ self.xml.get_object('jid_label').set_markup(
+ _('Jabber ID: <i>%s</i>') % contact.jid)
+ else:
+ self.xml.get_object('nickname_label').set_no_show_all(True)
+ self.xml.get_object('nickname_label').hide()
+ self.xml.get_object('jid_label').set_no_show_all(True)
+ self.xml.get_object('jid_label').hide()
+
+ self.xml.connect_signals(self)
+ self.init_list()
+
+ self.dialog.show_all()
+ if self.changes_made:
+ for (contact, account) in self.list_:
+ gajim.connections[account].update_contact(contact.jid, contact.name,
+ contact.groups)
+
+ def on_edit_groups_dialog_response(self, widget, response_id):
+ if response_id == gtk.RESPONSE_CLOSE:
+ self.dialog.destroy()
+
+ def remove_group(self, group):
+ """
+ Remove group group from all contacts and all their brothers
+ """
+ for (contact, account) in self.list_:
+ gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group])
+
+ # FIXME: Ugly workaround.
+ gajim.interface.roster.draw_group(_('General'), account)
+
+ def add_group(self, group):
+ """
+ Add group group to all contacts and all their brothers
+ """
+ for (contact, account) in self.list_:
+ gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group])
+
+ # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General)
+ gajim.interface.roster.draw_group(_('General'), account)
+
+ def on_add_button_clicked(self, widget):
+ group = self.xml.get_object('group_entry').get_text().decode('utf-8')
+ if not group:
+ return
+ # Do not allow special groups
+ if group in helpers.special_groups:
+ return
+ # check if it already exists
+ model = self.treeview.get_model()
+ iter_ = model.get_iter_root()
+ while iter_:
+ if model.get_value(iter_, 0).decode('utf-8') == group:
+ return
+ iter_ = model.iter_next(iter_)
+ self.changes_made = True
+ model.append((group, True, False))
+ self.add_group(group)
+ self.init_list() # Re-draw list to sort new item
+
+ def group_toggled_cb(self, cell, path):
+ self.changes_made = True
+ model = self.treeview.get_model()
+ if model[path][2]:
+ model[path][2] = False
+ model[path][1] = True
+ else:
+ model[path][1] = not model[path][1]
+ group = model[path][0].decode('utf-8')
+ if model[path][1]:
+ self.add_group(group)
+ else:
+ self.remove_group(group)
+
+ def init_list(self):
+ store = gtk.ListStore(str, bool, bool)
+ self.treeview.set_model(store)
+ for column in self.treeview.get_columns():
+ # Clear treeview when re-drawing
+ self.treeview.remove_column(column)
+ accounts = []
+ # Store groups in a list so we can sort them and the number of contacts in
+ # it
+ groups = {}
+ for (contact, account) in self.list_:
+ if account not in accounts:
+ accounts.append(account)
+ for g in gajim.groups[account].keys():
+ if g in groups:
+ continue
+ groups[g] = 0
+ c_groups = contact.groups
+ for g in c_groups:
+ groups[g] += 1
+ group_list = []
+ # Remove special groups if they are empty
+ for group in groups:
+ if group not in helpers.special_groups or groups[group] > 0:
+ group_list.append(group)
+ group_list.sort()
+ for group in group_list:
+ iter_ = store.append()
+ store.set(iter_, 0, group) # Group name
+ if groups[group] == 0:
+ store.set(iter_, 1, False)
+ else:
+ store.set(iter_, 1, True)
+ if groups[group] == len(self.list_):
+ # all contacts are in this group
+ store.set(iter_, 2, False)
+ else:
+ store.set(iter_, 2, True)
+ column = gtk.TreeViewColumn(_('Group'))
+ column.set_expand(True)
+ self.treeview.append_column(column)
+ renderer = gtk.CellRendererText()
+ column.pack_start(renderer)
+ column.set_attributes(renderer, text=0)
+
+ column = gtk.TreeViewColumn(_('In the group'))
+ column.set_expand(False)
+ self.treeview.append_column(column)
+ renderer = gtk.CellRendererToggle()
+ column.pack_start(renderer)
+ renderer.set_property('activatable', True)
+ renderer.connect('toggled', self.group_toggled_cb)
+ column.set_attributes(renderer, active=1, inconsistent=2)
class PassphraseDialog:
- """
- Class for Passphrase dialog
- """
- def __init__(self, titletext, labeltext, checkbuttontext=None,
- ok_handler=None, cancel_handler=None):
- self.xml = gtkgui_helpers.get_gtk_builder('passphrase_dialog.ui')
- self.window = self.xml.get_object('passphrase_dialog')
- self.passphrase_entry = self.xml.get_object('passphrase_entry')
- self.passphrase = -1
- self.window.set_title(titletext)
- self.xml.get_object('message_label').set_text(labeltext)
-
- self.ok = False
-
- self.cancel_handler = cancel_handler
- self.ok_handler = ok_handler
- okbutton = self.xml.get_object('ok_button')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancel_button')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
-
- self.xml.connect_signals(self)
- self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
- self.window.show_all()
-
- self.check = bool(checkbuttontext)
- checkbutton = self.xml.get_object('save_passphrase_checkbutton')
- if self.check:
- checkbutton.set_label(checkbuttontext)
- else:
- checkbutton.hide()
-
- def on_okbutton_clicked(self, widget):
- if not self.ok_handler:
- return
-
- passph = self.passphrase_entry.get_text().decode('utf-8')
-
- if self.check:
- checked = self.xml.get_object('save_passphrase_checkbutton').\
- get_active()
- else:
- checked = False
-
- self.ok = True
-
- self.window.destroy()
-
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](passph, checked, *self.ok_handler[1:])
- else:
- self.ok_handler(passph, checked)
-
- def on_cancelbutton_clicked(self, widget):
- self.window.destroy()
-
- def on_passphrase_dialog_destroy(self, widget):
- if self.cancel_handler and not self.ok:
- self.cancel_handler()
+ """
+ Class for Passphrase dialog
+ """
+ def __init__(self, titletext, labeltext, checkbuttontext=None,
+ ok_handler=None, cancel_handler=None):
+ self.xml = gtkgui_helpers.get_gtk_builder('passphrase_dialog.ui')
+ self.window = self.xml.get_object('passphrase_dialog')
+ self.passphrase_entry = self.xml.get_object('passphrase_entry')
+ self.passphrase = -1
+ self.window.set_title(titletext)
+ self.xml.get_object('message_label').set_text(labeltext)
+
+ self.ok = False
+
+ self.cancel_handler = cancel_handler
+ self.ok_handler = ok_handler
+ okbutton = self.xml.get_object('ok_button')
+ okbutton.connect('clicked', self.on_okbutton_clicked)
+ cancelbutton = self.xml.get_object('cancel_button')
+ cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
+
+ self.xml.connect_signals(self)
+ self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.window.show_all()
+
+ self.check = bool(checkbuttontext)
+ checkbutton = self.xml.get_object('save_passphrase_checkbutton')
+ if self.check:
+ checkbutton.set_label(checkbuttontext)
+ else:
+ checkbutton.hide()
+
+ def on_okbutton_clicked(self, widget):
+ if not self.ok_handler:
+ return
+
+ passph = self.passphrase_entry.get_text().decode('utf-8')
+
+ if self.check:
+ checked = self.xml.get_object('save_passphrase_checkbutton').\
+ get_active()
+ else:
+ checked = False
+
+ self.ok = True
+
+ self.window.destroy()
+
+ if isinstance(self.ok_handler, tuple):
+ self.ok_handler[0](passph, checked, *self.ok_handler[1:])
+ else:
+ self.ok_handler(passph, checked)
+
+ def on_cancelbutton_clicked(self, widget):
+ self.window.destroy()
+
+ def on_passphrase_dialog_destroy(self, widget):
+ if self.cancel_handler and not self.ok:
+ self.cancel_handler()
class ChooseGPGKeyDialog:
- """
- Class for GPG key dialog
- """
-
- def __init__(self, title_text, prompt_text, secret_keys, on_response,
- selected=None):
- '''secret_keys : {keyID: userName, ...}'''
- self.on_response = on_response
- xml = gtkgui_helpers.get_gtk_builder('choose_gpg_key_dialog.ui')
- self.window = xml.get_object('choose_gpg_key_dialog')
- self.window.set_title(title_text)
- self.keys_treeview = xml.get_object('keys_treeview')
- prompt_label = xml.get_object('prompt_label')
- prompt_label.set_text(prompt_text)
- model = gtk.ListStore(str, str)
- model.set_sort_func(1, self.sort_keys)
- model.set_sort_column_id(1, gtk.SORT_ASCENDING)
- self.keys_treeview.set_model(model)
- #columns
- renderer = gtk.CellRendererText()
- col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'),
- renderer, text=0)
- col.set_sort_column_id(0)
- renderer = gtk.CellRendererText()
- col = self.keys_treeview.insert_column_with_attributes(-1,
- _('Contact name'), renderer, text=1)
- col.set_sort_column_id(1)
- self.keys_treeview.set_search_column(1)
- self.fill_tree(secret_keys, selected)
- self.window.connect('response', self.on_dialog_response)
- self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
- self.window.show_all()
-
- def sort_keys(self, model, iter1, iter2):
- value1 = model[iter1][1]
- value2 = model[iter2][1]
- if value1 == _('None'):
- return -1
- elif value2 == _('None'):
- return 1
- elif value1 < value2:
- return -1
- return 1
-
- def on_dialog_response(self, dialog, response):
- selection = self.keys_treeview.get_selection()
- (model, iter_) = selection.get_selected()
- if iter_ and response == gtk.RESPONSE_OK:
- keyID = [ model[iter_][0].decode('utf-8'),
- model[iter_][1].decode('utf-8') ]
- else:
- keyID = None
- self.on_response(keyID)
- self.window.destroy()
-
- def fill_tree(self, list_, selected):
- model = self.keys_treeview.get_model()
- for keyID in list_.keys():
- iter_ = model.append((keyID, list_[keyID]))
- if keyID == selected:
- path = model.get_path(iter_)
- self.keys_treeview.set_cursor(path)
+ """
+ Class for GPG key dialog
+ """
+
+ def __init__(self, title_text, prompt_text, secret_keys, on_response,
+ selected=None):
+ '''secret_keys : {keyID: userName, ...}'''
+ self.on_response = on_response
+ xml = gtkgui_helpers.get_gtk_builder('choose_gpg_key_dialog.ui')
+ self.window = xml.get_object('choose_gpg_key_dialog')
+ self.window.set_title(title_text)
+ self.keys_treeview = xml.get_object('keys_treeview')
+ prompt_label = xml.get_object('prompt_label')
+ prompt_label.set_text(prompt_text)
+ model = gtk.ListStore(str, str)
+ model.set_sort_func(1, self.sort_keys)
+ model.set_sort_column_id(1, gtk.SORT_ASCENDING)
+ self.keys_treeview.set_model(model)
+ #columns
+ renderer = gtk.CellRendererText()
+ col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'),
+ renderer, text=0)
+ col.set_sort_column_id(0)
+ renderer = gtk.CellRendererText()
+ col = self.keys_treeview.insert_column_with_attributes(-1,
+ _('Contact name'), renderer, text=1)
+ col.set_sort_column_id(1)
+ self.keys_treeview.set_search_column(1)
+ self.fill_tree(secret_keys, selected)
+ self.window.connect('response', self.on_dialog_response)
+ self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.window.show_all()
+
+ def sort_keys(self, model, iter1, iter2):
+ value1 = model[iter1][1]
+ value2 = model[iter2][1]
+ if value1 == _('None'):
+ return -1
+ elif value2 == _('None'):
+ return 1
+ elif value1 < value2:
+ return -1
+ return 1
+
+ def on_dialog_response(self, dialog, response):
+ selection = self.keys_treeview.get_selection()
+ (model, iter_) = selection.get_selected()
+ if iter_ and response == gtk.RESPONSE_OK:
+ keyID = [ model[iter_][0].decode('utf-8'),
+ model[iter_][1].decode('utf-8') ]
+ else:
+ keyID = None
+ self.on_response(keyID)
+ self.window.destroy()
+
+ def fill_tree(self, list_, selected):
+ model = self.keys_treeview.get_model()
+ for keyID in list_.keys():
+ iter_ = model.append((keyID, list_[keyID]))
+ if keyID == selected:
+ path = model.get_path(iter_)
+ self.keys_treeview.set_cursor(path)
class ChangeActivityDialog:
- PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming',
- 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling',
- 'working']
-
- def __init__(self, on_response, activity=None, subactivity=None, text=''):
- self.on_response = on_response
- self.activity = activity
- self.subactivity = subactivity
- self.text = text
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'change_activity_dialog.ui')
- self.window = self.xml.get_object('change_activity_dialog')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- self.checkbutton = self.xml.get_object('enable_checkbutton')
- self.notebook = self.xml.get_object('notebook')
- self.entry = self.xml.get_object('description_entry')
-
- rbtns = {}
- group = None
-
- for category in pep.ACTIVITIES:
- item = self.xml.get_object(category + '_image')
- item.set_from_pixbuf(
- gtkgui_helpers.load_activity_icon(category).get_pixbuf())
- item.set_tooltip_text(pep.ACTIVITIES[category]['category'])
-
- vbox = self.xml.get_object(category + '_vbox')
- vbox.set_border_width(5)
-
- # Other
- act = category + '_other'
-
- if group:
- rbtns[act] = gtk.RadioButton(group)
- else:
- rbtns[act] = group = gtk.RadioButton()
-
- hbox = gtk.HBox(False, 5)
- hbox.pack_start(gtkgui_helpers.load_activity_icon(category), False,
- False, 0)
- lbl = gtk.Label('<b>' + pep.ACTIVITIES[category]['category'] + '</b>')
- lbl.set_use_markup(True)
- hbox.pack_start(lbl, False, False, 0)
- rbtns[act].add(hbox)
- rbtns[act].connect('toggled', self.on_rbtn_toggled,
- [category, 'other'])
- vbox.pack_start(rbtns[act], False, False, 0)
-
- activities = []
- for activity in pep.ACTIVITIES[category]:
- activities.append(activity)
- activities.sort()
-
- for activity in activities:
- if activity == 'category':
- continue
-
- act = category + '_' + activity
-
- if group:
- rbtns[act] = gtk.RadioButton(group)
- else:
- rbtns[act] = group = gtk.RadioButton()
-
- hbox = gtk.HBox(False, 5)
- hbox.pack_start(gtkgui_helpers.load_activity_icon(category,
- activity), False, False, 0)
- hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]),
- False, False, 0)
- rbtns[act].connect('toggled', self.on_rbtn_toggled,
- [category, activity])
- rbtns[act].add(hbox)
- vbox.pack_start(rbtns[act], False, False, 0)
-
-
- if self.activity in pep.ACTIVITIES:
- if not self.subactivity in pep.ACTIVITIES[self.activity]:
- self.subactivity = 'other'
-
- rbtns[self.activity + '_' + self.subactivity].set_active(True)
-
- self.checkbutton.set_active(True)
- self.notebook.set_sensitive(True)
- self.entry.set_sensitive(True)
-
- self.notebook.set_current_page(
- self.PAGELIST.index(self.activity))
-
- self.entry.set_text(text)
-
- else:
- self.checkbutton.set_active(False)
-
- self.xml.connect_signals(self)
- self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
- self.window.show_all()
-
- def on_enable_checkbutton_toggled(self, widget):
- self.notebook.set_sensitive(widget.get_active())
- self.entry.set_sensitive(widget.get_active())
-
- def on_rbtn_toggled(self, widget, data):
- if widget.get_active():
- self.activity = data[0]
- self.subactivity = data[1]
-
- def on_ok_button_clicked(self, widget):
- """
- Return activity and messsage (None if no activity selected)
- """
- if self.checkbutton.get_active():
- self.on_response(self.activity, self.subactivity,
- self.entry.get_text().decode('utf-8'))
- else:
- self.on_response(None, None, '')
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
+ PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming',
+ 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling',
+ 'working']
+
+ def __init__(self, on_response, activity=None, subactivity=None, text=''):
+ self.on_response = on_response
+ self.activity = activity
+ self.subactivity = subactivity
+ self.text = text
+ self.xml = gtkgui_helpers.get_gtk_builder(
+ 'change_activity_dialog.ui')
+ self.window = self.xml.get_object('change_activity_dialog')
+ self.window.set_transient_for(gajim.interface.roster.window)
+
+ self.checkbutton = self.xml.get_object('enable_checkbutton')
+ self.notebook = self.xml.get_object('notebook')
+ self.entry = self.xml.get_object('description_entry')
+
+ rbtns = {}
+ group = None
+
+ for category in pep.ACTIVITIES:
+ item = self.xml.get_object(category + '_image')
+ item.set_from_pixbuf(
+ gtkgui_helpers.load_activity_icon(category).get_pixbuf())
+ item.set_tooltip_text(pep.ACTIVITIES[category]['category'])
+
+ vbox = self.xml.get_object(category + '_vbox')
+ vbox.set_border_width(5)
+
+ # Other
+ act = category + '_other'
+
+ if group:
+ rbtns[act] = gtk.RadioButton(group)
+ else:
+ rbtns[act] = group = gtk.RadioButton()
+
+ hbox = gtk.HBox(False, 5)
+ hbox.pack_start(gtkgui_helpers.load_activity_icon(category), False,
+ False, 0)
+ lbl = gtk.Label('<b>' + pep.ACTIVITIES[category]['category'] + '</b>')
+ lbl.set_use_markup(True)
+ hbox.pack_start(lbl, False, False, 0)
+ rbtns[act].add(hbox)
+ rbtns[act].connect('toggled', self.on_rbtn_toggled,
+ [category, 'other'])
+ vbox.pack_start(rbtns[act], False, False, 0)
+
+ activities = []
+ for activity in pep.ACTIVITIES[category]:
+ activities.append(activity)
+ activities.sort()
+
+ for activity in activities:
+ if activity == 'category':
+ continue
+
+ act = category + '_' + activity
+
+ if group:
+ rbtns[act] = gtk.RadioButton(group)
+ else:
+ rbtns[act] = group = gtk.RadioButton()
+
+ hbox = gtk.HBox(False, 5)
+ hbox.pack_start(gtkgui_helpers.load_activity_icon(category,
+ activity), False, False, 0)
+ hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]),
+ False, False, 0)
+ rbtns[act].connect('toggled', self.on_rbtn_toggled,
+ [category, activity])
+ rbtns[act].add(hbox)
+ vbox.pack_start(rbtns[act], False, False, 0)
+
+
+ if self.activity in pep.ACTIVITIES:
+ if not self.subactivity in pep.ACTIVITIES[self.activity]:
+ self.subactivity = 'other'
+
+ rbtns[self.activity + '_' + self.subactivity].set_active(True)
+
+ self.checkbutton.set_active(True)
+ self.notebook.set_sensitive(True)
+ self.entry.set_sensitive(True)
+
+ self.notebook.set_current_page(
+ self.PAGELIST.index(self.activity))
+
+ self.entry.set_text(text)
+
+ else:
+ self.checkbutton.set_active(False)
+
+ self.xml.connect_signals(self)
+ self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.window.show_all()
+
+ def on_enable_checkbutton_toggled(self, widget):
+ self.notebook.set_sensitive(widget.get_active())
+ self.entry.set_sensitive(widget.get_active())
+
+ def on_rbtn_toggled(self, widget, data):
+ if widget.get_active():
+ self.activity = data[0]
+ self.subactivity = data[1]
+
+ def on_ok_button_clicked(self, widget):
+ """
+ Return activity and messsage (None if no activity selected)
+ """
+ if self.checkbutton.get_active():
+ self.on_response(self.activity, self.subactivity,
+ self.entry.get_text().decode('utf-8'))
+ else:
+ self.on_response(None, None, '')
+ self.window.destroy()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
class ChangeMoodDialog:
- COLS = 11
-
- def __init__(self, on_response, mood=None, text=''):
- self.on_response = on_response
- self.mood = mood
- self.text = text
- self.xml = gtkgui_helpers.get_gtk_builder('change_mood_dialog.ui')
-
- self.window = self.xml.get_object('change_mood_dialog')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.window.set_title(_('Set Mood'))
-
- table = self.xml.get_object('mood_icons_table')
- self.label = self.xml.get_object('mood_label')
- self.entry = self.xml.get_object('description_entry')
-
- no_mood_button = self.xml.get_object('no_mood_button')
- no_mood_button.set_mode(False)
- no_mood_button.connect('clicked',
- self.on_mood_button_clicked, None)
-
- x = 1
- y = 0
- self.mood_buttons = {}
-
- # Order them first
- self.MOODS = []
- for mood in pep.MOODS:
- self.MOODS.append(mood)
- self.MOODS.sort()
-
- for mood in self.MOODS:
- self.mood_buttons[mood] = gtk.RadioButton(no_mood_button)
- self.mood_buttons[mood].set_mode(False)
- self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood))
- self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE)
- self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood])
- self.mood_buttons[mood].connect('clicked',
- self.on_mood_button_clicked, mood)
- table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1)
-
- # Calculate the next position
- x += 1
- if x >= self.COLS:
- x = 0
- y += 1
-
- if self.mood in pep.MOODS:
- self.mood_buttons[self.mood].set_active(True)
- self.label.set_text(pep.MOODS[self.mood])
- self.entry.set_sensitive(True)
- if self.text:
- self.entry.set_text(self.text)
- else:
- self.label.set_text(_('None'))
- self.entry.set_text('')
- self.entry.set_sensitive(False)
-
- self.xml.connect_signals(self)
- self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
- self.window.show_all()
-
- def on_mood_button_clicked(self, widget, data):
- if data:
- self.label.set_text(pep.MOODS[data])
- self.entry.set_sensitive(True)
- else:
- self.label.set_text(_('None'))
- self.entry.set_text('')
- self.entry.set_sensitive(False)
- self.mood = data
-
- def on_ok_button_clicked(self, widget):
- '''Return mood and messsage (None if no mood selected)'''
- message = self.entry.get_text().decode('utf-8')
- self.on_response(self.mood, message)
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
+ COLS = 11
+
+ def __init__(self, on_response, mood=None, text=''):
+ self.on_response = on_response
+ self.mood = mood
+ self.text = text
+ self.xml = gtkgui_helpers.get_gtk_builder('change_mood_dialog.ui')
+
+ self.window = self.xml.get_object('change_mood_dialog')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ self.window.set_title(_('Set Mood'))
+
+ table = self.xml.get_object('mood_icons_table')
+ self.label = self.xml.get_object('mood_label')
+ self.entry = self.xml.get_object('description_entry')
+
+ no_mood_button = self.xml.get_object('no_mood_button')
+ no_mood_button.set_mode(False)
+ no_mood_button.connect('clicked',
+ self.on_mood_button_clicked, None)
+
+ x = 1
+ y = 0
+ self.mood_buttons = {}
+
+ # Order them first
+ self.MOODS = []
+ for mood in pep.MOODS:
+ self.MOODS.append(mood)
+ self.MOODS.sort()
+
+ for mood in self.MOODS:
+ self.mood_buttons[mood] = gtk.RadioButton(no_mood_button)
+ self.mood_buttons[mood].set_mode(False)
+ self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood))
+ self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE)
+ self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood])
+ self.mood_buttons[mood].connect('clicked',
+ self.on_mood_button_clicked, mood)
+ table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1)
+
+ # Calculate the next position
+ x += 1
+ if x >= self.COLS:
+ x = 0
+ y += 1
+
+ if self.mood in pep.MOODS:
+ self.mood_buttons[self.mood].set_active(True)
+ self.label.set_text(pep.MOODS[self.mood])
+ self.entry.set_sensitive(True)
+ if self.text:
+ self.entry.set_text(self.text)
+ else:
+ self.label.set_text(_('None'))
+ self.entry.set_text('')
+ self.entry.set_sensitive(False)
+
+ self.xml.connect_signals(self)
+ self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.window.show_all()
+
+ def on_mood_button_clicked(self, widget, data):
+ if data:
+ self.label.set_text(pep.MOODS[data])
+ self.entry.set_sensitive(True)
+ else:
+ self.label.set_text(_('None'))
+ self.entry.set_text('')
+ self.entry.set_sensitive(False)
+ self.mood = data
+
+ def on_ok_button_clicked(self, widget):
+ '''Return mood and messsage (None if no mood selected)'''
+ message = self.entry.get_text().decode('utf-8')
+ self.on_response(self.mood, message)
+ self.window.destroy()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
class TimeoutDialog:
- """
- Class designed to be derivated to create timeout'd dialogs (dialogs that
- closes automatically after a timeout)
- """
- def __init__(self, timeout, on_timeout):
- self.countdown_left = timeout
- self.countdown_enabled = True
- self.title_text = ''
- self.on_timeout = on_timeout
-
- def run_timeout(self):
- if self.countdown_left > 0:
- self.countdown()
- gobject.timeout_add_seconds(1, self.countdown)
-
- def on_timeout():
- """
- To be implemented in derivated classes
- """
- pass
-
- def countdown(self):
- if self.countdown_enabled:
- if self.countdown_left <= 0:
- self.on_timeout()
- return False
- self.dialog.set_title('%s [%s]' % (self.title_text,
- str(self.countdown_left)))
- self.countdown_left -= 1
- return True
- else:
- self.dialog.set_title(self.title_text)
- return False
+ """
+ Class designed to be derivated to create timeout'd dialogs (dialogs that
+ closes automatically after a timeout)
+ """
+ def __init__(self, timeout, on_timeout):
+ self.countdown_left = timeout
+ self.countdown_enabled = True
+ self.title_text = ''
+ self.on_timeout = on_timeout
+
+ def run_timeout(self):
+ if self.countdown_left > 0:
+ self.countdown()
+ gobject.timeout_add_seconds(1, self.countdown)
+
+ def on_timeout():
+ """
+ To be implemented in derivated classes
+ """
+ pass
+
+ def countdown(self):
+ if self.countdown_enabled:
+ if self.countdown_left <= 0:
+ self.on_timeout()
+ return False
+ self.dialog.set_title('%s [%s]' % (self.title_text,
+ str(self.countdown_left)))
+ self.countdown_left -= 1
+ return True
+ else:
+ self.dialog.set_title(self.title_text)
+ return False
class ChangeStatusMessageDialog(TimeoutDialog):
- def __init__(self, on_response, show=None, show_pep=True):
- countdown_time = gajim.config.get('change_status_window_timeout')
- TimeoutDialog.__init__(self, countdown_time, self.on_timeout)
- self.show = show
- self.pep_dict = {}
- self.show_pep = show_pep
- self.on_response = on_response
- self.xml = gtkgui_helpers.get_gtk_builder('change_status_message_dialog.ui')
- self.dialog = self.xml.get_object('change_status_message_dialog')
- self.dialog.set_transient_for(gajim.interface.roster.window)
- msg = None
- if show:
- uf_show = helpers.get_uf_show(show)
- self.title_text = _('%s Status Message') % uf_show
- msg = gajim.config.get_per('statusmsg', '_last_' + self.show,
- 'message')
- self.pep_dict['activity'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'activity')
- self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'subactivity')
- self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'activity_text')
- self.pep_dict['mood'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'mood')
- self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'mood_text')
- else:
- self.title_text = _('Status Message')
- self.dialog.set_title(self.title_text)
-
- message_textview = self.xml.get_object('message_textview')
- self.message_buffer = message_textview.get_buffer()
- self.message_buffer.connect('changed', self.on_message_buffer_changed)
- if not msg:
- msg = ''
- msg = helpers.from_one_line(msg)
- self.message_buffer.set_text(msg)
-
- # have an empty string selectable, so user can clear msg
- self.preset_messages_dict = {'': ['', '', '', '', '', '']}
- for msg_name in gajim.config.get_per('statusmsg'):
- if msg_name.startswith('_last_'):
- continue
- opts = []
- for opt in ['message', 'activity', 'subactivity', 'activity_text',
- 'mood', 'mood_text']:
- opts.append(gajim.config.get_per('statusmsg', msg_name, opt))
- opts[0] = helpers.from_one_line(opts[0])
- self.preset_messages_dict[msg_name] = opts
- sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict)
-
- self.message_liststore = gtk.ListStore(str) # msg_name
- self.message_combobox = self.xml.get_object('message_combobox')
- self.message_combobox.set_model(self.message_liststore)
- cellrenderertext = gtk.CellRendererText()
- self.message_combobox.pack_start(cellrenderertext, True)
- self.message_combobox.add_attribute(cellrenderertext, 'text', 0)
- for msg_name in sorted_keys_list:
- self.message_liststore.append((msg_name,))
-
- if show_pep:
- self.draw_activity()
- self.draw_mood()
- else:
- # remove acvtivity / mood lines
- self.xml.get_object('activity_label').set_no_show_all(True)
- self.xml.get_object('activity_button').set_no_show_all(True)
- self.xml.get_object('mood_label').set_no_show_all(True)
- self.xml.get_object('mood_button').set_no_show_all(True)
- self.xml.get_object('activity_label').hide()
- self.xml.get_object('activity_button').hide()
- self.xml.get_object('mood_label').hide()
- self.xml.get_object('mood_button').hide()
-
- self.xml.connect_signals(self)
- self.run_timeout()
- self.dialog.connect('response', self.on_dialog_response)
- self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
- self.dialog.show_all()
-
- def draw_activity(self):
- """
- Set activity button
- """
- img = self.xml.get_object('activity_image')
- label = self.xml.get_object('activity_button_label')
- if 'activity' in self.pep_dict and self.pep_dict['activity'] in \
- pep.ACTIVITIES:
- if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] in \
- pep.ACTIVITIES[self.pep_dict['activity']]:
- img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
- self.pep_dict['activity'], self.pep_dict['subactivity']).\
- get_pixbuf())
- else:
- img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
- self.pep_dict['activity']).get_pixbuf())
-# item.set_tooltip_text(pep.ACTIVITIES[category]['category'])
- if self.pep_dict['activity_text']:
- label.set_text(self.pep_dict['activity_text'])
- else:
- label.set_text('')
- else:
- img.set_from_pixbuf(None)
- label.set_text('')
-
- def draw_mood(self):
- """
- Set mood button
- """
- img = self.xml.get_object('mood_image')
- label = self.xml.get_object('mood_button_label')
- if self.pep_dict['mood'] in pep.MOODS:
- img.set_from_pixbuf(gtkgui_helpers.load_mood_icon(
- self.pep_dict['mood']).get_pixbuf())
- if self.pep_dict['mood_text']:
- label.set_text(self.pep_dict['mood_text'])
- else:
- label.set_text('')
- else:
- img.set_from_pixbuf(None)
- label.set_text('')
-
- def on_timeout(self):
- # Prevent GUI freeze when the combobox menu is opened on close
- self.message_combobox.popdown()
- self.dialog.response(gtk.RESPONSE_OK)
-
- def on_dialog_response(self, dialog, response):
- if response == gtk.RESPONSE_OK:
- beg, end = self.message_buffer.get_bounds()
- message = self.message_buffer.get_text(beg, end).decode('utf-8')\
- .strip()
- message = helpers.remove_invalid_xml_chars(message)
- msg = helpers.to_one_line(message)
- if self.show:
- gajim.config.set_per('statusmsg', '_last_' + self.show, 'message',
- msg)
- if self.show_pep:
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'activity', self.pep_dict['activity'])
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'subactivity', self.pep_dict['subactivity'])
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'activity_text', self.pep_dict['activity_text'])
- gajim.config.set_per('statusmsg', '_last_' + self.show, 'mood',
- self.pep_dict['mood'])
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'mood_text', self.pep_dict['mood_text'])
- else:
- message = None # user pressed Cancel button or X wm button
- self.dialog.destroy()
- self.on_response(message, self.pep_dict)
-
- def on_message_combobox_changed(self, widget):
- self.countdown_enabled = False
- model = widget.get_model()
- active = widget.get_active()
- if active < 0:
- return None
- name = model[active][0].decode('utf-8')
- self.message_buffer.set_text(self.preset_messages_dict[name][0])
- self.pep_dict['activity'] = self.preset_messages_dict[name][1]
- self.pep_dict['subactivity'] = self.preset_messages_dict[name][2]
- self.pep_dict['activity_text'] = self.preset_messages_dict[name][3]
- self.pep_dict['mood'] = self.preset_messages_dict[name][4]
- self.pep_dict['mood_text'] = self.preset_messages_dict[name][5]
- self.draw_activity()
- self.draw_mood()
-
- def on_change_status_message_dialog_key_press_event(self, widget, event):
- self.countdown_enabled = False
- if event.keyval == gtk.keysyms.Return or \
- event.keyval == gtk.keysyms.KP_Enter: # catch CTRL+ENTER
- if (event.state & gtk.gdk.CONTROL_MASK):
- self.dialog.response(gtk.RESPONSE_OK)
- # Stop the event
- return True
-
- def on_message_buffer_changed(self, widget):
- self.countdown_enabled = False
- self.toggle_sensitiviy_of_save_as_preset()
-
- def toggle_sensitiviy_of_save_as_preset(self):
- btn = self.xml.get_object('save_as_preset_button')
- if self.message_buffer.get_char_count() == 0:
- btn.set_sensitive(False)
- else:
- btn.set_sensitive(True)
-
- def on_save_as_preset_button_clicked(self, widget):
- self.countdown_enabled = False
- start_iter, finish_iter = self.message_buffer.get_bounds()
- status_message_to_save_as_preset = self.message_buffer.get_text(
- start_iter, finish_iter)
- def on_ok(msg_name):
- msg_text = status_message_to_save_as_preset.decode('utf-8')
- msg_text_1l = helpers.to_one_line(msg_text)
- if not msg_name: # msg_name was ''
- msg_name = msg_text_1l.decode('utf-8')
-
- def on_ok2():
- self.preset_messages_dict[msg_name] = [msg_text, self.pep_dict.get(
- 'activity'), self.pep_dict.get('subactivity'), self.pep_dict.get(
- 'activity_text'), self.pep_dict.get('mood'), self.pep_dict.get(
- 'mood_text')]
- gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l)
- gajim.config.set_per('statusmsg', msg_name, 'activity',
- self.pep_dict.get('activity'))
- gajim.config.set_per('statusmsg', msg_name, 'subactivity',
- self.pep_dict.get('subactivity'))
- gajim.config.set_per('statusmsg', msg_name, 'activity_text',
- self.pep_dict.get('activity_text'))
- gajim.config.set_per('statusmsg', msg_name, 'mood',
- self.pep_dict.get('mood'))
- gajim.config.set_per('statusmsg', msg_name, 'mood_text',
- self.pep_dict.get('mood_text'))
- if msg_name in self.preset_messages_dict:
- ConfirmationDialog(_('Overwrite Status Message?'),
- _('This name is already used. Do you want to overwrite this '
- 'status message?'), on_response_ok=on_ok2)
- return
- gajim.config.add_per('statusmsg', msg_name)
- on_ok2()
- iter_ = self.message_liststore.append((msg_name,))
- # select in combobox the one we just saved
- self.message_combobox.set_active_iter(iter_)
- InputDialog(_('Save as Preset Status Message'),
- _('Please type a name for this status message'), is_modal=False,
- ok_handler=on_ok)
-
- def on_activity_button_clicked(self, widget):
- self.countdown_enabled = False
- def on_response(activity, subactivity, text):
- self.pep_dict['activity'] = activity or ''
- self.pep_dict['subactivity'] = subactivity or ''
- self.pep_dict['activity_text'] = text
- self.draw_activity()
- ChangeActivityDialog(on_response, self.pep_dict['activity'],
- self.pep_dict['subactivity'], self.pep_dict['activity_text'])
-
- def on_mood_button_clicked(self, widget):
- self.countdown_enabled = False
- def on_response(mood, text):
- self.pep_dict['mood'] = mood or ''
- self.pep_dict['mood_text'] = text
- self.draw_mood()
- ChangeMoodDialog(on_response, self.pep_dict['mood'],
- self.pep_dict['mood_text'])
+ def __init__(self, on_response, show=None, show_pep=True):
+ countdown_time = gajim.config.get('change_status_window_timeout')
+ TimeoutDialog.__init__(self, countdown_time, self.on_timeout)
+ self.show = show
+ self.pep_dict = {}
+ self.show_pep = show_pep
+ self.on_response = on_response
+ self.xml = gtkgui_helpers.get_gtk_builder('change_status_message_dialog.ui')
+ self.dialog = self.xml.get_object('change_status_message_dialog')
+ self.dialog.set_transient_for(gajim.interface.roster.window)
+ msg = None
+ if show:
+ uf_show = helpers.get_uf_show(show)
+ self.title_text = _('%s Status Message') % uf_show
+ msg = gajim.config.get_per('statusmsg', '_last_' + self.show,
+ 'message')
+ self.pep_dict['activity'] = gajim.config.get_per('statusmsg',
+ '_last_' + self.show, 'activity')
+ self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg',
+ '_last_' + self.show, 'subactivity')
+ self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg',
+ '_last_' + self.show, 'activity_text')
+ self.pep_dict['mood'] = gajim.config.get_per('statusmsg',
+ '_last_' + self.show, 'mood')
+ self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg',
+ '_last_' + self.show, 'mood_text')
+ else:
+ self.title_text = _('Status Message')
+ self.dialog.set_title(self.title_text)
+
+ message_textview = self.xml.get_object('message_textview')
+ self.message_buffer = message_textview.get_buffer()
+ self.message_buffer.connect('changed', self.on_message_buffer_changed)
+ if not msg:
+ msg = ''
+ msg = helpers.from_one_line(msg)
+ self.message_buffer.set_text(msg)
+
+ # have an empty string selectable, so user can clear msg
+ self.preset_messages_dict = {'': ['', '', '', '', '', '']}
+ for msg_name in gajim.config.get_per('statusmsg'):
+ if msg_name.startswith('_last_'):
+ continue
+ opts = []
+ for opt in ['message', 'activity', 'subactivity', 'activity_text',
+ 'mood', 'mood_text']:
+ opts.append(gajim.config.get_per('statusmsg', msg_name, opt))
+ opts[0] = helpers.from_one_line(opts[0])
+ self.preset_messages_dict[msg_name] = opts
+ sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict)
+
+ self.message_liststore = gtk.ListStore(str) # msg_name
+ self.message_combobox = self.xml.get_object('message_combobox')
+ self.message_combobox.set_model(self.message_liststore)
+ cellrenderertext = gtk.CellRendererText()
+ self.message_combobox.pack_start(cellrenderertext, True)
+ self.message_combobox.add_attribute(cellrenderertext, 'text', 0)
+ for msg_name in sorted_keys_list:
+ self.message_liststore.append((msg_name,))
+
+ if show_pep:
+ self.draw_activity()
+ self.draw_mood()
+ else:
+ # remove acvtivity / mood lines
+ self.xml.get_object('activity_label').set_no_show_all(True)
+ self.xml.get_object('activity_button').set_no_show_all(True)
+ self.xml.get_object('mood_label').set_no_show_all(True)
+ self.xml.get_object('mood_button').set_no_show_all(True)
+ self.xml.get_object('activity_label').hide()
+ self.xml.get_object('activity_button').hide()
+ self.xml.get_object('mood_label').hide()
+ self.xml.get_object('mood_button').hide()
+
+ self.xml.connect_signals(self)
+ self.run_timeout()
+ self.dialog.connect('response', self.on_dialog_response)
+ self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.dialog.show_all()
+
+ def draw_activity(self):
+ """
+ Set activity button
+ """
+ img = self.xml.get_object('activity_image')
+ label = self.xml.get_object('activity_button_label')
+ if 'activity' in self.pep_dict and self.pep_dict['activity'] in \
+ pep.ACTIVITIES:
+ if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] in \
+ pep.ACTIVITIES[self.pep_dict['activity']]:
+ img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
+ self.pep_dict['activity'], self.pep_dict['subactivity']).\
+ get_pixbuf())
+ else:
+ img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
+ self.pep_dict['activity']).get_pixbuf())
+# item.set_tooltip_text(pep.ACTIVITIES[category]['category'])
+ if self.pep_dict['activity_text']:
+ label.set_text(self.pep_dict['activity_text'])
+ else:
+ label.set_text('')
+ else:
+ img.set_from_pixbuf(None)
+ label.set_text('')
+
+ def draw_mood(self):
+ """
+ Set mood button
+ """
+ img = self.xml.get_object('mood_image')
+ label = self.xml.get_object('mood_button_label')
+ if self.pep_dict['mood'] in pep.MOODS:
+ img.set_from_pixbuf(gtkgui_helpers.load_mood_icon(
+ self.pep_dict['mood']).get_pixbuf())
+ if self.pep_dict['mood_text']:
+ label.set_text(self.pep_dict['mood_text'])
+ else:
+ label.set_text('')
+ else:
+ img.set_from_pixbuf(None)
+ label.set_text('')
+
+ def on_timeout(self):
+ # Prevent GUI freeze when the combobox menu is opened on close
+ self.message_combobox.popdown()
+ self.dialog.response(gtk.RESPONSE_OK)
+
+ def on_dialog_response(self, dialog, response):
+ if response == gtk.RESPONSE_OK:
+ beg, end = self.message_buffer.get_bounds()
+ message = self.message_buffer.get_text(beg, end).decode('utf-8')\
+ .strip()
+ message = helpers.remove_invalid_xml_chars(message)
+ msg = helpers.to_one_line(message)
+ if self.show:
+ gajim.config.set_per('statusmsg', '_last_' + self.show, 'message',
+ msg)
+ if self.show_pep:
+ gajim.config.set_per('statusmsg', '_last_' + self.show,
+ 'activity', self.pep_dict['activity'])
+ gajim.config.set_per('statusmsg', '_last_' + self.show,
+ 'subactivity', self.pep_dict['subactivity'])
+ gajim.config.set_per('statusmsg', '_last_' + self.show,
+ 'activity_text', self.pep_dict['activity_text'])
+ gajim.config.set_per('statusmsg', '_last_' + self.show, 'mood',
+ self.pep_dict['mood'])
+ gajim.config.set_per('statusmsg', '_last_' + self.show,
+ 'mood_text', self.pep_dict['mood_text'])
+ else:
+ message = None # user pressed Cancel button or X wm button
+ self.dialog.destroy()
+ self.on_response(message, self.pep_dict)
+
+ def on_message_combobox_changed(self, widget):
+ self.countdown_enabled = False
+ model = widget.get_model()
+ active = widget.get_active()
+ if active < 0:
+ return None
+ name = model[active][0].decode('utf-8')
+ self.message_buffer.set_text(self.preset_messages_dict[name][0])
+ self.pep_dict['activity'] = self.preset_messages_dict[name][1]
+ self.pep_dict['subactivity'] = self.preset_messages_dict[name][2]
+ self.pep_dict['activity_text'] = self.preset_messages_dict[name][3]
+ self.pep_dict['mood'] = self.preset_messages_dict[name][4]
+ self.pep_dict['mood_text'] = self.preset_messages_dict[name][5]
+ self.draw_activity()
+ self.draw_mood()
+
+ def on_change_status_message_dialog_key_press_event(self, widget, event):
+ self.countdown_enabled = False
+ if event.keyval == gtk.keysyms.Return or \
+ event.keyval == gtk.keysyms.KP_Enter: # catch CTRL+ENTER
+ if (event.state & gtk.gdk.CONTROL_MASK):
+ self.dialog.response(gtk.RESPONSE_OK)
+ # Stop the event
+ return True
+
+ def on_message_buffer_changed(self, widget):
+ self.countdown_enabled = False
+ self.toggle_sensitiviy_of_save_as_preset()
+
+ def toggle_sensitiviy_of_save_as_preset(self):
+ btn = self.xml.get_object('save_as_preset_button')
+ if self.message_buffer.get_char_count() == 0:
+ btn.set_sensitive(False)
+ else:
+ btn.set_sensitive(True)
+
+ def on_save_as_preset_button_clicked(self, widget):
+ self.countdown_enabled = False
+ start_iter, finish_iter = self.message_buffer.get_bounds()
+ status_message_to_save_as_preset = self.message_buffer.get_text(
+ start_iter, finish_iter)
+ def on_ok(msg_name):
+ msg_text = status_message_to_save_as_preset.decode('utf-8')
+ msg_text_1l = helpers.to_one_line(msg_text)
+ if not msg_name: # msg_name was ''
+ msg_name = msg_text_1l.decode('utf-8')
+
+ def on_ok2():
+ self.preset_messages_dict[msg_name] = [msg_text, self.pep_dict.get(
+ 'activity'), self.pep_dict.get('subactivity'), self.pep_dict.get(
+ 'activity_text'), self.pep_dict.get('mood'), self.pep_dict.get(
+ 'mood_text')]
+ gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l)
+ gajim.config.set_per('statusmsg', msg_name, 'activity',
+ self.pep_dict.get('activity'))
+ gajim.config.set_per('statusmsg', msg_name, 'subactivity',
+ self.pep_dict.get('subactivity'))
+ gajim.config.set_per('statusmsg', msg_name, 'activity_text',
+ self.pep_dict.get('activity_text'))
+ gajim.config.set_per('statusmsg', msg_name, 'mood',
+ self.pep_dict.get('mood'))
+ gajim.config.set_per('statusmsg', msg_name, 'mood_text',
+ self.pep_dict.get('mood_text'))
+ if msg_name in self.preset_messages_dict:
+ ConfirmationDialog(_('Overwrite Status Message?'),
+ _('This name is already used. Do you want to overwrite this '
+ 'status message?'), on_response_ok=on_ok2)
+ return
+ gajim.config.add_per('statusmsg', msg_name)
+ on_ok2()
+ iter_ = self.message_liststore.append((msg_name,))
+ # select in combobox the one we just saved
+ self.message_combobox.set_active_iter(iter_)
+ InputDialog(_('Save as Preset Status Message'),
+ _('Please type a name for this status message'), is_modal=False,
+ ok_handler=on_ok)
+
+ def on_activity_button_clicked(self, widget):
+ self.countdown_enabled = False
+ def on_response(activity, subactivity, text):
+ self.pep_dict['activity'] = activity or ''
+ self.pep_dict['subactivity'] = subactivity or ''
+ self.pep_dict['activity_text'] = text
+ self.draw_activity()
+ ChangeActivityDialog(on_response, self.pep_dict['activity'],
+ self.pep_dict['subactivity'], self.pep_dict['activity_text'])
+
+ def on_mood_button_clicked(self, widget):
+ self.countdown_enabled = False
+ def on_response(mood, text):
+ self.pep_dict['mood'] = mood or ''
+ self.pep_dict['mood_text'] = text
+ self.draw_mood()
+ ChangeMoodDialog(on_response, self.pep_dict['mood'],
+ self.pep_dict['mood_text'])
class 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:
- # fill accounts with active accounts
- accounts = []
- for account in gajim.connections.keys():
- if gajim.connections[account].connected > 1:
- accounts.append(account)
- if not accounts:
- return
- if len(accounts) == 1:
- self.account = account
- else:
- accounts = [self.account]
- if self.account:
- location = gajim.interface.instances[self.account]
- else:
- location = gajim.interface.instances
- if 'add_contact' in location:
- location['add_contact'].window.present()
- # An instance is already opened
- return
- location['add_contact'] = self
- self.xml = gtkgui_helpers.get_gtk_builder('add_new_contact_window.ui')
- self.xml.connect_signals(self)
- self.window = self.xml.get_object('add_new_contact_window')
- for w in ('account_combobox', 'account_hbox', 'account_label',
- 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox',
- 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow',
- 'save_message_checkbutton', 'register_hbox', 'subscription_table',
- 'add_button', 'message_textview', 'connected_label',
- 'group_comboboxentry', 'auto_authorize_checkbutton'):
- self.__dict__[w] = self.xml.get_object(w)
- if account and len(gajim.connections) >= 2:
- prompt_text = \
+ """
+ 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:
+ # fill accounts with active accounts
+ accounts = []
+ for account in gajim.connections.keys():
+ if gajim.connections[account].connected > 1:
+ accounts.append(account)
+ if not accounts:
+ return
+ if len(accounts) == 1:
+ self.account = account
+ else:
+ accounts = [self.account]
+ if self.account:
+ location = gajim.interface.instances[self.account]
+ else:
+ location = gajim.interface.instances
+ if 'add_contact' in location:
+ location['add_contact'].window.present()
+ # An instance is already opened
+ return
+ location['add_contact'] = self
+ self.xml = gtkgui_helpers.get_gtk_builder('add_new_contact_window.ui')
+ self.xml.connect_signals(self)
+ self.window = self.xml.get_object('add_new_contact_window')
+ for w in ('account_combobox', 'account_hbox', 'account_label',
+ 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox',
+ 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow',
+ 'save_message_checkbutton', 'register_hbox', 'subscription_table',
+ 'add_button', 'message_textview', 'connected_label',
+ 'group_comboboxentry', 'auto_authorize_checkbutton'):
+ self.__dict__[w] = self.xml.get_object(w)
+ if account and len(gajim.connections) >= 2:
+ prompt_text = \
_('Please fill in the data of the contact you want to add in account %s') % account
- else:
- prompt_text = \
- _('Please fill in the data of the contact you want to add')
- self.xml.get_object('prompt_label').set_text(prompt_text)
- self.agents = {'jabber': []}
- # types to which we are not subscribed but account has an agent for it
- self.available_types = []
- for acct in accounts:
- for j in gajim.contacts.get_jid_list(acct):
- if gajim.jid_is_transport(j):
- type_ = gajim.get_transport_name_from_jid(j, False)
- if not type_:
- continue
- if type_ in self.agents:
- self.agents[type_].append(j)
- else:
- self.agents[type_] = [j]
- # Now add the one to which we can register
- for acct in accounts:
- for type_ in gajim.connections[acct].available_transports:
- if type_ in self.agents:
- continue
- self.agents[type_] = []
- for jid_ in gajim.connections[acct].available_transports[type_]:
- if not jid_ in self.agents[type_]:
- self.agents[type_].append(jid_)
- self.available_types.append(type_)
- # Combobox with transport/jabber icons
- liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str)
- cell = gtk.CellRendererPixbuf()
- self.protocol_combobox.pack_start(cell, False)
- self.protocol_combobox.add_attribute(cell, 'pixbuf', 1)
- cell = gtk.CellRendererText()
- cell.set_property('xpad', 5)
- self.protocol_combobox.pack_start(cell, True)
- self.protocol_combobox.add_attribute(cell, 'text', 0)
- self.protocol_combobox.set_model(liststore)
- uf_type = {'jabber': 'Jabber', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu',
- 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'}
- # Jabber as first
- img = gajim.interface.jabber_state_images['16']['online']
- liststore.append(['Jabber', img.get_pixbuf(), 'jabber'])
- for type_ in self.agents:
- if type_ == 'jabber':
- continue
- imgs = gajim.interface.roster.transports_state_images
- img = None
- if type_ in imgs['16'] and 'online' in imgs['16'][type_]:
- img = imgs['16'][type_]['online']
- if type_ in uf_type:
- liststore.append([uf_type[type_], img.get_pixbuf(), type_])
- else:
- liststore.append([type_, img.get_pixbuf(), type_])
- else:
- liststore.append([type_, img, type_])
- self.protocol_combobox.set_active(0)
- self.auto_authorize_checkbutton.show()
- liststore = gtk.ListStore(str)
- self.protocol_jid_combobox.set_model(liststore)
- if jid:
- type_ = gajim.get_transport_name_from_jid(jid)
- if not type_:
- type_ = 'jabber'
- if type_ == 'jabber':
- self.uid_entry.set_text(jid)
- else:
- uid, transport = gajim.get_name_and_server_from_jid(jid)
- self.uid_entry.set_text(uid.replace('%', '@', 1))
- # set protocol_combobox
- model = self.protocol_combobox.get_model()
- iter_ = model.get_iter_first()
- i = 0
- while iter_:
- if model[iter_][2] == type_:
- self.protocol_combobox.set_active(i)
- break
- iter_ = model.iter_next(iter_)
- i += 1
-
- # set protocol_jid_combobox
- self.protocol_jid_combobox.set_active(0)
- model = self.protocol_jid_combobox.get_model()
- iter_ = model.get_iter_first()
- i = 0
- while iter_:
- if model[iter_][0] == transport:
- self.protocol_jid_combobox.set_active(i)
- break
- iter_ = model.iter_next(iter_)
- i += 1
- if user_nick:
- self.nickname_entry.set_text(user_nick)
- self.nickname_entry.grab_focus()
- else:
- self.uid_entry.grab_focus()
- group_names = []
- for acct in accounts:
- for g in gajim.groups[acct].keys():
- if g not in helpers.special_groups and g not in group_names:
- group_names.append(g)
- group_names.sort()
- i = 0
- for g in group_names:
- self.group_comboboxentry.append_text(g)
- if group == g:
- self.group_comboboxentry.set_active(i)
- i += 1
-
- self.window.show_all()
-
- if self.account:
- self.account_label.hide()
- self.account_hbox.hide()
- else:
- liststore = gtk.ListStore(str, str)
- for acct in accounts:
- liststore.append([acct, acct])
- self.account_combobox.set_model(liststore)
- self.account_combobox.set_active(0)
-
- if self.account:
- message_buffer = self.message_textview.get_buffer()
- message_buffer.set_text(helpers.get_subscription_request_msg(
- self.account))
-
- def on_add_new_contact_window_destroy(self, widget):
- if self.account:
- location = gajim.interface.instances[self.account]
- else:
- location = gajim.interface.instances
- del location['add_contact']
-
- def on_register_button_clicked(self, widget):
- jid = self.protocol_jid_combobox.get_active_text().decode('utf-8')
- gajim.connections[self.account].request_register_agent_info(jid)
-
- def on_add_new_contact_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape: # ESCAPE
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- """
- When Cancel button is clicked
- """
- self.window.destroy()
-
- def on_add_button_clicked(self, widget):
- """
- When Subscribe button is clicked
- """
- jid = self.uid_entry.get_text().decode('utf-8').strip()
- if not jid:
- return
-
- model = self.protocol_combobox.get_model()
- iter_ = self.protocol_combobox.get_active_iter()
- type_ = model[iter_][2]
- if type_ != 'jabber':
- transport = self.protocol_jid_combobox.get_active_text().decode(
- 'utf-8')
- jid = jid.replace('@', '%') + '@' + transport
-
- # check if jid is conform to RFC and stringprep it
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat, s:
- pritext = _('Invalid User ID')
- ErrorDialog(pritext, str(s))
- return
-
- # No resource in jid
- if jid.find('/') >= 0:
- pritext = _('Invalid User ID')
- ErrorDialog(pritext, _('The user ID must not contain a resource.'))
- return
-
- if jid == gajim.get_jid_from_account(self.account):
- pritext = _('Invalid User ID')
- ErrorDialog(pritext, _('You cannot add yourself to your roster.'))
- return
-
- nickname = self.nickname_entry.get_text().decode('utf-8') or ''
- # get value of account combobox, if account was not specified
- if not self.account:
- model = self.account_combobox.get_model()
- index = self.account_combobox.get_active()
- self.account = model[index][1]
-
- # Check if jid is already in roster
- if jid in gajim.contacts.get_jid_list(self.account):
- c = gajim.contacts.get_first_contact_from_jid(self.account, jid)
- if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'):
- ErrorDialog(_('Contact already in roster'),
- _('This contact is already listed in your roster.'))
- return
-
- if type_ == 'jabber':
- message_buffer = self.message_textview.get_buffer()
- start_iter = message_buffer.get_start_iter()
- end_iter = message_buffer.get_end_iter()
- message = message_buffer.get_text(start_iter, end_iter).decode('utf-8')
- if self.save_message_checkbutton.get_active():
- gajim.config.set_per('accounts', self.account,
- 'subscription_request_msg', message)
- else:
- message= ''
- group = self.group_comboboxentry.child.get_text().decode('utf-8')
- groups = []
- if group:
- groups = [group]
- auto_auth = self.auto_authorize_checkbutton.get_active()
- gajim.interface.roster.req_sub(self, jid, message, self.account,
- groups=groups, nickname=nickname, auto_auth=auto_auth)
- self.window.destroy()
-
- def on_account_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- account = model[iter_][0]
- message_buffer = self.message_textview.get_buffer()
- message_buffer.set_text(helpers.get_subscription_request_msg(account))
-
- def on_protocol_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- type_ = model[iter_][2]
- model = self.protocol_jid_combobox.get_model()
- model.clear()
- if len(self.agents[type_]):
- for jid_ in self.agents[type_]:
- model.append([jid_])
- self.protocol_jid_combobox.set_active(0)
- if len(self.agents[type_]) > 1:
- self.protocol_jid_combobox.show()
- else:
- self.protocol_jid_combobox.hide()
- if type_ in self.uid_labels:
- self.uid_label.set_text(self.uid_labels[type_])
- else:
- self.uid_label.set_text(_('User ID:'))
- if type_ == 'jabber':
- self.message_scrolledwindow.show()
- self.save_message_checkbutton.show()
- else:
- self.message_scrolledwindow.hide()
- self.save_message_checkbutton.hide()
- if type_ in self.available_types:
- self.register_hbox.show()
- self.auto_authorize_checkbutton.hide()
- self.connected_label.hide()
- self.subscription_table.hide()
- self.add_button.set_sensitive(False)
- else:
- self.register_hbox.hide()
- if type_ != 'jabber':
- jid = self.protocol_jid_combobox.get_active_text()
- contact = gajim.contacts.get_first_contact_from_jid(self.account,
- jid)
- if contact.show in ('offline', 'error'):
- self.subscription_table.hide()
- self.connected_label.show()
- self.add_button.set_sensitive(False)
- self.auto_authorize_checkbutton.hide()
- return
- self.subscription_table.show()
- self.auto_authorize_checkbutton.show()
- self.connected_label.hide()
- self.add_button.set_sensitive(True)
-
- def transport_signed_in(self, jid):
- if self.protocol_jid_combobox.get_active_text() == jid:
- self.register_hbox.hide()
- self.connected_label.hide()
- self.subscription_table.show()
- self.auto_authorize_checkbutton.show()
- self.add_button.set_sensitive(True)
-
- def transport_signed_out(self, jid):
- if self.protocol_jid_combobox.get_active_text() == jid:
- self.subscription_table.hide()
- self.auto_authorize_checkbutton.hide()
- self.connected_label.show()
- self.add_button.set_sensitive(False)
+ else:
+ prompt_text = \
+ _('Please fill in the data of the contact you want to add')
+ self.xml.get_object('prompt_label').set_text(prompt_text)
+ self.agents = {'jabber': []}
+ # types to which we are not subscribed but account has an agent for it
+ self.available_types = []
+ for acct in accounts:
+ for j in gajim.contacts.get_jid_list(acct):
+ if gajim.jid_is_transport(j):
+ type_ = gajim.get_transport_name_from_jid(j, False)
+ if not type_:
+ continue
+ if type_ in self.agents:
+ self.agents[type_].append(j)
+ else:
+ self.agents[type_] = [j]
+ # Now add the one to which we can register
+ for acct in accounts:
+ for type_ in gajim.connections[acct].available_transports:
+ if type_ in self.agents:
+ continue
+ self.agents[type_] = []
+ for jid_ in gajim.connections[acct].available_transports[type_]:
+ if not jid_ in self.agents[type_]:
+ self.agents[type_].append(jid_)
+ self.available_types.append(type_)
+ # Combobox with transport/jabber icons
+ liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str)
+ cell = gtk.CellRendererPixbuf()
+ self.protocol_combobox.pack_start(cell, False)
+ self.protocol_combobox.add_attribute(cell, 'pixbuf', 1)
+ cell = gtk.CellRendererText()
+ cell.set_property('xpad', 5)
+ self.protocol_combobox.pack_start(cell, True)
+ self.protocol_combobox.add_attribute(cell, 'text', 0)
+ self.protocol_combobox.set_model(liststore)
+ uf_type = {'jabber': 'Jabber', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu',
+ 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'}
+ # Jabber as first
+ img = gajim.interface.jabber_state_images['16']['online']
+ liststore.append(['Jabber', img.get_pixbuf(), 'jabber'])
+ for type_ in self.agents:
+ if type_ == 'jabber':
+ continue
+ imgs = gajim.interface.roster.transports_state_images
+ img = None
+ if type_ in imgs['16'] and 'online' in imgs['16'][type_]:
+ img = imgs['16'][type_]['online']
+ if type_ in uf_type:
+ liststore.append([uf_type[type_], img.get_pixbuf(), type_])
+ else:
+ liststore.append([type_, img.get_pixbuf(), type_])
+ else:
+ liststore.append([type_, img, type_])
+ self.protocol_combobox.set_active(0)
+ self.auto_authorize_checkbutton.show()
+ liststore = gtk.ListStore(str)
+ self.protocol_jid_combobox.set_model(liststore)
+ if jid:
+ type_ = gajim.get_transport_name_from_jid(jid)
+ if not type_:
+ type_ = 'jabber'
+ if type_ == 'jabber':
+ self.uid_entry.set_text(jid)
+ else:
+ uid, transport = gajim.get_name_and_server_from_jid(jid)
+ self.uid_entry.set_text(uid.replace('%', '@', 1))
+ # set protocol_combobox
+ model = self.protocol_combobox.get_model()
+ iter_ = model.get_iter_first()
+ i = 0
+ while iter_:
+ if model[iter_][2] == type_:
+ self.protocol_combobox.set_active(i)
+ break
+ iter_ = model.iter_next(iter_)
+ i += 1
+
+ # set protocol_jid_combobox
+ self.protocol_jid_combobox.set_active(0)
+ model = self.protocol_jid_combobox.get_model()
+ iter_ = model.get_iter_first()
+ i = 0
+ while iter_:
+ if model[iter_][0] == transport:
+ self.protocol_jid_combobox.set_active(i)
+ break
+ iter_ = model.iter_next(iter_)
+ i += 1
+ if user_nick:
+ self.nickname_entry.set_text(user_nick)
+ self.nickname_entry.grab_focus()
+ else:
+ self.uid_entry.grab_focus()
+ group_names = []
+ for acct in accounts:
+ for g in gajim.groups[acct].keys():
+ if g not in helpers.special_groups and g not in group_names:
+ group_names.append(g)
+ group_names.sort()
+ i = 0
+ for g in group_names:
+ self.group_comboboxentry.append_text(g)
+ if group == g:
+ self.group_comboboxentry.set_active(i)
+ i += 1
+
+ self.window.show_all()
+
+ if self.account:
+ self.account_label.hide()
+ self.account_hbox.hide()
+ else:
+ liststore = gtk.ListStore(str, str)
+ for acct in accounts:
+ liststore.append([acct, acct])
+ self.account_combobox.set_model(liststore)
+ self.account_combobox.set_active(0)
+
+ if self.account:
+ message_buffer = self.message_textview.get_buffer()
+ message_buffer.set_text(helpers.get_subscription_request_msg(
+ self.account))
+
+ def on_add_new_contact_window_destroy(self, widget):
+ if self.account:
+ location = gajim.interface.instances[self.account]
+ else:
+ location = gajim.interface.instances
+ del location['add_contact']
+
+ def on_register_button_clicked(self, widget):
+ jid = self.protocol_jid_combobox.get_active_text().decode('utf-8')
+ gajim.connections[self.account].request_register_agent_info(jid)
+
+ def on_add_new_contact_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape: # ESCAPE
+ self.window.destroy()
+
+ def on_cancel_button_clicked(self, widget):
+ """
+ When Cancel button is clicked
+ """
+ self.window.destroy()
+
+ def on_add_button_clicked(self, widget):
+ """
+ When Subscribe button is clicked
+ """
+ jid = self.uid_entry.get_text().decode('utf-8').strip()
+ if not jid:
+ return
+
+ model = self.protocol_combobox.get_model()
+ iter_ = self.protocol_combobox.get_active_iter()
+ type_ = model[iter_][2]
+ if type_ != 'jabber':
+ transport = self.protocol_jid_combobox.get_active_text().decode(
+ 'utf-8')
+ jid = jid.replace('@', '%') + '@' + transport
+
+ # check if jid is conform to RFC and stringprep it
+ try:
+ jid = helpers.parse_jid(jid)
+ except helpers.InvalidFormat, s:
+ pritext = _('Invalid User ID')
+ ErrorDialog(pritext, str(s))
+ return
+
+ # No resource in jid
+ if jid.find('/') >= 0:
+ pritext = _('Invalid User ID')
+ ErrorDialog(pritext, _('The user ID must not contain a resource.'))
+ return
+
+ if jid == gajim.get_jid_from_account(self.account):
+ pritext = _('Invalid User ID')
+ ErrorDialog(pritext, _('You cannot add yourself to your roster.'))
+ return
+
+ nickname = self.nickname_entry.get_text().decode('utf-8') or ''
+ # get value of account combobox, if account was not specified
+ if not self.account:
+ model = self.account_combobox.get_model()
+ index = self.account_combobox.get_active()
+ self.account = model[index][1]
+
+ # Check if jid is already in roster
+ if jid in gajim.contacts.get_jid_list(self.account):
+ c = gajim.contacts.get_first_contact_from_jid(self.account, jid)
+ if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'):
+ ErrorDialog(_('Contact already in roster'),
+ _('This contact is already listed in your roster.'))
+ return
+
+ if type_ == 'jabber':
+ message_buffer = self.message_textview.get_buffer()
+ start_iter = message_buffer.get_start_iter()
+ end_iter = message_buffer.get_end_iter()
+ message = message_buffer.get_text(start_iter, end_iter).decode('utf-8')
+ if self.save_message_checkbutton.get_active():
+ gajim.config.set_per('accounts', self.account,
+ 'subscription_request_msg', message)
+ else:
+ message= ''
+ group = self.group_comboboxentry.child.get_text().decode('utf-8')
+ groups = []
+ if group:
+ groups = [group]
+ auto_auth = self.auto_authorize_checkbutton.get_active()
+ gajim.interface.roster.req_sub(self, jid, message, self.account,
+ groups=groups, nickname=nickname, auto_auth=auto_auth)
+ self.window.destroy()
+
+ def on_account_combobox_changed(self, widget):
+ model = widget.get_model()
+ iter_ = widget.get_active_iter()
+ account = model[iter_][0]
+ message_buffer = self.message_textview.get_buffer()
+ message_buffer.set_text(helpers.get_subscription_request_msg(account))
+
+ def on_protocol_combobox_changed(self, widget):
+ model = widget.get_model()
+ iter_ = widget.get_active_iter()
+ type_ = model[iter_][2]
+ model = self.protocol_jid_combobox.get_model()
+ model.clear()
+ if len(self.agents[type_]):
+ for jid_ in self.agents[type_]:
+ model.append([jid_])
+ self.protocol_jid_combobox.set_active(0)
+ if len(self.agents[type_]) > 1:
+ self.protocol_jid_combobox.show()
+ else:
+ self.protocol_jid_combobox.hide()
+ if type_ in self.uid_labels:
+ self.uid_label.set_text(self.uid_labels[type_])
+ else:
+ self.uid_label.set_text(_('User ID:'))
+ if type_ == 'jabber':
+ self.message_scrolledwindow.show()
+ self.save_message_checkbutton.show()
+ else:
+ self.message_scrolledwindow.hide()
+ self.save_message_checkbutton.hide()
+ if type_ in self.available_types:
+ self.register_hbox.show()
+ self.auto_authorize_checkbutton.hide()
+ self.connected_label.hide()
+ self.subscription_table.hide()
+ self.add_button.set_sensitive(False)
+ else:
+ self.register_hbox.hide()
+ if type_ != 'jabber':
+ jid = self.protocol_jid_combobox.get_active_text()
+ contact = gajim.contacts.get_first_contact_from_jid(self.account,
+ jid)
+ if contact.show in ('offline', 'error'):
+ self.subscription_table.hide()
+ self.connected_label.show()
+ self.add_button.set_sensitive(False)
+ self.auto_authorize_checkbutton.hide()
+ return
+ self.subscription_table.show()
+ self.auto_authorize_checkbutton.show()
+ self.connected_label.hide()
+ self.add_button.set_sensitive(True)
+
+ def transport_signed_in(self, jid):
+ if self.protocol_jid_combobox.get_active_text() == jid:
+ self.register_hbox.hide()
+ self.connected_label.hide()
+ self.subscription_table.show()
+ self.auto_authorize_checkbutton.show()
+ self.add_button.set_sensitive(True)
+
+ def transport_signed_out(self, jid):
+ if self.protocol_jid_combobox.get_active_text() == jid:
+ self.subscription_table.hide()
+ self.auto_authorize_checkbutton.hide()
+ self.connected_label.show()
+ self.add_button.set_sensitive(False)
class AboutDialog:
- """
- Class for about dialog
- """
-
- def __init__(self):
- dlg = gtk.AboutDialog()
- dlg.set_transient_for(gajim.interface.roster.window)
- dlg.set_name('Gajim')
- dlg.set_version(gajim.version)
- s = u'Copyright © 2003-2009 Gajim Team'
- dlg.set_copyright(s)
- copying_file_path = self.get_path('COPYING')
- if copying_file_path:
- text = open(copying_file_path).read()
- dlg.set_license(text)
-
- dlg.set_comments('%s\n%s %s\n%s %s'
- % (_('A GTK+ jabber client'), \
- _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \
- _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version)))
- dlg.set_website('http://www.gajim.org/')
-
- authors_file_path = self.get_path('AUTHORS')
- if authors_file_path:
- authors = []
- authors_file = open(authors_file_path).read()
- authors_file = authors_file.split('\n')
- for author in authors_file:
- if author == 'CURRENT DEVELOPERS:':
- authors.append(_('Current Developers:'))
- elif author == 'PAST DEVELOPERS:':
- authors.append('\n' + _('Past Developers:'))
- elif author != '': # Real author line
- authors.append(author)
-
- thanks_file_path = self.get_path('THANKS')
- if thanks_file_path:
- authors.append('\n' + _('THANKS:'))
-
- text = open(thanks_file_path).read()
- text_splitted = text.split('\n')
- text = '\n'.join(text_splitted[:-2]) # remove one english sentence
- # and add it manually as translatable
- text += '\n%s\n' % _('Last but not least, we would like to thank all '
- 'the package maintainers.')
- authors.append(text)
-
- dlg.set_authors(authors)
-
- dlg.props.wrap_license = True
-
- pixbuf = gtkgui_helpers.get_icon_pixmap('gajim-about', 128)
-
- dlg.set_logo(pixbuf)
- #here you write your name in the form Name FamilyName <someone@somewhere>
- dlg.set_translator_credits(_('translator-credits'))
-
- thanks_artists_file_path = self.get_path('THANKS.artists')
- if thanks_artists_file_path:
- artists_text = open(thanks_artists_file_path).read()
- artists = artists_text.split('\n')
- dlg.set_artists(artists)
- # connect close button to destroy() function
- for button in dlg.action_area.get_children():
- if button.get_property('label') == gtk.STOCK_CLOSE:
- button.connect('clicked', lambda x:dlg.destroy())
- dlg.show_all()
-
- def tuple2str(self, tuple_):
- str_ = ''
- for num in tuple_:
- str_ += str(num) + '.'
- return str_[0:-1] # remove latest .
-
- def get_path(self, filename):
- """
- Where can we find this Credits file?
- """
- if os.path.isfile(os.path.join(gajim.defs.docdir, filename)):
- return os.path.join(gajim.defs.docdir, filename)
- elif os.path.isfile('../' + filename):
- return ('../' + filename)
- else:
- return None
+ """
+ Class for about dialog
+ """
+
+ def __init__(self):
+ dlg = gtk.AboutDialog()
+ dlg.set_transient_for(gajim.interface.roster.window)
+ dlg.set_name('Gajim')
+ dlg.set_version(gajim.version)
+ s = u'Copyright © 2003-2009 Gajim Team'
+ dlg.set_copyright(s)
+ copying_file_path = self.get_path('COPYING')
+ if copying_file_path:
+ text = open(copying_file_path).read()
+ dlg.set_license(text)
+
+ dlg.set_comments('%s\n%s %s\n%s %s'
+ % (_('A GTK+ jabber client'), \
+ _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \
+ _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version)))
+ dlg.set_website('http://www.gajim.org/')
+
+ authors_file_path = self.get_path('AUTHORS')
+ if authors_file_path:
+ authors = []
+ authors_file = open(authors_file_path).read()
+ authors_file = authors_file.split('\n')
+ for author in authors_file:
+ if author == 'CURRENT DEVELOPERS:':
+ authors.append(_('Current Developers:'))
+ elif author == 'PAST DEVELOPERS:':
+ authors.append('\n' + _('Past Developers:'))
+ elif author != '': # Real author line
+ authors.append(author)
+
+ thanks_file_path = self.get_path('THANKS')
+ if thanks_file_path:
+ authors.append('\n' + _('THANKS:'))
+
+ text = open(thanks_file_path).read()
+ text_splitted = text.split('\n')
+ text = '\n'.join(text_splitted[:-2]) # remove one english sentence
+ # and add it manually as translatable
+ text += '\n%s\n' % _('Last but not least, we would like to thank all '
+ 'the package maintainers.')
+ authors.append(text)
+
+ dlg.set_authors(authors)
+
+ dlg.props.wrap_license = True
+
+ pixbuf = gtkgui_helpers.get_icon_pixmap('gajim-about', 128)
+
+ dlg.set_logo(pixbuf)
+ #here you write your name in the form Name FamilyName <someone@somewhere>
+ dlg.set_translator_credits(_('translator-credits'))
+
+ thanks_artists_file_path = self.get_path('THANKS.artists')
+ if thanks_artists_file_path:
+ artists_text = open(thanks_artists_file_path).read()
+ artists = artists_text.split('\n')
+ dlg.set_artists(artists)
+ # connect close button to destroy() function
+ for button in dlg.action_area.get_children():
+ if button.get_property('label') == gtk.STOCK_CLOSE:
+ button.connect('clicked', lambda x:dlg.destroy())
+ dlg.show_all()
+
+ def tuple2str(self, tuple_):
+ str_ = ''
+ for num in tuple_:
+ str_ += str(num) + '.'
+ return str_[0:-1] # remove latest .
+
+ def get_path(self, filename):
+ """
+ Where can we find this Credits file?
+ """
+ if os.path.isfile(os.path.join(gajim.defs.docdir, filename)):
+ return os.path.join(gajim.defs.docdir, filename)
+ elif os.path.isfile('../' + filename):
+ return ('../' + filename)
+ else:
+ return None
class Dialog(gtk.Dialog):
- def __init__(self, parent, title, buttons, default=None,
- on_response_ok=None, on_response_cancel=None):
- gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR)
-
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
- self.set_border_width(6)
- self.vbox.set_spacing(12)
- self.set_resizable(False)
-
- possible_responses = {gtk.STOCK_OK: self.on_response_ok,
- gtk.STOCK_CANCEL: self.on_response_cancel}
- for stock, response in buttons:
- b = self.add_button(stock, response)
- for response in possible_responses:
- if stock == response:
- b.connect('clicked', possible_responses[response])
- break
-
- if default is not None:
- self.set_default_response(default)
- else:
- self.set_default_response(buttons[-1][1])
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](*self.user_response_ok[1:])
- else:
- self.user_response_ok()
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_ok[1:])
- else:
- self.user_response_cancel()
- self.destroy()
-
- def just_destroy(self, widget):
- self.destroy()
-
- def get_button(self, index):
- buttons = self.action_area.get_children()
- return index < len(buttons) and buttons[index] or None
+ def __init__(self, parent, title, buttons, default=None,
+ on_response_ok=None, on_response_cancel=None):
+ gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR)
+
+ self.user_response_ok = on_response_ok
+ self.user_response_cancel = on_response_cancel
+ self.set_border_width(6)
+ self.vbox.set_spacing(12)
+ self.set_resizable(False)
+
+ possible_responses = {gtk.STOCK_OK: self.on_response_ok,
+ gtk.STOCK_CANCEL: self.on_response_cancel}
+ for stock, response in buttons:
+ b = self.add_button(stock, response)
+ for response in possible_responses:
+ if stock == response:
+ b.connect('clicked', possible_responses[response])
+ break
+
+ if default is not None:
+ self.set_default_response(default)
+ else:
+ self.set_default_response(buttons[-1][1])
+
+ def on_response_ok(self, widget):
+ if self.user_response_ok:
+ if isinstance(self.user_response_ok, tuple):
+ self.user_response_ok[0](*self.user_response_ok[1:])
+ else:
+ self.user_response_ok()
+ self.destroy()
+
+ def on_response_cancel(self, widget):
+ if self.user_response_cancel:
+ if isinstance(self.user_response_cancel, tuple):
+ self.user_response_cancel[0](*self.user_response_ok[1:])
+ else:
+ self.user_response_cancel()
+ self.destroy()
+
+ def just_destroy(self, widget):
+ self.destroy()
+
+ def get_button(self, index):
+ buttons = self.action_area.get_children()
+ return index < len(buttons) and buttons[index] or None
class HigDialog(gtk.MessageDialog):
- def __init__(self, parent, type_, buttons, pritext, sectext,
- on_response_ok=None, on_response_cancel=None, on_response_yes=None,
- on_response_no=None):
- self.call_cancel_on_destroy = True
- gtk.MessageDialog.__init__(self, parent,
- gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
- type_, buttons, message_format = pritext)
-
- self.format_secondary_markup(sectext)
-
- buttons = self.action_area.get_children()
- self.possible_responses = {gtk.STOCK_OK: on_response_ok,
- gtk.STOCK_CANCEL: on_response_cancel, gtk.STOCK_YES: on_response_yes,
- gtk.STOCK_NO: on_response_no}
- for b in buttons:
- for response in self.possible_responses:
- if b.get_label() == response:
- if not self.possible_responses[response]:
- b.connect('clicked', self.just_destroy)
- elif isinstance(self.possible_responses[response], tuple):
- if len(self.possible_responses[response]) == 1:
- b.connect('clicked', self.possible_responses[response][0])
- else:
- b.connect('clicked', *self.possible_responses[response])
- else:
- b.connect('clicked', self.possible_responses[response])
- break
-
- self.connect('destroy', self.on_dialog_destroy)
-
- def on_dialog_destroy(self, widget):
- if not self.call_cancel_on_destroy:
- return
- cancel_handler = self.possible_responses[gtk.STOCK_CANCEL]
- if not cancel_handler:
- return False
- if isinstance(cancel_handler, tuple):
- cancel_handler[0](None, *cancel_handler[1:])
- else:
- cancel_handler(None)
-
- def just_destroy(self, widget):
- self.destroy()
-
- def popup(self):
- """
- Show dialog
- """
- vb = self.get_children()[0].get_children()[0] # Give focus to top vbox
- vb.set_flags(gtk.CAN_FOCUS)
- vb.grab_focus()
- self.show_all()
+ def __init__(self, parent, type_, buttons, pritext, sectext,
+ on_response_ok=None, on_response_cancel=None, on_response_yes=None,
+ on_response_no=None):
+ self.call_cancel_on_destroy = True
+ gtk.MessageDialog.__init__(self, parent,
+ gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
+ type_, buttons, message_format = pritext)
+
+ self.format_secondary_markup(sectext)
+
+ buttons = self.action_area.get_children()
+ self.possible_responses = {gtk.STOCK_OK: on_response_ok,
+ gtk.STOCK_CANCEL: on_response_cancel, gtk.STOCK_YES: on_response_yes,
+ gtk.STOCK_NO: on_response_no}
+ for b in buttons:
+ for response in self.possible_responses:
+ if b.get_label() == response:
+ if not self.possible_responses[response]:
+ b.connect('clicked', self.just_destroy)
+ elif isinstance(self.possible_responses[response], tuple):
+ if len(self.possible_responses[response]) == 1:
+ b.connect('clicked', self.possible_responses[response][0])
+ else:
+ b.connect('clicked', *self.possible_responses[response])
+ else:
+ b.connect('clicked', self.possible_responses[response])
+ break
+
+ self.connect('destroy', self.on_dialog_destroy)
+
+ def on_dialog_destroy(self, widget):
+ if not self.call_cancel_on_destroy:
+ return
+ cancel_handler = self.possible_responses[gtk.STOCK_CANCEL]
+ if not cancel_handler:
+ return False
+ if isinstance(cancel_handler, tuple):
+ cancel_handler[0](None, *cancel_handler[1:])
+ else:
+ cancel_handler(None)
+
+ def just_destroy(self, widget):
+ self.destroy()
+
+ def popup(self):
+ """
+ Show dialog
+ """
+ vb = self.get_children()[0].get_children()[0] # Give focus to top vbox
+ vb.set_flags(gtk.CAN_FOCUS)
+ vb.grab_focus()
+ self.show_all()
class FileChooserDialog(gtk.FileChooserDialog):
- """
- Non-blocking FileChooser Dialog around gtk.FileChooserDialog
- """
- def __init__(self, title_text, action, buttons, default_response,
- select_multiple = False, current_folder = None, on_response_ok = None,
- on_response_cancel = None):
-
- gtk.FileChooserDialog.__init__(self, title=title_text, action=action,
- buttons=buttons)
-
- self.set_default_response(default_response)
- self.set_select_multiple(select_multiple)
- if current_folder and os.path.isdir(current_folder):
- self.set_current_folder(current_folder)
- else:
- self.set_current_folder(helpers.get_documents_path())
- self.response_ok, self.response_cancel = \
- on_response_ok, on_response_cancel
- # in gtk+-2.10 clicked signal on some of the buttons in a dialog
- # is emitted twice, so we cannot rely on 'clicked' signal
- self.connect('response', self.on_dialog_response)
- self.show_all()
-
- def on_dialog_response(self, dialog, response):
- if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE):
- if self.response_cancel:
- if isinstance(self.response_cancel, tuple):
- self.response_cancel[0](dialog, *self.response_cancel[1:])
- else:
- self.response_cancel(dialog)
- else:
- self.just_destroy(dialog)
- elif response == gtk.RESPONSE_OK:
- if self.response_ok:
- if isinstance(self.response_ok, tuple):
- self.response_ok[0](dialog, *self.response_ok[1:])
- else:
- self.response_ok(dialog)
- else:
- self.just_destroy(dialog)
-
- def just_destroy(self, widget):
- self.destroy()
+ """
+ Non-blocking FileChooser Dialog around gtk.FileChooserDialog
+ """
+ def __init__(self, title_text, action, buttons, default_response,
+ select_multiple = False, current_folder = None, on_response_ok = None,
+ on_response_cancel = None):
+
+ gtk.FileChooserDialog.__init__(self, title=title_text, action=action,
+ buttons=buttons)
+
+ self.set_default_response(default_response)
+ self.set_select_multiple(select_multiple)
+ if current_folder and os.path.isdir(current_folder):
+ self.set_current_folder(current_folder)
+ else:
+ self.set_current_folder(helpers.get_documents_path())
+ self.response_ok, self.response_cancel = \
+ on_response_ok, on_response_cancel
+ # in gtk+-2.10 clicked signal on some of the buttons in a dialog
+ # is emitted twice, so we cannot rely on 'clicked' signal
+ self.connect('response', self.on_dialog_response)
+ self.show_all()
+
+ def on_dialog_response(self, dialog, response):
+ if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE):
+ if self.response_cancel:
+ if isinstance(self.response_cancel, tuple):
+ self.response_cancel[0](dialog, *self.response_cancel[1:])
+ else:
+ self.response_cancel(dialog)
+ else:
+ self.just_destroy(dialog)
+ elif response == gtk.RESPONSE_OK:
+ if self.response_ok:
+ if isinstance(self.response_ok, tuple):
+ self.response_ok[0](dialog, *self.response_ok[1:])
+ else:
+ self.response_ok(dialog)
+ else:
+ self.just_destroy(dialog)
+
+ def just_destroy(self, widget):
+ self.destroy()
class AspellDictError:
- def __init__(self, lang):
- ErrorDialog(
- _('Dictionary for lang %s not available') % lang,
- _('You have to install %s dictionary to use spellchecking, or '
- 'choose another language by setting the speller_language option.'
- '\n\nHighlighting misspelled words feature will not be used') % lang)
- gajim.config.set('use_speller', False)
+ def __init__(self, lang):
+ ErrorDialog(
+ _('Dictionary for lang %s not available') % lang,
+ _('You have to install %s dictionary to use spellchecking, or '
+ 'choose another language by setting the speller_language option.'
+ '\n\nHighlighting misspelled words feature will not be used') % lang)
+ gajim.config.set('use_speller', False)
class ConfirmationDialog(HigDialog):
- """
- HIG compliant confirmation dialog
- """
-
- def __init__(self, pritext, sectext='', on_response_ok=None,
- on_response_cancel=None):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
- HigDialog.__init__(self, None,
- gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext,
- self.on_response_ok, self.on_response_cancel)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](*self.user_response_ok[1:])
- else:
- self.user_response_ok()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_ok[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
+ """
+ HIG compliant confirmation dialog
+ """
+
+ def __init__(self, pritext, sectext='', on_response_ok=None,
+ on_response_cancel=None):
+ self.user_response_ok = on_response_ok
+ self.user_response_cancel = on_response_cancel
+ HigDialog.__init__(self, None,
+ gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext,
+ self.on_response_ok, self.on_response_cancel)
+ self.popup()
+
+ def on_response_ok(self, widget):
+ if self.user_response_ok:
+ if isinstance(self.user_response_ok, tuple):
+ self.user_response_ok[0](*self.user_response_ok[1:])
+ else:
+ self.user_response_ok()
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def on_response_cancel(self, widget):
+ if self.user_response_cancel:
+ if isinstance(self.user_response_cancel, tuple):
+ self.user_response_cancel[0](*self.user_response_ok[1:])
+ else:
+ self.user_response_cancel()
+ self.call_cancel_on_destroy = False
+ self.destroy()
class NonModalConfirmationDialog(HigDialog):
- """
- HIG compliant non modal confirmation dialog
- """
-
- def __init__(self, pritext, sectext='', on_response_ok=None,
- on_response_cancel=None):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
- HigDialog.__init__(self, None,
- gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext,
- self.on_response_ok, self.on_response_cancel)
- self.set_modal(False)
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](*self.user_response_ok[1:])
- else:
- self.user_response_ok()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_cancel[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
+ """
+ HIG compliant non modal confirmation dialog
+ """
+
+ def __init__(self, pritext, sectext='', on_response_ok=None,
+ on_response_cancel=None):
+ self.user_response_ok = on_response_ok
+ self.user_response_cancel = on_response_cancel
+ HigDialog.__init__(self, None,
+ gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext,
+ self.on_response_ok, self.on_response_cancel)
+ self.set_modal(False)
+
+ def on_response_ok(self, widget):
+ if self.user_response_ok:
+ if isinstance(self.user_response_ok, tuple):
+ self.user_response_ok[0](*self.user_response_ok[1:])
+ else:
+ self.user_response_ok()
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def on_response_cancel(self, widget):
+ if self.user_response_cancel:
+ if isinstance(self.user_response_cancel, tuple):
+ self.user_response_cancel[0](*self.user_response_cancel[1:])
+ else:
+ self.user_response_cancel()
+ self.call_cancel_on_destroy = False
+ self.destroy()
class WarningDialog(HigDialog):
- """
- HIG compliant warning dialog
- """
-
- def __init__(self, pritext, sectext=''):
- HigDialog.__init__( self, None,
- gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext)
- self.set_modal(False)
- if hasattr(gajim.interface, 'roster'):
- self.set_transient_for(gajim.interface.roster.window)
- self.popup()
+ """
+ HIG compliant warning dialog
+ """
+
+ def __init__(self, pritext, sectext=''):
+ HigDialog.__init__( self, None,
+ gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext)
+ self.set_modal(False)
+ if hasattr(gajim.interface, 'roster'):
+ self.set_transient_for(gajim.interface.roster.window)
+ self.popup()
class InformationDialog(HigDialog):
- """
- HIG compliant info dialog
- """
+ """
+ HIG compliant info dialog
+ """
- def __init__(self, pritext, sectext=''):
- HigDialog.__init__(self, None,
- gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext)
- self.set_modal(False)
- self.set_transient_for(gajim.interface.roster.window)
- self.popup()
+ def __init__(self, pritext, sectext=''):
+ HigDialog.__init__(self, None,
+ gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext)
+ self.set_modal(False)
+ self.set_transient_for(gajim.interface.roster.window)
+ self.popup()
class ErrorDialog(HigDialog):
- """
- HIG compliant error dialog
- """
+ """
+ HIG compliant error dialog
+ """
- def __init__(self, pritext, sectext=''):
- HigDialog.__init__( self, None,
- gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext)
- self.popup()
+ def __init__(self, pritext, sectext=''):
+ HigDialog.__init__( self, None,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext)
+ self.popup()
class YesNoDialog(HigDialog):
- """
- HIG compliant YesNo dialog
- """
-
- def __init__(self, pritext, sectext='', checktext='', on_response_yes=None,
- on_response_no=None):
- self.user_response_yes = on_response_yes
- self.user_response_no = on_response_no
- HigDialog.__init__( self, None,
- gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext,
- on_response_yes=self.on_response_yes,
- on_response_no=self.on_response_no)
-
- if checktext:
- self.checkbutton = gtk.CheckButton(checktext)
- self.vbox.pack_start(self.checkbutton, expand=False, fill=True)
- else:
- self.checkbutton = None
- self.set_modal(False)
- self.popup()
-
- def on_response_yes(self, widget):
- if self.user_response_yes:
- if isinstance(self.user_response_yes, tuple):
- self.user_response_yes[0](self.is_checked(),
- *self.user_response_yes[1:])
- else:
- self.user_response_yes(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_no(self, widget):
- if self.user_response_no:
- if isinstance(self.user_response_no, tuple):
- self.user_response_no[0](*self.user_response_no[1:])
- else:
- self.user_response_no()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- """
- Get active state of the checkbutton
- """
- if not self.checkbutton:
- return False
- return self.checkbutton.get_active()
+ """
+ HIG compliant YesNo dialog
+ """
+
+ def __init__(self, pritext, sectext='', checktext='', on_response_yes=None,
+ on_response_no=None):
+ self.user_response_yes = on_response_yes
+ self.user_response_no = on_response_no
+ HigDialog.__init__( self, None,
+ gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext,
+ on_response_yes=self.on_response_yes,
+ on_response_no=self.on_response_no)
+
+ if checktext:
+ self.checkbutton = gtk.CheckButton(checktext)
+ self.vbox.pack_start(self.checkbutton, expand=False, fill=True)
+ else:
+ self.checkbutton = None
+ self.set_modal(False)
+ self.popup()
+
+ def on_response_yes(self, widget):
+ if self.user_response_yes:
+ if isinstance(self.user_response_yes, tuple):
+ self.user_response_yes[0](self.is_checked(),
+ *self.user_response_yes[1:])
+ else:
+ self.user_response_yes(self.is_checked())
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def on_response_no(self, widget):
+ if self.user_response_no:
+ if isinstance(self.user_response_no, tuple):
+ self.user_response_no[0](*self.user_response_no[1:])
+ else:
+ self.user_response_no()
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def is_checked(self):
+ """
+ Get active state of the checkbutton
+ """
+ if not self.checkbutton:
+ return False
+ return self.checkbutton.get_active()
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):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
-
- HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION,
- gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok,
- self.on_response_cancel)
-
- self.set_default_response(gtk.RESPONSE_OK)
-
- ok_button = self.action_area.get_children()[0] # right to left
- ok_button.grab_focus()
-
- self.checkbutton = gtk.CheckButton(checktext)
- self.vbox.pack_start(self.checkbutton, expand=False, fill=True)
- self.set_modal(is_modal)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](self.is_checked(),
- *self.user_response_ok[1:])
- else:
- self.user_response_ok(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](self.is_checked(),
- *self.user_response_cancel[1:])
- else:
- self.user_response_cancel(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- """
- Get active state of the checkbutton
- """
- return self.checkbutton.get_active()
+ """
+ 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
+
+ HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok,
+ self.on_response_cancel)
+
+ self.set_default_response(gtk.RESPONSE_OK)
+
+ ok_button = self.action_area.get_children()[0] # right to left
+ ok_button.grab_focus()
+
+ self.checkbutton = gtk.CheckButton(checktext)
+ self.vbox.pack_start(self.checkbutton, expand=False, fill=True)
+ self.set_modal(is_modal)
+ self.popup()
+
+ def on_response_ok(self, widget):
+ if self.user_response_ok:
+ if isinstance(self.user_response_ok, tuple):
+ self.user_response_ok[0](self.is_checked(),
+ *self.user_response_ok[1:])
+ else:
+ self.user_response_ok(self.is_checked())
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def on_response_cancel(self, widget):
+ if self.user_response_cancel:
+ if isinstance(self.user_response_cancel, tuple):
+ self.user_response_cancel[0](self.is_checked(),
+ *self.user_response_cancel[1:])
+ else:
+ self.user_response_cancel(self.is_checked())
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def is_checked(self):
+ """
+ Get active state of the checkbutton
+ """
+ return self.checkbutton.get_active()
class ConfirmationDialogDubbleCheck(ConfirmationDialog):
- """
- HIG compliant confirmation dialog with 2 checkbuttons
- """
-
- def __init__(self, pritext, sectext='', checktext1='', checktext2='',
- on_response_ok=None, on_response_cancel=None, is_modal=True):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
-
- HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION,
- gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok,
- self.on_response_cancel)
-
- self.set_default_response(gtk.RESPONSE_OK)
-
- ok_button = self.action_area.get_children()[0] # right to left
- ok_button.grab_focus()
-
- if checktext1:
- self.checkbutton1 = gtk.CheckButton(checktext1)
- self.vbox.pack_start(self.checkbutton1, expand=False, fill=True)
- else:
- self.checkbutton1 = None
- if checktext2:
- self.checkbutton2 = gtk.CheckButton(checktext2)
- self.vbox.pack_start(self.checkbutton2, expand=False, fill=True)
- else:
- self.checkbutton2 = None
-
- self.set_modal(is_modal)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](self.is_checked(),
- *self.user_response_ok[1:])
- else:
- self.user_response_ok(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_cancel[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- ''' Get active state of the checkbutton '''
- if self.checkbutton1:
- is_checked_1 = self.checkbutton1.get_active()
- else:
- is_checked_1 = False
- if self.checkbutton2:
- is_checked_2 = self.checkbutton2.get_active()
- else:
- is_checked_2 = False
- return [is_checked_1, is_checked_2]
+ """
+ HIG compliant confirmation dialog with 2 checkbuttons
+ """
+
+ def __init__(self, pritext, sectext='', checktext1='', checktext2='',
+ on_response_ok=None, on_response_cancel=None, is_modal=True):
+ self.user_response_ok = on_response_ok
+ self.user_response_cancel = on_response_cancel
+
+ HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok,
+ self.on_response_cancel)
+
+ self.set_default_response(gtk.RESPONSE_OK)
+
+ ok_button = self.action_area.get_children()[0] # right to left
+ ok_button.grab_focus()
+
+ if checktext1:
+ self.checkbutton1 = gtk.CheckButton(checktext1)
+ self.vbox.pack_start(self.checkbutton1, expand=False, fill=True)
+ else:
+ self.checkbutton1 = None
+ if checktext2:
+ self.checkbutton2 = gtk.CheckButton(checktext2)
+ self.vbox.pack_start(self.checkbutton2, expand=False, fill=True)
+ else:
+ self.checkbutton2 = None
+
+ self.set_modal(is_modal)
+ self.popup()
+
+ def on_response_ok(self, widget):
+ if self.user_response_ok:
+ if isinstance(self.user_response_ok, tuple):
+ self.user_response_ok[0](self.is_checked(),
+ *self.user_response_ok[1:])
+ else:
+ self.user_response_ok(self.is_checked())
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def on_response_cancel(self, widget):
+ if self.user_response_cancel:
+ if isinstance(self.user_response_cancel, tuple):
+ self.user_response_cancel[0](*self.user_response_cancel[1:])
+ else:
+ self.user_response_cancel()
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def is_checked(self):
+ ''' Get active state of the checkbutton '''
+ if self.checkbutton1:
+ is_checked_1 = self.checkbutton1.get_active()
+ else:
+ is_checked_1 = False
+ if self.checkbutton2:
+ is_checked_2 = self.checkbutton2.get_active()
+ else:
+ is_checked_2 = False
+ return [is_checked_1, is_checked_2]
class ConfirmationDialogDoubleRadio(ConfirmationDialog):
- """
- HIG compliant confirmation dialog with 2 radios
- """
-
- def __init__(self, pritext, sectext='', radiotext1='', radiotext2='',
- on_response_ok=None, on_response_cancel=None, is_modal=True):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
-
- HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION,
- gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok,
- self.on_response_cancel)
-
- self.set_default_response(gtk.RESPONSE_OK)
-
- ok_button = self.action_area.get_children()[0] # right to left
- ok_button.grab_focus()
-
- self.radiobutton1 = gtk.RadioButton(label=radiotext1)
- self.vbox.pack_start(self.radiobutton1, expand=False, fill=True)
-
- self.radiobutton2 = gtk.RadioButton(group=self.radiobutton1,
- label=radiotext2)
- self.vbox.pack_start(self.radiobutton2, expand=False, fill=True)
-
- self.set_modal(is_modal)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](self.is_checked(),
- *self.user_response_ok[1:])
- else:
- self.user_response_ok(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_cancel[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- ''' Get active state of the checkbutton '''
- if self.radiobutton1:
- is_checked_1 = self.radiobutton1.get_active()
- else:
- is_checked_1 = False
- if self.radiobutton2:
- is_checked_2 = self.radiobutton2.get_active()
- else:
- is_checked_2 = False
- return [is_checked_1, is_checked_2]
+ """
+ HIG compliant confirmation dialog with 2 radios
+ """
+
+ def __init__(self, pritext, sectext='', radiotext1='', radiotext2='',
+ on_response_ok=None, on_response_cancel=None, is_modal=True):
+ self.user_response_ok = on_response_ok
+ self.user_response_cancel = on_response_cancel
+
+ HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok,
+ self.on_response_cancel)
+
+ self.set_default_response(gtk.RESPONSE_OK)
+
+ ok_button = self.action_area.get_children()[0] # right to left
+ ok_button.grab_focus()
+
+ self.radiobutton1 = gtk.RadioButton(label=radiotext1)
+ self.vbox.pack_start(self.radiobutton1, expand=False, fill=True)
+
+ self.radiobutton2 = gtk.RadioButton(group=self.radiobutton1,
+ label=radiotext2)
+ self.vbox.pack_start(self.radiobutton2, expand=False, fill=True)
+
+ self.set_modal(is_modal)
+ self.popup()
+
+ def on_response_ok(self, widget):
+ if self.user_response_ok:
+ if isinstance(self.user_response_ok, tuple):
+ self.user_response_ok[0](self.is_checked(),
+ *self.user_response_ok[1:])
+ else:
+ self.user_response_ok(self.is_checked())
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def on_response_cancel(self, widget):
+ if self.user_response_cancel:
+ if isinstance(self.user_response_cancel, tuple):
+ self.user_response_cancel[0](*self.user_response_cancel[1:])
+ else:
+ self.user_response_cancel()
+ self.call_cancel_on_destroy = False
+ self.destroy()
+
+ def is_checked(self):
+ ''' Get active state of the checkbutton '''
+ if self.radiobutton1:
+ is_checked_1 = self.radiobutton1.get_active()
+ else:
+ is_checked_1 = False
+ if self.radiobutton2:
+ is_checked_2 = self.radiobutton2.get_active()
+ else:
+ is_checked_2 = False
+ return [is_checked_1, is_checked_2]
class FTOverwriteConfirmationDialog(ConfirmationDialog):
- """
- HIG compliant confirmation dialog to overwrite or resume a file transfert
- """
-
- def __init__(self, pritext, sectext='', propose_resume=True,
- on_response=None):
- HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL,
- pritext, sectext)
-
- self.on_response = on_response
-
- if propose_resume:
- b = gtk.Button('', gtk.STOCK_REFRESH)
- align = b.get_children()[0]
- hbox = align.get_children()[0]
- label = hbox.get_children()[1]
- label.set_text('_Resume')
- label.set_use_underline(True)
- self.add_action_widget(b, 100)
-
- b = gtk.Button('', gtk.STOCK_SAVE_AS)
- align = b.get_children()[0]
- hbox = align.get_children()[0]
- label = hbox.get_children()[1]
- label.set_text('Re_place')
- label.set_use_underline(True)
- self.add_action_widget(b, 200)
-
- self.connect('response', self.on_dialog_response)
- self.show_all()
-
- def on_dialog_response(self, dialog, response):
- if self.on_response:
- if isinstance(self.on_response, tuple):
- self.on_response[0](response, *self.on_response[1:])
- else:
- self.on_response(response)
- self.call_cancel_on_destroy = False
- self.destroy()
+ """
+ HIG compliant confirmation dialog to overwrite or resume a file transfert
+ """
+
+ def __init__(self, pritext, sectext='', propose_resume=True,
+ on_response=None):
+ HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL,
+ pritext, sectext)
+
+ self.on_response = on_response
+
+ if propose_resume:
+ b = gtk.Button('', gtk.STOCK_REFRESH)
+ align = b.get_children()[0]
+ hbox = align.get_children()[0]
+ label = hbox.get_children()[1]
+ label.set_text('_Resume')
+ label.set_use_underline(True)
+ self.add_action_widget(b, 100)
+
+ b = gtk.Button('', gtk.STOCK_SAVE_AS)
+ align = b.get_children()[0]
+ hbox = align.get_children()[0]
+ label = hbox.get_children()[1]
+ label.set_text('Re_place')
+ label.set_use_underline(True)
+ self.add_action_widget(b, 200)
+
+ self.connect('response', self.on_dialog_response)
+ self.show_all()
+
+ def on_dialog_response(self, dialog, response):
+ if self.on_response:
+ if isinstance(self.on_response, tuple):
+ self.on_response[0](response, *self.on_response[1:])
+ else:
+ self.on_response(response)
+ self.call_cancel_on_destroy = False
+ self.destroy()
class CommonInputDialog:
- """
- Common Class for Input dialogs
- """
-
- def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler):
- self.dialog = self.xml.get_object('input_dialog')
- label = self.xml.get_object('label')
- self.dialog.set_title(title)
- label.set_markup(label_str)
- self.cancel_handler = cancel_handler
- self.vbox = self.xml.get_object('vbox')
-
- self.ok_handler = ok_handler
- okbutton = self.xml.get_object('okbutton')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancelbutton')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
- self.xml.connect_signals(self)
- self.dialog.show_all()
-
- def on_input_dialog_destroy(self, widget):
- if self.cancel_handler:
- self.cancel_handler()
-
- def on_okbutton_clicked(self, widget):
- user_input = self.get_text()
- if user_input:
- user_input = user_input.decode('utf-8')
- self.cancel_handler = None
- self.dialog.destroy()
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](user_input, *self.ok_handler[1:])
- else:
- self.ok_handler(user_input)
-
- def on_cancelbutton_clicked(self, widget):
- self.dialog.destroy()
+ """
+ Common Class for Input dialogs
+ """
+
+ def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler):
+ self.dialog = self.xml.get_object('input_dialog')
+ label = self.xml.get_object('label')
+ self.dialog.set_title(title)
+ label.set_markup(label_str)
+ self.cancel_handler = cancel_handler
+ self.vbox = self.xml.get_object('vbox')
+
+ self.ok_handler = ok_handler
+ okbutton = self.xml.get_object('okbutton')
+ okbutton.connect('clicked', self.on_okbutton_clicked)
+ cancelbutton = self.xml.get_object('cancelbutton')
+ cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
+ self.xml.connect_signals(self)
+ self.dialog.show_all()
+
+ def on_input_dialog_destroy(self, widget):
+ if self.cancel_handler:
+ self.cancel_handler()
+
+ def on_okbutton_clicked(self, widget):
+ user_input = self.get_text()
+ if user_input:
+ user_input = user_input.decode('utf-8')
+ self.cancel_handler = None
+ self.dialog.destroy()
+ if isinstance(self.ok_handler, tuple):
+ self.ok_handler[0](user_input, *self.ok_handler[1:])
+ else:
+ self.ok_handler(user_input)
+
+ def on_cancelbutton_clicked(self, widget):
+ self.dialog.destroy()
class InputDialog(CommonInputDialog):
- """
- Class for Input dialog
- """
-
- def __init__(self, title, label_str, input_str=None, is_modal=True,
- ok_handler=None, cancel_handler=None):
- self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui')
- CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler,
- cancel_handler)
- self.input_entry = self.xml.get_object('input_entry')
- if input_str:
- self.set_entry(input_str)
-
- def set_entry(self, value):
- self.input_entry.set_text(value)
- self.input_entry.select_region(0, -1) # select all
-
- def get_text(self):
- return self.input_entry.get_text().decode('utf-8')
+ """
+ Class for Input dialog
+ """
+
+ def __init__(self, title, label_str, input_str=None, is_modal=True,
+ ok_handler=None, cancel_handler=None):
+ self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui')
+ CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler,
+ cancel_handler)
+ self.input_entry = self.xml.get_object('input_entry')
+ if input_str:
+ self.set_entry(input_str)
+
+ def set_entry(self, value):
+ self.input_entry.set_text(value)
+ self.input_entry.select_region(0, -1) # select all
+
+ def get_text(self):
+ return self.input_entry.get_text().decode('utf-8')
class InputDialogCheck(InputDialog):
- """
- Class for Input dialog
- """
-
- def __init__(self, title, label_str, checktext='', input_str=None,
- is_modal=True, ok_handler=None, cancel_handler=None):
- self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui')
- InputDialog.__init__(self, title, label_str, input_str=input_str,
- is_modal=is_modal, ok_handler=ok_handler,
- cancel_handler=cancel_handler)
- self.input_entry = self.xml.get_object('input_entry')
- if input_str:
- self.input_entry.set_text(input_str)
- self.input_entry.select_region(0, -1) # select all
-
- if checktext:
- self.checkbutton = gtk.CheckButton(checktext)
- self.vbox.pack_start(self.checkbutton, expand=False, fill=True)
- self.checkbutton.show()
-
- def on_okbutton_clicked(self, widget):
- user_input = self.get_text()
- if user_input:
- user_input = user_input.decode('utf-8')
- self.cancel_handler = None
- self.dialog.destroy()
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:])
- else:
- self.ok_handler(user_input, self.is_checked())
-
- def get_text(self):
- return self.input_entry.get_text().decode('utf-8')
-
- def is_checked(self):
- """
- Get active state of the checkbutton
- """
- try:
- return self.checkbutton.get_active()
- except Exception:
- # There is no checkbutton
- return False
+ """
+ Class for Input dialog
+ """
+
+ def __init__(self, title, label_str, checktext='', input_str=None,
+ is_modal=True, ok_handler=None, cancel_handler=None):
+ self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui')
+ InputDialog.__init__(self, title, label_str, input_str=input_str,
+ is_modal=is_modal, ok_handler=ok_handler,
+ cancel_handler=cancel_handler)
+ self.input_entry = self.xml.get_object('input_entry')
+ if input_str:
+ self.input_entry.set_text(input_str)
+ self.input_entry.select_region(0, -1) # select all
+
+ if checktext:
+ self.checkbutton = gtk.CheckButton(checktext)
+ self.vbox.pack_start(self.checkbutton, expand=False, fill=True)
+ self.checkbutton.show()
+
+ def on_okbutton_clicked(self, widget):
+ user_input = self.get_text()
+ if user_input:
+ user_input = user_input.decode('utf-8')
+ self.cancel_handler = None
+ self.dialog.destroy()
+ if isinstance(self.ok_handler, tuple):
+ self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:])
+ else:
+ self.ok_handler(user_input, self.is_checked())
+
+ def get_text(self):
+ return self.input_entry.get_text().decode('utf-8')
+
+ def is_checked(self):
+ """
+ Get active state of the checkbutton
+ """
+ try:
+ return self.checkbutton.get_active()
+ except Exception:
+ # There is no checkbutton
+ return False
class ChangeNickDialog(InputDialogCheck):
- """
- Class for changing room nickname in case of conflict
- """
-
- def __init__(self, account, room_jid, title, prompt, check_text=None):
- InputDialogCheck.__init__(self, title, '', checktext=check_text,
- input_str='', is_modal=True, ok_handler=None, cancel_handler=None)
- self.room_queue = [(account, room_jid, prompt)]
- self.check_next()
-
- def on_input_dialog_delete_event(self, widget, event):
- self.on_cancelbutton_clicked(widget)
- return True
-
- def setup_dialog(self):
- self.gc_control = gajim.interface.msg_win_mgr.get_gc_control(
- self.room_jid, self.account)
- if not self.gc_control and \
- self.room_jid in gajim.interface.minimized_controls[self.account]:
- self.gc_control = \
- gajim.interface.minimized_controls[self.account][self.room_jid]
- if not self.gc_control:
- self.check_next()
- return
- label = self.xml.get_object('label')
- label.set_markup(self.prompt)
- self.set_entry(self.gc_control.nick + \
- gajim.config.get('gc_proposed_nick_char'))
-
- def check_next(self):
- if len(self.room_queue) == 0:
- self.cancel_handler = None
- self.dialog.destroy()
- if 'change_nick_dialog' in gajim.interface.instances:
- del gajim.interface.instances['change_nick_dialog']
- return
- self.account, self.room_jid, self.prompt = self.room_queue.pop(0)
- self.setup_dialog()
-
- if gajim.new_room_nick is not None and not gajim.gc_connected[
- self.account][self.room_jid] and self.gc_control.nick != \
- gajim.new_room_nick:
- self.dialog.hide()
- self.on_ok(gajim.new_room_nick, True)
- else:
- self.dialog.show()
-
- def on_okbutton_clicked(self, widget):
- nick = self.get_text()
- if nick:
- nick = nick.decode('utf-8')
- # send presence to room
- try:
- nick = helpers.parse_resource(nick)
- except Exception:
- # invalid char
- dialogs.ErrorDialog(_('Invalid nickname'),
- _('The nickname has not allowed characters.'))
- return
- self.on_ok(nick, self.is_checked())
-
- def on_ok(self, nick, is_checked):
- if is_checked:
- gajim.new_room_nick = nick
- gajim.connections[self.account].join_gc(nick, self.room_jid, None,
- change_nick=True)
- if gajim.gc_connected[self.account][self.room_jid]:
- # We are changing nick, we will change self.nick when we receive
- # presence that inform that it works
- self.gc_control.new_nick = nick
- else:
- # We are connecting, we will not get a changed nick presence so
- # change it NOW. We don't already have a nick so it's harmless
- self.gc_control.nick = nick
- self.check_next()
-
- def on_cancelbutton_clicked(self, widget):
- self.gc_control.new_nick = ''
- self.check_next()
-
- def add_room(self, account, room_jid, prompt):
- if (account, room_jid, prompt) not in self.room_queue:
- self.room_queue.append((account, room_jid, prompt))
+ """
+ Class for changing room nickname in case of conflict
+ """
+
+ def __init__(self, account, room_jid, title, prompt, check_text=None):
+ InputDialogCheck.__init__(self, title, '', checktext=check_text,
+ input_str='', is_modal=True, ok_handler=None, cancel_handler=None)
+ self.room_queue = [(account, room_jid, prompt)]
+ self.check_next()
+
+ def on_input_dialog_delete_event(self, widget, event):
+ self.on_cancelbutton_clicked(widget)
+ return True
+
+ def setup_dialog(self):
+ self.gc_control = gajim.interface.msg_win_mgr.get_gc_control(
+ self.room_jid, self.account)
+ if not self.gc_control and \
+ self.room_jid in gajim.interface.minimized_controls[self.account]:
+ self.gc_control = \
+ gajim.interface.minimized_controls[self.account][self.room_jid]
+ if not self.gc_control:
+ self.check_next()
+ return
+ label = self.xml.get_object('label')
+ label.set_markup(self.prompt)
+ self.set_entry(self.gc_control.nick + \
+ gajim.config.get('gc_proposed_nick_char'))
+
+ def check_next(self):
+ if len(self.room_queue) == 0:
+ self.cancel_handler = None
+ self.dialog.destroy()
+ if 'change_nick_dialog' in gajim.interface.instances:
+ del gajim.interface.instances['change_nick_dialog']
+ return
+ self.account, self.room_jid, self.prompt = self.room_queue.pop(0)
+ self.setup_dialog()
+
+ if gajim.new_room_nick is not None and not gajim.gc_connected[
+ self.account][self.room_jid] and self.gc_control.nick != \
+ gajim.new_room_nick:
+ self.dialog.hide()
+ self.on_ok(gajim.new_room_nick, True)
+ else:
+ self.dialog.show()
+
+ def on_okbutton_clicked(self, widget):
+ nick = self.get_text()
+ if nick:
+ nick = nick.decode('utf-8')
+ # send presence to room
+ try:
+ nick = helpers.parse_resource(nick)
+ except Exception:
+ # invalid char
+ dialogs.ErrorDialog(_('Invalid nickname'),
+ _('The nickname has not allowed characters.'))
+ return
+ self.on_ok(nick, self.is_checked())
+
+ def on_ok(self, nick, is_checked):
+ if is_checked:
+ gajim.new_room_nick = nick
+ gajim.connections[self.account].join_gc(nick, self.room_jid, None,
+ change_nick=True)
+ if gajim.gc_connected[self.account][self.room_jid]:
+ # We are changing nick, we will change self.nick when we receive
+ # presence that inform that it works
+ self.gc_control.new_nick = nick
+ else:
+ # We are connecting, we will not get a changed nick presence so
+ # change it NOW. We don't already have a nick so it's harmless
+ self.gc_control.nick = nick
+ self.check_next()
+
+ def on_cancelbutton_clicked(self, widget):
+ self.gc_control.new_nick = ''
+ self.check_next()
+
+ def add_room(self, account, room_jid, prompt):
+ if (account, room_jid, prompt) not in self.room_queue:
+ self.room_queue.append((account, room_jid, prompt))
class InputTextDialog(CommonInputDialog):
- """
- Class for multilines Input dialog (more place than InputDialog)
- """
-
- def __init__(self, title, label_str, input_str=None, is_modal=True,
- ok_handler=None, cancel_handler=None):
- self.xml = gtkgui_helpers.get_gtk_builder('input_text_dialog.ui')
- CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler,
- cancel_handler)
- self.input_buffer = self.xml.get_object('input_textview').get_buffer()
- if input_str:
- self.input_buffer.set_text(input_str)
- start_iter, end_iter = self.input_buffer.get_bounds()
- self.input_buffer.select_range(start_iter, end_iter) # select all
-
- def get_text(self):
- start_iter, end_iter = self.input_buffer.get_bounds()
- return self.input_buffer.get_text(start_iter, end_iter).decode('utf-8')
+ """
+ Class for multilines Input dialog (more place than InputDialog)
+ """
+
+ def __init__(self, title, label_str, input_str=None, is_modal=True,
+ ok_handler=None, cancel_handler=None):
+ self.xml = gtkgui_helpers.get_gtk_builder('input_text_dialog.ui')
+ CommonInputDialog.__init__(self, title, label_str, is_modal, ok_handler,
+ cancel_handler)
+ self.input_buffer = self.xml.get_object('input_textview').get_buffer()
+ if input_str:
+ self.input_buffer.set_text(input_str)
+ start_iter, end_iter = self.input_buffer.get_bounds()
+ self.input_buffer.select_range(start_iter, end_iter) # select all
+
+ def get_text(self):
+ start_iter, end_iter = self.input_buffer.get_bounds()
+ return self.input_buffer.get_text(start_iter, end_iter).decode('utf-8')
class DubbleInputDialog:
- """
- Class for Dubble Input dialog
- """
-
- def __init__(self, title, label_str1, label_str2, input_str1=None,
- input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None):
- self.xml = gtkgui_helpers.get_gtk_builder('dubbleinput_dialog.ui')
- self.dialog = self.xml.get_object('dubbleinput_dialog')
- label1 = self.xml.get_object('label1')
- self.input_entry1 = self.xml.get_object('input_entry1')
- label2 = self.xml.get_object('label2')
- self.input_entry2 = self.xml.get_object('input_entry2')
- self.dialog.set_title(title)
- label1.set_markup(label_str1)
- label2.set_markup(label_str2)
- self.cancel_handler = cancel_handler
- if input_str1:
- self.input_entry1.set_text(input_str1)
- self.input_entry1.select_region(0, -1) # select all
- if input_str2:
- self.input_entry2.set_text(input_str2)
- self.input_entry2.select_region(0, -1) # select all
-
- self.dialog.set_modal(is_modal)
-
- self.ok_handler = ok_handler
- okbutton = self.xml.get_object('okbutton')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancelbutton')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
- self.xml.connect_signals(self)
- self.dialog.show_all()
-
- def on_dubbleinput_dialog_destroy(self, widget):
- if not self.cancel_handler:
- return False
- if isinstance(self.cancel_handler, tuple):
- self.cancel_handler[0](*self.cancel_handler[1:])
- else:
- self.cancel_handler()
-
- def on_okbutton_clicked(self, widget):
- user_input1 = self.input_entry1.get_text().decode('utf-8')
- user_input2 = self.input_entry2.get_text().decode('utf-8')
- self.dialog.destroy()
- if not self.ok_handler:
- return
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:])
- else:
- self.ok_handler(user_input1, user_input2)
-
- def on_cancelbutton_clicked(self, widget):
- self.dialog.destroy()
- if not self.cancel_handler:
- return
- if isinstance(self.cancel_handler, tuple):
- self.cancel_handler[0](*self.cancel_handler[1:])
- else:
- self.cancel_handler()
+ """
+ Class for Dubble Input dialog
+ """
+
+ def __init__(self, title, label_str1, label_str2, input_str1=None,
+ input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None):
+ self.xml = gtkgui_helpers.get_gtk_builder('dubbleinput_dialog.ui')
+ self.dialog = self.xml.get_object('dubbleinput_dialog')
+ label1 = self.xml.get_object('label1')
+ self.input_entry1 = self.xml.get_object('input_entry1')
+ label2 = self.xml.get_object('label2')
+ self.input_entry2 = self.xml.get_object('input_entry2')
+ self.dialog.set_title(title)
+ label1.set_markup(label_str1)
+ label2.set_markup(label_str2)
+ self.cancel_handler = cancel_handler
+ if input_str1:
+ self.input_entry1.set_text(input_str1)
+ self.input_entry1.select_region(0, -1) # select all
+ if input_str2:
+ self.input_entry2.set_text(input_str2)
+ self.input_entry2.select_region(0, -1) # select all
+
+ self.dialog.set_modal(is_modal)
+
+ self.ok_handler = ok_handler
+ okbutton = self.xml.get_object('okbutton')
+ okbutton.connect('clicked', self.on_okbutton_clicked)
+ cancelbutton = self.xml.get_object('cancelbutton')
+ cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
+ self.xml.connect_signals(self)
+ self.dialog.show_all()
+
+ def on_dubbleinput_dialog_destroy(self, widget):
+ if not self.cancel_handler:
+ return False
+ if isinstance(self.cancel_handler, tuple):
+ self.cancel_handler[0](*self.cancel_handler[1:])
+ else:
+ self.cancel_handler()
+
+ def on_okbutton_clicked(self, widget):
+ user_input1 = self.input_entry1.get_text().decode('utf-8')
+ user_input2 = self.input_entry2.get_text().decode('utf-8')
+ self.dialog.destroy()
+ if not self.ok_handler:
+ return
+ if isinstance(self.ok_handler, tuple):
+ self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:])
+ else:
+ self.ok_handler(user_input1, user_input2)
+
+ def on_cancelbutton_clicked(self, widget):
+ self.dialog.destroy()
+ if not self.cancel_handler:
+ return
+ if isinstance(self.cancel_handler, tuple):
+ self.cancel_handler[0](*self.cancel_handler[1:])
+ else:
+ self.cancel_handler()
class SubscriptionRequestWindow:
- def __init__(self, jid, text, account, user_nick=None):
- xml = gtkgui_helpers.get_gtk_builder('subscription_request_window.ui')
- self.window = xml.get_object('subscription_request_window')
- self.jid = jid
- self.account = account
- self.user_nick = user_nick
- if len(gajim.connections) >= 2:
- prompt_text = \
- _('Subscription request for account %(account)s from %(jid)s')\
- % {'account': account, 'jid': self.jid}
- else:
- prompt_text = _('Subscription request from %s') % self.jid
- xml.get_object('from_label').set_text(prompt_text)
- xml.get_object('message_textview').get_buffer().set_text(text)
- xml.connect_signals(self)
- self.window.show_all()
-
- def prepare_popup_menu(self):
- xml = gtkgui_helpers.get_gtk_builder('subscription_request_popup_menu.ui')
- menu = xml.get_object('subscription_request_popup_menu')
- xml.connect_signals(self)
- return menu
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_authorize_button_clicked(self, widget):
- """
- Accept the request
- """
- gajim.connections[self.account].send_authorization(self.jid)
- self.window.destroy()
- contact = gajim.contacts.get_contact(self.account, self.jid)
- if not contact or _('Not in Roster') in contact.groups:
- AddNewContactWindow(self.account, self.jid, self.user_nick)
-
- def on_contact_info_activate(self, widget):
- """
- Ask vcard
- """
- if self.jid in gajim.interface.instances[self.account]['infos']:
- gajim.interface.instances[self.account]['infos'][self.jid].window.present()
- else:
- contact = gajim.contacts.create_contact(jid=self.jid, account=self.account)
- gajim.interface.instances[self.account]['infos'][self.jid] = \
- vcard.VcardWindow(contact, self.account)
- # Remove jabber page
- gajim.interface.instances[self.account]['infos'][self.jid].xml.\
- get_object('information_notebook').remove_page(0)
-
- def on_start_chat_activate(self, widget):
- """
- Open chat
- """
- gajim.interface.new_chat_from_jid(self.account, self.jid)
-
- def on_deny_button_clicked(self, widget):
- """
- Refuse the request
- """
- gajim.connections[self.account].refuse_authorization(self.jid)
- contact = gajim.contacts.get_contact(self.account, self.jid)
- if contact and _('Not in Roster') in contact.get_shown_groups():
- gajim.interface.roster.remove_contact(self.jid, self.account)
- self.window.destroy()
-
- def on_actions_button_clicked(self, widget):
- """
- Popup action menu
- """
- menu = self.prepare_popup_menu()
- menu.show_all()
- gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window)
+ def __init__(self, jid, text, account, user_nick=None):
+ xml = gtkgui_helpers.get_gtk_builder('subscription_request_window.ui')
+ self.window = xml.get_object('subscription_request_window')
+ self.jid = jid
+ self.account = account
+ self.user_nick = user_nick
+ if len(gajim.connections) >= 2:
+ prompt_text = \
+ _('Subscription request for account %(account)s from %(jid)s')\
+ % {'account': account, 'jid': self.jid}
+ else:
+ prompt_text = _('Subscription request from %s') % self.jid
+ xml.get_object('from_label').set_text(prompt_text)
+ xml.get_object('message_textview').get_buffer().set_text(text)
+ xml.connect_signals(self)
+ self.window.show_all()
+
+ def prepare_popup_menu(self):
+ xml = gtkgui_helpers.get_gtk_builder('subscription_request_popup_menu.ui')
+ menu = xml.get_object('subscription_request_popup_menu')
+ xml.connect_signals(self)
+ return menu
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_authorize_button_clicked(self, widget):
+ """
+ Accept the request
+ """
+ gajim.connections[self.account].send_authorization(self.jid)
+ self.window.destroy()
+ contact = gajim.contacts.get_contact(self.account, self.jid)
+ if not contact or _('Not in Roster') in contact.groups:
+ AddNewContactWindow(self.account, self.jid, self.user_nick)
+
+ def on_contact_info_activate(self, widget):
+ """
+ Ask vcard
+ """
+ if self.jid in gajim.interface.instances[self.account]['infos']:
+ gajim.interface.instances[self.account]['infos'][self.jid].window.present()
+ else:
+ contact = gajim.contacts.create_contact(jid=self.jid, account=self.account)
+ gajim.interface.instances[self.account]['infos'][self.jid] = \
+ vcard.VcardWindow(contact, self.account)
+ # Remove jabber page
+ gajim.interface.instances[self.account]['infos'][self.jid].xml.\
+ get_object('information_notebook').remove_page(0)
+
+ def on_start_chat_activate(self, widget):
+ """
+ Open chat
+ """
+ gajim.interface.new_chat_from_jid(self.account, self.jid)
+
+ def on_deny_button_clicked(self, widget):
+ """
+ Refuse the request
+ """
+ gajim.connections[self.account].refuse_authorization(self.jid)
+ contact = gajim.contacts.get_contact(self.account, self.jid)
+ if contact and _('Not in Roster') in contact.get_shown_groups():
+ gajim.interface.roster.remove_contact(self.jid, self.account)
+ self.window.destroy()
+
+ def on_actions_button_clicked(self, widget):
+ """
+ Popup action menu
+ """
+ menu = self.prepare_popup_menu()
+ menu.show_all()
+ gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.window)
class JoinGroupchatWindow:
- def __init__(self, account=None, room_jid='', nick='', password='',
- automatic=False):
- """
- Automatic is a dict like {'invities': []}. If automatic is not empty,
- this means room must be automaticaly configured and when done, invities
- must be automatically invited
- """
- if account:
- if room_jid != '' and room_jid in gajim.gc_connected[account] and\
- gajim.gc_connected[account][room_jid]:
- ErrorDialog(_('You are already in group chat %s') % room_jid)
- raise GajimGeneralException, 'You are already in this group chat'
- if nick == '':
- nick = gajim.nicks[account]
- if gajim.connections[account].connected < 2:
- ErrorDialog(_('You are not connected to the server'),
- _('You can not join a group chat unless you are connected.'))
- raise GajimGeneralException, 'You must be connected to join a groupchat'
-
- self.xml = gtkgui_helpers.get_gtk_builder('join_groupchat_window.ui')
-
- account_label = self.xml.get_object('account_label')
- account_combobox = self.xml.get_object('account_combobox')
- account_label.set_no_show_all(False)
- account_combobox.set_no_show_all(False)
- liststore = gtk.ListStore(str)
- account_combobox.set_model(liststore)
- cell = gtk.CellRendererText()
- account_combobox.pack_start(cell, True)
- account_combobox.add_attribute(cell, 'text', 0)
- account_combobox.set_active(-1)
-
- # Add accounts, set current as active if it matches 'account'
- for acct in [a for a in gajim.connections if \
- gajim.account_is_connected(a)]:
- account_combobox.append_text(acct)
- if account and account == acct:
- account_combobox.set_active(liststore.iter_n_children(None)-1)
-
- self.account = account
- self.automatic = automatic
- self._empty_required_widgets = []
-
- self.window = self.xml.get_object('join_groupchat_window')
- self._room_jid_entry = self.xml.get_object('room_jid_entry')
- self._nickname_entry = self.xml.get_object('nickname_entry')
- self._password_entry = self.xml.get_object('password_entry')
-
- self._room_jid_entry.set_text(room_jid)
- self._nickname_entry.set_text(nick)
- if password:
- self._password_entry.set_text(password)
- self.xml.connect_signals(self)
- title = None
- if account:
- # now add us to open windows
- gajim.interface.instances[account]['join_gc'] = self
- if len(gajim.connections) > 1:
- title = _('Join Group Chat with account %s') % account
- if title is None:
- title = _('Join Group Chat')
- self.window.set_title(title)
-
- self.recently_combobox = self.xml.get_object('recently_combobox')
- liststore = gtk.ListStore(str)
- self.recently_combobox.set_model(liststore)
- cell = gtk.CellRendererText()
- self.recently_combobox.pack_start(cell, True)
- self.recently_combobox.add_attribute(cell, 'text', 0)
- self.recently_groupchat = gajim.config.get('recently_groupchat').split()
- for g in self.recently_groupchat:
- self.recently_combobox.append_text(g)
- if len(self.recently_groupchat) == 0:
- self.recently_combobox.set_sensitive(False)
- elif room_jid == '':
- self.recently_combobox.set_active(0)
- self._room_jid_entry.select_region(0, -1)
- elif room_jid != '':
- self.xml.get_object('join_button').grab_focus()
-
- if not self._room_jid_entry.get_text():
- self._empty_required_widgets.append(self._room_jid_entry)
- if not self._nickname_entry.get_text():
- self._empty_required_widgets.append(self._nickname_entry)
- if len(self._empty_required_widgets):
- self.xml.get_object('join_button').set_sensitive(False)
-
- if account and not gajim.connections[account].private_storage_supported:
- self.xml.get_object('bookmark_checkbutton').set_sensitive(False)
-
- self.window.show_all()
-
- def on_join_groupchat_window_destroy(self, widget):
- """
- Close window
- """
- if self.account and 'join_gc' in gajim.interface.instances[self.account]:
- # remove us from open windows
- del gajim.interface.instances[self.account]['join_gc']
- gajim.interface.roster.window.present()
-
- def on_join_groupchat_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape: # ESCAPE
- widget.destroy()
-
- def on_required_entry_changed(self, widget):
- if not widget.get_text():
- self._empty_required_widgets.append(widget)
- self.xml.get_object('join_button').set_sensitive(False)
- else:
- if widget in self._empty_required_widgets:
- self._empty_required_widgets.remove(widget)
- if len(self._empty_required_widgets) == 0 and self.account:
- self.xml.get_object('join_button').set_sensitive(True)
-
- def on_account_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- self.account = model[iter_][0].decode('utf-8')
- self.on_required_entry_changed(self._nickname_entry)
-
- def on_recently_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- room_jid = model[iter_][0].decode('utf-8')
- self._room_jid_entry.set_text(room_jid)
-
- def on_cancel_button_clicked(self, widget):
- """
- When Cancel button is clicked
- """
- self.window.destroy()
-
- def on_bookmark_checkbutton_toggled(self, widget):
- auto_join_checkbutton = self.xml.get_object('auto_join_checkbutton')
- if widget.get_active():
- auto_join_checkbutton.set_sensitive(True)
- else:
- auto_join_checkbutton.set_sensitive(False)
-
- def on_join_button_clicked(self, widget):
- """
- When Join button is clicked
- """
- if not self.account:
- ErrorDialog(_('Invalid Account'),
- _('You have to choose an account from which you want to join the '
- 'groupchat.'))
- return
- nickname = self._nickname_entry.get_text().decode('utf-8')
- room_jid = self._room_jid_entry.get_text().decode('utf-8')
- password = self._password_entry.get_text().decode('utf-8')
- try:
- nickname = helpers.parse_resource(nickname)
- except Exception:
- ErrorDialog(_('Invalid Nickname'),
- _('The nickname has not allowed characters.'))
- return
- user, server, resource = helpers.decompose_jid(room_jid)
- if not user or not server or resource:
- ErrorDialog(_('Invalid group chat Jabber ID'),
- _('Please enter the group chat Jabber ID as room@server.'))
- return
- try:
- room_jid = helpers.parse_jid(room_jid)
- except Exception:
- ErrorDialog(_('Invalid group chat Jabber ID'),
- _('The group chat Jabber ID has not allowed characters.'))
- return
-
- if gajim.interface.msg_win_mgr.has_window(room_jid, self.account):
- ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- self.account)
- if ctrl.type_id != message_control.TYPE_GC:
- ErrorDialog(_('This is not a group chat'),
- _('%s is not the name of a group chat.') % room_jid)
- return
- if room_jid in self.recently_groupchat:
- self.recently_groupchat.remove(room_jid)
- self.recently_groupchat.insert(0, room_jid)
- if len(self.recently_groupchat) > 10:
- self.recently_groupchat = self.recently_groupchat[0:10]
- gajim.config.set('recently_groupchat',
- ' '.join(self.recently_groupchat))
-
- if self.xml.get_object('bookmark_checkbutton').get_active():
- if self.xml.get_object('auto_join_checkbutton').get_active():
- autojoin = '1'
- else:
- autojoin = '0'
- # Add as bookmark, with autojoin and not minimized
- name = gajim.get_nick_from_jid(room_jid)
- gajim.interface.add_gc_bookmark(self.account, name, room_jid, autojoin,
- '0', password, nickname)
-
- if self.automatic:
- gajim.automatic_rooms[self.account][room_jid] = self.automatic
- gajim.interface.join_gc_room(self.account, room_jid, nickname, password)
-
- self.window.destroy()
+ def __init__(self, account=None, room_jid='', nick='', password='',
+ automatic=False):
+ """
+ Automatic is a dict like {'invities': []}. If automatic is not empty,
+ this means room must be automaticaly configured and when done, invities
+ must be automatically invited
+ """
+ if account:
+ if room_jid != '' and room_jid in gajim.gc_connected[account] and\
+ gajim.gc_connected[account][room_jid]:
+ ErrorDialog(_('You are already in group chat %s') % room_jid)
+ raise GajimGeneralException, 'You are already in this group chat'
+ if nick == '':
+ nick = gajim.nicks[account]
+ if gajim.connections[account].connected < 2:
+ ErrorDialog(_('You are not connected to the server'),
+ _('You can not join a group chat unless you are connected.'))
+ raise GajimGeneralException, 'You must be connected to join a groupchat'
+
+ self.xml = gtkgui_helpers.get_gtk_builder('join_groupchat_window.ui')
+
+ account_label = self.xml.get_object('account_label')
+ account_combobox = self.xml.get_object('account_combobox')
+ account_label.set_no_show_all(False)
+ account_combobox.set_no_show_all(False)
+ liststore = gtk.ListStore(str)
+ account_combobox.set_model(liststore)
+ cell = gtk.CellRendererText()
+ account_combobox.pack_start(cell, True)
+ account_combobox.add_attribute(cell, 'text', 0)
+ account_combobox.set_active(-1)
+
+ # Add accounts, set current as active if it matches 'account'
+ for acct in [a for a in gajim.connections if \
+ gajim.account_is_connected(a)]:
+ account_combobox.append_text(acct)
+ if account and account == acct:
+ account_combobox.set_active(liststore.iter_n_children(None)-1)
+
+ self.account = account
+ self.automatic = automatic
+ self._empty_required_widgets = []
+
+ self.window = self.xml.get_object('join_groupchat_window')
+ self._room_jid_entry = self.xml.get_object('room_jid_entry')
+ self._nickname_entry = self.xml.get_object('nickname_entry')
+ self._password_entry = self.xml.get_object('password_entry')
+
+ self._room_jid_entry.set_text(room_jid)
+ self._nickname_entry.set_text(nick)
+ if password:
+ self._password_entry.set_text(password)
+ self.xml.connect_signals(self)
+ title = None
+ if account:
+ # now add us to open windows
+ gajim.interface.instances[account]['join_gc'] = self
+ if len(gajim.connections) > 1:
+ title = _('Join Group Chat with account %s') % account
+ if title is None:
+ title = _('Join Group Chat')
+ self.window.set_title(title)
+
+ self.recently_combobox = self.xml.get_object('recently_combobox')
+ liststore = gtk.ListStore(str)
+ self.recently_combobox.set_model(liststore)
+ cell = gtk.CellRendererText()
+ self.recently_combobox.pack_start(cell, True)
+ self.recently_combobox.add_attribute(cell, 'text', 0)
+ self.recently_groupchat = gajim.config.get('recently_groupchat').split()
+ for g in self.recently_groupchat:
+ self.recently_combobox.append_text(g)
+ if len(self.recently_groupchat) == 0:
+ self.recently_combobox.set_sensitive(False)
+ elif room_jid == '':
+ self.recently_combobox.set_active(0)
+ self._room_jid_entry.select_region(0, -1)
+ elif room_jid != '':
+ self.xml.get_object('join_button').grab_focus()
+
+ if not self._room_jid_entry.get_text():
+ self._empty_required_widgets.append(self._room_jid_entry)
+ if not self._nickname_entry.get_text():
+ self._empty_required_widgets.append(self._nickname_entry)
+ if len(self._empty_required_widgets):
+ self.xml.get_object('join_button').set_sensitive(False)
+
+ if account and not gajim.connections[account].private_storage_supported:
+ self.xml.get_object('bookmark_checkbutton').set_sensitive(False)
+
+ self.window.show_all()
+
+ def on_join_groupchat_window_destroy(self, widget):
+ """
+ Close window
+ """
+ if self.account and 'join_gc' in gajim.interface.instances[self.account]:
+ # remove us from open windows
+ del gajim.interface.instances[self.account]['join_gc']
+ gajim.interface.roster.window.present()
+
+ def on_join_groupchat_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape: # ESCAPE
+ widget.destroy()
+
+ def on_required_entry_changed(self, widget):
+ if not widget.get_text():
+ self._empty_required_widgets.append(widget)
+ self.xml.get_object('join_button').set_sensitive(False)
+ else:
+ if widget in self._empty_required_widgets:
+ self._empty_required_widgets.remove(widget)
+ if len(self._empty_required_widgets) == 0 and self.account:
+ self.xml.get_object('join_button').set_sensitive(True)
+
+ def on_account_combobox_changed(self, widget):
+ model = widget.get_model()
+ iter_ = widget.get_active_iter()
+ self.account = model[iter_][0].decode('utf-8')
+ self.on_required_entry_changed(self._nickname_entry)
+
+ def on_recently_combobox_changed(self, widget):
+ model = widget.get_model()
+ iter_ = widget.get_active_iter()
+ room_jid = model[iter_][0].decode('utf-8')
+ self._room_jid_entry.set_text(room_jid)
+
+ def on_cancel_button_clicked(self, widget):
+ """
+ When Cancel button is clicked
+ """
+ self.window.destroy()
+
+ def on_bookmark_checkbutton_toggled(self, widget):
+ auto_join_checkbutton = self.xml.get_object('auto_join_checkbutton')
+ if widget.get_active():
+ auto_join_checkbutton.set_sensitive(True)
+ else:
+ auto_join_checkbutton.set_sensitive(False)
+
+ def on_join_button_clicked(self, widget):
+ """
+ When Join button is clicked
+ """
+ if not self.account:
+ ErrorDialog(_('Invalid Account'),
+ _('You have to choose an account from which you want to join the '
+ 'groupchat.'))
+ return
+ nickname = self._nickname_entry.get_text().decode('utf-8')
+ room_jid = self._room_jid_entry.get_text().decode('utf-8')
+ password = self._password_entry.get_text().decode('utf-8')
+ try:
+ nickname = helpers.parse_resource(nickname)
+ except Exception:
+ ErrorDialog(_('Invalid Nickname'),
+ _('The nickname has not allowed characters.'))
+ return
+ user, server, resource = helpers.decompose_jid(room_jid)
+ if not user or not server or resource:
+ ErrorDialog(_('Invalid group chat Jabber ID'),
+ _('Please enter the group chat Jabber ID as room@server.'))
+ return
+ try:
+ room_jid = helpers.parse_jid(room_jid)
+ except Exception:
+ ErrorDialog(_('Invalid group chat Jabber ID'),
+ _('The group chat Jabber ID has not allowed characters.'))
+ return
+
+ if gajim.interface.msg_win_mgr.has_window(room_jid, self.account):
+ ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
+ self.account)
+ if ctrl.type_id != message_control.TYPE_GC:
+ ErrorDialog(_('This is not a group chat'),
+ _('%s is not the name of a group chat.') % room_jid)
+ return
+ if room_jid in self.recently_groupchat:
+ self.recently_groupchat.remove(room_jid)
+ self.recently_groupchat.insert(0, room_jid)
+ if len(self.recently_groupchat) > 10:
+ self.recently_groupchat = self.recently_groupchat[0:10]
+ gajim.config.set('recently_groupchat',
+ ' '.join(self.recently_groupchat))
+
+ if self.xml.get_object('bookmark_checkbutton').get_active():
+ if self.xml.get_object('auto_join_checkbutton').get_active():
+ autojoin = '1'
+ else:
+ autojoin = '0'
+ # Add as bookmark, with autojoin and not minimized
+ name = gajim.get_nick_from_jid(room_jid)
+ gajim.interface.add_gc_bookmark(self.account, name, room_jid, autojoin,
+ '0', password, nickname)
+
+ if self.automatic:
+ gajim.automatic_rooms[self.account][room_jid] = self.automatic
+ gajim.interface.join_gc_room(self.account, room_jid, nickname, password)
+
+ self.window.destroy()
class SynchroniseSelectAccountDialog:
- def __init__(self, account):
- # 'account' can be None if we are about to create our first one
- if not account or gajim.connections[account].connected < 2:
- ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not synchronise your contacts.'))
- raise GajimGeneralException, 'You are not connected to the server'
- self.account = account
- self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui')
- self.dialog = self.xml.get_object('synchronise_select_account_dialog')
- self.accounts_treeview = self.xml.get_object('accounts_treeview')
- model = gtk.ListStore(str, str, bool)
- self.accounts_treeview.set_model(model)
- # columns
- renderer = gtk.CellRendererText()
- self.accounts_treeview.insert_column_with_attributes(-1,
- _('Name'), renderer, text=0)
- renderer = gtk.CellRendererText()
- self.accounts_treeview.insert_column_with_attributes(-1,
- _('Server'), renderer, text=1)
-
- self.xml.connect_signals(self)
- self.init_accounts()
- self.dialog.show_all()
-
- def on_accounts_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.window.destroy()
-
- def init_accounts(self):
- """
- Initialize listStore with existing accounts
- """
- model = self.accounts_treeview.get_model()
- model.clear()
- for remote_account in gajim.connections:
- if remote_account == self.account:
- # Do not show the account we're sync'ing
- continue
- iter_ = model.append()
- model.set(iter_, 0, remote_account, 1, gajim.get_hostname_from_account(
- remote_account))
-
- def on_cancel_button_clicked(self, widget):
- self.dialog.destroy()
-
- def on_ok_button_clicked(self, widget):
- sel = self.accounts_treeview.get_selection()
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- remote_account = model.get_value(iter_, 0).decode('utf-8')
-
- if gajim.connections[remote_account].connected < 2:
- ErrorDialog(_('This account is not connected to the server'),
- _('You cannot synchronize with an account unless it is connected.'))
- return
- else:
- try:
- SynchroniseSelectContactsDialog(self.account, remote_account)
- except GajimGeneralException:
- # if we showed ErrorDialog, there will not be dialog instance
- return
- self.dialog.destroy()
+ def __init__(self, account):
+ # 'account' can be None if we are about to create our first one
+ if not account or gajim.connections[account].connected < 2:
+ ErrorDialog(_('You are not connected to the server'),
+ _('Without a connection, you can not synchronise your contacts.'))
+ raise GajimGeneralException, 'You are not connected to the server'
+ self.account = account
+ self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui')
+ self.dialog = self.xml.get_object('synchronise_select_account_dialog')
+ self.accounts_treeview = self.xml.get_object('accounts_treeview')
+ model = gtk.ListStore(str, str, bool)
+ self.accounts_treeview.set_model(model)
+ # columns
+ renderer = gtk.CellRendererText()
+ self.accounts_treeview.insert_column_with_attributes(-1,
+ _('Name'), renderer, text=0)
+ renderer = gtk.CellRendererText()
+ self.accounts_treeview.insert_column_with_attributes(-1,
+ _('Server'), renderer, text=1)
+
+ self.xml.connect_signals(self)
+ self.init_accounts()
+ self.dialog.show_all()
+
+ def on_accounts_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def init_accounts(self):
+ """
+ Initialize listStore with existing accounts
+ """
+ model = self.accounts_treeview.get_model()
+ model.clear()
+ for remote_account in gajim.connections:
+ if remote_account == self.account:
+ # Do not show the account we're sync'ing
+ continue
+ iter_ = model.append()
+ model.set(iter_, 0, remote_account, 1, gajim.get_hostname_from_account(
+ remote_account))
+
+ def on_cancel_button_clicked(self, widget):
+ self.dialog.destroy()
+
+ def on_ok_button_clicked(self, widget):
+ sel = self.accounts_treeview.get_selection()
+ (model, iter_) = sel.get_selected()
+ if not iter_:
+ return
+ remote_account = model.get_value(iter_, 0).decode('utf-8')
+
+ if gajim.connections[remote_account].connected < 2:
+ ErrorDialog(_('This account is not connected to the server'),
+ _('You cannot synchronize with an account unless it is connected.'))
+ return
+ else:
+ try:
+ SynchroniseSelectContactsDialog(self.account, remote_account)
+ except GajimGeneralException:
+ # if we showed ErrorDialog, there will not be dialog instance
+ return
+ self.dialog.destroy()
class SynchroniseSelectContactsDialog:
- def __init__(self, account, remote_account):
- self.local_account = account
- self.remote_account = remote_account
- self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_contacts_dialog.ui')
- self.dialog = self.xml.get_object('synchronise_select_contacts_dialog')
- self.contacts_treeview = self.xml.get_object('contacts_treeview')
- model = gtk.ListStore(bool, str)
- self.contacts_treeview.set_model(model)
- # columns
- renderer1 = gtk.CellRendererToggle()
- renderer1.set_property('activatable', True)
- renderer1.connect('toggled', self.toggled_callback)
- self.contacts_treeview.insert_column_with_attributes(-1,
- _('Synchronise'), renderer1, active=0)
- renderer2 = gtk.CellRendererText()
- self.contacts_treeview.insert_column_with_attributes(-1,
- _('Name'), renderer2, text=1)
-
- self.xml.connect_signals(self)
- self.init_contacts()
- self.dialog.show_all()
-
- def toggled_callback(self, cell, path):
- model = self.contacts_treeview.get_model()
- iter_ = model.get_iter(path)
- model[iter_][0] = not cell.get_active()
-
- def on_contacts_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.window.destroy()
-
- def init_contacts(self):
- """
- Initialize listStore with existing accounts
- """
- model = self.contacts_treeview.get_model()
- model.clear()
-
- # recover local contacts
- local_jid_list = gajim.contacts.get_contacts_jid_list(self.local_account)
-
- remote_jid_list = gajim.contacts.get_contacts_jid_list(
- self.remote_account)
- for remote_jid in remote_jid_list:
- if remote_jid not in local_jid_list:
- iter_ = model.append()
- model.set(iter_, 0, True, 1, remote_jid)
-
- def on_cancel_button_clicked(self, widget):
- self.dialog.destroy()
-
- def on_ok_button_clicked(self, widget):
- model = self.contacts_treeview.get_model()
- iter_ = model.get_iter_root()
- while iter_:
- if model[iter_][0]:
- # it is selected
- remote_jid = model[iter_][1].decode('utf-8')
- message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \
- gajim.get_hostname_from_account(self.remote_account)
- remote_contact = gajim.contacts.get_first_contact_from_jid(
- self.remote_account, remote_jid)
- # keep same groups and same nickname
- gajim.interface.roster.req_sub(self, remote_jid, message,
- self.local_account, groups = remote_contact.groups,
- nickname = remote_contact.name, auto_auth = True)
- iter_ = model.iter_next(iter_)
- self.dialog.destroy()
+ def __init__(self, account, remote_account):
+ self.local_account = account
+ self.remote_account = remote_account
+ self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_contacts_dialog.ui')
+ self.dialog = self.xml.get_object('synchronise_select_contacts_dialog')
+ self.contacts_treeview = self.xml.get_object('contacts_treeview')
+ model = gtk.ListStore(bool, str)
+ self.contacts_treeview.set_model(model)
+ # columns
+ renderer1 = gtk.CellRendererToggle()
+ renderer1.set_property('activatable', True)
+ renderer1.connect('toggled', self.toggled_callback)
+ self.contacts_treeview.insert_column_with_attributes(-1,
+ _('Synchronise'), renderer1, active=0)
+ renderer2 = gtk.CellRendererText()
+ self.contacts_treeview.insert_column_with_attributes(-1,
+ _('Name'), renderer2, text=1)
+
+ self.xml.connect_signals(self)
+ self.init_contacts()
+ self.dialog.show_all()
+
+ def toggled_callback(self, cell, path):
+ model = self.contacts_treeview.get_model()
+ iter_ = model.get_iter(path)
+ model[iter_][0] = not cell.get_active()
+
+ def on_contacts_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def init_contacts(self):
+ """
+ Initialize listStore with existing accounts
+ """
+ model = self.contacts_treeview.get_model()
+ model.clear()
+
+ # recover local contacts
+ local_jid_list = gajim.contacts.get_contacts_jid_list(self.local_account)
+
+ remote_jid_list = gajim.contacts.get_contacts_jid_list(
+ self.remote_account)
+ for remote_jid in remote_jid_list:
+ if remote_jid not in local_jid_list:
+ iter_ = model.append()
+ model.set(iter_, 0, True, 1, remote_jid)
+
+ def on_cancel_button_clicked(self, widget):
+ self.dialog.destroy()
+
+ def on_ok_button_clicked(self, widget):
+ model = self.contacts_treeview.get_model()
+ iter_ = model.get_iter_root()
+ while iter_:
+ if model[iter_][0]:
+ # it is selected
+ remote_jid = model[iter_][1].decode('utf-8')
+ message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \
+ gajim.get_hostname_from_account(self.remote_account)
+ remote_contact = gajim.contacts.get_first_contact_from_jid(
+ self.remote_account, remote_jid)
+ # keep same groups and same nickname
+ gajim.interface.roster.req_sub(self, remote_jid, message,
+ self.local_account, groups = remote_contact.groups,
+ nickname = remote_contact.name, auto_auth = True)
+ iter_ = model.iter_next(iter_)
+ self.dialog.destroy()
class NewChatDialog(InputDialog):
- def __init__(self, account):
- self.account = account
-
- if len(gajim.connections) > 1:
- title = _('Start Chat with account %s') % account
- else:
- title = _('Start Chat')
- prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:')
- InputDialog.__init__(self, title, prompt_text, is_modal=False)
-
- self.completion_dict = {}
- liststore = gtkgui_helpers.get_completion_liststore(self.input_entry)
- self.completion_dict = helpers.get_contact_dict_for_account(account)
- # add all contacts to the model
- keys = sorted(self.completion_dict.keys())
- for jid in keys:
- contact = self.completion_dict[jid]
- img = gajim.interface.jabber_state_images['16'][contact.show]
- liststore.append((img.get_pixbuf(), jid))
-
- self.ok_handler = self.new_chat_response
- okbutton = self.xml.get_object('okbutton')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancelbutton')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
- self.dialog.show_all()
-
- def new_chat_response(self, jid):
- """
- Called when ok button is clicked
- """
- if gajim.connections[self.account].connected <= 1:
- #if offline or connecting
- ErrorDialog(_('Connection not available'),
- _('Please make sure you are connected with "%s".') % self.account)
- return
-
- if jid in self.completion_dict:
- jid = self.completion_dict[jid].jid
- else:
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat, e:
- ErrorDialog(_('Invalid JID'), e[0])
- return
- except:
- ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid)
- return
- gajim.interface.new_chat_from_jid(self.account, jid)
+ def __init__(self, account):
+ self.account = account
+
+ if len(gajim.connections) > 1:
+ title = _('Start Chat with account %s') % account
+ else:
+ title = _('Start Chat')
+ prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:')
+ InputDialog.__init__(self, title, prompt_text, is_modal=False)
+
+ self.completion_dict = {}
+ liststore = gtkgui_helpers.get_completion_liststore(self.input_entry)
+ self.completion_dict = helpers.get_contact_dict_for_account(account)
+ # add all contacts to the model
+ keys = sorted(self.completion_dict.keys())
+ for jid in keys:
+ contact = self.completion_dict[jid]
+ img = gajim.interface.jabber_state_images['16'][contact.show]
+ liststore.append((img.get_pixbuf(), jid))
+
+ self.ok_handler = self.new_chat_response
+ okbutton = self.xml.get_object('okbutton')
+ okbutton.connect('clicked', self.on_okbutton_clicked)
+ cancelbutton = self.xml.get_object('cancelbutton')
+ cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
+ self.dialog.show_all()
+
+ def new_chat_response(self, jid):
+ """
+ Called when ok button is clicked
+ """
+ if gajim.connections[self.account].connected <= 1:
+ #if offline or connecting
+ ErrorDialog(_('Connection not available'),
+ _('Please make sure you are connected with "%s".') % self.account)
+ return
+
+ if jid in self.completion_dict:
+ jid = self.completion_dict[jid].jid
+ else:
+ try:
+ jid = helpers.parse_jid(jid)
+ except helpers.InvalidFormat, e:
+ ErrorDialog(_('Invalid JID'), e[0])
+ return
+ except:
+ ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid)
+ return
+ gajim.interface.new_chat_from_jid(self.account, jid)
class ChangePasswordDialog:
- def __init__(self, account, on_response):
- # 'account' can be None if we are about to create our first one
- if not account or gajim.connections[account].connected < 2:
- ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not change your password.'))
- raise GajimGeneralException, 'You are not connected to the server'
- self.account = account
- self.on_response = on_response
- self.xml = gtkgui_helpers.get_gtk_builder('change_password_dialog.ui')
- self.dialog = self.xml.get_object('change_password_dialog')
- self.password1_entry = self.xml.get_object('password1_entry')
- self.password2_entry = self.xml.get_object('password2_entry')
- self.dialog.connect('response', self.on_dialog_response)
-
- self.dialog.show_all()
-
- def on_dialog_response(self, dialog, response):
- if response != gtk.RESPONSE_OK:
- dialog.destroy()
- self.on_response(None)
- return
- password1 = self.password1_entry.get_text().decode('utf-8')
- if not password1:
- ErrorDialog(_('Invalid password'), _('You must enter a password.'))
- return
- password2 = self.password2_entry.get_text().decode('utf-8')
- if password1 != password2:
- ErrorDialog(_('Passwords do not match'),
- _('The passwords typed in both fields must be identical.'))
- return
- dialog.destroy()
- self.on_response(password1)
+ def __init__(self, account, on_response):
+ # 'account' can be None if we are about to create our first one
+ if not account or gajim.connections[account].connected < 2:
+ ErrorDialog(_('You are not connected to the server'),
+ _('Without a connection, you can not change your password.'))
+ raise GajimGeneralException, 'You are not connected to the server'
+ self.account = account
+ self.on_response = on_response
+ self.xml = gtkgui_helpers.get_gtk_builder('change_password_dialog.ui')
+ self.dialog = self.xml.get_object('change_password_dialog')
+ self.password1_entry = self.xml.get_object('password1_entry')
+ self.password2_entry = self.xml.get_object('password2_entry')
+ self.dialog.connect('response', self.on_dialog_response)
+
+ self.dialog.show_all()
+
+ def on_dialog_response(self, dialog, response):
+ if response != gtk.RESPONSE_OK:
+ dialog.destroy()
+ self.on_response(None)
+ return
+ password1 = self.password1_entry.get_text().decode('utf-8')
+ if not password1:
+ ErrorDialog(_('Invalid password'), _('You must enter a password.'))
+ return
+ password2 = self.password2_entry.get_text().decode('utf-8')
+ if password1 != password2:
+ ErrorDialog(_('Passwords do not match'),
+ _('The passwords typed in both fields must be identical.'))
+ return
+ dialog.destroy()
+ self.on_response(password1)
class PopupNotificationWindow:
- def __init__(self, event_type, jid, account, msg_type='',
- path_to_image=None, title=None, text=None):
- self.account = account
- self.jid = jid
- self.msg_type = msg_type
-
- xml = gtkgui_helpers.get_gtk_builder('popup_notification_window.ui')
- self.window = xml.get_object('popup_notification_window')
- self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)
- close_button = xml.get_object('close_button')
- event_type_label = xml.get_object('event_type_label')
- event_description_label = xml.get_object('event_description_label')
- eventbox = xml.get_object('eventbox')
- image = xml.get_object('notification_image')
-
- if not text:
- text = gajim.get_name_from_jid(account, jid) # default value of text
- if not title:
- title = ''
-
- event_type_label.set_markup(
- '<span foreground="black" weight="bold">%s</span>' %
- gobject.markup_escape_text(title))
-
- # set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ]
- self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('black'))
-
- # default image
- if not path_to_image:
- path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
-
- if event_type == _('Contact Signed In'):
- bg_color = 'limegreen'
- elif event_type == _('Contact Signed Out'):
- bg_color = 'red'
- elif event_type in (_('New Message'), _('New Single Message'),
- _('New Private Message'), _('New E-mail')):
- bg_color = 'dodgerblue'
- elif event_type == _('File Transfer Request'):
- bg_color = 'khaki'
- elif event_type == _('File Transfer Error'):
- bg_color = 'firebrick'
- elif event_type in (_('File Transfer Completed'),
- _('File Transfer Stopped')):
- bg_color = 'yellowgreen'
- elif event_type == _('Groupchat Invitation'):
- bg_color = 'tan1'
- elif event_type == _('Contact Changed Status'):
- bg_color = 'thistle2'
- else: # Unknown event! Shouldn't happen but deal with it
- bg_color = 'white'
- popup_bg_color = gtk.gdk.color_parse(bg_color)
- close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
- eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
- event_description_label.set_markup('<span foreground="black">%s</span>' %
- gobject.markup_escape_text(text))
-
- # set the image
- image.set_from_file(path_to_image)
-
- # position the window to bottom-right of screen
- window_width, self.window_height = self.window.get_size()
- gajim.interface.roster.popups_notification_height += self.window_height
- pos_x = gajim.config.get('notification_position_x')
- if pos_x < 0:
- pos_x = gtk.gdk.screen_width() - window_width + pos_x + 1
- pos_y = gajim.config.get('notification_position_y')
- if pos_y < 0:
- pos_y = gtk.gdk.screen_height() - \
- gajim.interface.roster.popups_notification_height + pos_y + 1
- self.window.move(pos_x, pos_y)
-
- xml.connect_signals(self)
- self.window.show_all()
- timeout = gajim.config.get('notification_timeout')
- gobject.timeout_add_seconds(timeout, self.on_timeout)
-
- def on_close_button_clicked(self, widget):
- self.adjust_height_and_move_popup_notification_windows()
-
- def on_timeout(self):
- self.adjust_height_and_move_popup_notification_windows()
-
- def adjust_height_and_move_popup_notification_windows(self):
- #remove
- gajim.interface.roster.popups_notification_height -= self.window_height
- self.window.destroy()
-
- if len(gajim.interface.roster.popup_notification_windows) > 0:
- # we want to remove the first window added in the list
- gajim.interface.roster.popup_notification_windows.pop(0)
-
- # move the rest of popup windows
- gajim.interface.roster.popups_notification_height = 0
- for window_instance in gajim.interface.roster.popup_notification_windows:
- window_width, window_height = window_instance.window.get_size()
- gajim.interface.roster.popups_notification_height += window_height
- window_instance.window.move(gtk.gdk.screen_width() - window_width,
- gtk.gdk.screen_height() - \
- gajim.interface.roster.popups_notification_height)
-
- def on_popup_notification_window_button_press_event(self, widget, event):
- if event.button != 1:
- self.window.destroy()
- return
- gajim.interface.handle_event(self.account, self.jid, self.msg_type)
- self.adjust_height_and_move_popup_notification_windows()
+ def __init__(self, event_type, jid, account, msg_type='',
+ path_to_image=None, title=None, text=None):
+ self.account = account
+ self.jid = jid
+ self.msg_type = msg_type
+
+ xml = gtkgui_helpers.get_gtk_builder('popup_notification_window.ui')
+ self.window = xml.get_object('popup_notification_window')
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)
+ close_button = xml.get_object('close_button')
+ event_type_label = xml.get_object('event_type_label')
+ event_description_label = xml.get_object('event_description_label')
+ eventbox = xml.get_object('eventbox')
+ image = xml.get_object('notification_image')
+
+ if not text:
+ text = gajim.get_name_from_jid(account, jid) # default value of text
+ if not title:
+ title = ''
+
+ event_type_label.set_markup(
+ '<span foreground="black" weight="bold">%s</span>' %
+ gobject.markup_escape_text(title))
+
+ # set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ]
+ self.window.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('black'))
+
+ # default image
+ if not path_to_image:
+ path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
+
+ if event_type == _('Contact Signed In'):
+ bg_color = 'limegreen'
+ elif event_type == _('Contact Signed Out'):
+ bg_color = 'red'
+ elif event_type in (_('New Message'), _('New Single Message'),
+ _('New Private Message'), _('New E-mail')):
+ bg_color = 'dodgerblue'
+ elif event_type == _('File Transfer Request'):
+ bg_color = 'khaki'
+ elif event_type == _('File Transfer Error'):
+ bg_color = 'firebrick'
+ elif event_type in (_('File Transfer Completed'),
+ _('File Transfer Stopped')):
+ bg_color = 'yellowgreen'
+ elif event_type == _('Groupchat Invitation'):
+ bg_color = 'tan1'
+ elif event_type == _('Contact Changed Status'):
+ bg_color = 'thistle2'
+ else: # Unknown event! Shouldn't happen but deal with it
+ bg_color = 'white'
+ popup_bg_color = gtk.gdk.color_parse(bg_color)
+ close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
+ eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
+ event_description_label.set_markup('<span foreground="black">%s</span>' %
+ gobject.markup_escape_text(text))
+
+ # set the image
+ image.set_from_file(path_to_image)
+
+ # position the window to bottom-right of screen
+ window_width, self.window_height = self.window.get_size()
+ gajim.interface.roster.popups_notification_height += self.window_height
+ pos_x = gajim.config.get('notification_position_x')
+ if pos_x < 0:
+ pos_x = gtk.gdk.screen_width() - window_width + pos_x + 1
+ pos_y = gajim.config.get('notification_position_y')
+ if pos_y < 0:
+ pos_y = gtk.gdk.screen_height() - \
+ gajim.interface.roster.popups_notification_height + pos_y + 1
+ self.window.move(pos_x, pos_y)
+
+ xml.connect_signals(self)
+ self.window.show_all()
+ timeout = gajim.config.get('notification_timeout')
+ gobject.timeout_add_seconds(timeout, self.on_timeout)
+
+ def on_close_button_clicked(self, widget):
+ self.adjust_height_and_move_popup_notification_windows()
+
+ def on_timeout(self):
+ self.adjust_height_and_move_popup_notification_windows()
+
+ def adjust_height_and_move_popup_notification_windows(self):
+ #remove
+ gajim.interface.roster.popups_notification_height -= self.window_height
+ self.window.destroy()
+
+ if len(gajim.interface.roster.popup_notification_windows) > 0:
+ # we want to remove the first window added in the list
+ gajim.interface.roster.popup_notification_windows.pop(0)
+
+ # move the rest of popup windows
+ gajim.interface.roster.popups_notification_height = 0
+ for window_instance in gajim.interface.roster.popup_notification_windows:
+ window_width, window_height = window_instance.window.get_size()
+ gajim.interface.roster.popups_notification_height += window_height
+ window_instance.window.move(gtk.gdk.screen_width() - window_width,
+ gtk.gdk.screen_height() - \
+ gajim.interface.roster.popups_notification_height)
+
+ def on_popup_notification_window_button_press_event(self, widget, event):
+ if event.button != 1:
+ self.window.destroy()
+ return
+ gajim.interface.handle_event(self.account, self.jid, self.msg_type)
+ self.adjust_height_and_move_popup_notification_windows()
class SingleMessageWindow:
- """
- SingleMessageWindow can send or show a received singled message depending on
- action argument which can be 'send' or 'receive'
- """
- # Keep a reference on windows so garbage collector don't restroy them
- instances = []
- def __init__(self, account, to='', action='', from_whom='', subject='',
- message='', resource='', session=None, form_node=None):
- self.instances.append(self)
- self.account = account
- self.action = action
-
- self.subject = subject
- self.message = message
- self.to = to
- self.from_whom = from_whom
- self.resource = resource
- self.session = session
-
- self.xml = gtkgui_helpers.get_gtk_builder('single_message_window.ui')
- self.window = self.xml.get_object('single_message_window')
- self.count_chars_label = self.xml.get_object('count_chars_label')
- self.from_label = self.xml.get_object('from_label')
- self.from_entry = self.xml.get_object('from_entry')
- self.to_label = self.xml.get_object('to_label')
- self.to_entry = self.xml.get_object('to_entry')
- self.subject_entry = self.xml.get_object('subject_entry')
- self.message_scrolledwindow = self.xml.get_object(
- 'message_scrolledwindow')
- self.message_textview = self.xml.get_object('message_textview')
- self.message_tv_buffer = self.message_textview.get_buffer()
- self.conversation_scrolledwindow = self.xml.get_object(
- 'conversation_scrolledwindow')
- self.conversation_textview = conversation_textview.ConversationTextview(
- account)
- self.conversation_textview.tv.show()
- self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer()
- self.xml.get_object('conversation_scrolledwindow').add(
- self.conversation_textview.tv)
-
- self.form_widget = None
- parent_box = self.xml.get_object('conversation_scrolledwindow').\
- get_parent()
- if form_node:
- dataform = dataforms.ExtendForm(node=form_node)
- self.form_widget = dataforms_widget.DataFormWidget(dataform)
- self.form_widget.show_all()
- parent_box.add(self.form_widget)
- parent_box.child_set_property(self.form_widget, 'position',
- parent_box.child_get_property(self.xml.get_object(
- 'conversation_scrolledwindow'), 'position'))
- self.action = 'form'
-
- self.send_button = self.xml.get_object('send_button')
- self.reply_button = self.xml.get_object('reply_button')
- self.send_and_close_button = self.xml.get_object('send_and_close_button')
- self.cancel_button = self.xml.get_object('cancel_button')
- self.close_button = self.xml.get_object('close_button')
- self.message_tv_buffer.connect('changed', self.update_char_counter)
- if isinstance(to, list):
- jid = ', '.join( [i[0].jid + '/' + i[0].resource for i in to])
- self.to_entry.set_text(jid)
- self.to_entry.set_sensitive(False)
- else:
- self.to_entry.set_text(to)
-
- if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send':
- try:
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- gtkspell.Spell(self.conversation_textview.tv, lang)
- gtkspell.Spell(self.message_textview, lang)
- except (gobject.GError, TypeError, RuntimeError, OSError):
- AspellDictError(lang)
-
- self.prepare_widgets_for(self.action)
-
- # set_text(None) raises TypeError exception
- if self.subject is None:
- self.subject = ''
- self.subject_entry.set_text(self.subject)
-
-
- if to == '':
- liststore = gtkgui_helpers.get_completion_liststore(self.to_entry)
- self.completion_dict = helpers.get_contact_dict_for_account(account)
- keys = sorted(self.completion_dict.keys())
- for jid in keys:
- contact = self.completion_dict[jid]
- img = gajim.interface.jabber_state_images['16'][contact.show]
- liststore.append((img.get_pixbuf(), jid))
- else:
- self.completion_dict = {}
- self.xml.connect_signals(self)
-
- # get window position and size from config
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('single-msg-width'),
- gajim.config.get('single-msg-height'))
- gtkgui_helpers.move_window(self.window,
- gajim.config.get('single-msg-x-position'),
- gajim.config.get('single-msg-y-position'))
-
- self.window.show_all()
-
- def on_single_message_window_destroy(self, widget):
- self.instances.remove(self)
- c = gajim.contacts.get_contact_with_highest_priority(self.account,
- self.from_whom)
- if not c:
- # Groupchat is maybe already destroyed
- return
- if c.is_groupchat() and not self.from_whom in \
- gajim.interface.minimized_controls[self.account] and self.action == \
- 'receive' and gajim.events.get_nb_roster_events(self.account,
- self.from_whom, types=['chat', 'normal']) == 0:
- gajim.interface.roster.remove_groupchat(self.from_whom, self.account)
-
- def set_cursor_to_end(self):
- end_iter = self.message_tv_buffer.get_end_iter()
- self.message_tv_buffer.place_cursor(end_iter)
-
- def save_pos(self):
- # save the window size and position
- x, y = self.window.get_position()
- gajim.config.set('single-msg-x-position', x)
- gajim.config.set('single-msg-y-position', y)
- width, height = self.window.get_size()
- gajim.config.set('single-msg-width', width)
- gajim.config.set('single-msg-height', height)
- gajim.interface.save_config()
-
- def on_single_message_window_delete_event(self, window, ev):
- self.save_pos()
-
- def prepare_widgets_for(self, action):
- if len(gajim.connections) > 1:
- if action == 'send':
- title = _('Single Message using account %s') % self.account
- else:
- title = _('Single Message in account %s') % self.account
- else:
- title = _('Single Message')
-
- if action == 'send': # prepare UI for Sending
- title = _('Send %s') % title
- self.send_button.show()
- self.send_and_close_button.show()
- self.to_label.show()
- self.to_entry.show()
- self.reply_button.hide()
- self.from_label.hide()
- self.from_entry.hide()
- self.conversation_scrolledwindow.hide()
- self.message_scrolledwindow.show()
-
- if self.message: # we come from a reply?
- self.message_textview.grab_focus()
- self.cancel_button.hide()
- self.close_button.show()
- self.message_tv_buffer.set_text(self.message)
- gobject.idle_add(self.set_cursor_to_end)
- else: # we write a new message (not from reply)
- self.close_button.hide()
- if self.to: # do we already have jid?
- self.subject_entry.grab_focus()
-
- elif action == 'receive': # prepare UI for Receiving
- title = _('Received %s') % title
- self.reply_button.show()
- self.from_label.show()
- self.from_entry.show()
- self.send_button.hide()
- self.send_and_close_button.hide()
- self.to_label.hide()
- self.to_entry.hide()
- self.conversation_scrolledwindow.show()
- self.message_scrolledwindow.hide()
-
- if self.message:
- self.conversation_textview.print_real_text(self.message)
- fjid = self.from_whom
- if self.resource:
- fjid += '/' + self.resource # Full jid of sender (with resource)
- self.from_entry.set_text(fjid)
- self.from_entry.set_property('editable', False)
- self.subject_entry.set_property('editable', False)
- self.reply_button.grab_focus()
- self.cancel_button.hide()
- self.close_button.show()
- elif action == 'form': # prepare UI for Receiving
- title = _('Form %s') % title
- self.send_button.show()
- self.send_and_close_button.show()
- self.to_label.show()
- self.to_entry.show()
- self.reply_button.hide()
- self.from_label.hide()
- self.from_entry.hide()
- self.conversation_scrolledwindow.hide()
- self.message_scrolledwindow.hide()
-
- self.window.set_title(title)
-
- def on_cancel_button_clicked(self, widget):
- self.save_pos()
- self.window.destroy()
-
- def on_close_button_clicked(self, widget):
- self.save_pos()
- self.window.destroy()
-
- def update_char_counter(self, widget):
- characters_no = self.message_tv_buffer.get_char_count()
- self.count_chars_label.set_text(unicode(characters_no))
-
- def send_single_message(self):
- if gajim.connections[self.account].connected <= 1:
- # if offline or connecting
- ErrorDialog(_('Connection not available'),
- _('Please make sure you are connected with "%s".') % self.account)
- return
- if isinstance(self.to, list):
- sender_list = [i[0].jid + '/' + i[0].resource for i in self.to]
- else:
- sender_list = [self.to_entry.get_text().decode('utf-8')]
-
- for to_whom_jid in sender_list:
- if to_whom_jid in self.completion_dict:
- to_whom_jid = self.completion_dict[to_whom_jid].jid
- try:
- to_whom_jid = helpers.parse_jid(to_whom_jid)
- except helpers.InvalidFormat:
- ErrorDialog(_('Invalid Jabber ID'),
- _('It is not possible to send a message to %s, this JID is not '
- 'valid.') % to_whom_jid)
- return
-
- subject = self.subject_entry.get_text().decode('utf-8')
- begin, end = self.message_tv_buffer.get_bounds()
- message = self.message_tv_buffer.get_text(begin, end).decode('utf-8')
-
- if '/announce/' in to_whom_jid:
- gajim.connections[self.account].send_motd(to_whom_jid, subject,
- message)
- continue
-
- if self.session:
- session = self.session
- else:
- session = gajim.connections[self.account].make_new_session(
- to_whom_jid)
-
- if self.form_widget:
- form_node = self.form_widget.data_form
- else:
- form_node = None
- # FIXME: allow GPG message some day
- gajim.connections[self.account].send_message(to_whom_jid, message,
- keyID=None, type_='normal', subject=subject, session=session,
- form_node=form_node)
-
- self.subject_entry.set_text('') # we sent ok, clear the subject
- self.message_tv_buffer.set_text('') # we sent ok, clear the textview
-
- def on_send_button_clicked(self, widget):
- self.send_single_message()
-
- def on_reply_button_clicked(self, widget):
- # we create a new blank window to send and we preset RE: and to jid
- self.subject = _('RE: %s') % self.subject
- self.message = _('%s wrote:\n') % self.from_whom + self.message
- # add > at the begining of each line
- self.message = self.message.replace('\n', '\n> ') + '\n\n'
- self.window.destroy()
- SingleMessageWindow(self.account, to=self.from_whom, action='send',
- from_whom=self.from_whom, subject=self.subject, message=self.message,
- session=self.session)
-
- def on_send_and_close_button_clicked(self, widget):
- self.send_single_message()
- self.save_pos()
- self.window.destroy()
-
- def on_single_message_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape: # ESCAPE
- self.save_pos()
- self.window.destroy()
+ """
+ SingleMessageWindow can send or show a received singled message depending on
+ action argument which can be 'send' or 'receive'
+ """
+ # Keep a reference on windows so garbage collector don't restroy them
+ instances = []
+ def __init__(self, account, to='', action='', from_whom='', subject='',
+ message='', resource='', session=None, form_node=None):
+ self.instances.append(self)
+ self.account = account
+ self.action = action
+
+ self.subject = subject
+ self.message = message
+ self.to = to
+ self.from_whom = from_whom
+ self.resource = resource
+ self.session = session
+
+ self.xml = gtkgui_helpers.get_gtk_builder('single_message_window.ui')
+ self.window = self.xml.get_object('single_message_window')
+ self.count_chars_label = self.xml.get_object('count_chars_label')
+ self.from_label = self.xml.get_object('from_label')
+ self.from_entry = self.xml.get_object('from_entry')
+ self.to_label = self.xml.get_object('to_label')
+ self.to_entry = self.xml.get_object('to_entry')
+ self.subject_entry = self.xml.get_object('subject_entry')
+ self.message_scrolledwindow = self.xml.get_object(
+ 'message_scrolledwindow')
+ self.message_textview = self.xml.get_object('message_textview')
+ self.message_tv_buffer = self.message_textview.get_buffer()
+ self.conversation_scrolledwindow = self.xml.get_object(
+ 'conversation_scrolledwindow')
+ self.conversation_textview = conversation_textview.ConversationTextview(
+ account)
+ self.conversation_textview.tv.show()
+ self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer()
+ self.xml.get_object('conversation_scrolledwindow').add(
+ self.conversation_textview.tv)
+
+ self.form_widget = None
+ parent_box = self.xml.get_object('conversation_scrolledwindow').\
+ get_parent()
+ if form_node:
+ dataform = dataforms.ExtendForm(node=form_node)
+ self.form_widget = dataforms_widget.DataFormWidget(dataform)
+ self.form_widget.show_all()
+ parent_box.add(self.form_widget)
+ parent_box.child_set_property(self.form_widget, 'position',
+ parent_box.child_get_property(self.xml.get_object(
+ 'conversation_scrolledwindow'), 'position'))
+ self.action = 'form'
+
+ self.send_button = self.xml.get_object('send_button')
+ self.reply_button = self.xml.get_object('reply_button')
+ self.send_and_close_button = self.xml.get_object('send_and_close_button')
+ self.cancel_button = self.xml.get_object('cancel_button')
+ self.close_button = self.xml.get_object('close_button')
+ self.message_tv_buffer.connect('changed', self.update_char_counter)
+ if isinstance(to, list):
+ jid = ', '.join( [i[0].jid + '/' + i[0].resource for i in to])
+ self.to_entry.set_text(jid)
+ self.to_entry.set_sensitive(False)
+ else:
+ self.to_entry.set_text(to)
+
+ if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send':
+ try:
+ lang = gajim.config.get('speller_language')
+ if not lang:
+ lang = gajim.LANG
+ gtkspell.Spell(self.conversation_textview.tv, lang)
+ gtkspell.Spell(self.message_textview, lang)
+ except (gobject.GError, TypeError, RuntimeError, OSError):
+ AspellDictError(lang)
+
+ self.prepare_widgets_for(self.action)
+
+ # set_text(None) raises TypeError exception
+ if self.subject is None:
+ self.subject = ''
+ self.subject_entry.set_text(self.subject)
+
+
+ if to == '':
+ liststore = gtkgui_helpers.get_completion_liststore(self.to_entry)
+ self.completion_dict = helpers.get_contact_dict_for_account(account)
+ keys = sorted(self.completion_dict.keys())
+ for jid in keys:
+ contact = self.completion_dict[jid]
+ img = gajim.interface.jabber_state_images['16'][contact.show]
+ liststore.append((img.get_pixbuf(), jid))
+ else:
+ self.completion_dict = {}
+ self.xml.connect_signals(self)
+
+ # get window position and size from config
+ gtkgui_helpers.resize_window(self.window,
+ gajim.config.get('single-msg-width'),
+ gajim.config.get('single-msg-height'))
+ gtkgui_helpers.move_window(self.window,
+ gajim.config.get('single-msg-x-position'),
+ gajim.config.get('single-msg-y-position'))
+
+ self.window.show_all()
+
+ def on_single_message_window_destroy(self, widget):
+ self.instances.remove(self)
+ c = gajim.contacts.get_contact_with_highest_priority(self.account,
+ self.from_whom)
+ if not c:
+ # Groupchat is maybe already destroyed
+ return
+ if c.is_groupchat() and not self.from_whom in \
+ gajim.interface.minimized_controls[self.account] and self.action == \
+ 'receive' and gajim.events.get_nb_roster_events(self.account,
+ self.from_whom, types=['chat', 'normal']) == 0:
+ gajim.interface.roster.remove_groupchat(self.from_whom, self.account)
+
+ def set_cursor_to_end(self):
+ end_iter = self.message_tv_buffer.get_end_iter()
+ self.message_tv_buffer.place_cursor(end_iter)
+
+ def save_pos(self):
+ # save the window size and position
+ x, y = self.window.get_position()
+ gajim.config.set('single-msg-x-position', x)
+ gajim.config.set('single-msg-y-position', y)
+ width, height = self.window.get_size()
+ gajim.config.set('single-msg-width', width)
+ gajim.config.set('single-msg-height', height)
+ gajim.interface.save_config()
+
+ def on_single_message_window_delete_event(self, window, ev):
+ self.save_pos()
+
+ def prepare_widgets_for(self, action):
+ if len(gajim.connections) > 1:
+ if action == 'send':
+ title = _('Single Message using account %s') % self.account
+ else:
+ title = _('Single Message in account %s') % self.account
+ else:
+ title = _('Single Message')
+
+ if action == 'send': # prepare UI for Sending
+ title = _('Send %s') % title
+ self.send_button.show()
+ self.send_and_close_button.show()
+ self.to_label.show()
+ self.to_entry.show()
+ self.reply_button.hide()
+ self.from_label.hide()
+ self.from_entry.hide()
+ self.conversation_scrolledwindow.hide()
+ self.message_scrolledwindow.show()
+
+ if self.message: # we come from a reply?
+ self.message_textview.grab_focus()
+ self.cancel_button.hide()
+ self.close_button.show()
+ self.message_tv_buffer.set_text(self.message)
+ gobject.idle_add(self.set_cursor_to_end)
+ else: # we write a new message (not from reply)
+ self.close_button.hide()
+ if self.to: # do we already have jid?
+ self.subject_entry.grab_focus()
+
+ elif action == 'receive': # prepare UI for Receiving
+ title = _('Received %s') % title
+ self.reply_button.show()
+ self.from_label.show()
+ self.from_entry.show()
+ self.send_button.hide()
+ self.send_and_close_button.hide()
+ self.to_label.hide()
+ self.to_entry.hide()
+ self.conversation_scrolledwindow.show()
+ self.message_scrolledwindow.hide()
+
+ if self.message:
+ self.conversation_textview.print_real_text(self.message)
+ fjid = self.from_whom
+ if self.resource:
+ fjid += '/' + self.resource # Full jid of sender (with resource)
+ self.from_entry.set_text(fjid)
+ self.from_entry.set_property('editable', False)
+ self.subject_entry.set_property('editable', False)
+ self.reply_button.grab_focus()
+ self.cancel_button.hide()
+ self.close_button.show()
+ elif action == 'form': # prepare UI for Receiving
+ title = _('Form %s') % title
+ self.send_button.show()
+ self.send_and_close_button.show()
+ self.to_label.show()
+ self.to_entry.show()
+ self.reply_button.hide()
+ self.from_label.hide()
+ self.from_entry.hide()
+ self.conversation_scrolledwindow.hide()
+ self.message_scrolledwindow.hide()
+
+ self.window.set_title(title)
+
+ def on_cancel_button_clicked(self, widget):
+ self.save_pos()
+ self.window.destroy()
+
+ def on_close_button_clicked(self, widget):
+ self.save_pos()
+ self.window.destroy()
+
+ def update_char_counter(self, widget):
+ characters_no = self.message_tv_buffer.get_char_count()
+ self.count_chars_label.set_text(unicode(characters_no))
+
+ def send_single_message(self):
+ if gajim.connections[self.account].connected <= 1:
+ # if offline or connecting
+ ErrorDialog(_('Connection not available'),
+ _('Please make sure you are connected with "%s".') % self.account)
+ return
+ if isinstance(self.to, list):
+ sender_list = [i[0].jid + '/' + i[0].resource for i in self.to]
+ else:
+ sender_list = [self.to_entry.get_text().decode('utf-8')]
+
+ for to_whom_jid in sender_list:
+ if to_whom_jid in self.completion_dict:
+ to_whom_jid = self.completion_dict[to_whom_jid].jid
+ try:
+ to_whom_jid = helpers.parse_jid(to_whom_jid)
+ except helpers.InvalidFormat:
+ ErrorDialog(_('Invalid Jabber ID'),
+ _('It is not possible to send a message to %s, this JID is not '
+ 'valid.') % to_whom_jid)
+ return
+
+ subject = self.subject_entry.get_text().decode('utf-8')
+ begin, end = self.message_tv_buffer.get_bounds()
+ message = self.message_tv_buffer.get_text(begin, end).decode('utf-8')
+
+ if '/announce/' in to_whom_jid:
+ gajim.connections[self.account].send_motd(to_whom_jid, subject,
+ message)
+ continue
+
+ if self.session:
+ session = self.session
+ else:
+ session = gajim.connections[self.account].make_new_session(
+ to_whom_jid)
+
+ if self.form_widget:
+ form_node = self.form_widget.data_form
+ else:
+ form_node = None
+ # FIXME: allow GPG message some day
+ gajim.connections[self.account].send_message(to_whom_jid, message,
+ keyID=None, type_='normal', subject=subject, session=session,
+ form_node=form_node)
+
+ self.subject_entry.set_text('') # we sent ok, clear the subject
+ self.message_tv_buffer.set_text('') # we sent ok, clear the textview
+
+ def on_send_button_clicked(self, widget):
+ self.send_single_message()
+
+ def on_reply_button_clicked(self, widget):
+ # we create a new blank window to send and we preset RE: and to jid
+ self.subject = _('RE: %s') % self.subject
+ self.message = _('%s wrote:\n') % self.from_whom + self.message
+ # add > at the begining of each line
+ self.message = self.message.replace('\n', '\n> ') + '\n\n'
+ self.window.destroy()
+ SingleMessageWindow(self.account, to=self.from_whom, action='send',
+ from_whom=self.from_whom, subject=self.subject, message=self.message,
+ session=self.session)
+
+ def on_send_and_close_button_clicked(self, widget):
+ self.send_single_message()
+ self.save_pos()
+ self.window.destroy()
+
+ def on_single_message_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape: # ESCAPE
+ self.save_pos()
+ self.window.destroy()
class XMLConsoleWindow:
- def __init__(self, account):
- self.account = account
-
- self.xml = gtkgui_helpers.get_gtk_builder('xml_console_window.ui')
- self.window = self.xml.get_object('xml_console_window')
- self.input_textview = self.xml.get_object('input_textview')
- self.stanzas_log_textview = self.xml.get_object('stanzas_log_textview')
- self.input_tv_buffer = self.input_textview.get_buffer()
- buffer_ = self.stanzas_log_textview.get_buffer()
- end_iter = buffer_.get_end_iter()
- buffer_.create_mark('end', end_iter, False)
-
- self.tagIn = buffer_.create_tag('incoming')
- color = gajim.config.get('inmsgcolor')
- self.tagIn.set_property('foreground', color)
- self.tagInPresence = buffer_.create_tag('incoming_presence')
- self.tagInPresence.set_property('foreground', color)
- self.tagInMessage = buffer_.create_tag('incoming_message')
- self.tagInMessage.set_property('foreground', color)
- self.tagInIq = buffer_.create_tag('incoming_iq')
- self.tagInIq.set_property('foreground', color)
-
- self.tagOut = buffer_.create_tag('outgoing')
- color = gajim.config.get('outmsgcolor')
- self.tagOut.set_property('foreground', color)
- self.tagOutPresence = buffer_.create_tag('outgoing_presence')
- self.tagOutPresence.set_property('foreground', color)
- self.tagOutMessage = buffer_.create_tag('outgoing_message')
- self.tagOutMessage.set_property('foreground', color)
- self.tagOutIq = buffer_.create_tag('outgoing_iq')
- self.tagOutIq.set_property('foreground', color)
- buffer_.create_tag('') # Default tag
-
- self.enabled = False
-
- self.input_textview.modify_text(
- gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
-
- if len(gajim.connections) > 1:
- title = _('XML Console for %s') % self.account
- else:
- title = _('XML Console')
-
- self.window.set_title(title)
- self.window.show_all()
-
- self.xml.connect_signals(self)
-
- def on_xml_console_window_delete_event(self, widget, event):
- self.window.hide()
- return True # do NOT destroy the window
-
- def on_clear_button_clicked(self, widget):
- buffer_ = self.stanzas_log_textview.get_buffer()
- buffer_.set_text('')
-
- def on_enable_checkbutton_toggled(self, widget):
- self.enabled = widget.get_active()
-
- def on_in_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagIn.set_property('invisible', active)
- self.tagInPresence.set_property('invisible', active)
- self.tagInMessage.set_property('invisible', active)
- self.tagInIq.set_property('invisible', active)
-
- def on_presence_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagInPresence.set_property('invisible', active)
- self.tagOutPresence.set_property('invisible', active)
-
- def on_out_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagOut.set_property('invisible', active)
- self.tagOutPresence.set_property('invisible', active)
- self.tagOutMessage.set_property('invisible', active)
- self.tagOutIq.set_property('invisible', active)
-
- def on_message_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagInMessage.set_property('invisible', active)
- self.tagOutMessage.set_property('invisible', active)
-
- def on_iq_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagInIq.set_property('invisible', active)
- self.tagOutIq.set_property('invisible', active)
-
- def scroll_to_end(self, ):
- parent = self.stanzas_log_textview.get_parent()
- buffer_ = self.stanzas_log_textview.get_buffer()
- end_mark = buffer_.get_mark('end')
- if not end_mark:
- return False
- self.stanzas_log_textview.scroll_to_mark(end_mark, 0, True, 0, 1)
- adjustment = parent.get_hadjustment()
- adjustment.set_value(0)
- return False
-
- def print_stanza(self, stanza, kind):
- # kind must be 'incoming' or 'outgoing'
- if not self.enabled:
- return
- if not stanza:
- return
-
- buffer = self.stanzas_log_textview.get_buffer()
- at_the_end = False
- end_iter = buffer.get_end_iter()
- end_rect = self.stanzas_log_textview.get_iter_location(end_iter)
- visible_rect = self.stanzas_log_textview.get_visible_rect()
- if end_rect.y <= (visible_rect.y + visible_rect.height):
- at_the_end = True
- end_iter = buffer.get_end_iter()
-
- type_ = ''
- if stanza[1:9] == 'presence':
- type_ = 'presence'
- elif stanza[1:8] == 'message':
- type_ = 'message'
- elif stanza[1:3] == 'iq':
- type_ = 'iq'
-
- if type_:
- type_ = kind + '_' + type_
- else:
- type_ = kind # 'incoming' or 'outgoing'
-
- if kind == 'incoming':
- buffer.insert_with_tags_by_name(end_iter, '<!-- In -->\n',
- type_)
- elif kind == 'outgoing':
- buffer.insert_with_tags_by_name(end_iter, '<!-- Out -->\n',
- type_)
- end_iter = buffer.get_end_iter()
- buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') +\
- '\n\n', type_)
- if at_the_end:
- gobject.idle_add(self.scroll_to_end)
-
- def on_send_button_clicked(self, widget):
- if gajim.connections[self.account].connected <= 1:
- #if offline or connecting
- ErrorDialog(_('Connection not available'),
- _('Please make sure you are connected with "%s".') % self.account)
- return
- begin_iter, end_iter = self.input_tv_buffer.get_bounds()
- stanza = self.input_tv_buffer.get_text(begin_iter, end_iter).decode(
- 'utf-8')
- if stanza:
- gajim.connections[self.account].send_stanza(stanza)
- self.input_tv_buffer.set_text('') # we sent ok, clear the textview
-
- def on_presence_button_clicked(self, widget):
- self.input_tv_buffer.set_text(
- '<presence><show></show><status></status><priority></priority>'
- '</presence>')
-
- def on_iq_button_clicked(self, widget):
- self.input_tv_buffer.set_text(
- '<iq to="" type=""><query xmlns=""></query></iq>')
-
- def on_message_button_clicked(self, widget):
- self.input_tv_buffer.set_text(
- '<message to="" type=""><body></body></message>')
-
- def on_expander_activate(self, widget):
- if not widget.get_expanded(): # it's the opposite!
- # it's expanded!!
- self.input_textview.grab_focus()
+ def __init__(self, account):
+ self.account = account
+
+ self.xml = gtkgui_helpers.get_gtk_builder('xml_console_window.ui')
+ self.window = self.xml.get_object('xml_console_window')
+ self.input_textview = self.xml.get_object('input_textview')
+ self.stanzas_log_textview = self.xml.get_object('stanzas_log_textview')
+ self.input_tv_buffer = self.input_textview.get_buffer()
+ buffer_ = self.stanzas_log_textview.get_buffer()
+ end_iter = buffer_.get_end_iter()
+ buffer_.create_mark('end', end_iter, False)
+
+ self.tagIn = buffer_.create_tag('incoming')
+ color = gajim.config.get('inmsgcolor')
+ self.tagIn.set_property('foreground', color)
+ self.tagInPresence = buffer_.create_tag('incoming_presence')
+ self.tagInPresence.set_property('foreground', color)
+ self.tagInMessage = buffer_.create_tag('incoming_message')
+ self.tagInMessage.set_property('foreground', color)
+ self.tagInIq = buffer_.create_tag('incoming_iq')
+ self.tagInIq.set_property('foreground', color)
+
+ self.tagOut = buffer_.create_tag('outgoing')
+ color = gajim.config.get('outmsgcolor')
+ self.tagOut.set_property('foreground', color)
+ self.tagOutPresence = buffer_.create_tag('outgoing_presence')
+ self.tagOutPresence.set_property('foreground', color)
+ self.tagOutMessage = buffer_.create_tag('outgoing_message')
+ self.tagOutMessage.set_property('foreground', color)
+ self.tagOutIq = buffer_.create_tag('outgoing_iq')
+ self.tagOutIq.set_property('foreground', color)
+ buffer_.create_tag('') # Default tag
+
+ self.enabled = False
+
+ self.input_textview.modify_text(
+ gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
+
+ if len(gajim.connections) > 1:
+ title = _('XML Console for %s') % self.account
+ else:
+ title = _('XML Console')
+
+ self.window.set_title(title)
+ self.window.show_all()
+
+ self.xml.connect_signals(self)
+
+ def on_xml_console_window_delete_event(self, widget, event):
+ self.window.hide()
+ return True # do NOT destroy the window
+
+ def on_clear_button_clicked(self, widget):
+ buffer_ = self.stanzas_log_textview.get_buffer()
+ buffer_.set_text('')
+
+ def on_enable_checkbutton_toggled(self, widget):
+ self.enabled = widget.get_active()
+
+ def on_in_stanza_checkbutton_toggled(self, widget):
+ active = widget.get_active()
+ self.tagIn.set_property('invisible', active)
+ self.tagInPresence.set_property('invisible', active)
+ self.tagInMessage.set_property('invisible', active)
+ self.tagInIq.set_property('invisible', active)
+
+ def on_presence_stanza_checkbutton_toggled(self, widget):
+ active = widget.get_active()
+ self.tagInPresence.set_property('invisible', active)
+ self.tagOutPresence.set_property('invisible', active)
+
+ def on_out_stanza_checkbutton_toggled(self, widget):
+ active = widget.get_active()
+ self.tagOut.set_property('invisible', active)
+ self.tagOutPresence.set_property('invisible', active)
+ self.tagOutMessage.set_property('invisible', active)
+ self.tagOutIq.set_property('invisible', active)
+
+ def on_message_stanza_checkbutton_toggled(self, widget):
+ active = widget.get_active()
+ self.tagInMessage.set_property('invisible', active)
+ self.tagOutMessage.set_property('invisible', active)
+
+ def on_iq_stanza_checkbutton_toggled(self, widget):
+ active = widget.get_active()
+ self.tagInIq.set_property('invisible', active)
+ self.tagOutIq.set_property('invisible', active)
+
+ def scroll_to_end(self, ):
+ parent = self.stanzas_log_textview.get_parent()
+ buffer_ = self.stanzas_log_textview.get_buffer()
+ end_mark = buffer_.get_mark('end')
+ if not end_mark:
+ return False
+ self.stanzas_log_textview.scroll_to_mark(end_mark, 0, True, 0, 1)
+ adjustment = parent.get_hadjustment()
+ adjustment.set_value(0)
+ return False
+
+ def print_stanza(self, stanza, kind):
+ # kind must be 'incoming' or 'outgoing'
+ if not self.enabled:
+ return
+ if not stanza:
+ return
+
+ buffer = self.stanzas_log_textview.get_buffer()
+ at_the_end = False
+ end_iter = buffer.get_end_iter()
+ end_rect = self.stanzas_log_textview.get_iter_location(end_iter)
+ visible_rect = self.stanzas_log_textview.get_visible_rect()
+ if end_rect.y <= (visible_rect.y + visible_rect.height):
+ at_the_end = True
+ end_iter = buffer.get_end_iter()
+
+ type_ = ''
+ if stanza[1:9] == 'presence':
+ type_ = 'presence'
+ elif stanza[1:8] == 'message':
+ type_ = 'message'
+ elif stanza[1:3] == 'iq':
+ type_ = 'iq'
+
+ if type_:
+ type_ = kind + '_' + type_
+ else:
+ type_ = kind # 'incoming' or 'outgoing'
+
+ if kind == 'incoming':
+ buffer.insert_with_tags_by_name(end_iter, '<!-- In -->\n',
+ type_)
+ elif kind == 'outgoing':
+ buffer.insert_with_tags_by_name(end_iter, '<!-- Out -->\n',
+ type_)
+ end_iter = buffer.get_end_iter()
+ buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') +\
+ '\n\n', type_)
+ if at_the_end:
+ gobject.idle_add(self.scroll_to_end)
+
+ def on_send_button_clicked(self, widget):
+ if gajim.connections[self.account].connected <= 1:
+ #if offline or connecting
+ ErrorDialog(_('Connection not available'),
+ _('Please make sure you are connected with "%s".') % self.account)
+ return
+ begin_iter, end_iter = self.input_tv_buffer.get_bounds()
+ stanza = self.input_tv_buffer.get_text(begin_iter, end_iter).decode(
+ 'utf-8')
+ if stanza:
+ gajim.connections[self.account].send_stanza(stanza)
+ self.input_tv_buffer.set_text('') # we sent ok, clear the textview
+
+ def on_presence_button_clicked(self, widget):
+ self.input_tv_buffer.set_text(
+ '<presence><show></show><status></status><priority></priority>'
+ '</presence>')
+
+ def on_iq_button_clicked(self, widget):
+ self.input_tv_buffer.set_text(
+ '<iq to="" type=""><query xmlns=""></query></iq>')
+
+ def on_message_button_clicked(self, widget):
+ self.input_tv_buffer.set_text(
+ '<message to="" type=""><body></body></message>')
+
+ def on_expander_activate(self, widget):
+ if not widget.get_expanded(): # it's the opposite!
+ # it's expanded!!
+ self.input_textview.grab_focus()
#Action that can be done with an incoming list of contacts
TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'),
- 'remove': _('remove')}
+ 'remove': _('remove')}
class RosterItemExchangeWindow:
- """
- Windows used when someone send you a exchange contact suggestion
- """
-
- def __init__(self, account, action, exchange_list, jid_from,
- message_body=None):
- self.account = account
- self.action = action
- self.exchange_list = exchange_list
- self.message_body = message_body
- self.jid_from = jid_from
-
- show_dialog = False
-
- # Connect to gtk builder
- self.xml = gtkgui_helpers.get_gtk_builder('roster_item_exchange_window.ui')
- self.window = self.xml.get_object('roster_item_exchange_window')
-
- # Add Widgets.
- for widget_to_add in ['accept_button_label', 'type_label',
- 'body_scrolledwindow', 'body_textview', 'items_list_treeview']:
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- # Set labels
- # self.action can be 'add', 'modify' or 'remove'
- self.type_label.set_label(
- _('<b>%(jid)s</b> would like you to <b>%(action)s</b> some contacts '
- 'in your roster.') % {'jid': jid_from,
- 'action': TRANSLATED_ACTION[self.action]})
- if message_body:
- buffer_ = self.body_textview.get_buffer()
- buffer_.set_text(self.message_body)
- else:
- self.body_scrolledwindow.hide()
- # Treeview
- model = gtk.ListStore(bool, str, str, str, str)
- self.items_list_treeview.set_model(model)
- # columns
- renderer1 = gtk.CellRendererToggle()
- renderer1.set_property('activatable', True)
- renderer1.connect('toggled', self.toggled_callback)
- if self.action == 'add':
- title = _('Add')
- elif self.action == 'modify':
- title = _('Modify')
- elif self.action == 'delete':
- title = _('Delete')
- self.items_list_treeview.insert_column_with_attributes(-1, title,
- renderer1, active=0)
- renderer2 = gtk.CellRendererText()
- self.items_list_treeview.insert_column_with_attributes(-1, _('Jabber ID'),
- renderer2, text=1)
- renderer3 = gtk.CellRendererText()
- self.items_list_treeview.insert_column_with_attributes(-1, _('Name'),
- renderer3, text=2)
- renderer4 = gtk.CellRendererText()
- self.items_list_treeview.insert_column_with_attributes(-1, _('Groups'),
- renderer4, text=3)
-
- # Init contacts
- model = self.items_list_treeview.get_model()
- model.clear()
-
- if action == 'add':
- for jid in self.exchange_list:
- groups = ''
- is_in_roster = True
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, jid)
- if not contact:
- is_in_roster = False
- name = self.exchange_list[jid][0]
- num_list = len(self.exchange_list[jid][1])
- current = 0
- for group in self.exchange_list[jid][1]:
- current += 1
- if contact and not group in contact.groups:
- is_in_roster = False
- if current == num_list:
- groups = groups + group
- else:
- groups = groups + group + ', '
- if not is_in_roster:
- show_dialog = True
- iter_ = model.append()
- model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
-
- # Change label for accept_button to action name instead of 'OK'.
- self.accept_button_label.set_label(_('Add'))
- elif action == 'modify':
- for jid in self.exchange_list:
- groups = ''
- is_in_roster = True
- is_right = True
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, jid)
- name = self.exchange_list[jid][0]
- if not contact:
- is_in_roster = False
- is_right = False
- else:
- if name != contact.name:
- is_right = False
- num_list = len(self.exchange_list[jid][1])
- current = 0
- for group in self.exchange_list[jid][1]:
- current += 1
- if contact and not group in contact.groups:
- is_right = False
- if current == num_list:
- groups = groups + group
- else:
- groups = groups + group + ', '
- if not is_right and is_in_roster:
- show_dialog = True
- iter_ = model.append()
- model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
-
- # Change label for accept_button to action name instead of 'OK'.
- self.accept_button_label.set_label(_('Modify'))
- elif action == 'delete':
- for jid in self.exchange_list:
- groups = ''
- is_in_roster = True
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, jid)
- name = self.exchange_list[jid][0]
- if not contact:
- is_in_roster = False
- num_list = len(self.exchange_list[jid][1])
- current = 0
- for group in self.exchange_list[jid][1]:
- current += 1
- if current == num_list:
- groups = groups + group
- else:
- groups = groups + group + ', '
- if is_in_roster:
- show_dialog = True
- iter_ = model.append()
- model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
-
- # Change label for accept_button to action name instead of 'OK'.
- self.accept_button_label.set_label(_('Delete'))
-
- if show_dialog:
- self.window.show_all()
- self.xml.connect_signals(self)
-
- def toggled_callback(self, cell, path):
- model = self.items_list_treeview.get_model()
- iter_ = model.get_iter(path)
- model[iter_][0] = not cell.get_active()
-
- def on_accept_button_clicked(self, widget):
- model = self.items_list_treeview.get_model()
- iter_ = model.get_iter_root()
- if self.action == 'add':
- a = 0
- while iter_:
- if model[iter_][0]:
- a+=1
- # it is selected
- #remote_jid = model[iter_][1].decode('utf-8')
- message = _('%s suggested me to add you in my roster.'
- % self.jid_from)
- # keep same groups and same nickname
- groups = model[iter_][3].split(', ')
- if groups == ['']:
- groups = []
- jid = model[iter_][1].decode('utf-8')
- if gajim.jid_is_transport(self.jid_from):
- gajim.connections[self.account].automatically_added.append(
- jid)
- gajim.interface.roster.req_sub(self, jid, message,
- self.account, groups=groups, nickname=model[iter_][2],
- auto_auth=True)
- iter_ = model.iter_next(iter_)
- InformationDialog(_('Added %s contacts') % str(a))
- elif self.action == 'modify':
- a = 0
- while iter_:
- if model[iter_][0]:
- a+=1
- # it is selected
- jid = model[iter_][1].decode('utf-8')
- # keep same groups and same nickname
- groups = model[iter_][3].split(', ')
- if groups == ['']:
- groups = []
- for u in gajim.contacts.get_contact(self.account, jid):
- u.name = model[iter_][2]
- gajim.connections[self.account].update_contact(jid,
- model[iter_][2], groups)
- self.draw_contact(jid, account)
- # Update opened chat
- ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account)
- if ctrl:
- ctrl.update_ui()
- win = gajim.interface.msg_win_mgr.get_window(jid,
- self.account)
- win.redraw_tab(ctrl)
- win.show_title()
- iter_ = model.iter_next(iter_)
- elif self.action == 'delete':
- a = 0
- while iter_:
- if model[iter_][0]:
- a+=1
- # it is selected
- jid = model[iter_][1].decode('utf-8')
- gajim.connections[self.account].unsubscribe(jid)
- gajim.interface.roster.remove_contact(jid, self.account)
- gajim.contacts.remove_jid(self.account, jid)
- iter_ = model.iter_next(iter_)
- InformationDialog(_('Removed %s contacts') % str(a))
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
+ """
+ Windows used when someone send you a exchange contact suggestion
+ """
+
+ def __init__(self, account, action, exchange_list, jid_from,
+ message_body=None):
+ self.account = account
+ self.action = action
+ self.exchange_list = exchange_list
+ self.message_body = message_body
+ self.jid_from = jid_from
+
+ show_dialog = False
+
+ # Connect to gtk builder
+ self.xml = gtkgui_helpers.get_gtk_builder('roster_item_exchange_window.ui')
+ self.window = self.xml.get_object('roster_item_exchange_window')
+
+ # Add Widgets.
+ for widget_to_add in ['accept_button_label', 'type_label',
+ 'body_scrolledwindow', 'body_textview', 'items_list_treeview']:
+ self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
+
+ # Set labels
+ # self.action can be 'add', 'modify' or 'remove'
+ self.type_label.set_label(
+ _('<b>%(jid)s</b> would like you to <b>%(action)s</b> some contacts '
+ 'in your roster.') % {'jid': jid_from,
+ 'action': TRANSLATED_ACTION[self.action]})
+ if message_body:
+ buffer_ = self.body_textview.get_buffer()
+ buffer_.set_text(self.message_body)
+ else:
+ self.body_scrolledwindow.hide()
+ # Treeview
+ model = gtk.ListStore(bool, str, str, str, str)
+ self.items_list_treeview.set_model(model)
+ # columns
+ renderer1 = gtk.CellRendererToggle()
+ renderer1.set_property('activatable', True)
+ renderer1.connect('toggled', self.toggled_callback)
+ if self.action == 'add':
+ title = _('Add')
+ elif self.action == 'modify':
+ title = _('Modify')
+ elif self.action == 'delete':
+ title = _('Delete')
+ self.items_list_treeview.insert_column_with_attributes(-1, title,
+ renderer1, active=0)
+ renderer2 = gtk.CellRendererText()
+ self.items_list_treeview.insert_column_with_attributes(-1, _('Jabber ID'),
+ renderer2, text=1)
+ renderer3 = gtk.CellRendererText()
+ self.items_list_treeview.insert_column_with_attributes(-1, _('Name'),
+ renderer3, text=2)
+ renderer4 = gtk.CellRendererText()
+ self.items_list_treeview.insert_column_with_attributes(-1, _('Groups'),
+ renderer4, text=3)
+
+ # Init contacts
+ model = self.items_list_treeview.get_model()
+ model.clear()
+
+ if action == 'add':
+ for jid in self.exchange_list:
+ groups = ''
+ is_in_roster = True
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ self.account, jid)
+ if not contact:
+ is_in_roster = False
+ name = self.exchange_list[jid][0]
+ num_list = len(self.exchange_list[jid][1])
+ current = 0
+ for group in self.exchange_list[jid][1]:
+ current += 1
+ if contact and not group in contact.groups:
+ is_in_roster = False
+ if current == num_list:
+ groups = groups + group
+ else:
+ groups = groups + group + ', '
+ if not is_in_roster:
+ show_dialog = True
+ iter_ = model.append()
+ model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
+
+ # Change label for accept_button to action name instead of 'OK'.
+ self.accept_button_label.set_label(_('Add'))
+ elif action == 'modify':
+ for jid in self.exchange_list:
+ groups = ''
+ is_in_roster = True
+ is_right = True
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ self.account, jid)
+ name = self.exchange_list[jid][0]
+ if not contact:
+ is_in_roster = False
+ is_right = False
+ else:
+ if name != contact.name:
+ is_right = False
+ num_list = len(self.exchange_list[jid][1])
+ current = 0
+ for group in self.exchange_list[jid][1]:
+ current += 1
+ if contact and not group in contact.groups:
+ is_right = False
+ if current == num_list:
+ groups = groups + group
+ else:
+ groups = groups + group + ', '
+ if not is_right and is_in_roster:
+ show_dialog = True
+ iter_ = model.append()
+ model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
+
+ # Change label for accept_button to action name instead of 'OK'.
+ self.accept_button_label.set_label(_('Modify'))
+ elif action == 'delete':
+ for jid in self.exchange_list:
+ groups = ''
+ is_in_roster = True
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ self.account, jid)
+ name = self.exchange_list[jid][0]
+ if not contact:
+ is_in_roster = False
+ num_list = len(self.exchange_list[jid][1])
+ current = 0
+ for group in self.exchange_list[jid][1]:
+ current += 1
+ if current == num_list:
+ groups = groups + group
+ else:
+ groups = groups + group + ', '
+ if is_in_roster:
+ show_dialog = True
+ iter_ = model.append()
+ model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
+
+ # Change label for accept_button to action name instead of 'OK'.
+ self.accept_button_label.set_label(_('Delete'))
+
+ if show_dialog:
+ self.window.show_all()
+ self.xml.connect_signals(self)
+
+ def toggled_callback(self, cell, path):
+ model = self.items_list_treeview.get_model()
+ iter_ = model.get_iter(path)
+ model[iter_][0] = not cell.get_active()
+
+ def on_accept_button_clicked(self, widget):
+ model = self.items_list_treeview.get_model()
+ iter_ = model.get_iter_root()
+ if self.action == 'add':
+ a = 0
+ while iter_:
+ if model[iter_][0]:
+ a+=1
+ # it is selected
+ #remote_jid = model[iter_][1].decode('utf-8')
+ message = _('%s suggested me to add you in my roster.'
+ % self.jid_from)
+ # keep same groups and same nickname
+ groups = model[iter_][3].split(', ')
+ if groups == ['']:
+ groups = []
+ jid = model[iter_][1].decode('utf-8')
+ if gajim.jid_is_transport(self.jid_from):
+ gajim.connections[self.account].automatically_added.append(
+ jid)
+ gajim.interface.roster.req_sub(self, jid, message,
+ self.account, groups=groups, nickname=model[iter_][2],
+ auto_auth=True)
+ iter_ = model.iter_next(iter_)
+ InformationDialog(_('Added %s contacts') % str(a))
+ elif self.action == 'modify':
+ a = 0
+ while iter_:
+ if model[iter_][0]:
+ a+=1
+ # it is selected
+ jid = model[iter_][1].decode('utf-8')
+ # keep same groups and same nickname
+ groups = model[iter_][3].split(', ')
+ if groups == ['']:
+ groups = []
+ for u in gajim.contacts.get_contact(self.account, jid):
+ u.name = model[iter_][2]
+ gajim.connections[self.account].update_contact(jid,
+ model[iter_][2], groups)
+ self.draw_contact(jid, account)
+ # Update opened chat
+ ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account)
+ if ctrl:
+ ctrl.update_ui()
+ win = gajim.interface.msg_win_mgr.get_window(jid,
+ self.account)
+ win.redraw_tab(ctrl)
+ win.show_title()
+ iter_ = model.iter_next(iter_)
+ elif self.action == 'delete':
+ a = 0
+ while iter_:
+ if model[iter_][0]:
+ a+=1
+ # it is selected
+ jid = model[iter_][1].decode('utf-8')
+ gajim.connections[self.account].unsubscribe(jid)
+ gajim.interface.roster.remove_contact(jid, self.account)
+ gajim.contacts.remove_jid(self.account, jid)
+ iter_ = model.iter_next(iter_)
+ InformationDialog(_('Removed %s contacts') % str(a))
+ self.window.destroy()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
class PrivacyListWindow:
- """
- Window that is used for creating NEW or EDITING already there privacy lists
- """
-
- def __init__(self, account, privacy_list_name, action):
- '''action is 'EDIT' or 'NEW' depending on if we create a new priv list
- or edit an already existing one'''
- self.account = account
- self.privacy_list_name = privacy_list_name
-
- # Dicts and Default Values
- self.active_rule = ''
- self.global_rules = {}
- self.list_of_groups = {}
-
- self.max_order = 0
-
- # Default Edit Values
- self.edit_rule_type = 'jid'
- self.allow_deny = 'allow'
-
- # Connect to gtk builder
- self.xml = gtkgui_helpers.get_gtk_builder('privacy_list_window.ui')
- self.window = self.xml.get_object('privacy_list_edit_window')
-
- # Add Widgets
-
- for widget_to_add in ('title_hbox', 'privacy_lists_title_label',
- 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox',
- 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton',
- 'list_of_rules_combobox', 'delete_open_buttons_hbox',
- 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton',
- 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton',
- 'edit_type_jabberid_entry', 'edit_type_group_radiobutton',
- 'edit_type_group_combobox', 'edit_type_subscription_radiobutton',
- 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton',
- 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton',
- 'edit_view_status_checkbutton', 'edit_all_checkbutton',
- 'edit_order_spinbutton', 'new_rule_button', 'save_rule_button',
- 'privacy_list_refresh_button', 'privacy_list_close_button',
- 'edit_send_status_checkbutton', 'add_edit_vbox',
- 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'):
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- self.privacy_lists_title_label.set_label(
- _('Privacy List <b><i>%s</i></b>') % \
- gobject.markup_escape_text(self.privacy_list_name))
-
- if len(gajim.connections) > 1:
- title = _('Privacy List for %s') % self.account
- else:
- title = _('Privacy List')
-
- self.delete_rule_button.set_sensitive(False)
- self.open_rule_button.set_sensitive(False)
- self.privacy_list_active_checkbutton.set_sensitive(False)
- self.privacy_list_default_checkbutton.set_sensitive(False)
- self.list_of_rules_combobox.set_sensitive(False)
-
- # set jabber id completion
- jids_list_store = gtk.ListStore(gobject.TYPE_STRING)
- for jid in gajim.contacts.get_jid_list(self.account):
- jids_list_store.append([jid])
- jid_entry_completion = gtk.EntryCompletion()
- jid_entry_completion.set_text_column(0)
- jid_entry_completion.set_model(jids_list_store)
- jid_entry_completion.set_popup_completion(True)
- self.edit_type_jabberid_entry.set_completion(jid_entry_completion)
- if action == 'EDIT':
- self.refresh_rules()
-
- count = 0
- for group in gajim.groups[self.account]:
- self.list_of_groups[group] = count
- count += 1
- self.edit_type_group_combobox.append_text(group)
- self.edit_type_group_combobox.set_active(0)
-
- self.window.set_title(title)
-
- self.window.show_all()
- self.add_edit_vbox.hide()
-
- self.xml.connect_signals(self)
-
- def on_privacy_list_edit_window_destroy(self, widget):
- key_name = 'privacy_list_%s' % self.privacy_list_name
- if key_name in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account][key_name]
-
- def check_active_default(self, a_d_dict):
- if a_d_dict['active'] == self.privacy_list_name:
- self.privacy_list_active_checkbutton.set_active(True)
- else:
- self.privacy_list_active_checkbutton.set_active(False)
- if a_d_dict['default'] == self.privacy_list_name:
- self.privacy_list_default_checkbutton.set_active(True)
- else:
- self.privacy_list_default_checkbutton.set_active(False)
-
- def privacy_list_received(self, rules):
- self.list_of_rules_combobox.get_model().clear()
- self.global_rules = {}
- for rule in rules:
- if 'type' in rule:
- text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s'
- ', value: %(value)s') % {'order': rule['order'],
- 'action': rule['action'], 'type': rule['type'],
- 'value': rule['value']}
- else:
- text_item = _('Order: %(order)s, action: %(action)s') % \
- {'order': rule['order'], 'action': rule['action']}
- if int(rule['order']) > self.max_order:
- self.max_order = int(rule['order'])
- self.global_rules[text_item] = rule
- self.list_of_rules_combobox.append_text(text_item)
- if len(rules) == 0:
- self.title_hbox.set_sensitive(False)
- self.list_of_rules_combobox.set_sensitive(False)
- self.delete_rule_button.set_sensitive(False)
- self.open_rule_button.set_sensitive(False)
- self.privacy_list_active_checkbutton.set_sensitive(False)
- self.privacy_list_default_checkbutton.set_sensitive(False)
- else:
- self.list_of_rules_combobox.set_active(0)
- self.title_hbox.set_sensitive(True)
- self.list_of_rules_combobox.set_sensitive(True)
- self.delete_rule_button.set_sensitive(True)
- self.open_rule_button.set_sensitive(True)
- self.privacy_list_active_checkbutton.set_sensitive(True)
- self.privacy_list_default_checkbutton.set_sensitive(True)
- self.reset_fields()
- gajim.connections[self.account].get_active_default_lists()
-
- def refresh_rules(self):
- gajim.connections[self.account].get_privacy_list(self.privacy_list_name)
-
- def on_delete_rule_button_clicked(self, widget):
- tags = []
- for rule in self.global_rules:
- if rule != self.list_of_rules_combobox.get_active_text():
- tags.append(self.global_rules[rule])
- gajim.connections[self.account].set_privacy_list(
- self.privacy_list_name, tags)
- self.privacy_list_received(tags)
- self.add_edit_vbox.hide()
- if not tags: # we removed latest rule
- if 'privacy_lists' in gajim.interface.instances[self.account]:
- win = gajim.interface.instances[self.account]['privacy_lists']
- win.remove_privacy_list_from_combobox(self.privacy_list_name)
- win.draw_widgets()
-
- def on_open_rule_button_clicked(self, widget):
- self.add_edit_rule_label.set_label(
- _('<b>Edit a rule</b>'))
- active_num = self.list_of_rules_combobox.get_active()
- if active_num == -1:
- self.active_rule = ''
- else:
- self.active_rule = \
- self.list_of_rules_combobox.get_active_text().decode('utf-8')
- if self.active_rule != '':
- rule_info = self.global_rules[self.active_rule]
- self.edit_order_spinbutton.set_value(int(rule_info['order']))
- if 'type' in rule_info:
- if rule_info['type'] == 'jid':
- self.edit_type_jabberid_radiobutton.set_active(True)
- self.edit_type_jabberid_entry.set_text(rule_info['value'])
- elif rule_info['type'] == 'group':
- self.edit_type_group_radiobutton.set_active(True)
- if rule_info['value'] in self.list_of_groups:
- self.edit_type_group_combobox.set_active(
- self.list_of_groups[rule_info['value']])
- else:
- self.edit_type_group_combobox.set_active(0)
- elif rule_info['type'] == 'subscription':
- self.edit_type_subscription_radiobutton.set_active(True)
- sub_value = rule_info['value']
- if sub_value == 'none':
- self.edit_type_subscription_combobox.set_active(0)
- elif sub_value == 'both':
- self.edit_type_subscription_combobox.set_active(1)
- elif sub_value == 'from':
- self.edit_type_subscription_combobox.set_active(2)
- elif sub_value == 'to':
- self.edit_type_subscription_combobox.set_active(3)
- else:
- self.edit_type_select_all_radiobutton.set_active(True)
- else:
- self.edit_type_select_all_radiobutton.set_active(True)
- self.edit_send_messages_checkbutton.set_active(False)
- self.edit_queries_send_checkbutton.set_active(False)
- self.edit_view_status_checkbutton.set_active(False)
- self.edit_send_status_checkbutton.set_active(False)
- self.edit_all_checkbutton.set_active(False)
- if not rule_info['child']:
- self.edit_all_checkbutton.set_active(True)
- else:
- if 'presence-out' in rule_info['child']:
- self.edit_send_status_checkbutton.set_active(True)
- if 'presence-in' in rule_info['child']:
- self.edit_view_status_checkbutton.set_active(True)
- if 'iq' in rule_info['child']:
- self.edit_queries_send_checkbutton.set_active(True)
- if 'message' in rule_info['child']:
- self.edit_send_messages_checkbutton.set_active(True)
-
- if rule_info['action'] == 'allow':
- self.edit_allow_radiobutton.set_active(True)
- else:
- self.edit_deny_radiobutton.set_active(True)
- self.add_edit_vbox.show()
-
- def on_edit_all_checkbutton_toggled(self, widget):
- if widget.get_active():
- self.edit_send_messages_checkbutton.set_active(True)
- self.edit_queries_send_checkbutton.set_active(True)
- self.edit_view_status_checkbutton.set_active(True)
- self.edit_send_status_checkbutton.set_active(True)
- self.edit_send_messages_checkbutton.set_sensitive(False)
- self.edit_queries_send_checkbutton.set_sensitive(False)
- self.edit_view_status_checkbutton.set_sensitive(False)
- self.edit_send_status_checkbutton.set_sensitive(False)
- else:
- self.edit_send_messages_checkbutton.set_active(False)
- self.edit_queries_send_checkbutton.set_active(False)
- self.edit_view_status_checkbutton.set_active(False)
- self.edit_send_status_checkbutton.set_active(False)
- self.edit_send_messages_checkbutton.set_sensitive(True)
- self.edit_queries_send_checkbutton.set_sensitive(True)
- self.edit_view_status_checkbutton.set_sensitive(True)
- self.edit_send_status_checkbutton.set_sensitive(True)
-
- def on_privacy_list_active_checkbutton_toggled(self, widget):
- if widget.get_active():
- gajim.connections[self.account].set_active_list(
- self.privacy_list_name)
- else:
- gajim.connections[self.account].set_active_list(None)
-
- def on_privacy_list_default_checkbutton_toggled(self, widget):
- if widget.get_active():
- gajim.connections[self.account].set_default_list(
- self.privacy_list_name)
- else:
- gajim.connections[self.account].set_default_list(None)
-
- def on_new_rule_button_clicked(self, widget):
- self.reset_fields()
- self.add_edit_vbox.show()
-
- def reset_fields(self):
- self.edit_type_jabberid_entry.set_text('')
- self.edit_allow_radiobutton.set_active(True)
- self.edit_type_jabberid_radiobutton.set_active(True)
- self.active_rule = ''
- self.edit_send_messages_checkbutton.set_active(False)
- self.edit_queries_send_checkbutton.set_active(False)
- self.edit_view_status_checkbutton.set_active(False)
- self.edit_send_status_checkbutton.set_active(False)
- self.edit_all_checkbutton.set_active(False)
- self.edit_order_spinbutton.set_value(self.max_order + 1)
- self.edit_type_group_combobox.set_active(0)
- self.edit_type_subscription_combobox.set_active(0)
- self.add_edit_rule_label.set_label(
- _('<b>Add a rule</b>'))
-
- def get_current_tags(self):
- if self.edit_type_jabberid_radiobutton.get_active():
- edit_type = 'jid'
- edit_value = self.edit_type_jabberid_entry.get_text()
- elif self.edit_type_group_radiobutton.get_active():
- edit_type = 'group'
- edit_value = self.edit_type_group_combobox.get_active_text()
- elif self.edit_type_subscription_radiobutton.get_active():
- edit_type = 'subscription'
- subs = ['none', 'both', 'from', 'to']
- edit_value = subs[self.edit_type_subscription_combobox.get_active()]
- elif self.edit_type_select_all_radiobutton.get_active():
- edit_type = ''
- edit_value = ''
- edit_order = str(self.edit_order_spinbutton.get_value_as_int())
- if self.edit_allow_radiobutton.get_active():
- edit_deny = 'allow'
- else:
- edit_deny = 'deny'
- child = []
- if not self.edit_all_checkbutton.get_active():
- if self.edit_send_messages_checkbutton.get_active():
- child.append('message')
- if self.edit_queries_send_checkbutton.get_active():
- child.append('iq')
- if self.edit_send_status_checkbutton.get_active():
- child.append('presence-out')
- if self.edit_view_status_checkbutton.get_active():
- child.append('presence-in')
- if edit_type != '':
- return {'order': edit_order, 'action': edit_deny,
- 'type': edit_type, 'value': edit_value, 'child': child}
- return {'order': edit_order, 'action': edit_deny, 'child': child}
-
- def on_save_rule_button_clicked(self, widget):
- tags=[]
- current_tags = self.get_current_tags()
- if int(current_tags['order']) > self.max_order:
- self.max_order = int(current_tags['order'])
- if self.active_rule == '':
- tags.append(current_tags)
-
- for rule in self.global_rules:
- if rule != self.active_rule:
- tags.append(self.global_rules[rule])
- else:
- tags.append(current_tags)
-
- gajim.connections[self.account].set_privacy_list(
- self.privacy_list_name, tags)
- self.refresh_rules()
- self.add_edit_vbox.hide()
- if 'privacy_lists' in gajim.interface.instances[self.account]:
- win = gajim.interface.instances[self.account]['privacy_lists']
- win.add_privacy_list_to_combobox(self.privacy_list_name)
- win.draw_widgets()
-
- def on_list_of_rules_combobox_changed(self, widget):
- self.add_edit_vbox.hide()
-
- def on_edit_type_radiobutton_changed(self, widget, radiobutton):
- active_bool = widget.get_active()
- if active_bool:
- self.edit_rule_type = radiobutton
-
- def on_edit_allow_radiobutton_changed(self, widget, radiobutton):
- active_bool = widget.get_active()
- if active_bool:
- self.allow_deny = radiobutton
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
+ """
+ Window that is used for creating NEW or EDITING already there privacy lists
+ """
+
+ def __init__(self, account, privacy_list_name, action):
+ '''action is 'EDIT' or 'NEW' depending on if we create a new priv list
+ or edit an already existing one'''
+ self.account = account
+ self.privacy_list_name = privacy_list_name
+
+ # Dicts and Default Values
+ self.active_rule = ''
+ self.global_rules = {}
+ self.list_of_groups = {}
+
+ self.max_order = 0
+
+ # Default Edit Values
+ self.edit_rule_type = 'jid'
+ self.allow_deny = 'allow'
+
+ # Connect to gtk builder
+ self.xml = gtkgui_helpers.get_gtk_builder('privacy_list_window.ui')
+ self.window = self.xml.get_object('privacy_list_edit_window')
+
+ # Add Widgets
+
+ for widget_to_add in ('title_hbox', 'privacy_lists_title_label',
+ 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox',
+ 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton',
+ 'list_of_rules_combobox', 'delete_open_buttons_hbox',
+ 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton',
+ 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton',
+ 'edit_type_jabberid_entry', 'edit_type_group_radiobutton',
+ 'edit_type_group_combobox', 'edit_type_subscription_radiobutton',
+ 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton',
+ 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton',
+ 'edit_view_status_checkbutton', 'edit_all_checkbutton',
+ 'edit_order_spinbutton', 'new_rule_button', 'save_rule_button',
+ 'privacy_list_refresh_button', 'privacy_list_close_button',
+ 'edit_send_status_checkbutton', 'add_edit_vbox',
+ 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'):
+ self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
+
+ self.privacy_lists_title_label.set_label(
+ _('Privacy List <b><i>%s</i></b>') % \
+ gobject.markup_escape_text(self.privacy_list_name))
+
+ if len(gajim.connections) > 1:
+ title = _('Privacy List for %s') % self.account
+ else:
+ title = _('Privacy List')
+
+ self.delete_rule_button.set_sensitive(False)
+ self.open_rule_button.set_sensitive(False)
+ self.privacy_list_active_checkbutton.set_sensitive(False)
+ self.privacy_list_default_checkbutton.set_sensitive(False)
+ self.list_of_rules_combobox.set_sensitive(False)
+
+ # set jabber id completion
+ jids_list_store = gtk.ListStore(gobject.TYPE_STRING)
+ for jid in gajim.contacts.get_jid_list(self.account):
+ jids_list_store.append([jid])
+ jid_entry_completion = gtk.EntryCompletion()
+ jid_entry_completion.set_text_column(0)
+ jid_entry_completion.set_model(jids_list_store)
+ jid_entry_completion.set_popup_completion(True)
+ self.edit_type_jabberid_entry.set_completion(jid_entry_completion)
+ if action == 'EDIT':
+ self.refresh_rules()
+
+ count = 0
+ for group in gajim.groups[self.account]:
+ self.list_of_groups[group] = count
+ count += 1
+ self.edit_type_group_combobox.append_text(group)
+ self.edit_type_group_combobox.set_active(0)
+
+ self.window.set_title(title)
+
+ self.window.show_all()
+ self.add_edit_vbox.hide()
+
+ self.xml.connect_signals(self)
+
+ def on_privacy_list_edit_window_destroy(self, widget):
+ key_name = 'privacy_list_%s' % self.privacy_list_name
+ if key_name in gajim.interface.instances[self.account]:
+ del gajim.interface.instances[self.account][key_name]
+
+ def check_active_default(self, a_d_dict):
+ if a_d_dict['active'] == self.privacy_list_name:
+ self.privacy_list_active_checkbutton.set_active(True)
+ else:
+ self.privacy_list_active_checkbutton.set_active(False)
+ if a_d_dict['default'] == self.privacy_list_name:
+ self.privacy_list_default_checkbutton.set_active(True)
+ else:
+ self.privacy_list_default_checkbutton.set_active(False)
+
+ def privacy_list_received(self, rules):
+ self.list_of_rules_combobox.get_model().clear()
+ self.global_rules = {}
+ for rule in rules:
+ if 'type' in rule:
+ text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s'
+ ', value: %(value)s') % {'order': rule['order'],
+ 'action': rule['action'], 'type': rule['type'],
+ 'value': rule['value']}
+ else:
+ text_item = _('Order: %(order)s, action: %(action)s') % \
+ {'order': rule['order'], 'action': rule['action']}
+ if int(rule['order']) > self.max_order:
+ self.max_order = int(rule['order'])
+ self.global_rules[text_item] = rule
+ self.list_of_rules_combobox.append_text(text_item)
+ if len(rules) == 0:
+ self.title_hbox.set_sensitive(False)
+ self.list_of_rules_combobox.set_sensitive(False)
+ self.delete_rule_button.set_sensitive(False)
+ self.open_rule_button.set_sensitive(False)
+ self.privacy_list_active_checkbutton.set_sensitive(False)
+ self.privacy_list_default_checkbutton.set_sensitive(False)
+ else:
+ self.list_of_rules_combobox.set_active(0)
+ self.title_hbox.set_sensitive(True)
+ self.list_of_rules_combobox.set_sensitive(True)
+ self.delete_rule_button.set_sensitive(True)
+ self.open_rule_button.set_sensitive(True)
+ self.privacy_list_active_checkbutton.set_sensitive(True)
+ self.privacy_list_default_checkbutton.set_sensitive(True)
+ self.reset_fields()
+ gajim.connections[self.account].get_active_default_lists()
+
+ def refresh_rules(self):
+ gajim.connections[self.account].get_privacy_list(self.privacy_list_name)
+
+ def on_delete_rule_button_clicked(self, widget):
+ tags = []
+ for rule in self.global_rules:
+ if rule != self.list_of_rules_combobox.get_active_text():
+ tags.append(self.global_rules[rule])
+ gajim.connections[self.account].set_privacy_list(
+ self.privacy_list_name, tags)
+ self.privacy_list_received(tags)
+ self.add_edit_vbox.hide()
+ if not tags: # we removed latest rule
+ if 'privacy_lists' in gajim.interface.instances[self.account]:
+ win = gajim.interface.instances[self.account]['privacy_lists']
+ win.remove_privacy_list_from_combobox(self.privacy_list_name)
+ win.draw_widgets()
+
+ def on_open_rule_button_clicked(self, widget):
+ self.add_edit_rule_label.set_label(
+ _('<b>Edit a rule</b>'))
+ active_num = self.list_of_rules_combobox.get_active()
+ if active_num == -1:
+ self.active_rule = ''
+ else:
+ self.active_rule = \
+ self.list_of_rules_combobox.get_active_text().decode('utf-8')
+ if self.active_rule != '':
+ rule_info = self.global_rules[self.active_rule]
+ self.edit_order_spinbutton.set_value(int(rule_info['order']))
+ if 'type' in rule_info:
+ if rule_info['type'] == 'jid':
+ self.edit_type_jabberid_radiobutton.set_active(True)
+ self.edit_type_jabberid_entry.set_text(rule_info['value'])
+ elif rule_info['type'] == 'group':
+ self.edit_type_group_radiobutton.set_active(True)
+ if rule_info['value'] in self.list_of_groups:
+ self.edit_type_group_combobox.set_active(
+ self.list_of_groups[rule_info['value']])
+ else:
+ self.edit_type_group_combobox.set_active(0)
+ elif rule_info['type'] == 'subscription':
+ self.edit_type_subscription_radiobutton.set_active(True)
+ sub_value = rule_info['value']
+ if sub_value == 'none':
+ self.edit_type_subscription_combobox.set_active(0)
+ elif sub_value == 'both':
+ self.edit_type_subscription_combobox.set_active(1)
+ elif sub_value == 'from':
+ self.edit_type_subscription_combobox.set_active(2)
+ elif sub_value == 'to':
+ self.edit_type_subscription_combobox.set_active(3)
+ else:
+ self.edit_type_select_all_radiobutton.set_active(True)
+ else:
+ self.edit_type_select_all_radiobutton.set_active(True)
+ self.edit_send_messages_checkbutton.set_active(False)
+ self.edit_queries_send_checkbutton.set_active(False)
+ self.edit_view_status_checkbutton.set_active(False)
+ self.edit_send_status_checkbutton.set_active(False)
+ self.edit_all_checkbutton.set_active(False)
+ if not rule_info['child']:
+ self.edit_all_checkbutton.set_active(True)
+ else:
+ if 'presence-out' in rule_info['child']:
+ self.edit_send_status_checkbutton.set_active(True)
+ if 'presence-in' in rule_info['child']:
+ self.edit_view_status_checkbutton.set_active(True)
+ if 'iq' in rule_info['child']:
+ self.edit_queries_send_checkbutton.set_active(True)
+ if 'message' in rule_info['child']:
+ self.edit_send_messages_checkbutton.set_active(True)
+
+ if rule_info['action'] == 'allow':
+ self.edit_allow_radiobutton.set_active(True)
+ else:
+ self.edit_deny_radiobutton.set_active(True)
+ self.add_edit_vbox.show()
+
+ def on_edit_all_checkbutton_toggled(self, widget):
+ if widget.get_active():
+ self.edit_send_messages_checkbutton.set_active(True)
+ self.edit_queries_send_checkbutton.set_active(True)
+ self.edit_view_status_checkbutton.set_active(True)
+ self.edit_send_status_checkbutton.set_active(True)
+ self.edit_send_messages_checkbutton.set_sensitive(False)
+ self.edit_queries_send_checkbutton.set_sensitive(False)
+ self.edit_view_status_checkbutton.set_sensitive(False)
+ self.edit_send_status_checkbutton.set_sensitive(False)
+ else:
+ self.edit_send_messages_checkbutton.set_active(False)
+ self.edit_queries_send_checkbutton.set_active(False)
+ self.edit_view_status_checkbutton.set_active(False)
+ self.edit_send_status_checkbutton.set_active(False)
+ self.edit_send_messages_checkbutton.set_sensitive(True)
+ self.edit_queries_send_checkbutton.set_sensitive(True)
+ self.edit_view_status_checkbutton.set_sensitive(True)
+ self.edit_send_status_checkbutton.set_sensitive(True)
+
+ def on_privacy_list_active_checkbutton_toggled(self, widget):
+ if widget.get_active():
+ gajim.connections[self.account].set_active_list(
+ self.privacy_list_name)
+ else:
+ gajim.connections[self.account].set_active_list(None)
+
+ def on_privacy_list_default_checkbutton_toggled(self, widget):
+ if widget.get_active():
+ gajim.connections[self.account].set_default_list(
+ self.privacy_list_name)
+ else:
+ gajim.connections[self.account].set_default_list(None)
+
+ def on_new_rule_button_clicked(self, widget):
+ self.reset_fields()
+ self.add_edit_vbox.show()
+
+ def reset_fields(self):
+ self.edit_type_jabberid_entry.set_text('')
+ self.edit_allow_radiobutton.set_active(True)
+ self.edit_type_jabberid_radiobutton.set_active(True)
+ self.active_rule = ''
+ self.edit_send_messages_checkbutton.set_active(False)
+ self.edit_queries_send_checkbutton.set_active(False)
+ self.edit_view_status_checkbutton.set_active(False)
+ self.edit_send_status_checkbutton.set_active(False)
+ self.edit_all_checkbutton.set_active(False)
+ self.edit_order_spinbutton.set_value(self.max_order + 1)
+ self.edit_type_group_combobox.set_active(0)
+ self.edit_type_subscription_combobox.set_active(0)
+ self.add_edit_rule_label.set_label(
+ _('<b>Add a rule</b>'))
+
+ def get_current_tags(self):
+ if self.edit_type_jabberid_radiobutton.get_active():
+ edit_type = 'jid'
+ edit_value = self.edit_type_jabberid_entry.get_text()
+ elif self.edit_type_group_radiobutton.get_active():
+ edit_type = 'group'
+ edit_value = self.edit_type_group_combobox.get_active_text()
+ elif self.edit_type_subscription_radiobutton.get_active():
+ edit_type = 'subscription'
+ subs = ['none', 'both', 'from', 'to']
+ edit_value = subs[self.edit_type_subscription_combobox.get_active()]
+ elif self.edit_type_select_all_radiobutton.get_active():
+ edit_type = ''
+ edit_value = ''
+ edit_order = str(self.edit_order_spinbutton.get_value_as_int())
+ if self.edit_allow_radiobutton.get_active():
+ edit_deny = 'allow'
+ else:
+ edit_deny = 'deny'
+ child = []
+ if not self.edit_all_checkbutton.get_active():
+ if self.edit_send_messages_checkbutton.get_active():
+ child.append('message')
+ if self.edit_queries_send_checkbutton.get_active():
+ child.append('iq')
+ if self.edit_send_status_checkbutton.get_active():
+ child.append('presence-out')
+ if self.edit_view_status_checkbutton.get_active():
+ child.append('presence-in')
+ if edit_type != '':
+ return {'order': edit_order, 'action': edit_deny,
+ 'type': edit_type, 'value': edit_value, 'child': child}
+ return {'order': edit_order, 'action': edit_deny, 'child': child}
+
+ def on_save_rule_button_clicked(self, widget):
+ tags=[]
+ current_tags = self.get_current_tags()
+ if int(current_tags['order']) > self.max_order:
+ self.max_order = int(current_tags['order'])
+ if self.active_rule == '':
+ tags.append(current_tags)
+
+ for rule in self.global_rules:
+ if rule != self.active_rule:
+ tags.append(self.global_rules[rule])
+ else:
+ tags.append(current_tags)
+
+ gajim.connections[self.account].set_privacy_list(
+ self.privacy_list_name, tags)
+ self.refresh_rules()
+ self.add_edit_vbox.hide()
+ if 'privacy_lists' in gajim.interface.instances[self.account]:
+ win = gajim.interface.instances[self.account]['privacy_lists']
+ win.add_privacy_list_to_combobox(self.privacy_list_name)
+ win.draw_widgets()
+
+ def on_list_of_rules_combobox_changed(self, widget):
+ self.add_edit_vbox.hide()
+
+ def on_edit_type_radiobutton_changed(self, widget, radiobutton):
+ active_bool = widget.get_active()
+ if active_bool:
+ self.edit_rule_type = radiobutton
+
+ def on_edit_allow_radiobutton_changed(self, widget, radiobutton):
+ active_bool = widget.get_active()
+ if active_bool:
+ self.allow_deny = radiobutton
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
class PrivacyListsWindow:
- """
- Window that is the main window for Privacy Lists; we can list there the
- privacy lists and ask to create a new one or edit an already there one
- """
- def __init__(self, account):
- self.account = account
- self.privacy_lists_save = []
-
- self.xml = gtkgui_helpers.get_gtk_builder('privacy_lists_window.ui')
-
- self.window = self.xml.get_object('privacy_lists_first_window')
- for widget_to_add in ('list_of_privacy_lists_combobox',
- 'delete_privacy_list_button', 'open_privacy_list_button',
- 'new_privacy_list_button', 'new_privacy_list_entry',
- 'privacy_lists_refresh_button', 'close_privacy_lists_window_button'):
- self.__dict__[widget_to_add] = self.xml.get_object(
- widget_to_add)
-
- self.draw_privacy_lists_in_combobox([])
- self.privacy_lists_refresh()
-
- self.enabled = True
-
- if len(gajim.connections) > 1:
- title = _('Privacy Lists for %s') % self.account
- else:
- title = _('Privacy Lists')
-
- self.window.set_title(title)
-
- self.window.show_all()
-
- self.xml.connect_signals(self)
-
- def on_privacy_lists_first_window_destroy(self, widget):
- if 'privacy_lists' in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account]['privacy_lists']
-
- def remove_privacy_list_from_combobox(self, privacy_list):
- if privacy_list not in self.privacy_lists_save:
- return
- privacy_list_index = self.privacy_lists_save.index(privacy_list)
- self.list_of_privacy_lists_combobox.remove_text(privacy_list_index)
- self.privacy_lists_save.remove(privacy_list)
-
- def add_privacy_list_to_combobox(self, privacy_list):
- if privacy_list in self.privacy_lists_save:
- return
- self.list_of_privacy_lists_combobox.append_text(privacy_list)
- self.privacy_lists_save.append(privacy_list)
-
- def draw_privacy_lists_in_combobox(self, privacy_lists):
- self.list_of_privacy_lists_combobox.set_active(-1)
- self.list_of_privacy_lists_combobox.get_model().clear()
- self.privacy_lists_save = []
- for add_item in privacy_lists:
- self.add_privacy_list_to_combobox(add_item)
- self.draw_widgets()
-
- def draw_widgets(self):
- if len(self.privacy_lists_save) == 0:
- self.list_of_privacy_lists_combobox.set_sensitive(False)
- self.open_privacy_list_button.set_sensitive(False)
- self.delete_privacy_list_button.set_sensitive(False)
- else:
- self.list_of_privacy_lists_combobox.set_sensitive(True)
- self.list_of_privacy_lists_combobox.set_active(0)
- self.open_privacy_list_button.set_sensitive(True)
- self.delete_privacy_list_button.set_sensitive(True)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_delete_privacy_list_button_clicked(self, widget):
- active_list = self.privacy_lists_save[
- self.list_of_privacy_lists_combobox.get_active()]
- gajim.connections[self.account].del_privacy_list(active_list)
-
- def privacy_list_removed(self, active_list):
- self.privacy_lists_save.remove(active_list)
- self.privacy_lists_received({'lists': self.privacy_lists_save})
-
- def privacy_lists_received(self, lists):
- if not lists:
- return
- privacy_lists = []
- for privacy_list in lists['lists']:
- privacy_lists.append(privacy_list)
- self.draw_privacy_lists_in_combobox(privacy_lists)
-
- def privacy_lists_refresh(self):
- gajim.connections[self.account].get_privacy_lists()
-
- def on_new_privacy_list_button_clicked(self, widget):
- name = self.new_privacy_list_entry.get_text()
- if not name:
- ErrorDialog(_('Invalid List Name'),
- _('You must enter a name to create a privacy list.'))
- return
- key_name = 'privacy_list_%s' % name
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].window.present()
- else:
- gajim.interface.instances[self.account][key_name] = \
- PrivacyListWindow(self.account, name, 'NEW')
- self.new_privacy_list_entry.set_text('')
-
- def on_privacy_lists_refresh_button_clicked(self, widget):
- self.privacy_lists_refresh()
-
- def on_open_privacy_list_button_clicked(self, widget):
- name = self.privacy_lists_save[
- self.list_of_privacy_lists_combobox.get_active()]
- key_name = 'privacy_list_%s' % name
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].window.present()
- else:
- gajim.interface.instances[self.account][key_name] = \
- PrivacyListWindow(self.account, name, 'EDIT')
+ """
+ Window that is the main window for Privacy Lists; we can list there the
+ privacy lists and ask to create a new one or edit an already there one
+ """
+ def __init__(self, account):
+ self.account = account
+ self.privacy_lists_save = []
+
+ self.xml = gtkgui_helpers.get_gtk_builder('privacy_lists_window.ui')
+
+ self.window = self.xml.get_object('privacy_lists_first_window')
+ for widget_to_add in ('list_of_privacy_lists_combobox',
+ 'delete_privacy_list_button', 'open_privacy_list_button',
+ 'new_privacy_list_button', 'new_privacy_list_entry',
+ 'privacy_lists_refresh_button', 'close_privacy_lists_window_button'):
+ self.__dict__[widget_to_add] = self.xml.get_object(
+ widget_to_add)
+
+ self.draw_privacy_lists_in_combobox([])
+ self.privacy_lists_refresh()
+
+ self.enabled = True
+
+ if len(gajim.connections) > 1:
+ title = _('Privacy Lists for %s') % self.account
+ else:
+ title = _('Privacy Lists')
+
+ self.window.set_title(title)
+
+ self.window.show_all()
+
+ self.xml.connect_signals(self)
+
+ def on_privacy_lists_first_window_destroy(self, widget):
+ if 'privacy_lists' in gajim.interface.instances[self.account]:
+ del gajim.interface.instances[self.account]['privacy_lists']
+
+ def remove_privacy_list_from_combobox(self, privacy_list):
+ if privacy_list not in self.privacy_lists_save:
+ return
+ privacy_list_index = self.privacy_lists_save.index(privacy_list)
+ self.list_of_privacy_lists_combobox.remove_text(privacy_list_index)
+ self.privacy_lists_save.remove(privacy_list)
+
+ def add_privacy_list_to_combobox(self, privacy_list):
+ if privacy_list in self.privacy_lists_save:
+ return
+ self.list_of_privacy_lists_combobox.append_text(privacy_list)
+ self.privacy_lists_save.append(privacy_list)
+
+ def draw_privacy_lists_in_combobox(self, privacy_lists):
+ self.list_of_privacy_lists_combobox.set_active(-1)
+ self.list_of_privacy_lists_combobox.get_model().clear()
+ self.privacy_lists_save = []
+ for add_item in privacy_lists:
+ self.add_privacy_list_to_combobox(add_item)
+ self.draw_widgets()
+
+ def draw_widgets(self):
+ if len(self.privacy_lists_save) == 0:
+ self.list_of_privacy_lists_combobox.set_sensitive(False)
+ self.open_privacy_list_button.set_sensitive(False)
+ self.delete_privacy_list_button.set_sensitive(False)
+ else:
+ self.list_of_privacy_lists_combobox.set_sensitive(True)
+ self.list_of_privacy_lists_combobox.set_active(0)
+ self.open_privacy_list_button.set_sensitive(True)
+ self.delete_privacy_list_button.set_sensitive(True)
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_delete_privacy_list_button_clicked(self, widget):
+ active_list = self.privacy_lists_save[
+ self.list_of_privacy_lists_combobox.get_active()]
+ gajim.connections[self.account].del_privacy_list(active_list)
+
+ def privacy_list_removed(self, active_list):
+ self.privacy_lists_save.remove(active_list)
+ self.privacy_lists_received({'lists': self.privacy_lists_save})
+
+ def privacy_lists_received(self, lists):
+ if not lists:
+ return
+ privacy_lists = []
+ for privacy_list in lists['lists']:
+ privacy_lists.append(privacy_list)
+ self.draw_privacy_lists_in_combobox(privacy_lists)
+
+ def privacy_lists_refresh(self):
+ gajim.connections[self.account].get_privacy_lists()
+
+ def on_new_privacy_list_button_clicked(self, widget):
+ name = self.new_privacy_list_entry.get_text()
+ if not name:
+ ErrorDialog(_('Invalid List Name'),
+ _('You must enter a name to create a privacy list.'))
+ return
+ key_name = 'privacy_list_%s' % name
+ if key_name in gajim.interface.instances[self.account]:
+ gajim.interface.instances[self.account][key_name].window.present()
+ else:
+ gajim.interface.instances[self.account][key_name] = \
+ PrivacyListWindow(self.account, name, 'NEW')
+ self.new_privacy_list_entry.set_text('')
+
+ def on_privacy_lists_refresh_button_clicked(self, widget):
+ self.privacy_lists_refresh()
+
+ def on_open_privacy_list_button_clicked(self, widget):
+ name = self.privacy_lists_save[
+ self.list_of_privacy_lists_combobox.get_active()]
+ key_name = 'privacy_list_%s' % name
+ if key_name in gajim.interface.instances[self.account]:
+ gajim.interface.instances[self.account][key_name].window.present()
+ else:
+ gajim.interface.instances[self.account][key_name] = \
+ PrivacyListWindow(self.account, name, 'EDIT')
class InvitationReceivedDialog:
- def __init__(self, account, room_jid, contact_jid, password=None,
- comment=None, is_continued=False):
-
- self.room_jid = room_jid
- self.account = account
- self.password = password
- self.is_continued = is_continued
-
- pritext = _('''You are invited to a groupchat''')
- #Don't translate $Contact
- if is_continued:
- sectext = _('$Contact has invited you to join a discussion')
- else:
- sectext = _('$Contact has invited you to group chat %(room_jid)s')\
- % {'room_jid': room_jid}
- contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid)
- contact_text = contact and contact.name or contact_jid
- sectext = sectext.replace('$Contact', contact_text)
-
- if comment: # only if not None and not ''
- comment = gobject.markup_escape_text(comment)
- comment = _('Comment: %s') % comment
- sectext += '\n\n%s' % comment
- sectext += '\n\n' + _('Do you want to accept the invitation?')
-
- def on_yes(checked):
- try:
- if self.is_continued:
- gajim.interface.join_gc_room(self.account, self.room_jid,
- gajim.nicks[self.account], None, is_continued=True)
- else:
- JoinGroupchatWindow(self.account, self.room_jid)
- except GajimGeneralException:
- pass
-
- YesNoDialog(pritext, sectext, on_response_yes=on_yes)
+ def __init__(self, account, room_jid, contact_jid, password=None,
+ comment=None, is_continued=False):
+
+ self.room_jid = room_jid
+ self.account = account
+ self.password = password
+ self.is_continued = is_continued
+
+ pritext = _('''You are invited to a groupchat''')
+ #Don't translate $Contact
+ if is_continued:
+ sectext = _('$Contact has invited you to join a discussion')
+ else:
+ sectext = _('$Contact has invited you to group chat %(room_jid)s')\
+ % {'room_jid': room_jid}
+ contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid)
+ contact_text = contact and contact.name or contact_jid
+ sectext = sectext.replace('$Contact', contact_text)
+
+ if comment: # only if not None and not ''
+ comment = gobject.markup_escape_text(comment)
+ comment = _('Comment: %s') % comment
+ sectext += '\n\n%s' % comment
+ sectext += '\n\n' + _('Do you want to accept the invitation?')
+
+ def on_yes(checked):
+ try:
+ if self.is_continued:
+ gajim.interface.join_gc_room(self.account, self.room_jid,
+ gajim.nicks[self.account], None, is_continued=True)
+ else:
+ JoinGroupchatWindow(self.account, self.room_jid)
+ except GajimGeneralException:
+ pass
+
+ YesNoDialog(pritext, sectext, on_response_yes=on_yes)
class ProgressDialog:
- def __init__(self, title_text, during_text, messages_queue):
- """
- During text is what to show during the procedure, messages_queue has the
- message to show in the textview
- """
- self.xml = gtkgui_helpers.get_gtk_builder('progress_dialog.ui')
- self.dialog = self.xml.get_object('progress_dialog')
- self.label = self.xml.get_object('label')
- self.label.set_markup('<big>' + during_text + '</big>')
- self.progressbar = self.xml.get_object('progressbar')
- self.dialog.set_title(title_text)
- self.dialog.set_default_size(450, 250)
- self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
- self.dialog.show_all()
- self.xml.connect_signals(self)
-
- self.update_progressbar_timeout_id = gobject.timeout_add(100,
- self.update_progressbar)
-
- def update_progressbar(self):
- if self.dialog:
- self.progressbar.pulse()
- return True # loop forever
- return False
-
- def on_progress_dialog_delete_event(self, widget, event):
- return True # WM's X button or Escape key should not destroy the window
+ def __init__(self, title_text, during_text, messages_queue):
+ """
+ During text is what to show during the procedure, messages_queue has the
+ message to show in the textview
+ """
+ self.xml = gtkgui_helpers.get_gtk_builder('progress_dialog.ui')
+ self.dialog = self.xml.get_object('progress_dialog')
+ self.label = self.xml.get_object('label')
+ self.label.set_markup('<big>' + during_text + '</big>')
+ self.progressbar = self.xml.get_object('progressbar')
+ self.dialog.set_title(title_text)
+ self.dialog.set_default_size(450, 250)
+ self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.dialog.show_all()
+ self.xml.connect_signals(self)
+
+ self.update_progressbar_timeout_id = gobject.timeout_add(100,
+ self.update_progressbar)
+
+ def update_progressbar(self):
+ if self.dialog:
+ self.progressbar.pulse()
+ return True # loop forever
+ return False
+
+ def on_progress_dialog_delete_event(self, widget, event):
+ return True # WM's X button or Escape key should not destroy the window
class SoundChooserDialog(FileChooserDialog):
- def __init__(self, path_to_snd_file='', on_response_ok=None,
- on_response_cancel=None):
- """
- Optionally accepts path_to_snd_file so it has that as selected
- """
- def on_ok(widget, callback):
- """
- Check if file exists and call callback
- """
- path_to_snd_file = self.get_filename()
- path_to_snd_file = gtkgui_helpers.decode_filechooser_file_paths(
- (path_to_snd_file,))[0]
- if os.path.exists(path_to_snd_file):
- callback(widget, path_to_snd_file)
-
- FileChooserDialog.__init__(self,
- title_text = _('Choose Sound'),
- action = gtk.FILE_CHOOSER_ACTION_OPEN,
- buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
- gtk.STOCK_OPEN, gtk.RESPONSE_OK),
- default_response = gtk.RESPONSE_OK,
- current_folder = gajim.config.get('last_sounds_dir'),
- on_response_ok = (on_ok, on_response_ok),
- on_response_cancel = on_response_cancel)
-
- filter_ = gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = gtk.FileFilter()
- filter_.set_name(_('Wav Sounds'))
- filter_.add_pattern('*.wav')
- self.add_filter(filter_)
- self.set_filter(filter_)
-
- path_to_snd_file = helpers.check_soundfile_path(path_to_snd_file)
- if path_to_snd_file:
- # set_filename accept only absolute path
- path_to_snd_file = os.path.abspath(path_to_snd_file)
- self.set_filename(path_to_snd_file)
+ def __init__(self, path_to_snd_file='', on_response_ok=None,
+ on_response_cancel=None):
+ """
+ Optionally accepts path_to_snd_file so it has that as selected
+ """
+ def on_ok(widget, callback):
+ """
+ Check if file exists and call callback
+ """
+ path_to_snd_file = self.get_filename()
+ path_to_snd_file = gtkgui_helpers.decode_filechooser_file_paths(
+ (path_to_snd_file,))[0]
+ if os.path.exists(path_to_snd_file):
+ callback(widget, path_to_snd_file)
+
+ FileChooserDialog.__init__(self,
+ title_text = _('Choose Sound'),
+ action = gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK),
+ default_response = gtk.RESPONSE_OK,
+ current_folder = gajim.config.get('last_sounds_dir'),
+ on_response_ok = (on_ok, on_response_ok),
+ on_response_cancel = on_response_cancel)
+
+ filter_ = gtk.FileFilter()
+ filter_.set_name(_('All files'))
+ filter_.add_pattern('*')
+ self.add_filter(filter_)
+
+ filter_ = gtk.FileFilter()
+ filter_.set_name(_('Wav Sounds'))
+ filter_.add_pattern('*.wav')
+ self.add_filter(filter_)
+ self.set_filter(filter_)
+
+ path_to_snd_file = helpers.check_soundfile_path(path_to_snd_file)
+ if path_to_snd_file:
+ # set_filename accept only absolute path
+ path_to_snd_file = os.path.abspath(path_to_snd_file)
+ self.set_filename(path_to_snd_file)
class ImageChooserDialog(FileChooserDialog):
- def __init__(self, path_to_file='', on_response_ok=None,
- on_response_cancel=None):
- """
- Optionally accepts path_to_snd_file so it has that as selected
- """
- def on_ok(widget, callback):
- '''check if file exists and call callback'''
- path_to_file = self.get_filename()
- if not path_to_file:
- return
- path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
- (path_to_file,))[0]
- if os.path.exists(path_to_file):
- if isinstance(callback, tuple):
- callback[0](widget, path_to_file, *callback[1:])
- else:
- callback(widget, path_to_file)
-
- try:
- if os.name == 'nt':
- path = helpers.get_my_pictures_path()
- else:
- path = os.environ['HOME']
- except Exception:
- path = ''
- FileChooserDialog.__init__(self,
- title_text = _('Choose Image'),
- action = gtk.FILE_CHOOSER_ACTION_OPEN,
- buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
- gtk.STOCK_OPEN, gtk.RESPONSE_OK),
- default_response = gtk.RESPONSE_OK,
- current_folder = path,
- on_response_ok = (on_ok, on_response_ok),
- on_response_cancel = on_response_cancel)
-
- if on_response_cancel:
- self.connect('destroy', on_response_cancel)
-
- filter_ = gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = gtk.FileFilter()
- filter_.set_name(_('Images'))
- filter_.add_mime_type('image/png')
- filter_.add_mime_type('image/jpeg')
- filter_.add_mime_type('image/gif')
- filter_.add_mime_type('image/tiff')
- filter_.add_mime_type('image/svg+xml')
- filter_.add_mime_type('image/x-xpixmap') # xpm
- self.add_filter(filter_)
- self.set_filter(filter_)
-
- if path_to_file:
- self.set_filename(path_to_file)
-
- self.set_use_preview_label(False)
- self.set_preview_widget(gtk.Image())
- self.connect('selection-changed', self.update_preview)
-
- def update_preview(self, widget):
- path_to_file = widget.get_preview_filename()
- if path_to_file is None or os.path.isdir(path_to_file):
- # nothing to preview or directory
- # make sure you clean image do show nothing
- widget.get_preview_widget().set_from_file(None)
- return
- try:
- pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(path_to_file, 100, 100)
- except gobject.GError:
- return
- widget.get_preview_widget().set_from_pixbuf(pixbuf)
+ def __init__(self, path_to_file='', on_response_ok=None,
+ on_response_cancel=None):
+ """
+ Optionally accepts path_to_snd_file so it has that as selected
+ """
+ def on_ok(widget, callback):
+ '''check if file exists and call callback'''
+ path_to_file = self.get_filename()
+ if not path_to_file:
+ return
+ path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
+ (path_to_file,))[0]
+ if os.path.exists(path_to_file):
+ if isinstance(callback, tuple):
+ callback[0](widget, path_to_file, *callback[1:])
+ else:
+ callback(widget, path_to_file)
+
+ try:
+ if os.name == 'nt':
+ path = helpers.get_my_pictures_path()
+ else:
+ path = os.environ['HOME']
+ except Exception:
+ path = ''
+ FileChooserDialog.__init__(self,
+ title_text = _('Choose Image'),
+ action = gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK),
+ default_response = gtk.RESPONSE_OK,
+ current_folder = path,
+ on_response_ok = (on_ok, on_response_ok),
+ on_response_cancel = on_response_cancel)
+
+ if on_response_cancel:
+ self.connect('destroy', on_response_cancel)
+
+ filter_ = gtk.FileFilter()
+ filter_.set_name(_('All files'))
+ filter_.add_pattern('*')
+ self.add_filter(filter_)
+
+ filter_ = gtk.FileFilter()
+ filter_.set_name(_('Images'))
+ filter_.add_mime_type('image/png')
+ filter_.add_mime_type('image/jpeg')
+ filter_.add_mime_type('image/gif')
+ filter_.add_mime_type('image/tiff')
+ filter_.add_mime_type('image/svg+xml')
+ filter_.add_mime_type('image/x-xpixmap') # xpm
+ self.add_filter(filter_)
+ self.set_filter(filter_)
+
+ if path_to_file:
+ self.set_filename(path_to_file)
+
+ self.set_use_preview_label(False)
+ self.set_preview_widget(gtk.Image())
+ self.connect('selection-changed', self.update_preview)
+
+ def update_preview(self, widget):
+ path_to_file = widget.get_preview_filename()
+ if path_to_file is None or os.path.isdir(path_to_file):
+ # nothing to preview or directory
+ # make sure you clean image do show nothing
+ widget.get_preview_widget().set_from_file(None)
+ return
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(path_to_file, 100, 100)
+ except gobject.GError:
+ return
+ widget.get_preview_widget().set_from_pixbuf(pixbuf)
class AvatarChooserDialog(ImageChooserDialog):
- def __init__(self, path_to_file='', on_response_ok=None,
- on_response_cancel=None, on_response_clear=None):
- ImageChooserDialog.__init__(self, path_to_file, on_response_ok,
- on_response_cancel)
- button = gtk.Button(None, gtk.STOCK_CLEAR)
- self.response_clear = on_response_clear
- if on_response_clear:
- button.connect('clicked', self.on_clear)
- button.show_all()
- self.action_area.pack_start(button)
- self.action_area.reorder_child(button, 0)
-
- def on_clear(self, widget):
- if isinstance(self.response_clear, tuple):
- self.response_clear[0](widget, *self.response_clear[1:])
- else:
- self.response_clear(widget)
+ def __init__(self, path_to_file='', on_response_ok=None,
+ on_response_cancel=None, on_response_clear=None):
+ ImageChooserDialog.__init__(self, path_to_file, on_response_ok,
+ on_response_cancel)
+ button = gtk.Button(None, gtk.STOCK_CLEAR)
+ self.response_clear = on_response_clear
+ if on_response_clear:
+ button.connect('clicked', self.on_clear)
+ button.show_all()
+ self.action_area.pack_start(button)
+ self.action_area.reorder_child(button, 0)
+
+ def on_clear(self, widget):
+ if isinstance(self.response_clear, tuple):
+ self.response_clear[0](widget, *self.response_clear[1:])
+ else:
+ self.response_clear(widget)
class AddSpecialNotificationDialog:
- def __init__(self, jid):
- """
- jid is the jid for which we want to add special notification (sound and
- notification popups)
- """
- self.xml = gtkgui_helpers.get_gtk_builder('add_special_notification_window.ui')
- self.window = self.xml.get_object('add_special_notification_window')
- self.condition_combobox = self.xml.get_object('condition_combobox')
- self.condition_combobox.set_active(0)
- self.notification_popup_yes_no_combobox = self.xml.get_object(
- 'notification_popup_yes_no_combobox')
- self.notification_popup_yes_no_combobox.set_active(0)
- self.listen_sound_combobox = self.xml.get_object('listen_sound_combobox')
- self.listen_sound_combobox.set_active(0)
-
- self.jid = jid
- self.xml.get_object('when_foo_becomes_label').set_text(
- _('When %s becomes:') % self.jid)
-
- self.window.set_title(_('Adding Special Notification for %s') % jid)
- self.window.show_all()
- self.xml.connect_signals(self)
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_add_special_notification_window_delete_event(self, widget, event):
- self.window.destroy()
-
- def on_listen_sound_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 1: # user selected 'choose sound'
- def on_ok(widget, path_to_snd_file):
- pass
- #print path_to_snd_file
-
- def on_cancel(widget):
- widget.set_active(0) # go back to No Sound
-
- self.dialog = SoundChooserDialog(on_response_ok=on_ok,
- on_response_cancel=on_cancel)
-
- def on_ok_button_clicked(self, widget):
- conditions = ('online', 'chat', 'online_and_chat',
- 'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline')
- active = self.condition_combobox.get_active()
-
- active_iter = self.listen_sound_combobox.get_active_iter()
- listen_sound_model = self.listen_sound_combobox.get_model()
+ def __init__(self, jid):
+ """
+ jid is the jid for which we want to add special notification (sound and
+ notification popups)
+ """
+ self.xml = gtkgui_helpers.get_gtk_builder('add_special_notification_window.ui')
+ self.window = self.xml.get_object('add_special_notification_window')
+ self.condition_combobox = self.xml.get_object('condition_combobox')
+ self.condition_combobox.set_active(0)
+ self.notification_popup_yes_no_combobox = self.xml.get_object(
+ 'notification_popup_yes_no_combobox')
+ self.notification_popup_yes_no_combobox.set_active(0)
+ self.listen_sound_combobox = self.xml.get_object('listen_sound_combobox')
+ self.listen_sound_combobox.set_active(0)
+
+ self.jid = jid
+ self.xml.get_object('when_foo_becomes_label').set_text(
+ _('When %s becomes:') % self.jid)
+
+ self.window.set_title(_('Adding Special Notification for %s') % jid)
+ self.window.show_all()
+ self.xml.connect_signals(self)
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_add_special_notification_window_delete_event(self, widget, event):
+ self.window.destroy()
+
+ def on_listen_sound_combobox_changed(self, widget):
+ active = widget.get_active()
+ if active == 1: # user selected 'choose sound'
+ def on_ok(widget, path_to_snd_file):
+ pass
+ #print path_to_snd_file
+
+ def on_cancel(widget):
+ widget.set_active(0) # go back to No Sound
+
+ self.dialog = SoundChooserDialog(on_response_ok=on_ok,
+ on_response_cancel=on_cancel)
+
+ def on_ok_button_clicked(self, widget):
+ conditions = ('online', 'chat', 'online_and_chat',
+ 'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline')
+ active = self.condition_combobox.get_active()
+
+ active_iter = self.listen_sound_combobox.get_active_iter()
+ listen_sound_model = self.listen_sound_combobox.get_model()
class AdvancedNotificationsWindow:
- events_list = ['message_received', 'contact_connected',
- 'contact_disconnected', 'contact_change_status', 'gc_msg_highlight',
- 'gc_msg', 'ft_request', 'ft_started', 'ft_finished']
- recipient_types_list = ['contact', 'group', 'all']
- config_options = ['event', 'recipient_type', 'recipients', 'status',
- 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open',
- 'run_command', 'command', 'systray', 'roster', 'urgency_hint']
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('advanced_notifications_window.ui')
- self.window = self.xml.get_object('advanced_notifications_window')
- for w in ('conditions_treeview', 'config_vbox', 'event_combobox',
- 'recipient_type_combobox', 'recipient_list_entry', 'delete_button',
- 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb',
- 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb',
- 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb',
- 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb',
- 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button',
- 'run_command_cb', 'command_entry', 'urgency_hint_cb'):
- self.__dict__[w] = self.xml.get_object(w)
-
- # Contains status checkboxes
- childs = self.status_hbox.get_children()
-
- self.all_status_rb = childs[0]
- self.special_status_rb = childs[1]
- self.online_cb = childs[2]
- self.away_cb = childs[3]
- self.xa_cb = childs[4]
- self.dnd_cb = childs[5]
- self.invisible_cb = childs[6]
-
- model = gtk.ListStore(int, str)
- model.set_sort_column_id(0, gtk.SORT_ASCENDING)
- model.clear()
- self.conditions_treeview.set_model(model)
-
- ## means number
- col = gtk.TreeViewColumn(_('#'))
- self.conditions_treeview.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, expand=False)
- col.set_attributes(renderer, text=0)
-
- col = gtk.TreeViewColumn(_('Condition'))
- self.conditions_treeview.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, expand=True)
- col.set_attributes(renderer, text=1)
-
- self.xml.connect_signals(self)
-
- # Fill conditions_treeview
- num = 0
- while gajim.config.get_per('notifications', str(num)):
- iter_ = model.append((num, ''))
- path = model.get_path(iter_)
- self.conditions_treeview.set_cursor(path)
- self.active_num = num
- self.initiate_rule_state()
- self.set_treeview_string()
- num += 1
-
- # No rule selected at init time
- self.conditions_treeview.get_selection().unselect_all()
- self.active_num = -1
- self.config_vbox.set_sensitive(False)
- self.delete_button.set_sensitive(False)
- self.down_button.set_sensitive(False)
- self.up_button.set_sensitive(False)
-
- self.window.show_all()
-
- def initiate_rule_state(self):
- """
- Set values for all widgets
- """
- if self.active_num < 0:
- return
- # event
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'event')
- if value:
- self.event_combobox.set_active(self.events_list.index(value))
- else:
- self.event_combobox.set_active(-1)
- # recipient_type
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'recipient_type')
- if value:
- self.recipient_type_combobox.set_active(
- self.recipient_types_list.index(value))
- else:
- self.recipient_type_combobox.set_active(-1)
- # recipient
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'recipients')
- if not value:
- value = ''
- self.recipient_list_entry.set_text(value)
- # status
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'status')
- if value == 'all':
- self.all_status_rb.set_active(True)
- else:
- self.special_status_rb.set_active(True)
- values = value.split()
- for v in ('online', 'away', 'xa', 'dnd', 'invisible'):
- if v in values:
- self.__dict__[v + '_cb'].set_active(True)
- else:
- self.__dict__[v + '_cb'].set_active(False)
- self.on_status_radiobutton_toggled(self.all_status_rb)
- # tab_opened
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'tab_opened')
- self.tab_opened_cb.set_active(True)
- self.not_tab_opened_cb.set_active(True)
- if value == 'no':
- self.tab_opened_cb.set_active(False)
- elif value == 'yes':
- self.not_tab_opened_cb.set_active(False)
- # sound_file
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'sound_file')
- self.sound_entry.set_text(value)
- # sound, popup, auto_open, systray, roster
- for option in ('sound', 'popup', 'auto_open', 'systray', 'roster'):
- value = gajim.config.get_per('notifications', str(self.active_num),
- option)
- if value == 'yes':
- self.__dict__['use_' + option + '_cb'].set_active(True)
- else:
- self.__dict__['use_' + option + '_cb'].set_active(False)
- if value == 'no':
- self.__dict__['disable_' + option + '_cb'].set_active(True)
- else:
- self.__dict__['disable_' + option + '_cb'].set_active(False)
- # run_command
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'run_command')
- self.run_command_cb.set_active(value)
- # command
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'command')
- self.command_entry.set_text(value)
- # urgency_hint
- value = gajim.config.get_per('notifications', str(self.active_num),
- 'urgency_hint')
- self.urgency_hint_cb.set_active(value)
-
- def set_treeview_string(self):
- (model, iter_) = self.conditions_treeview.get_selection().get_selected()
- if not iter_:
- return
- event = self.event_combobox.get_active_text()
- recipient_type = self.recipient_type_combobox.get_active_text()
- recipient = ''
- if recipient_type != 'everybody':
- recipient = self.recipient_list_entry.get_text()
- if self.all_status_rb.get_active():
- status = ''
- else:
- status = _('when I am ')
- for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
- if self.__dict__[st + '_cb'].get_active():
- status += helpers.get_uf_show(st) + ' '
- model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type,
- recipient, status)
-
- def on_conditions_treeview_cursor_changed(self, widget):
- (model, iter_) = widget.get_selection().get_selected()
- if not iter_:
- self.active_num = -1
- return
- self.active_num = model[iter_][0]
- if self.active_num == 0:
- self.up_button.set_sensitive(False)
- else:
- self.up_button.set_sensitive(True)
- max = self.conditions_treeview.get_model().iter_n_children(None)
- if self.active_num == max - 1:
- self.down_button.set_sensitive(False)
- else:
- self.down_button.set_sensitive(True)
- self.initiate_rule_state()
- self.config_vbox.set_sensitive(True)
- self.delete_button.set_sensitive(True)
-
- def on_new_button_clicked(self, widget):
- model = self.conditions_treeview.get_model()
- num = self.conditions_treeview.get_model().iter_n_children(None)
- gajim.config.add_per('notifications', str(num))
- iter_ = model.append((num, ''))
- path = model.get_path(iter_)
- self.conditions_treeview.set_cursor(path)
- self.active_num = num
- self.set_treeview_string()
- self.config_vbox.set_sensitive(True)
-
- def on_delete_button_clicked(self, widget):
- (model, iter_) = self.conditions_treeview.get_selection().get_selected()
- if not iter_:
- return
- # up all others
- iter2 = model.iter_next(iter_)
- num = self.active_num
- while iter2:
- num = model[iter2][0]
- model[iter2][0] = num - 1
- for opt in self.config_options:
- val = gajim.config.get_per('notifications', str(num), opt)
- gajim.config.set_per('notifications', str(num - 1), opt, val)
- iter2 = model.iter_next(iter2)
- model.remove(iter_)
- gajim.config.del_per('notifications', str(num)) # delete latest
- self.active_num = -1
- self.config_vbox.set_sensitive(False)
- self.delete_button.set_sensitive(False)
- self.up_button.set_sensitive(False)
- self.down_button.set_sensitive(False)
-
- def on_up_button_clicked(self, widget):
- (model, iter_) = self.conditions_treeview.get_selection().\
- get_selected()
- if not iter_:
- return
- for opt in self.config_options:
- val = gajim.config.get_per('notifications', str(self.active_num), opt)
- val2 = gajim.config.get_per('notifications', str(self.active_num - 1),
- opt)
- gajim.config.set_per('notifications', str(self.active_num), opt, val2)
- gajim.config.set_per('notifications', str(self.active_num - 1), opt,
- val)
-
- model[iter_][0] = self.active_num - 1
- # get previous iter
- path = model.get_path(iter_)
- iter_ = model.get_iter((path[0] - 1,))
- model[iter_][0] = self.active_num
- self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
-
- def on_down_button_clicked(self, widget):
- (model, iter_) = self.conditions_treeview.get_selection().get_selected()
- if not iter_:
- return
- for opt in self.config_options:
- val = gajim.config.get_per('notifications', str(self.active_num), opt)
- val2 = gajim.config.get_per('notifications', str(self.active_num + 1),
- opt)
- gajim.config.set_per('notifications', str(self.active_num), opt, val2)
- gajim.config.set_per('notifications', str(self.active_num + 1), opt,
- val)
-
- model[iter_][0] = self.active_num + 1
- iter_ = model.iter_next(iter_)
- model[iter_][0] = self.active_num
- self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
-
- def on_event_combobox_changed(self, widget):
- if self.active_num < 0:
- return
- active = self.event_combobox.get_active()
- if active == -1:
- event = ''
- else:
- event = self.events_list[active]
- gajim.config.set_per('notifications', str(self.active_num), 'event',
- event)
- self.set_treeview_string()
-
- def on_recipient_type_combobox_changed(self, widget):
- if self.active_num < 0:
- return
- recipient_type = self.recipient_types_list[self.recipient_type_combobox.\
- get_active()]
- gajim.config.set_per('notifications', str(self.active_num),
- 'recipient_type', recipient_type)
- if recipient_type == 'all':
- self.recipient_list_entry.hide()
- else:
- self.recipient_list_entry.show()
- self.set_treeview_string()
-
- def on_recipient_list_entry_changed(self, widget):
- if self.active_num < 0:
- return
- recipients = widget.get_text().decode('utf-8')
- #TODO: do some check
- gajim.config.set_per('notifications', str(self.active_num),
- 'recipients', recipients)
- self.set_treeview_string()
-
- def set_status_config(self):
- if self.active_num < 0:
- return
- status = ''
- for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
- if self.__dict__[st + '_cb'].get_active():
- status += st + ' '
- if status:
- status = status[:-1]
- gajim.config.set_per('notifications', str(self.active_num), 'status',
- status)
- self.set_treeview_string()
-
- def on_status_radiobutton_toggled(self, widget):
- if self.active_num < 0:
- return
- if self.all_status_rb.get_active():
- gajim.config.set_per('notifications', str(self.active_num), 'status',
- 'all')
- # 'All status' clicked
- for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
- self.__dict__[st + '_cb'].hide()
-
- self.special_status_rb.show()
- else:
- self.set_status_config()
- # 'special status' clicked
- for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
- self.__dict__[st + '_cb'].show()
-
- self.special_status_rb.hide()
- self.set_treeview_string()
-
- def on_status_cb_toggled(self, widget):
- if self.active_num < 0:
- return
- self.set_status_config()
-
- # tab_opened OR (not xor) not_tab_opened must be active
- def on_tab_opened_cb_toggled(self, widget):
- if self.active_num < 0:
- return
- if self.tab_opened_cb.get_active():
- if self.not_tab_opened_cb.get_active():
- gajim.config.set_per('notifications', str(self.active_num),
- 'tab_opened', 'both')
- else:
- gajim.config.set_per('notifications', str(self.active_num),
- 'tab_opened', 'yes')
- elif not self.not_tab_opened_cb.get_active():
- self.not_tab_opened_cb.set_active(True)
- gajim.config.set_per('notifications', str(self.active_num),
- 'tab_opened', 'no')
-
- def on_not_tab_opened_cb_toggled(self, widget):
- if self.active_num < 0:
- return
- if self.not_tab_opened_cb.get_active():
- if self.tab_opened_cb.get_active():
- gajim.config.set_per('notifications', str(self.active_num),
- 'tab_opened', 'both')
- else:
- gajim.config.set_per('notifications', str(self.active_num),
- 'tab_opened', 'no')
- elif not self.tab_opened_cb.get_active():
- self.tab_opened_cb.set_active(True)
- gajim.config.set_per('notifications', str(self.active_num),
- 'tab_opened', 'yes')
-
- def on_use_it_toggled(self, widget, oposite_widget, option):
- if widget.get_active():
- if oposite_widget.get_active():
- oposite_widget.set_active(False)
- gajim.config.set_per('notifications', str(self.active_num), option,
- 'yes')
- elif oposite_widget.get_active():
- gajim.config.set_per('notifications', str(self.active_num), option,
- 'no')
- else:
- gajim.config.set_per('notifications', str(self.active_num), option, '')
-
- def on_disable_it_toggled(self, widget, oposite_widget, option):
- if widget.get_active():
- if oposite_widget.get_active():
- oposite_widget.set_active(False)
- gajim.config.set_per('notifications', str(self.active_num), option,
- 'no')
- elif oposite_widget.get_active():
- gajim.config.set_per('notifications', str(self.active_num), option,
- 'yes')
- else:
- gajim.config.set_per('notifications', str(self.active_num), option, '')
-
- def on_use_sound_cb_toggled(self, widget):
- self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound')
- if widget.get_active():
- self.sound_file_hbox.set_sensitive(True)
- else:
- self.sound_file_hbox.set_sensitive(False)
-
- def on_browse_for_sounds_button_clicked(self, widget, data=None):
- if self.active_num < 0:
- return
-
- def on_ok(widget, path_to_snd_file):
- dialog.destroy()
- if not path_to_snd_file:
- path_to_snd_file = ''
- gajim.config.set_per('notifications', str(self.active_num),
- 'sound_file', path_to_snd_file)
- self.sound_entry.set_text(path_to_snd_file)
-
- path_to_snd_file = self.sound_entry.get_text().decode('utf-8')
- path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file)
- dialog = SoundChooserDialog(path_to_snd_file, on_ok)
-
- def on_play_button_clicked(self, widget):
- helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8'))
-
- def on_disable_sound_cb_toggled(self, widget):
- self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound')
-
- def on_sound_entry_changed(self, widget):
- gajim.config.set_per('notifications', str(self.active_num),
- 'sound_file', widget.get_text().decode('utf-8'))
-
- def on_use_popup_cb_toggled(self, widget):
- self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup')
-
- def on_disable_popup_cb_toggled(self, widget):
- self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup')
-
- def on_use_auto_open_cb_toggled(self, widget):
- self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open')
-
- def on_disable_auto_open_cb_toggled(self, widget):
- self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open')
-
- def on_run_command_cb_toggled(self, widget):
- gajim.config.set_per('notifications', str(self.active_num), 'run_command',
- widget.get_active())
- if widget.get_active():
- self.command_entry.set_sensitive(True)
- else:
- self.command_entry.set_sensitive(False)
-
- def on_command_entry_changed(self, widget):
- gajim.config.set_per('notifications', str(self.active_num), 'command',
- widget.get_text().decode('utf-8'))
-
- def on_use_systray_cb_toggled(self, widget):
- self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray')
-
- def on_disable_systray_cb_toggled(self, widget):
- self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray')
-
- def on_use_roster_cb_toggled(self, widget):
- self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster')
-
- def on_disable_roster_cb_toggled(self, widget):
- self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster')
-
- def on_urgency_hint_cb_toggled(self, widget):
- gajim.config.set_per('notifications', str(self.active_num),
- 'uregency_hint', widget.get_active())
-
- def on_close_window(self, widget):
- self.window.destroy()
+ events_list = ['message_received', 'contact_connected',
+ 'contact_disconnected', 'contact_change_status', 'gc_msg_highlight',
+ 'gc_msg', 'ft_request', 'ft_started', 'ft_finished']
+ recipient_types_list = ['contact', 'group', 'all']
+ config_options = ['event', 'recipient_type', 'recipients', 'status',
+ 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open',
+ 'run_command', 'command', 'systray', 'roster', 'urgency_hint']
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('advanced_notifications_window.ui')
+ self.window = self.xml.get_object('advanced_notifications_window')
+ for w in ('conditions_treeview', 'config_vbox', 'event_combobox',
+ 'recipient_type_combobox', 'recipient_list_entry', 'delete_button',
+ 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb',
+ 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb',
+ 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb',
+ 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb',
+ 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button',
+ 'run_command_cb', 'command_entry', 'urgency_hint_cb'):
+ self.__dict__[w] = self.xml.get_object(w)
+
+ # Contains status checkboxes
+ childs = self.status_hbox.get_children()
+
+ self.all_status_rb = childs[0]
+ self.special_status_rb = childs[1]
+ self.online_cb = childs[2]
+ self.away_cb = childs[3]
+ self.xa_cb = childs[4]
+ self.dnd_cb = childs[5]
+ self.invisible_cb = childs[6]
+
+ model = gtk.ListStore(int, str)
+ model.set_sort_column_id(0, gtk.SORT_ASCENDING)
+ model.clear()
+ self.conditions_treeview.set_model(model)
+
+ ## means number
+ col = gtk.TreeViewColumn(_('#'))
+ self.conditions_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, expand=False)
+ col.set_attributes(renderer, text=0)
+
+ col = gtk.TreeViewColumn(_('Condition'))
+ self.conditions_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, expand=True)
+ col.set_attributes(renderer, text=1)
+
+ self.xml.connect_signals(self)
+
+ # Fill conditions_treeview
+ num = 0
+ while gajim.config.get_per('notifications', str(num)):
+ iter_ = model.append((num, ''))
+ path = model.get_path(iter_)
+ self.conditions_treeview.set_cursor(path)
+ self.active_num = num
+ self.initiate_rule_state()
+ self.set_treeview_string()
+ num += 1
+
+ # No rule selected at init time
+ self.conditions_treeview.get_selection().unselect_all()
+ self.active_num = -1
+ self.config_vbox.set_sensitive(False)
+ self.delete_button.set_sensitive(False)
+ self.down_button.set_sensitive(False)
+ self.up_button.set_sensitive(False)
+
+ self.window.show_all()
+
+ def initiate_rule_state(self):
+ """
+ Set values for all widgets
+ """
+ if self.active_num < 0:
+ return
+ # event
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'event')
+ if value:
+ self.event_combobox.set_active(self.events_list.index(value))
+ else:
+ self.event_combobox.set_active(-1)
+ # recipient_type
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'recipient_type')
+ if value:
+ self.recipient_type_combobox.set_active(
+ self.recipient_types_list.index(value))
+ else:
+ self.recipient_type_combobox.set_active(-1)
+ # recipient
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'recipients')
+ if not value:
+ value = ''
+ self.recipient_list_entry.set_text(value)
+ # status
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'status')
+ if value == 'all':
+ self.all_status_rb.set_active(True)
+ else:
+ self.special_status_rb.set_active(True)
+ values = value.split()
+ for v in ('online', 'away', 'xa', 'dnd', 'invisible'):
+ if v in values:
+ self.__dict__[v + '_cb'].set_active(True)
+ else:
+ self.__dict__[v + '_cb'].set_active(False)
+ self.on_status_radiobutton_toggled(self.all_status_rb)
+ # tab_opened
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'tab_opened')
+ self.tab_opened_cb.set_active(True)
+ self.not_tab_opened_cb.set_active(True)
+ if value == 'no':
+ self.tab_opened_cb.set_active(False)
+ elif value == 'yes':
+ self.not_tab_opened_cb.set_active(False)
+ # sound_file
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'sound_file')
+ self.sound_entry.set_text(value)
+ # sound, popup, auto_open, systray, roster
+ for option in ('sound', 'popup', 'auto_open', 'systray', 'roster'):
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ option)
+ if value == 'yes':
+ self.__dict__['use_' + option + '_cb'].set_active(True)
+ else:
+ self.__dict__['use_' + option + '_cb'].set_active(False)
+ if value == 'no':
+ self.__dict__['disable_' + option + '_cb'].set_active(True)
+ else:
+ self.__dict__['disable_' + option + '_cb'].set_active(False)
+ # run_command
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'run_command')
+ self.run_command_cb.set_active(value)
+ # command
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'command')
+ self.command_entry.set_text(value)
+ # urgency_hint
+ value = gajim.config.get_per('notifications', str(self.active_num),
+ 'urgency_hint')
+ self.urgency_hint_cb.set_active(value)
+
+ def set_treeview_string(self):
+ (model, iter_) = self.conditions_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ event = self.event_combobox.get_active_text()
+ recipient_type = self.recipient_type_combobox.get_active_text()
+ recipient = ''
+ if recipient_type != 'everybody':
+ recipient = self.recipient_list_entry.get_text()
+ if self.all_status_rb.get_active():
+ status = ''
+ else:
+ status = _('when I am ')
+ for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
+ if self.__dict__[st + '_cb'].get_active():
+ status += helpers.get_uf_show(st) + ' '
+ model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type,
+ recipient, status)
+
+ def on_conditions_treeview_cursor_changed(self, widget):
+ (model, iter_) = widget.get_selection().get_selected()
+ if not iter_:
+ self.active_num = -1
+ return
+ self.active_num = model[iter_][0]
+ if self.active_num == 0:
+ self.up_button.set_sensitive(False)
+ else:
+ self.up_button.set_sensitive(True)
+ max = self.conditions_treeview.get_model().iter_n_children(None)
+ if self.active_num == max - 1:
+ self.down_button.set_sensitive(False)
+ else:
+ self.down_button.set_sensitive(True)
+ self.initiate_rule_state()
+ self.config_vbox.set_sensitive(True)
+ self.delete_button.set_sensitive(True)
+
+ def on_new_button_clicked(self, widget):
+ model = self.conditions_treeview.get_model()
+ num = self.conditions_treeview.get_model().iter_n_children(None)
+ gajim.config.add_per('notifications', str(num))
+ iter_ = model.append((num, ''))
+ path = model.get_path(iter_)
+ self.conditions_treeview.set_cursor(path)
+ self.active_num = num
+ self.set_treeview_string()
+ self.config_vbox.set_sensitive(True)
+
+ def on_delete_button_clicked(self, widget):
+ (model, iter_) = self.conditions_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ # up all others
+ iter2 = model.iter_next(iter_)
+ num = self.active_num
+ while iter2:
+ num = model[iter2][0]
+ model[iter2][0] = num - 1
+ for opt in self.config_options:
+ val = gajim.config.get_per('notifications', str(num), opt)
+ gajim.config.set_per('notifications', str(num - 1), opt, val)
+ iter2 = model.iter_next(iter2)
+ model.remove(iter_)
+ gajim.config.del_per('notifications', str(num)) # delete latest
+ self.active_num = -1
+ self.config_vbox.set_sensitive(False)
+ self.delete_button.set_sensitive(False)
+ self.up_button.set_sensitive(False)
+ self.down_button.set_sensitive(False)
+
+ def on_up_button_clicked(self, widget):
+ (model, iter_) = self.conditions_treeview.get_selection().\
+ get_selected()
+ if not iter_:
+ return
+ for opt in self.config_options:
+ val = gajim.config.get_per('notifications', str(self.active_num), opt)
+ val2 = gajim.config.get_per('notifications', str(self.active_num - 1),
+ opt)
+ gajim.config.set_per('notifications', str(self.active_num), opt, val2)
+ gajim.config.set_per('notifications', str(self.active_num - 1), opt,
+ val)
+
+ model[iter_][0] = self.active_num - 1
+ # get previous iter
+ path = model.get_path(iter_)
+ iter_ = model.get_iter((path[0] - 1,))
+ model[iter_][0] = self.active_num
+ self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
+
+ def on_down_button_clicked(self, widget):
+ (model, iter_) = self.conditions_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ for opt in self.config_options:
+ val = gajim.config.get_per('notifications', str(self.active_num), opt)
+ val2 = gajim.config.get_per('notifications', str(self.active_num + 1),
+ opt)
+ gajim.config.set_per('notifications', str(self.active_num), opt, val2)
+ gajim.config.set_per('notifications', str(self.active_num + 1), opt,
+ val)
+
+ model[iter_][0] = self.active_num + 1
+ iter_ = model.iter_next(iter_)
+ model[iter_][0] = self.active_num
+ self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
+
+ def on_event_combobox_changed(self, widget):
+ if self.active_num < 0:
+ return
+ active = self.event_combobox.get_active()
+ if active == -1:
+ event = ''
+ else:
+ event = self.events_list[active]
+ gajim.config.set_per('notifications', str(self.active_num), 'event',
+ event)
+ self.set_treeview_string()
+
+ def on_recipient_type_combobox_changed(self, widget):
+ if self.active_num < 0:
+ return
+ recipient_type = self.recipient_types_list[self.recipient_type_combobox.\
+ get_active()]
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'recipient_type', recipient_type)
+ if recipient_type == 'all':
+ self.recipient_list_entry.hide()
+ else:
+ self.recipient_list_entry.show()
+ self.set_treeview_string()
+
+ def on_recipient_list_entry_changed(self, widget):
+ if self.active_num < 0:
+ return
+ recipients = widget.get_text().decode('utf-8')
+ #TODO: do some check
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'recipients', recipients)
+ self.set_treeview_string()
+
+ def set_status_config(self):
+ if self.active_num < 0:
+ return
+ status = ''
+ for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
+ if self.__dict__[st + '_cb'].get_active():
+ status += st + ' '
+ if status:
+ status = status[:-1]
+ gajim.config.set_per('notifications', str(self.active_num), 'status',
+ status)
+ self.set_treeview_string()
+
+ def on_status_radiobutton_toggled(self, widget):
+ if self.active_num < 0:
+ return
+ if self.all_status_rb.get_active():
+ gajim.config.set_per('notifications', str(self.active_num), 'status',
+ 'all')
+ # 'All status' clicked
+ for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
+ self.__dict__[st + '_cb'].hide()
+
+ self.special_status_rb.show()
+ else:
+ self.set_status_config()
+ # 'special status' clicked
+ for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
+ self.__dict__[st + '_cb'].show()
+
+ self.special_status_rb.hide()
+ self.set_treeview_string()
+
+ def on_status_cb_toggled(self, widget):
+ if self.active_num < 0:
+ return
+ self.set_status_config()
+
+ # tab_opened OR (not xor) not_tab_opened must be active
+ def on_tab_opened_cb_toggled(self, widget):
+ if self.active_num < 0:
+ return
+ if self.tab_opened_cb.get_active():
+ if self.not_tab_opened_cb.get_active():
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'both')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'yes')
+ elif not self.not_tab_opened_cb.get_active():
+ self.not_tab_opened_cb.set_active(True)
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'no')
+
+ def on_not_tab_opened_cb_toggled(self, widget):
+ if self.active_num < 0:
+ return
+ if self.not_tab_opened_cb.get_active():
+ if self.tab_opened_cb.get_active():
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'both')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'no')
+ elif not self.tab_opened_cb.get_active():
+ self.tab_opened_cb.set_active(True)
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'tab_opened', 'yes')
+
+ def on_use_it_toggled(self, widget, oposite_widget, option):
+ if widget.get_active():
+ if oposite_widget.get_active():
+ oposite_widget.set_active(False)
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'yes')
+ elif oposite_widget.get_active():
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'no')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num), option, '')
+
+ def on_disable_it_toggled(self, widget, oposite_widget, option):
+ if widget.get_active():
+ if oposite_widget.get_active():
+ oposite_widget.set_active(False)
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'no')
+ elif oposite_widget.get_active():
+ gajim.config.set_per('notifications', str(self.active_num), option,
+ 'yes')
+ else:
+ gajim.config.set_per('notifications', str(self.active_num), option, '')
+
+ def on_use_sound_cb_toggled(self, widget):
+ self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound')
+ if widget.get_active():
+ self.sound_file_hbox.set_sensitive(True)
+ else:
+ self.sound_file_hbox.set_sensitive(False)
+
+ def on_browse_for_sounds_button_clicked(self, widget, data=None):
+ if self.active_num < 0:
+ return
+
+ def on_ok(widget, path_to_snd_file):
+ dialog.destroy()
+ if not path_to_snd_file:
+ path_to_snd_file = ''
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'sound_file', path_to_snd_file)
+ self.sound_entry.set_text(path_to_snd_file)
+
+ path_to_snd_file = self.sound_entry.get_text().decode('utf-8')
+ path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file)
+ dialog = SoundChooserDialog(path_to_snd_file, on_ok)
+
+ def on_play_button_clicked(self, widget):
+ helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8'))
+
+ def on_disable_sound_cb_toggled(self, widget):
+ self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound')
+
+ def on_sound_entry_changed(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'sound_file', widget.get_text().decode('utf-8'))
+
+ def on_use_popup_cb_toggled(self, widget):
+ self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup')
+
+ def on_disable_popup_cb_toggled(self, widget):
+ self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup')
+
+ def on_use_auto_open_cb_toggled(self, widget):
+ self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open')
+
+ def on_disable_auto_open_cb_toggled(self, widget):
+ self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open')
+
+ def on_run_command_cb_toggled(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num), 'run_command',
+ widget.get_active())
+ if widget.get_active():
+ self.command_entry.set_sensitive(True)
+ else:
+ self.command_entry.set_sensitive(False)
+
+ def on_command_entry_changed(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num), 'command',
+ widget.get_text().decode('utf-8'))
+
+ def on_use_systray_cb_toggled(self, widget):
+ self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray')
+
+ def on_disable_systray_cb_toggled(self, widget):
+ self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray')
+
+ def on_use_roster_cb_toggled(self, widget):
+ self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster')
+
+ def on_disable_roster_cb_toggled(self, widget):
+ self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster')
+
+ def on_urgency_hint_cb_toggled(self, widget):
+ gajim.config.set_per('notifications', str(self.active_num),
+ 'uregency_hint', widget.get_active())
+
+ def on_close_window(self, widget):
+ self.window.destroy()
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
- """
-
- self.instances.append(self)
- self.account = account
- self.auto_jids = jids
- self.preselected_jids = preselected
-
- self.xml = gtkgui_helpers.get_gtk_builder('chat_to_muc_window.ui')
- self.window = self.xml.get_object('chat_to_muc_window')
-
- for widget_to_add in ('invite_button', 'cancel_button',
- 'server_list_comboboxentry', 'guests_treeview',
- 'server_and_guests_hseparator', 'server_select_label'):
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- server_list = []
- self.servers = gtk.ListStore(str)
- self.server_list_comboboxentry.set_model(self.servers)
-
- self.server_list_comboboxentry.set_text_column(0)
-
- # get the muc server of our server
- if 'jabber' in gajim.connections[account].muc_jid:
- server_list.append(gajim.connections[account].muc_jid['jabber'])
- # add servers or recently joined groupchats
- recently_groupchat = gajim.config.get('recently_groupchat').split()
- for g in recently_groupchat:
- server = gajim.get_server_from_jid(g)
- if server not in server_list and not server.startswith('irc'):
- server_list.append(server)
- # add a default server
- if not server_list:
- server_list.append('conference.jabber.org')
-
- for s in server_list:
- self.servers.append([s])
-
- self.server_list_comboboxentry.set_active(0)
-
- # set treeview
- # name, jid
- self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str)
- self.store.set_sort_column_id(1, gtk.SORT_ASCENDING)
- self.guests_treeview.set_model(self.store)
-
- renderer1 = gtk.CellRendererText()
- renderer2 = gtk.CellRendererPixbuf()
- column = gtk.TreeViewColumn('Status', renderer2, pixbuf=0)
- self.guests_treeview.append_column(column)
- column = gtk.TreeViewColumn('Name', renderer1, text=1)
- self.guests_treeview.append_column(column)
-
- self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
-
- # All contacts beside the following can be invited:
- # transports, zeroconf contacts, minimized groupchats
- def invitable(contact, contact_transport=None):
- return (contact.jid not in self.auto_jids and
- contact.jid != gajim.get_jid_from_account(self.account) and
- contact.jid not in gajim.interface.minimized_controls[account] and
- not contact.is_transport() and
- not contact_transport)
-
- # set jabber id and pseudos
- for account in gajim.contacts.get_accounts():
- if gajim.connections[account].is_zeroconf:
- continue
- for jid in gajim.contacts.get_jid_list(account):
- contact = \
- gajim.contacts.get_contact_with_highest_priority(account, jid)
- contact_transport = gajim.get_transport_name_from_jid(jid)
- # Add contact if it can be invited
- if invitable(contact, contact_transport) and \
- contact.show not in ('offline', 'error'):
- img = gajim.interface.jabber_state_images['16'][contact.show]
- name = contact.name
- if name == '':
- name = jid.split('@')[0]
- iter_ = self.store.append([img.get_pixbuf(), name, jid])
- # preselect treeview rows
- if self.preselected_jids and jid in self.preselected_jids:
- path = self.store.get_path(iter_)
- self.guests_treeview.get_selection().\
- select_path(path)
-
- # show all
- self.window.show_all()
-
- self.xml.connect_signals(self)
-
- def on_chat_to_muc_window_destroy(self, widget):
- self.instances.remove(self)
-
- def on_chat_to_muc_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape: # ESCAPE
- self.window.destroy()
-
- def on_invite_button_clicked(self, widget):
- server = self.server_list_comboboxentry.get_active_text()
- if server == '':
- return
- gajim.connections[self.account].check_unique_room_id_support(server, self)
-
- def unique_room_id_supported(self, server, room_id):
- guest_list = []
- guests = self.guests_treeview.get_selection().get_selected_rows()
- for guest in guests[1]:
- iter_ = self.store.get_iter(guest)
- guest_list.append(self.store[iter_][2].decode('utf-8'))
- for guest in self.auto_jids:
- guest_list.append(guest)
- room_jid = room_id + '@' + server
- gajim.automatic_rooms[self.account][room_jid] = {}
- gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list
- gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True
- gajim.interface.join_gc_room(self.account, room_jid,
- gajim.nicks[self.account], None, is_continued=True)
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def unique_room_id_error(self, server):
- self.unique_room_id_supported(server,
- gajim.nicks[self.account].lower().replace(' ','') + str(randrange(
- 9999999)))
+ # Keep a reference on windows so garbage collector don't restroy them
+ instances = []
+ def __init__(self, account, jids, preselected=None):
+ """
+ This window is used to trasform a one-to-one chat to a MUC. We do 2
+ things: first select the server and then make a guests list
+ """
+
+ self.instances.append(self)
+ self.account = account
+ self.auto_jids = jids
+ self.preselected_jids = preselected
+
+ self.xml = gtkgui_helpers.get_gtk_builder('chat_to_muc_window.ui')
+ self.window = self.xml.get_object('chat_to_muc_window')
+
+ for widget_to_add in ('invite_button', 'cancel_button',
+ 'server_list_comboboxentry', 'guests_treeview',
+ 'server_and_guests_hseparator', 'server_select_label'):
+ self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
+
+ server_list = []
+ self.servers = gtk.ListStore(str)
+ self.server_list_comboboxentry.set_model(self.servers)
+
+ self.server_list_comboboxentry.set_text_column(0)
+
+ # get the muc server of our server
+ if 'jabber' in gajim.connections[account].muc_jid:
+ server_list.append(gajim.connections[account].muc_jid['jabber'])
+ # add servers or recently joined groupchats
+ recently_groupchat = gajim.config.get('recently_groupchat').split()
+ for g in recently_groupchat:
+ server = gajim.get_server_from_jid(g)
+ if server not in server_list and not server.startswith('irc'):
+ server_list.append(server)
+ # add a default server
+ if not server_list:
+ server_list.append('conference.jabber.org')
+
+ for s in server_list:
+ self.servers.append([s])
+
+ self.server_list_comboboxentry.set_active(0)
+
+ # set treeview
+ # name, jid
+ self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str)
+ self.store.set_sort_column_id(1, gtk.SORT_ASCENDING)
+ self.guests_treeview.set_model(self.store)
+
+ renderer1 = gtk.CellRendererText()
+ renderer2 = gtk.CellRendererPixbuf()
+ column = gtk.TreeViewColumn('Status', renderer2, pixbuf=0)
+ self.guests_treeview.append_column(column)
+ column = gtk.TreeViewColumn('Name', renderer1, text=1)
+ self.guests_treeview.append_column(column)
+
+ self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+
+ # All contacts beside the following can be invited:
+ # transports, zeroconf contacts, minimized groupchats
+ def invitable(contact, contact_transport=None):
+ return (contact.jid not in self.auto_jids and
+ contact.jid != gajim.get_jid_from_account(self.account) and
+ contact.jid not in gajim.interface.minimized_controls[account] and
+ not contact.is_transport() and
+ not contact_transport)
+
+ # set jabber id and pseudos
+ for account in gajim.contacts.get_accounts():
+ if gajim.connections[account].is_zeroconf:
+ continue
+ for jid in gajim.contacts.get_jid_list(account):
+ contact = \
+ gajim.contacts.get_contact_with_highest_priority(account, jid)
+ contact_transport = gajim.get_transport_name_from_jid(jid)
+ # Add contact if it can be invited
+ if invitable(contact, contact_transport) and \
+ contact.show not in ('offline', 'error'):
+ img = gajim.interface.jabber_state_images['16'][contact.show]
+ name = contact.name
+ if name == '':
+ name = jid.split('@')[0]
+ iter_ = self.store.append([img.get_pixbuf(), name, jid])
+ # preselect treeview rows
+ if self.preselected_jids and jid in self.preselected_jids:
+ path = self.store.get_path(iter_)
+ self.guests_treeview.get_selection().\
+ select_path(path)
+
+ # show all
+ self.window.show_all()
+
+ self.xml.connect_signals(self)
+
+ def on_chat_to_muc_window_destroy(self, widget):
+ self.instances.remove(self)
+
+ def on_chat_to_muc_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape: # ESCAPE
+ self.window.destroy()
+
+ def on_invite_button_clicked(self, widget):
+ server = self.server_list_comboboxentry.get_active_text()
+ if server == '':
+ return
+ gajim.connections[self.account].check_unique_room_id_support(server, self)
+
+ def unique_room_id_supported(self, server, room_id):
+ guest_list = []
+ guests = self.guests_treeview.get_selection().get_selected_rows()
+ for guest in guests[1]:
+ iter_ = self.store.get_iter(guest)
+ guest_list.append(self.store[iter_][2].decode('utf-8'))
+ for guest in self.auto_jids:
+ guest_list.append(guest)
+ room_jid = room_id + '@' + server
+ gajim.automatic_rooms[self.account][room_jid] = {}
+ gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list
+ gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True
+ gajim.interface.join_gc_room(self.account, room_jid,
+ gajim.nicks[self.account], None, is_continued=True)
+ self.window.destroy()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def unique_room_id_error(self, server):
+ self.unique_room_id_supported(server,
+ gajim.nicks[self.account].lower().replace(' ','') + str(randrange(
+ 9999999)))
class DataFormWindow(Dialog):
- def __init__(self, form, on_response_ok):
- self.df_response_ok = on_response_ok
- Dialog.__init__(self, None, 'test', [(gtk.STOCK_CANCEL,
- gtk.RESPONSE_REJECT), (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)],
- on_response_ok=self.on_ok)
- self.set_resizable(True)
- gtkgui_helpers.resize_window(self, 600, 400)
- self.dataform_widget = dataforms_widget.DataFormWidget()
- self.dataform = dataforms.ExtendForm(node=form)
- self.dataform_widget.set_sensitive(True)
- self.dataform_widget.data_form = self.dataform
- self.dataform_widget.show_all()
- self.vbox.pack_start(self.dataform_widget)
-
- def on_ok(self):
- form = self.dataform_widget.data_form
- if isinstance(self.df_response_ok, tuple):
- self.df_response_ok[0](form, *self.df_response_ok[1:])
- else:
- self.df_response_ok(form)
- self.destroy()
+ def __init__(self, form, on_response_ok):
+ self.df_response_ok = on_response_ok
+ Dialog.__init__(self, None, 'test', [(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_REJECT), (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)],
+ on_response_ok=self.on_ok)
+ self.set_resizable(True)
+ gtkgui_helpers.resize_window(self, 600, 400)
+ self.dataform_widget = dataforms_widget.DataFormWidget()
+ self.dataform = dataforms.ExtendForm(node=form)
+ self.dataform_widget.set_sensitive(True)
+ self.dataform_widget.data_form = self.dataform
+ self.dataform_widget.show_all()
+ self.vbox.pack_start(self.dataform_widget)
+
+ def on_ok(self):
+ form = self.dataform_widget.data_form
+ if isinstance(self.df_response_ok, tuple):
+ self.df_response_ok[0](form, *self.df_response_ok[1:])
+ else:
+ self.df_response_ok(form)
+ self.destroy()
class ESessionInfoWindow:
- """
- Class for displaying information about a XEP-0116 encrypted session
- """
- def __init__(self, session):
- self.session = session
+ """
+ Class for displaying information about a XEP-0116 encrypted session
+ """
+ def __init__(self, session):
+ self.session = session
- self.xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui')
- self.xml.connect_signals(self)
+ self.xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui')
+ self.xml.connect_signals(self)
- self.security_image = self.xml.get_object('security_image')
- self.verify_now_button = self.xml.get_object('verify_now_button')
- self.button_label = self.xml.get_object('button_label')
- self.window = self.xml.get_object('esession_info_window')
- self.update_info()
+ self.security_image = self.xml.get_object('security_image')
+ self.verify_now_button = self.xml.get_object('verify_now_button')
+ self.button_label = self.xml.get_object('button_label')
+ self.window = self.xml.get_object('esession_info_window')
+ self.update_info()
- self.window.show_all()
+ self.window.show_all()
- def update_info(self):
- labeltext = _('''Your chat session with <b>%(jid)s</b> is encrypted.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'jid': self.session.jid, 'sas': self.session.sas}
+ def update_info(self):
+ labeltext = _('''Your chat session with <b>%(jid)s</b> is encrypted.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'jid': self.session.jid, 'sas': self.session.sas}
- if self.session.verified_identity:
- labeltext += '\n\n' + _('''You have already verified this contact's identity.''')
- security_image = 'gajim-security_high'
- if self.session.control:
- self.session.control._show_lock_image(True, 'E2E', True,
- self.session.is_loggable(), True)
+ if self.session.verified_identity:
+ labeltext += '\n\n' + _('''You have already verified this contact's identity.''')
+ security_image = 'gajim-security_high'
+ if self.session.control:
+ self.session.control._show_lock_image(True, 'E2E', True,
+ self.session.is_loggable(), True)
- verification_status = _('''Contact's identity verified''')
- self.window.set_title(verification_status)
- self.xml.get_object('verification_status_label').set_markup(
- '<b><span size="x-large">%s</span></b>' % verification_status)
+ verification_status = _('''Contact's identity verified''')
+ self.window.set_title(verification_status)
+ self.xml.get_object('verification_status_label').set_markup(
+ '<b><span size="x-large">%s</span></b>' % verification_status)
- self.xml.get_object('dialog-action_area1').set_no_show_all(True)
- self.button_label.set_text(_('Verify again...'))
- else:
- if self.session.control:
- self.session.control._show_lock_image(True, 'E2E', True,
- self.session.is_loggable(), False)
- labeltext += '\n\n' + _('''To be certain that <b>only</b> the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''')
- security_image = 'gajim-security_low'
+ self.xml.get_object('dialog-action_area1').set_no_show_all(True)
+ self.button_label.set_text(_('Verify again...'))
+ else:
+ if self.session.control:
+ self.session.control._show_lock_image(True, 'E2E', True,
+ self.session.is_loggable(), False)
+ labeltext += '\n\n' + _('''To be certain that <b>only</b> the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''')
+ security_image = 'gajim-security_low'
- verification_status = _('''Contact's identity NOT verified''')
- self.window.set_title(verification_status)
- self.xml.get_object('verification_status_label').set_markup(
- '<b><span size="x-large">%s</span></b>' % verification_status)
+ verification_status = _('''Contact's identity NOT verified''')
+ self.window.set_title(verification_status)
+ self.xml.get_object('verification_status_label').set_markup(
+ '<b><span size="x-large">%s</span></b>' % verification_status)
- self.button_label.set_text(_('Verify...'))
+ self.button_label.set_text(_('Verify...'))
- path = gtkgui_helpers.get_icon_path(security_image, 32)
- self.security_image.set_from_file(path)
+ path = gtkgui_helpers.get_icon_path(security_image, 32)
+ self.security_image.set_from_file(path)
- self.xml.get_object('info_display').set_markup(labeltext)
+ self.xml.get_object('info_display').set_markup(labeltext)
- def on_close_button_clicked(self, widget):
- self.window.destroy()
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
- def on_verify_now_button_clicked(self, widget):
- pritext = _('''Have you verified the contact's identity?''')
- sectext = _('''To prevent talking to an unknown person, you should speak to <b>%(jid)s</b> directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'jid': self.session.jid, 'sas': self.session.sas}
- sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?')
+ def on_verify_now_button_clicked(self, widget):
+ pritext = _('''Have you verified the contact's identity?''')
+ sectext = _('''To prevent talking to an unknown person, you should speak to <b>%(jid)s</b> directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'jid': self.session.jid, 'sas': self.session.sas}
+ sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?')
- def on_yes(checked):
- self.session._verified_srs_cb()
- self.session.verified_identity = True
- self.update_info()
+ def on_yes(checked):
+ self.session._verified_srs_cb()
+ self.session.verified_identity = True
+ self.update_info()
- def on_no():
- self.session._unverified_srs_cb()
- self.session.verified_identity = False
- self.update_info()
+ def on_no():
+ self.session._unverified_srs_cb()
+ self.session.verified_identity = False
+ self.update_info()
- YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no)
+ YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no)
class GPGInfoWindow:
- """
- Class for displaying information about a XEP-0116 encrypted session
- """
- def __init__(self, control):
- xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui')
- security_image = xml.get_object('security_image')
- status_label = xml.get_object('verification_status_label')
- info_label = xml.get_object('info_display')
- verify_now_button = xml.get_object('verify_now_button')
- self.window = xml.get_object('esession_info_window')
- account = control.account
- keyID = control.contact.keyID
- error = None
-
- verify_now_button.set_no_show_all(True)
- verify_now_button.hide()
-
- if keyID.endswith('MISMATCH'):
- verification_status = _('''Contact's identity NOT verified''')
- info = _('The contact\'s key (%s) <b>does not match</b> the key '
- 'assigned in Gajim.') % keyID[:8]
- image = 'gajim-security_low'
- elif not keyID:
- # No key assigned nor a key is used by remote contact
- verification_status = _('No GPG key assigned')
- info = _('No GPG key is assigned to this contact. So you cannot '
- 'encrypt messages.')
- image = 'gajim-security_low'
- else:
- error = gajim.connections[account].gpg.encrypt('test', [keyID])[1]
- if error:
- verification_status = _('''Contact's identity NOT verified''')
- info = _('GPG key is assigned to this contact, but <b>you do not '
- 'trust his key</b>, so message <b>cannot</b> be encrypted. Use '
- 'your GPG client to trust this key.')
- image = 'gajim-security_low'
- else:
- verification_status = _('''Contact's identity verified''')
- info = _('GPG Key is assigned to this contact, and you trust his '
- 'key, so messages will be encrypted.')
- image = 'gajim-security_high'
-
- status_label.set_markup('<b><span size="x-large">%s</span></b>' % \
- verification_status)
- info_label.set_markup(info)
-
- path = gtkgui_helpers.get_icon_path(image, 32)
- security_image.set_from_file(path)
-
- xml.connect_signals(self)
- self.window.show_all()
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
+ """
+ Class for displaying information about a XEP-0116 encrypted session
+ """
+ def __init__(self, control):
+ xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui')
+ security_image = xml.get_object('security_image')
+ status_label = xml.get_object('verification_status_label')
+ info_label = xml.get_object('info_display')
+ verify_now_button = xml.get_object('verify_now_button')
+ self.window = xml.get_object('esession_info_window')
+ account = control.account
+ keyID = control.contact.keyID
+ error = None
+
+ verify_now_button.set_no_show_all(True)
+ verify_now_button.hide()
+
+ if keyID.endswith('MISMATCH'):
+ verification_status = _('''Contact's identity NOT verified''')
+ info = _('The contact\'s key (%s) <b>does not match</b> the key '
+ 'assigned in Gajim.') % keyID[:8]
+ image = 'gajim-security_low'
+ elif not keyID:
+ # No key assigned nor a key is used by remote contact
+ verification_status = _('No GPG key assigned')
+ info = _('No GPG key is assigned to this contact. So you cannot '
+ 'encrypt messages.')
+ image = 'gajim-security_low'
+ else:
+ error = gajim.connections[account].gpg.encrypt('test', [keyID])[1]
+ if error:
+ verification_status = _('''Contact's identity NOT verified''')
+ info = _('GPG key is assigned to this contact, but <b>you do not '
+ 'trust his key</b>, so message <b>cannot</b> be encrypted. Use '
+ 'your GPG client to trust this key.')
+ image = 'gajim-security_low'
+ else:
+ verification_status = _('''Contact's identity verified''')
+ info = _('GPG Key is assigned to this contact, and you trust his '
+ 'key, so messages will be encrypted.')
+ image = 'gajim-security_high'
+
+ status_label.set_markup('<b><span size="x-large">%s</span></b>' % \
+ verification_status)
+ info_label.set_markup(info)
+
+ path = gtkgui_helpers.get_icon_path(image, 32)
+ security_image.set_from_file(path)
+
+ xml.connect_signals(self)
+ self.window.show_all()
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
class ResourceConflictDialog(TimeoutDialog, InputDialog):
- def __init__(self, title, text, resource, ok_handler):
- TimeoutDialog.__init__(self, 15, self.on_timeout)
- InputDialog.__init__(self, title, text, input_str=resource,
- is_modal=False, ok_handler=ok_handler)
- self.title_text = title
- self.run_timeout()
+ def __init__(self, title, text, resource, ok_handler):
+ TimeoutDialog.__init__(self, 15, self.on_timeout)
+ InputDialog.__init__(self, title, text, input_str=resource,
+ is_modal=False, ok_handler=ok_handler)
+ self.title_text = title
+ self.run_timeout()
- def on_timeout(self):
- self.on_okbutton_clicked(None)
+ def on_timeout(self):
+ self.on_okbutton_clicked(None)
class VoIPCallReceivedDialog(object):
- instances = {}
- def __init__(self, account, contact_jid, sid, content_types):
- self.instances[(contact_jid, sid)] = self
- self.account = account
- self.fjid = contact_jid
- self.sid = sid
- self.content_types = content_types
-
- xml = gtkgui_helpers.get_gtk_builder('voip_call_received_dialog.ui')
- xml.connect_signals(self)
-
- jid = gajim.get_jid_without_resource(self.fjid)
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if contact and contact.name:
- self.contact_text = '%s (%s)' % (contact.name, jid)
- else:
- self.contact_text = contact_jid
-
- self.dialog = xml.get_object('voip_call_received_messagedialog')
- self.set_secondary_text()
-
- self.dialog.show_all()
-
- @classmethod
- def get_dialog(cls, jid, sid):
- if (jid, sid) in cls.instances:
- return cls.instances[(jid, sid)]
- else:
- return None
-
- def set_secondary_text(self):
- if 'audio' in self.content_types and 'video' in self.content_types:
- types_text = _('an audio and video')
- elif 'audio' in self.content_types:
- types_text = _('an audio')
- elif 'video' in self.content_types:
- types_text = _('a video')
-
- # do the substitution
- self.dialog.set_property('secondary-text',
- _('%(contact)s wants to start %(type)s session with you. Do you want '
- 'to answer the call?') % {'contact': self.contact_text, 'type': types_text})
-
- def add_contents(self, content_types):
- for type_ in content_types:
- if type_ not in self.content_types:
- self.content_types.add(type_)
- self.set_secondary_text()
-
- def on_voip_call_received_messagedialog_destroy(self, dialog):
- if (self.fjid, self.sid) in self.instances:
- del self.instances[(self.fjid, self.sid)]
-
- def on_voip_call_received_messagedialog_close(self, dialog):
- return self.on_voip_call_received_messagedialog_response(dialog,
- gtk.RESPONSE_NO)
-
- def on_voip_call_received_messagedialog_response(self, dialog, response):
- # we've got response from user, either stop connecting or accept the call
- session = gajim.connections[self.account].get_jingle_session(self.fjid,
- self.sid)
- if not session:
- return
- if response == gtk.RESPONSE_YES:
- #TODO: Ensure that ctrl.contact.resource == resource
- jid = gajim.get_jid_without_resource(self.fjid)
- resource = gajim.get_resource_from_jid(self.fjid)
- ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account)
- if not ctrl:
- ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account)
- if not ctrl:
- # open chat control
- contact = gajim.contacts.get_contact(self.account, jid, resource)
- if not contact:
- contact = gajim.contacts.get_contact(self.account, jid)
- if not contact:
- return
- ctrl = gajim.interface.new_chat(contact, self.account, resource)
- # Chat control opened, update content's status
- audio = session.get_content('audio')
- video = session.get_content('video')
- if audio and not audio.negotiated:
- ctrl.set_audio_state('connecting', self.sid)
- if video and not video.negotiated:
- ctrl.set_video_state('connecting', self.sid)
- # Now, accept the content/sessions.
- # This should be done after the chat control is running
- if not session.accepted:
- session.approve_session()
- for content in self.content_types:
- session.approve_content(content)
- else: # response==gtk.RESPONSE_NO
- if not session.accepted:
- session.decline_session()
- else:
- for content in self.content_types:
- session.reject_content(content)
-
- dialog.destroy()
-
-
-# vim: se ts=3:
+ instances = {}
+ def __init__(self, account, contact_jid, sid, content_types):
+ self.instances[(contact_jid, sid)] = self
+ self.account = account
+ self.fjid = contact_jid
+ self.sid = sid
+ self.content_types = content_types
+
+ xml = gtkgui_helpers.get_gtk_builder('voip_call_received_dialog.ui')
+ xml.connect_signals(self)
+
+ jid = gajim.get_jid_without_resource(self.fjid)
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ if contact and contact.name:
+ self.contact_text = '%s (%s)' % (contact.name, jid)
+ else:
+ self.contact_text = contact_jid
+
+ self.dialog = xml.get_object('voip_call_received_messagedialog')
+ self.set_secondary_text()
+
+ self.dialog.show_all()
+
+ @classmethod
+ def get_dialog(cls, jid, sid):
+ if (jid, sid) in cls.instances:
+ return cls.instances[(jid, sid)]
+ else:
+ return None
+
+ def set_secondary_text(self):
+ if 'audio' in self.content_types and 'video' in self.content_types:
+ types_text = _('an audio and video')
+ elif 'audio' in self.content_types:
+ types_text = _('an audio')
+ elif 'video' in self.content_types:
+ types_text = _('a video')
+
+ # do the substitution
+ self.dialog.set_property('secondary-text',
+ _('%(contact)s wants to start %(type)s session with you. Do you want '
+ 'to answer the call?') % {'contact': self.contact_text, 'type': types_text})
+
+ def add_contents(self, content_types):
+ for type_ in content_types:
+ if type_ not in self.content_types:
+ self.content_types.add(type_)
+ self.set_secondary_text()
+
+ def on_voip_call_received_messagedialog_destroy(self, dialog):
+ if (self.fjid, self.sid) in self.instances:
+ del self.instances[(self.fjid, self.sid)]
+
+ def on_voip_call_received_messagedialog_close(self, dialog):
+ return self.on_voip_call_received_messagedialog_response(dialog,
+ gtk.RESPONSE_NO)
+
+ def on_voip_call_received_messagedialog_response(self, dialog, response):
+ # we've got response from user, either stop connecting or accept the call
+ session = gajim.connections[self.account].get_jingle_session(self.fjid,
+ self.sid)
+ if not session:
+ return
+ if response == gtk.RESPONSE_YES:
+ #TODO: Ensure that ctrl.contact.resource == resource
+ jid = gajim.get_jid_without_resource(self.fjid)
+ resource = gajim.get_resource_from_jid(self.fjid)
+ ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account)
+ if not ctrl:
+ ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account)
+ if not ctrl:
+ # open chat control
+ contact = gajim.contacts.get_contact(self.account, jid, resource)
+ if not contact:
+ contact = gajim.contacts.get_contact(self.account, jid)
+ if not contact:
+ return
+ ctrl = gajim.interface.new_chat(contact, self.account, resource)
+ # Chat control opened, update content's status
+ audio = session.get_content('audio')
+ video = session.get_content('video')
+ if audio and not audio.negotiated:
+ ctrl.set_audio_state('connecting', self.sid)
+ if video and not video.negotiated:
+ ctrl.set_video_state('connecting', self.sid)
+ # Now, accept the content/sessions.
+ # This should be done after the chat control is running
+ if not session.accepted:
+ session.approve_session()
+ for content in self.content_types:
+ session.approve_content(content)
+ else: # response==gtk.RESPONSE_NO
+ if not session.accepted:
+ session.decline_session()
+ else:
+ for content in self.content_types:
+ session.reject_content(content)
+
+ dialog.destroy()
+
diff --git a/src/disco.py b/src/disco.py
index c557e5777..46f0e82ac 100644
--- a/src/disco.py
+++ b/src/disco.py
@@ -69,2189 +69,2187 @@ from common import ged
# For the browser class, None means that the service will only be browsable
# when it advertises disco as it's feature, False means it's never browsable.
def _gen_agent_type_info():
- return {
- # Defaults
- (0, 0): (None, None),
-
- # Jabber server
- ('server', 'im'): (ToplevelAgentBrowser, 'jabber'),
- ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber'),
- ('hierarchy', 'branch'): (AgentBrowser, 'jabber'),
-
- # Services
- ('conference', 'text'): (MucBrowser, 'conference'),
- ('headline', 'rss'): (AgentBrowser, 'rss'),
- ('headline', 'weather'): (False, 'weather'),
- ('gateway', 'weather'): (False, 'weather'),
- ('_jid', 'weather'): (False, 'weather'),
- ('gateway', 'sip'): (False, 'sip'),
- ('directory', 'user'): (None, 'jud'),
- ('pubsub', 'generic'): (PubSubBrowser, 'pubsub'),
- ('pubsub', 'service'): (PubSubBrowser, 'pubsub'),
- ('proxy', 'bytestreams'): (None, 'bytestreams'), # Socks5 FT proxy
- ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail'),
-
- # Transports
- ('conference', 'irc'): (ToplevelAgentBrowser, 'irc'),
- ('_jid', 'irc'): (False, 'irc'),
- ('gateway', 'aim'): (False, 'aim'),
- ('_jid', 'aim'): (False, 'aim'),
- ('gateway', 'gadu-gadu'): (False, 'gadu-gadu'),
- ('_jid', 'gadugadu'): (False, 'gadu-gadu'),
- ('gateway', 'http-ws'): (False, 'http-ws'),
- ('gateway', 'icq'): (False, 'icq'),
- ('_jid', 'icq'): (False, 'icq'),
- ('gateway', 'msn'): (False, 'msn'),
- ('_jid', 'msn'): (False, 'msn'),
- ('gateway', 'sms'): (False, 'sms'),
- ('_jid', 'sms'): (False, 'sms'),
- ('gateway', 'smtp'): (False, 'mail'),
- ('gateway', 'yahoo'): (False, 'yahoo'),
- ('_jid', 'yahoo'): (False, 'yahoo'),
- ('gateway', 'mrim'): (False, 'mrim'),
- ('_jid', 'mrim'): (False, 'mrim'),
- ('gateway', 'facebook'): (False, 'facebook'),
- ('_jid', 'facebook'): (False, 'facebook'),
- }
+ return {
+ # Defaults
+ (0, 0): (None, None),
+
+ # Jabber server
+ ('server', 'im'): (ToplevelAgentBrowser, 'jabber'),
+ ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber'),
+ ('hierarchy', 'branch'): (AgentBrowser, 'jabber'),
+
+ # Services
+ ('conference', 'text'): (MucBrowser, 'conference'),
+ ('headline', 'rss'): (AgentBrowser, 'rss'),
+ ('headline', 'weather'): (False, 'weather'),
+ ('gateway', 'weather'): (False, 'weather'),
+ ('_jid', 'weather'): (False, 'weather'),
+ ('gateway', 'sip'): (False, 'sip'),
+ ('directory', 'user'): (None, 'jud'),
+ ('pubsub', 'generic'): (PubSubBrowser, 'pubsub'),
+ ('pubsub', 'service'): (PubSubBrowser, 'pubsub'),
+ ('proxy', 'bytestreams'): (None, 'bytestreams'), # Socks5 FT proxy
+ ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail'),
+
+ # Transports
+ ('conference', 'irc'): (ToplevelAgentBrowser, 'irc'),
+ ('_jid', 'irc'): (False, 'irc'),
+ ('gateway', 'aim'): (False, 'aim'),
+ ('_jid', 'aim'): (False, 'aim'),
+ ('gateway', 'gadu-gadu'): (False, 'gadu-gadu'),
+ ('_jid', 'gadugadu'): (False, 'gadu-gadu'),
+ ('gateway', 'http-ws'): (False, 'http-ws'),
+ ('gateway', 'icq'): (False, 'icq'),
+ ('_jid', 'icq'): (False, 'icq'),
+ ('gateway', 'msn'): (False, 'msn'),
+ ('_jid', 'msn'): (False, 'msn'),
+ ('gateway', 'sms'): (False, 'sms'),
+ ('_jid', 'sms'): (False, 'sms'),
+ ('gateway', 'smtp'): (False, 'mail'),
+ ('gateway', 'yahoo'): (False, 'yahoo'),
+ ('_jid', 'yahoo'): (False, 'yahoo'),
+ ('gateway', 'mrim'): (False, 'mrim'),
+ ('_jid', 'mrim'): (False, 'mrim'),
+ ('gateway', 'facebook'): (False, 'facebook'),
+ ('_jid', 'facebook'): (False, 'facebook'),
+ }
# Category type to "human-readable" description string, and sort priority
_cat_to_descr = {
- 'other': (_('Others'), 2),
- 'gateway': (_('Transports'), 0),
- '_jid': (_('Transports'), 0),
- #conference is a category for listing mostly groupchats in service discovery
- 'conference': (_('Conference'), 1),
+ 'other': (_('Others'), 2),
+ 'gateway': (_('Transports'), 0),
+ '_jid': (_('Transports'), 0),
+ #conference is a category for listing mostly groupchats in service discovery
+ 'conference': (_('Conference'), 1),
}
class CacheDictionary:
- """
- A dictionary that keeps items around for only a specific time. Lifetime is
- in minutes. Getrefresh specifies whether to refresh when an item is merely
- accessed instead of set aswell
- """
-
- def __init__(self, lifetime, getrefresh = True):
- self.lifetime = lifetime * 1000 * 60
- self.getrefresh = getrefresh
- self.cache = {}
-
- class CacheItem:
- """
- An object to store cache items and their timeouts
- """
- def __init__(self, value):
- self.value = value
- self.source = None
-
- def __call__(self):
- return self.value
-
- def cleanup(self):
- for key in self.cache.keys():
- item = self.cache[key]
- if item.source:
- gobject.source_remove(item.source)
- del self.cache[key]
-
- def _expire_timeout(self, key):
- """
- The timeout has expired, remove the object
- """
- if key in self.cache:
- del self.cache[key]
- return False
-
- def _refresh_timeout(self, key):
- """
- The object was accessed, refresh the timeout
- """
- item = self.cache[key]
- if item.source:
- gobject.source_remove(item.source)
- if self.lifetime:
- source = gobject.timeout_add_seconds(self.lifetime/1000, self._expire_timeout, key)
- item.source = source
-
- def __getitem__(self, key):
- item = self.cache[key]
- if self.getrefresh:
- self._refresh_timeout(key)
- return item()
-
- def __setitem__(self, key, value):
- item = self.CacheItem(value)
- self.cache[key] = item
- self._refresh_timeout(key)
-
- def __delitem__(self, key):
- item = self.cache[key]
- if item.source:
- gobject.source_remove(item.source)
- del self.cache[key]
-
- def __contains__(self, key):
- return key in self.cache
- has_key = __contains__
+ """
+ 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
+ """
+ def __init__(self, value):
+ self.value = value
+ self.source = None
+
+ def __call__(self):
+ return self.value
+
+ def cleanup(self):
+ for key in self.cache.keys():
+ item = self.cache[key]
+ if item.source:
+ gobject.source_remove(item.source)
+ del self.cache[key]
+
+ def _expire_timeout(self, key):
+ """
+ The timeout has expired, remove the object
+ """
+ if key in self.cache:
+ del self.cache[key]
+ return False
+
+ def _refresh_timeout(self, key):
+ """
+ The object was accessed, refresh the timeout
+ """
+ item = self.cache[key]
+ if item.source:
+ gobject.source_remove(item.source)
+ if self.lifetime:
+ source = gobject.timeout_add_seconds(self.lifetime/1000, self._expire_timeout, key)
+ item.source = source
+
+ def __getitem__(self, key):
+ item = self.cache[key]
+ if self.getrefresh:
+ self._refresh_timeout(key)
+ return item()
+
+ def __setitem__(self, key, value):
+ item = self.CacheItem(value)
+ self.cache[key] = item
+ self._refresh_timeout(key)
+
+ def __delitem__(self, key):
+ item = self.cache[key]
+ if item.source:
+ gobject.source_remove(item.source)
+ del self.cache[key]
+
+ def __contains__(self, key):
+ return key in self.cache
+ has_key = __contains__
_icon_cache = CacheDictionary(15)
def get_agent_address(jid, node = None):
- """
- Get an agent's address for displaying in the GUI
- """
- if node:
- return '%s@%s' % (node, str(jid))
- else:
- return str(jid)
+ """
+ Get an agent's address for displaying in the GUI
+ """
+ if node:
+ return '%s@%s' % (node, str(jid))
+ else:
+ return str(jid)
class Closure(object):
- """
- A weak reference to a callback with arguments as an object
-
- 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.
- """
-
- def __init__(self, cb, userargs = (), remove = None, removeargs = ()):
- self.userargs = userargs
- self.remove = remove
- self.removeargs = removeargs
- if isinstance(cb, types.MethodType):
- self.meth_self = weakref.ref(cb.im_self, self._remove)
- self.meth_name = cb.func_name
- elif callable(cb):
- self.meth_self = None
- self.cb = weakref.ref(cb, self._remove)
- else:
- raise TypeError('Object is not callable')
-
- def _remove(self, ref):
- if self.remove:
- self.remove(self, *self.removeargs)
-
- def __call__(self, *args, **kwargs):
- if self.meth_self:
- obj = self.meth_self()
- cb = getattr(obj, self.meth_name)
- else:
- cb = self.cb()
- args = args + self.userargs
- return cb(*args, **kwargs)
+ """
+ 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.
+ """
+
+ def __init__(self, cb, userargs = (), remove = None, removeargs = ()):
+ self.userargs = userargs
+ self.remove = remove
+ self.removeargs = removeargs
+ if isinstance(cb, types.MethodType):
+ self.meth_self = weakref.ref(cb.im_self, self._remove)
+ self.meth_name = cb.func_name
+ elif callable(cb):
+ self.meth_self = None
+ self.cb = weakref.ref(cb, self._remove)
+ else:
+ raise TypeError('Object is not callable')
+
+ def _remove(self, ref):
+ if self.remove:
+ self.remove(self, *self.removeargs)
+
+ def __call__(self, *args, **kwargs):
+ if self.meth_self:
+ obj = self.meth_self()
+ cb = getattr(obj, self.meth_name)
+ else:
+ cb = self.cb()
+ args = args + self.userargs
+ return cb(*args, **kwargs)
class ServicesCache:
- """
- Class that caches our query results. Each connection will have it's own
- ServiceCache instance
- """
-
- def __init__(self, account):
- self.account = account
- self._items = CacheDictionary(0, getrefresh = False)
- self._info = CacheDictionary(0, getrefresh = False)
- self._subscriptions = CacheDictionary(5, getrefresh=False)
- self._cbs = {}
- gajim.ged.register_event_handler('AGENT_ERROR_INFO', ged.CORE,
- self.agent_info_error)
- gajim.ged.register_event_handler('AGENT_ERROR_ITEMS', ged.CORE,
- self.agent_items_error)
- gajim.ged.register_event_handler('AGENT_INFO_ITEMS', ged.CORE,
- self.agent_items)
- gajim.ged.register_event_handler('AGENT_INFO_INFO', ged.CORE,
- self.agent_info)
-
- def __del__(self):
- gajim.ged.remove_event_handler('AGENT_ERROR_INFO', ged.CORE,
- self.agent_info_error)
- gajim.ged.remove_event_handler('AGENT_ERROR_ITEMS', ged.CORE,
- self.agent_items_error)
- gajim.ged.remove_event_handler('AGENT_INFO_ITEMS', ged.CORE,
- self.agent_items)
- gajim.ged.remove_event_handler('AGENT_INFO_INFO', ged.CORE,
- self.agent_info)
-
- def cleanup(self):
- self._items.cleanup()
- self._info.cleanup()
-
- def _clean_closure(self, cb, type_, addr):
- # A closure died, clean up
- cbkey = (type_, addr)
- try:
- self._cbs[cbkey].remove(cb)
- except KeyError:
- return
- except ValueError:
- return
- # Clean an empty list
- if not self._cbs[cbkey]:
- del self._cbs[cbkey]
-
- def get_icon(self, identities = []):
- """
- Return the icon for an agent
- """
- # Grab the first identity with an icon
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- info = _agent_type_info[(cat, type_)]
- except KeyError:
- continue
- filename = info[1]
- if filename:
- break
- else:
- # Loop fell through, default to unknown
- info = _agent_type_info[(0, 0)]
- filename = info[1]
- if not filename: # we don't have an image to show for this type
- filename = 'jabber'
- # Use the cache if possible
- if filename in _icon_cache:
- return _icon_cache[filename]
- # Or load it
- pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-' + filename, size=32)
- # Store in cache
- _icon_cache[filename] = pix
- return pix
-
- def get_browser(self, identities=[], features=[]):
- """
- Return the browser class for an agent
- """
- # First pass, we try to find a ToplevelAgentBrowser
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- info = _agent_type_info[(cat, type_)]
- except KeyError:
- continue
- browser = info[0]
- if browser and browser == ToplevelAgentBrowser:
- return browser
-
- # second pass, we haven't found a ToplevelAgentBrowser
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- info = _agent_type_info[(cat, type_)]
- except KeyError:
- continue
- browser = info[0]
- if browser:
- return browser
- # NS_BROWSE is deprecated, but we check for it anyways.
- # Some services list it in features and respond to
- # NS_DISCO_ITEMS anyways.
- # Allow browsing for unknown types aswell.
- if (not features and not identities) or \
- xmpp.NS_DISCO_ITEMS in features or xmpp.NS_BROWSE in features:
- return ToplevelAgentBrowser
- return None
-
- def get_info(self, jid, node, cb, force = False, nofetch = False, args = ()):
- """
- Get info for an agent
- """
- addr = get_agent_address(jid, node)
- # Check the cache
- if addr in self._info:
- args = self._info[addr] + args
- cb(jid, node, *args)
- return
- if nofetch:
- return
-
- # Create a closure object
- cbkey = ('info', addr)
- cb = Closure(cb, userargs = args, remove = self._clean_closure,
- removeargs = cbkey)
- # Are we already fetching this?
- if cbkey in self._cbs:
- self._cbs[cbkey].append(cb)
- else:
- self._cbs[cbkey] = [cb]
- gajim.connections[self.account].discoverInfo(jid, node)
-
- def get_items(self, jid, node, cb, force = False, nofetch = False, args = ()):
- """
- Get a list of items in an agent
- """
- addr = get_agent_address(jid, node)
- # Check the cache
- if addr in self._items:
- args = (self._items[addr],) + args
- cb(jid, node, *args)
- return
- if nofetch:
- return
-
- # Create a closure object
- cbkey = ('items', addr)
- cb = Closure(cb, userargs = args, remove = self._clean_closure,
- removeargs = cbkey)
- # Are we already fetching this?
- if cbkey in self._cbs:
- self._cbs[cbkey].append(cb)
- else:
- self._cbs[cbkey] = [cb]
- gajim.connections[self.account].discoverItems(jid, node)
-
- def agent_info(self, account, array):
- """
- Callback for when we receive an agent's info
- array is (agent, node, identities, features, data)
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- jid, node, identities, features, data = array
- addr = get_agent_address(jid, node)
-
- # Store in cache
- self._info[addr] = (identities, features, data)
-
- # Call callbacks
- cbkey = ('info', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(jid, node, identities, features, data)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
-
- def agent_items(self, account, array):
- """
- Callback for when we receive an agent's items
- array is (agent, node, items)
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- jid, node, items = array
- addr = get_agent_address(jid, node)
-
- # Store in cache
- self._items[addr] = items
-
- # Call callbacks
- cbkey = ('items', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(jid, node, items)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
-
- def agent_info_error(self, account, jid):
- """
- Callback for when a query fails. Even after the browse and agents
- namespaces
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- addr = get_agent_address(jid)
-
- # Call callbacks
- cbkey = ('info', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(jid, '', 0, 0, 0)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
-
- def agent_items_error(self, account, jid):
- """
- Callback for when a query fails. Even after the browse and agents
- namespaces
- """
- # We receive events from all accounts from GED
- if account != self.account:
- return
- addr = get_agent_address(jid)
-
- # Call callbacks
- cbkey = ('items', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(jid, '', 0)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
+ """
+ Class that caches our query results. Each connection will have it's own
+ ServiceCache instance
+ """
+
+ def __init__(self, account):
+ self.account = account
+ self._items = CacheDictionary(0, getrefresh = False)
+ self._info = CacheDictionary(0, getrefresh = False)
+ self._subscriptions = CacheDictionary(5, getrefresh=False)
+ self._cbs = {}
+ gajim.ged.register_event_handler('AGENT_ERROR_INFO', ged.CORE,
+ self.agent_info_error)
+ gajim.ged.register_event_handler('AGENT_ERROR_ITEMS', ged.CORE,
+ self.agent_items_error)
+ gajim.ged.register_event_handler('AGENT_INFO_ITEMS', ged.CORE,
+ self.agent_items)
+ gajim.ged.register_event_handler('AGENT_INFO_INFO', ged.CORE,
+ self.agent_info)
+
+ def __del__(self):
+ gajim.ged.remove_event_handler('AGENT_ERROR_INFO', ged.CORE,
+ self.agent_info_error)
+ gajim.ged.remove_event_handler('AGENT_ERROR_ITEMS', ged.CORE,
+ self.agent_items_error)
+ gajim.ged.remove_event_handler('AGENT_INFO_ITEMS', ged.CORE,
+ self.agent_items)
+ gajim.ged.remove_event_handler('AGENT_INFO_INFO', ged.CORE,
+ self.agent_info)
+
+ def cleanup(self):
+ self._items.cleanup()
+ self._info.cleanup()
+
+ def _clean_closure(self, cb, type_, addr):
+ # A closure died, clean up
+ cbkey = (type_, addr)
+ try:
+ self._cbs[cbkey].remove(cb)
+ except KeyError:
+ return
+ except ValueError:
+ return
+ # Clean an empty list
+ if not self._cbs[cbkey]:
+ del self._cbs[cbkey]
+
+ def get_icon(self, identities = []):
+ """
+ Return the icon for an agent
+ """
+ # Grab the first identity with an icon
+ for identity in identities:
+ try:
+ cat, type_ = identity['category'], identity['type']
+ info = _agent_type_info[(cat, type_)]
+ except KeyError:
+ continue
+ filename = info[1]
+ if filename:
+ break
+ else:
+ # Loop fell through, default to unknown
+ info = _agent_type_info[(0, 0)]
+ filename = info[1]
+ if not filename: # we don't have an image to show for this type
+ filename = 'jabber'
+ # Use the cache if possible
+ if filename in _icon_cache:
+ return _icon_cache[filename]
+ # Or load it
+ pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-' + filename, size=32)
+ # Store in cache
+ _icon_cache[filename] = pix
+ return pix
+
+ def get_browser(self, identities=[], features=[]):
+ """
+ Return the browser class for an agent
+ """
+ # First pass, we try to find a ToplevelAgentBrowser
+ for identity in identities:
+ try:
+ cat, type_ = identity['category'], identity['type']
+ info = _agent_type_info[(cat, type_)]
+ except KeyError:
+ continue
+ browser = info[0]
+ if browser and browser == ToplevelAgentBrowser:
+ return browser
+
+ # second pass, we haven't found a ToplevelAgentBrowser
+ for identity in identities:
+ try:
+ cat, type_ = identity['category'], identity['type']
+ info = _agent_type_info[(cat, type_)]
+ except KeyError:
+ continue
+ browser = info[0]
+ if browser:
+ return browser
+ # NS_BROWSE is deprecated, but we check for it anyways.
+ # Some services list it in features and respond to
+ # NS_DISCO_ITEMS anyways.
+ # Allow browsing for unknown types aswell.
+ if (not features and not identities) or \
+ xmpp.NS_DISCO_ITEMS in features or xmpp.NS_BROWSE in features:
+ return ToplevelAgentBrowser
+ return None
+
+ def get_info(self, jid, node, cb, force = False, nofetch = False, args = ()):
+ """
+ Get info for an agent
+ """
+ addr = get_agent_address(jid, node)
+ # Check the cache
+ if addr in self._info:
+ args = self._info[addr] + args
+ cb(jid, node, *args)
+ return
+ if nofetch:
+ return
+
+ # Create a closure object
+ cbkey = ('info', addr)
+ cb = Closure(cb, userargs = args, remove = self._clean_closure,
+ removeargs = cbkey)
+ # Are we already fetching this?
+ if cbkey in self._cbs:
+ self._cbs[cbkey].append(cb)
+ else:
+ self._cbs[cbkey] = [cb]
+ gajim.connections[self.account].discoverInfo(jid, node)
+
+ def get_items(self, jid, node, cb, force = False, nofetch = False, args = ()):
+ """
+ Get a list of items in an agent
+ """
+ addr = get_agent_address(jid, node)
+ # Check the cache
+ if addr in self._items:
+ args = (self._items[addr],) + args
+ cb(jid, node, *args)
+ return
+ if nofetch:
+ return
+
+ # Create a closure object
+ cbkey = ('items', addr)
+ cb = Closure(cb, userargs = args, remove = self._clean_closure,
+ removeargs = cbkey)
+ # Are we already fetching this?
+ if cbkey in self._cbs:
+ self._cbs[cbkey].append(cb)
+ else:
+ self._cbs[cbkey] = [cb]
+ gajim.connections[self.account].discoverItems(jid, node)
+
+ def agent_info(self, account, array):
+ """
+ Callback for when we receive an agent's info
+ array is (agent, node, identities, features, data)
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ jid, node, identities, features, data = array
+ addr = get_agent_address(jid, node)
+
+ # Store in cache
+ self._info[addr] = (identities, features, data)
+
+ # Call callbacks
+ cbkey = ('info', addr)
+ if cbkey in self._cbs:
+ for cb in self._cbs[cbkey]:
+ cb(jid, node, identities, features, data)
+ # clean_closure may have beaten us to it
+ if cbkey in self._cbs:
+ del self._cbs[cbkey]
+
+ def agent_items(self, account, array):
+ """
+ Callback for when we receive an agent's items
+ array is (agent, node, items)
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ jid, node, items = array
+ addr = get_agent_address(jid, node)
+
+ # Store in cache
+ self._items[addr] = items
+
+ # Call callbacks
+ cbkey = ('items', addr)
+ if cbkey in self._cbs:
+ for cb in self._cbs[cbkey]:
+ cb(jid, node, items)
+ # clean_closure may have beaten us to it
+ if cbkey in self._cbs:
+ del self._cbs[cbkey]
+
+ def agent_info_error(self, account, jid):
+ """
+ Callback for when a query fails. Even after the browse and agents
+ namespaces
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ addr = get_agent_address(jid)
+
+ # Call callbacks
+ cbkey = ('info', addr)
+ if cbkey in self._cbs:
+ for cb in self._cbs[cbkey]:
+ cb(jid, '', 0, 0, 0)
+ # clean_closure may have beaten us to it
+ if cbkey in self._cbs:
+ del self._cbs[cbkey]
+
+ def agent_items_error(self, account, jid):
+ """
+ Callback for when a query fails. Even after the browse and agents
+ namespaces
+ """
+ # We receive events from all accounts from GED
+ if account != self.account:
+ return
+ addr = get_agent_address(jid)
+
+ # Call callbacks
+ cbkey = ('items', addr)
+ if cbkey in self._cbs:
+ for cb in self._cbs[cbkey]:
+ cb(jid, '', 0)
+ # clean_closure may have beaten us to it
+ if cbkey in self._cbs:
+ del self._cbs[cbkey]
# object is needed so that @property works
class ServiceDiscoveryWindow(object):
- """
- Class that represents the Services Discovery window
- """
-
- def __init__(self, account, jid = '', node = '',
- address_entry = False, parent = None):
- self.account = account
- self.parent = parent
- if not jid:
- jid = gajim.config.get_per('accounts', account, 'hostname')
- node = ''
-
- self.jid = None
- self.browser = None
- self.children = []
- self.dying = False
- self.node = None
-
- # Check connection
- if gajim.connections[account].connected < 2:
- dialogs.ErrorDialog(_('You are not connected to the server'),
+ """
+ Class that represents the Services Discovery window
+ """
+
+ def __init__(self, account, jid = '', node = '',
+ address_entry = False, parent = None):
+ self.account = account
+ self.parent = parent
+ if not jid:
+ jid = gajim.config.get_per('accounts', account, 'hostname')
+ node = ''
+
+ self.jid = None
+ self.browser = None
+ self.children = []
+ self.dying = False
+ self.node = None
+
+ # Check connection
+ if gajim.connections[account].connected < 2:
+ dialogs.ErrorDialog(_('You are not connected to the server'),
_('Without a connection, you can not browse available services'))
- raise RuntimeError, 'You must be connected to browse services'
-
- # Get a ServicesCache object.
- try:
- self.cache = gajim.connections[account].services_cache
- except AttributeError:
- self.cache = ServicesCache(account)
- gajim.connections[account].services_cache = self.cache
-
- self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui')
- self.window = self.xml.get_object('service_discovery_window')
- self.services_treeview = self.xml.get_object('services_treeview')
- self.model = None
- # This is more reliable than the cursor-changed signal.
- selection = self.services_treeview.get_selection()
- selection.connect_after('changed',
- self.on_services_treeview_selection_changed)
- self.services_scrollwin = self.xml.get_object('services_scrollwin')
- self.progressbar = self.xml.get_object('services_progressbar')
- self.banner = self.xml.get_object('banner_agent_label')
- self.banner_icon = self.xml.get_object('banner_agent_icon')
- self.banner_eventbox = self.xml.get_object('banner_agent_eventbox')
- self.style_event_id = 0
- self.banner.realize()
- self.paint_banner()
- self.action_buttonbox = self.xml.get_object('action_buttonbox')
-
- # Address combobox
- self.address_comboboxentry = None
- address_table = self.xml.get_object('address_table')
- if address_entry:
- self.address_comboboxentry = self.xml.get_object(
- 'address_comboboxentry')
- self.address_comboboxentry_entry = self.address_comboboxentry.child
- self.address_comboboxentry_entry.set_activates_default(True)
-
- liststore = gtk.ListStore(str)
- self.address_comboboxentry.set_model(liststore)
- self.latest_addresses = gajim.config.get(
- 'latest_disco_addresses').split()
- if jid in self.latest_addresses:
- self.latest_addresses.remove(jid)
- self.latest_addresses.insert(0, jid)
- if len(self.latest_addresses) > 10:
- self.latest_addresses = self.latest_addresses[0:10]
- for j in self.latest_addresses:
- self.address_comboboxentry.append_text(j)
- self.address_comboboxentry.child.set_text(jid)
- else:
- # Don't show it at all if we didn't ask for it
- address_table.set_no_show_all(True)
- address_table.hide()
-
- self._initial_state()
- self.xml.connect_signals(self)
- self.travel(jid, node)
- self.window.show_all()
-
- @property
- def _get_account(self):
- return self.account
-
- @property
- def _set_account(self, value):
- self.account = value
- self.cache.account = value
- if self.browser:
- self.browser.account = value
-
- def _initial_state(self):
- """
- Set some initial state on the window. Separated in a method because it's
- handy to use within browser's cleanup method
- """
- self.progressbar.hide()
- title_text = _('Service Discovery using account %s') % self.account
- self.window.set_title(title_text)
- self._set_window_banner_text(_('Service Discovery'))
- self.banner_icon.clear()
- self.banner_icon.hide() # Just clearing it doesn't work
-
- def _set_window_banner_text(self, text, text_after = None):
- theme = gajim.config.get('roster_theme')
- bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
- bannerfontattrs = gajim.config.get_per('themes', theme,
- 'bannerfontattrs')
-
- if bannerfont:
- font = pango.FontDescription(bannerfont)
- else:
- font = pango.FontDescription('Normal')
- if bannerfontattrs:
- # B is attribute set by default
- if 'B' in bannerfontattrs:
- font.set_weight(pango.WEIGHT_HEAVY)
- if 'I' in bannerfontattrs:
- font.set_style(pango.STYLE_ITALIC)
-
- font_attrs = 'font_desc="%s"' % font.to_string()
- font_size = font.get_size()
-
- # in case there is no font specified we use x-large font size
- if font_size == 0:
- font_attrs = '%s size="large"' % font_attrs
- markup = '<span %s>%s</span>' % (font_attrs, text)
- if text_after:
- font.set_weight(pango.WEIGHT_NORMAL)
- markup = '%s\n<span font_desc="%s" size="small">%s</span>' % \
- (markup, font.to_string(), text_after)
- self.banner.set_markup(markup)
-
- def paint_banner(self):
- """
- Repaint the banner with theme color
- """
- theme = gajim.config.get('roster_theme')
- bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
- textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
- self.disconnect_style_event()
- if bgcolor:
- color = gtk.gdk.color_parse(bgcolor)
- self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, color)
- default_bg = False
- else:
- default_bg = True
-
- if textcolor:
- color = gtk.gdk.color_parse(textcolor)
- self.banner.modify_fg(gtk.STATE_NORMAL, color)
- default_fg = False
- else:
- default_fg = True
- if default_fg or default_bg:
- self._on_style_set_event(self.banner, None, default_fg, default_bg)
- if self.browser:
- self.browser.update_theme()
-
- def disconnect_style_event(self):
- if self.style_event_id:
- self.banner.disconnect(self.style_event_id)
- self.style_event_id = 0
-
- def connect_style_event(self, set_fg = False, set_bg = False):
- self.disconnect_style_event()
- self.style_event_id = self.banner.connect('style-set',
- self._on_style_set_event, set_fg, set_bg)
-
- def _on_style_set_event(self, widget, style, *opts):
- """
- Set style of widget from style class *.Frame.Eventbox
- opts[0] == True -> set fg color
- opts[1] == True -> set bg color
- """
- self.disconnect_style_event()
- if opts[1]:
- bg_color = widget.style.bg[gtk.STATE_SELECTED]
- self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
- if opts[0]:
- fg_color = widget.style.fg[gtk.STATE_SELECTED]
- self.banner.modify_fg(gtk.STATE_NORMAL, fg_color)
- self.banner.ensure_style()
- self.connect_style_event(opts[0], opts[1])
-
- def destroy(self, chain = False):
- """
- Close the browser. This can optionally close its children and propagate
- to the parent. This should happen on actions like register, or join to
- kill off the entire browser chain
- """
- if self.dying:
- return
- self.dying = True
-
- # self.browser._get_agent_address() would break when no browser.
- addr = get_agent_address(self.jid, self.node)
- if addr in gajim.interface.instances[self.account]['disco']:
- del gajim.interface.instances[self.account]['disco'][addr]
-
- if self.browser:
- self.window.hide()
- self.browser.cleanup()
- self.browser = None
- self.window.destroy()
-
- for child in self.children[:]:
- child.parent = None
- if chain:
- child.destroy(chain = chain)
- self.children.remove(child)
- if self.parent:
- if self in self.parent.children:
- self.parent.children.remove(self)
- if chain and not self.parent.children:
- self.parent.destroy(chain = chain)
- self.parent = None
- else:
- self.cache.cleanup()
-
- def travel(self, jid, node):
- """
- Travel to an agent within the current services window
- """
- if self.browser:
- self.browser.cleanup()
- self.browser = None
- # Update the window list
- if self.jid:
- old_addr = get_agent_address(self.jid, self.node)
- if old_addr in gajim.interface.instances[self.account]['disco']:
- del gajim.interface.instances[self.account]['disco'][old_addr]
- addr = get_agent_address(jid, node)
- gajim.interface.instances[self.account]['disco'][addr] = self
- # We need to store these, self.browser is not always available.
- self.jid = jid
- self.node = node
- self.cache.get_info(jid, node, self._travel)
-
- def _travel(self, jid, node, identities, features, data):
- """
- Continuation of travel
- """
- if self.dying or jid != self.jid or node != self.node:
- return
- if not identities:
- if not self.address_comboboxentry:
- # We can't travel anywhere else.
- self.destroy()
- dialogs.ErrorDialog(_('The service could not be found'),
+ raise RuntimeError, 'You must be connected to browse services'
+
+ # Get a ServicesCache object.
+ try:
+ self.cache = gajim.connections[account].services_cache
+ except AttributeError:
+ self.cache = ServicesCache(account)
+ gajim.connections[account].services_cache = self.cache
+
+ self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui')
+ self.window = self.xml.get_object('service_discovery_window')
+ self.services_treeview = self.xml.get_object('services_treeview')
+ self.model = None
+ # This is more reliable than the cursor-changed signal.
+ selection = self.services_treeview.get_selection()
+ selection.connect_after('changed',
+ self.on_services_treeview_selection_changed)
+ self.services_scrollwin = self.xml.get_object('services_scrollwin')
+ self.progressbar = self.xml.get_object('services_progressbar')
+ self.banner = self.xml.get_object('banner_agent_label')
+ self.banner_icon = self.xml.get_object('banner_agent_icon')
+ self.banner_eventbox = self.xml.get_object('banner_agent_eventbox')
+ self.style_event_id = 0
+ self.banner.realize()
+ self.paint_banner()
+ self.action_buttonbox = self.xml.get_object('action_buttonbox')
+
+ # Address combobox
+ self.address_comboboxentry = None
+ address_table = self.xml.get_object('address_table')
+ if address_entry:
+ self.address_comboboxentry = self.xml.get_object(
+ 'address_comboboxentry')
+ self.address_comboboxentry_entry = self.address_comboboxentry.child
+ self.address_comboboxentry_entry.set_activates_default(True)
+
+ liststore = gtk.ListStore(str)
+ self.address_comboboxentry.set_model(liststore)
+ self.latest_addresses = gajim.config.get(
+ 'latest_disco_addresses').split()
+ if jid in self.latest_addresses:
+ self.latest_addresses.remove(jid)
+ self.latest_addresses.insert(0, jid)
+ if len(self.latest_addresses) > 10:
+ self.latest_addresses = self.latest_addresses[0:10]
+ for j in self.latest_addresses:
+ self.address_comboboxentry.append_text(j)
+ self.address_comboboxentry.child.set_text(jid)
+ else:
+ # Don't show it at all if we didn't ask for it
+ address_table.set_no_show_all(True)
+ address_table.hide()
+
+ self._initial_state()
+ self.xml.connect_signals(self)
+ self.travel(jid, node)
+ self.window.show_all()
+
+ @property
+ def _get_account(self):
+ return self.account
+
+ @property
+ def _set_account(self, value):
+ self.account = value
+ self.cache.account = value
+ if self.browser:
+ self.browser.account = value
+
+ def _initial_state(self):
+ """
+ Set some initial state on the window. Separated in a method because it's
+ handy to use within browser's cleanup method
+ """
+ self.progressbar.hide()
+ title_text = _('Service Discovery using account %s') % self.account
+ self.window.set_title(title_text)
+ self._set_window_banner_text(_('Service Discovery'))
+ self.banner_icon.clear()
+ self.banner_icon.hide() # Just clearing it doesn't work
+
+ def _set_window_banner_text(self, text, text_after = None):
+ theme = gajim.config.get('roster_theme')
+ bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
+ bannerfontattrs = gajim.config.get_per('themes', theme,
+ 'bannerfontattrs')
+
+ if bannerfont:
+ font = pango.FontDescription(bannerfont)
+ else:
+ font = pango.FontDescription('Normal')
+ if bannerfontattrs:
+ # B is attribute set by default
+ if 'B' in bannerfontattrs:
+ font.set_weight(pango.WEIGHT_HEAVY)
+ if 'I' in bannerfontattrs:
+ font.set_style(pango.STYLE_ITALIC)
+
+ font_attrs = 'font_desc="%s"' % font.to_string()
+ font_size = font.get_size()
+
+ # in case there is no font specified we use x-large font size
+ if font_size == 0:
+ font_attrs = '%s size="large"' % font_attrs
+ markup = '<span %s>%s</span>' % (font_attrs, text)
+ if text_after:
+ font.set_weight(pango.WEIGHT_NORMAL)
+ markup = '%s\n<span font_desc="%s" size="small">%s</span>' % \
+ (markup, font.to_string(), text_after)
+ self.banner.set_markup(markup)
+
+ def paint_banner(self):
+ """
+ Repaint the banner with theme color
+ """
+ theme = gajim.config.get('roster_theme')
+ bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
+ textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
+ self.disconnect_style_event()
+ if bgcolor:
+ color = gtk.gdk.color_parse(bgcolor)
+ self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, color)
+ default_bg = False
+ else:
+ default_bg = True
+
+ if textcolor:
+ color = gtk.gdk.color_parse(textcolor)
+ self.banner.modify_fg(gtk.STATE_NORMAL, color)
+ default_fg = False
+ else:
+ default_fg = True
+ if default_fg or default_bg:
+ self._on_style_set_event(self.banner, None, default_fg, default_bg)
+ if self.browser:
+ self.browser.update_theme()
+
+ def disconnect_style_event(self):
+ if self.style_event_id:
+ self.banner.disconnect(self.style_event_id)
+ self.style_event_id = 0
+
+ def connect_style_event(self, set_fg = False, set_bg = False):
+ self.disconnect_style_event()
+ self.style_event_id = self.banner.connect('style-set',
+ self._on_style_set_event, set_fg, set_bg)
+
+ def _on_style_set_event(self, widget, style, *opts):
+ """
+ Set style of widget from style class *.Frame.Eventbox
+ opts[0] == True -> set fg color
+ opts[1] == True -> set bg color
+ """
+ self.disconnect_style_event()
+ if opts[1]:
+ bg_color = widget.style.bg[gtk.STATE_SELECTED]
+ self.banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
+ if opts[0]:
+ fg_color = widget.style.fg[gtk.STATE_SELECTED]
+ self.banner.modify_fg(gtk.STATE_NORMAL, fg_color)
+ self.banner.ensure_style()
+ self.connect_style_event(opts[0], opts[1])
+
+ def destroy(self, chain = False):
+ """
+ Close the browser. This can optionally close its children and propagate
+ to the parent. This should happen on actions like register, or join to
+ kill off the entire browser chain
+ """
+ if self.dying:
+ return
+ self.dying = True
+
+ # self.browser._get_agent_address() would break when no browser.
+ addr = get_agent_address(self.jid, self.node)
+ if addr in gajim.interface.instances[self.account]['disco']:
+ del gajim.interface.instances[self.account]['disco'][addr]
+
+ if self.browser:
+ self.window.hide()
+ self.browser.cleanup()
+ self.browser = None
+ self.window.destroy()
+
+ for child in self.children[:]:
+ child.parent = None
+ if chain:
+ child.destroy(chain = chain)
+ self.children.remove(child)
+ if self.parent:
+ if self in self.parent.children:
+ self.parent.children.remove(self)
+ if chain and not self.parent.children:
+ self.parent.destroy(chain = chain)
+ self.parent = None
+ else:
+ self.cache.cleanup()
+
+ def travel(self, jid, node):
+ """
+ Travel to an agent within the current services window
+ """
+ if self.browser:
+ self.browser.cleanup()
+ self.browser = None
+ # Update the window list
+ if self.jid:
+ old_addr = get_agent_address(self.jid, self.node)
+ if old_addr in gajim.interface.instances[self.account]['disco']:
+ del gajim.interface.instances[self.account]['disco'][old_addr]
+ addr = get_agent_address(jid, node)
+ gajim.interface.instances[self.account]['disco'][addr] = self
+ # We need to store these, self.browser is not always available.
+ self.jid = jid
+ self.node = node
+ self.cache.get_info(jid, node, self._travel)
+
+ def _travel(self, jid, node, identities, features, data):
+ """
+ Continuation of travel
+ """
+ if self.dying or jid != self.jid or node != self.node:
+ return
+ if not identities:
+ if not self.address_comboboxentry:
+ # We can't travel anywhere else.
+ self.destroy()
+ dialogs.ErrorDialog(_('The service could not be found'),
_('There is no service at the address you entered, or it is not responding. Check the address and try again.'))
- return
- klass = self.cache.get_browser(identities, features)
- if not klass:
- dialogs.ErrorDialog(_('The service is not browsable'),
+ return
+ klass = self.cache.get_browser(identities, features)
+ if not klass:
+ dialogs.ErrorDialog(_('The service is not browsable'),
_('This type of service does not contain any items to browse.'))
- return
- elif klass is None:
- klass = AgentBrowser
- self.browser = klass(self.account, jid, node)
- self.browser.prepare_window(self)
- self.browser.browse()
-
- def open(self, jid, node):
- """
- Open an agent. By default, this happens in a new window
- """
- try:
- win = gajim.interface.instances[self.account]['disco']\
- [get_agent_address(jid, node)]
- win.window.present()
- return
- except KeyError:
- pass
- try:
- win = ServiceDiscoveryWindow(self.account, jid, node, parent=self)
- except RuntimeError:
- # Disconnected, perhaps
- return
- self.children.append(win)
-
- def on_service_discovery_window_destroy(self, widget):
- self.destroy()
-
- def on_close_button_clicked(self, widget):
- self.destroy()
-
- def on_address_comboboxentry_changed(self, widget):
- if self.address_comboboxentry.get_active() != -1:
- # user selected one of the entries so do auto-visit
- jid = self.address_comboboxentry.child.get_text().decode('utf-8')
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat, s:
- pritext = _('Invalid Server Name')
- dialogs.ErrorDialog(pritext, str(s))
- return
- self.travel(jid, '')
-
- def on_go_button_clicked(self, widget):
- jid = self.address_comboboxentry.child.get_text().decode('utf-8')
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat, s:
- pritext = _('Invalid Server Name')
- dialogs.ErrorDialog(pritext, str(s))
- return
- if jid == self.jid: # jid has not changed
- return
- if jid in self.latest_addresses:
- self.latest_addresses.remove(jid)
- self.latest_addresses.insert(0, jid)
- if len(self.latest_addresses) > 10:
- self.latest_addresses = self.latest_addresses[0:10]
- self.address_comboboxentry.get_model().clear()
- for j in self.latest_addresses:
- self.address_comboboxentry.append_text(j)
- gajim.config.set('latest_disco_addresses',
- ' '.join(self.latest_addresses))
- gajim.interface.save_config()
- self.travel(jid, '')
-
- def on_services_treeview_row_activated(self, widget, path, col = 0):
- if self.browser:
- self.browser.default_action()
-
- def on_services_treeview_selection_changed(self, widget):
- if self.browser:
- self.browser.update_actions()
+ return
+ elif klass is None:
+ klass = AgentBrowser
+ self.browser = klass(self.account, jid, node)
+ self.browser.prepare_window(self)
+ self.browser.browse()
+
+ def open(self, jid, node):
+ """
+ Open an agent. By default, this happens in a new window
+ """
+ try:
+ win = gajim.interface.instances[self.account]['disco']\
+ [get_agent_address(jid, node)]
+ win.window.present()
+ return
+ except KeyError:
+ pass
+ try:
+ win = ServiceDiscoveryWindow(self.account, jid, node, parent=self)
+ except RuntimeError:
+ # Disconnected, perhaps
+ return
+ self.children.append(win)
+
+ def on_service_discovery_window_destroy(self, widget):
+ self.destroy()
+
+ def on_close_button_clicked(self, widget):
+ self.destroy()
+
+ def on_address_comboboxentry_changed(self, widget):
+ if self.address_comboboxentry.get_active() != -1:
+ # user selected one of the entries so do auto-visit
+ jid = self.address_comboboxentry.child.get_text().decode('utf-8')
+ try:
+ jid = helpers.parse_jid(jid)
+ except helpers.InvalidFormat, s:
+ pritext = _('Invalid Server Name')
+ dialogs.ErrorDialog(pritext, str(s))
+ return
+ self.travel(jid, '')
+
+ def on_go_button_clicked(self, widget):
+ jid = self.address_comboboxentry.child.get_text().decode('utf-8')
+ try:
+ jid = helpers.parse_jid(jid)
+ except helpers.InvalidFormat, s:
+ pritext = _('Invalid Server Name')
+ dialogs.ErrorDialog(pritext, str(s))
+ return
+ if jid == self.jid: # jid has not changed
+ return
+ if jid in self.latest_addresses:
+ self.latest_addresses.remove(jid)
+ self.latest_addresses.insert(0, jid)
+ if len(self.latest_addresses) > 10:
+ self.latest_addresses = self.latest_addresses[0:10]
+ self.address_comboboxentry.get_model().clear()
+ for j in self.latest_addresses:
+ self.address_comboboxentry.append_text(j)
+ gajim.config.set('latest_disco_addresses',
+ ' '.join(self.latest_addresses))
+ gajim.interface.save_config()
+ self.travel(jid, '')
+
+ def on_services_treeview_row_activated(self, widget, path, col = 0):
+ if self.browser:
+ self.browser.default_action()
+
+ def on_services_treeview_selection_changed(self, widget):
+ if self.browser:
+ self.browser.update_actions()
class AgentBrowser:
- """
- Class that deals with browsing agents and appearance of the browser window.
- This class and subclasses should basically be treated as "part" of the
- ServiceDiscoveryWindow class, but had to be separated because this part is
- dynamic
- """
-
- def __init__(self, account, jid, node):
- self.account = account
- self.jid = jid
- self.node = node
- self._total_items = 0
- self.browse_button = None
- # This is for some timeout callbacks
- self.active = False
-
- def _get_agent_address(self):
- """
- Get the agent's address for displaying in the GUI
- """
- return get_agent_address(self.jid, self.node)
-
- def _set_initial_title(self):
- """
- Set the initial window title based on agent address
- """
- self.window.window.set_title(_('Browsing %(address)s using account '
- '%(account)s') % {'address': self._get_agent_address(),
- 'account': self.account})
- self.window._set_window_banner_text(self._get_agent_address())
-
- def _create_treemodel(self):
- """
- Create the treemodel for the services treeview. When subclassing, note
- that the first two columns should ALWAYS be of type string and contain
- the JID and node of the item respectively
- """
- # JID, node, name, address
- self.model = gtk.ListStore(str, str, str, str)
- self.model.set_sort_column_id(3, gtk.SORT_ASCENDING)
- self.window.services_treeview.set_model(self.model)
- # Name column
- col = gtk.TreeViewColumn(_('Name'))
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = 2)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Address column
- col = gtk.TreeViewColumn(_('JID'))
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = 3)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- self.window.services_treeview.set_headers_visible(True)
-
- def _clean_treemodel(self):
- self.model.clear()
- for col in self.window.services_treeview.get_columns():
- self.window.services_treeview.remove_column(col)
- self.window.services_treeview.set_headers_visible(False)
-
- def _add_actions(self):
- """
- Add the action buttons to the buttonbox for actions the browser can
- perform
- """
- self.browse_button = gtk.Button()
- image = gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
- label = gtk.Label(_('_Browse'))
- label.set_use_underline(True)
- hbox = gtk.HBox()
- hbox.pack_start(image, False, True, 6)
- hbox.pack_end(label, True, True)
- self.browse_button.add(hbox)
- self.browse_button.connect('clicked', self.on_browse_button_clicked)
- self.window.action_buttonbox.add(self.browse_button)
- self.browse_button.show_all()
-
- def _clean_actions(self):
- """
- Remove the action buttons specific to this browser
- """
- if self.browse_button:
- self.browse_button.destroy()
- self.browse_button = None
-
- def _set_title(self, jid, node, identities, features, data):
- """
- Set the window title based on agent info
- """
- # Set the banner and window title
- if 'name' in identities[0]:
- name = identities[0]['name']
- self.window._set_window_banner_text(self._get_agent_address(), name)
-
- # Add an icon to the banner.
- pix = self.cache.get_icon(identities)
- self.window.banner_icon.set_from_pixbuf(pix)
- self.window.banner_icon.show()
-
- def _clean_title(self):
- # Everything done here is done in window._initial_state
- # This is for subclasses.
- pass
-
- def prepare_window(self, window):
- """
- Prepare the service discovery window. Called when a browser is hooked up
- with a ServiceDiscoveryWindow instance
- """
- self.window = window
- self.cache = window.cache
-
- self._set_initial_title()
- self._create_treemodel()
- self._add_actions()
-
- # This is a hack. The buttonbox apparently doesn't care about pack_start
- # or pack_end, so we repack the close button here to make sure it's last
- close_button = self.window.xml.get_object('close_button')
- self.window.action_buttonbox.remove(close_button)
- self.window.action_buttonbox.pack_end(close_button)
- close_button.show_all()
-
- self.update_actions()
-
- self.active = True
- self.cache.get_info(self.jid, self.node, self._set_title)
-
- def cleanup(self):
- """
- Cleanup when the window intends to switch browsers
- """
- self.active = False
-
- self._clean_actions()
- self._clean_treemodel()
- self._clean_title()
-
- self.window._initial_state()
-
- def update_theme(self):
- """
- Called when the default theme is changed
- """
- pass
-
- def on_browse_button_clicked(self, widget = None):
- """
- When we want to browse an agent: open a new services window with a
- browser for the agent type
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0].decode('utf-8')
- if jid:
- node = model[iter_][1].decode('utf-8')
- self.window.open(jid, node)
-
- def update_actions(self):
- """
- When we select a row: activate action buttons based on the agent's info
- """
- if self.browse_button:
- self.browse_button.set_sensitive(False)
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0].decode('utf-8')
- node = model[iter_][1].decode('utf-8')
- if jid:
- self.cache.get_info(jid, node, self._update_actions, nofetch = True)
-
- def _update_actions(self, jid, node, identities, features, data):
- """
- Continuation of update_actions
- """
- if not identities or not self.browse_button:
- return
- klass = self.cache.get_browser(identities, features)
- if klass:
- self.browse_button.set_sensitive(True)
-
- def default_action(self):
- """
- When we double-click a row: perform the default action on the selected
- item
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0].decode('utf-8')
- node = model[iter_][1].decode('utf-8')
- if jid:
- self.cache.get_info(jid, node, self._default_action, nofetch = True)
-
- def _default_action(self, jid, node, identities, features, data):
- """
- Continuation of default_action
- """
- if self.cache.get_browser(identities, features):
- # Browse if we can
- self.on_browse_button_clicked()
- return True
- return False
-
- def browse(self, force = False):
- """
- Fill the treeview with agents, fetching the info if necessary
- """
- self.model.clear()
- self._total_items = self._progress = 0
- self.window.progressbar.show()
- self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb)
- self.cache.get_items(self.jid, self.node, self._agent_items,
- force=force, args=(force,))
-
- def _pulse_timeout_cb(self, *args):
- """
- Simple callback to keep the progressbar pulsing
- """
- if not self.active:
- return False
- self.window.progressbar.pulse()
- return True
-
- def _find_item(self, jid, node):
- """
- Check if an item is already in the treeview. Return an iter to it if so,
- None otherwise
- """
- iter_ = self.model.get_iter_root()
- while iter_:
- cjid = self.model.get_value(iter_, 0).decode('utf-8')
- cnode = self.model.get_value(iter_, 1).decode('utf-8')
- if jid == cjid and node == cnode:
- break
- iter_ = self.model.iter_next(iter_)
- if iter_:
- return iter_
- return None
-
- def _agent_items(self, jid, node, items, force):
- """
- Callback for when we receive a list of agent items
- """
- self.model.clear()
- self._total_items = 0
- gobject.source_remove(self._pulse_timeout)
- self.window.progressbar.hide()
- # The server returned an error
- if items == 0:
- if not self.window.address_comboboxentry:
- # We can't travel anywhere else.
- self.window.destroy()
- dialogs.ErrorDialog(_('The service is not browsable'),
+ """
+ Class that deals with browsing agents and appearance of the browser window.
+ This class and subclasses should basically be treated as "part" of the
+ ServiceDiscoveryWindow class, but had to be separated because this part is
+ dynamic
+ """
+
+ def __init__(self, account, jid, node):
+ self.account = account
+ self.jid = jid
+ self.node = node
+ self._total_items = 0
+ self.browse_button = None
+ # This is for some timeout callbacks
+ self.active = False
+
+ def _get_agent_address(self):
+ """
+ Get the agent's address for displaying in the GUI
+ """
+ return get_agent_address(self.jid, self.node)
+
+ def _set_initial_title(self):
+ """
+ Set the initial window title based on agent address
+ """
+ self.window.window.set_title(_('Browsing %(address)s using account '
+ '%(account)s') % {'address': self._get_agent_address(),
+ 'account': self.account})
+ self.window._set_window_banner_text(self._get_agent_address())
+
+ def _create_treemodel(self):
+ """
+ Create the treemodel for the services treeview. When subclassing, note
+ that the first two columns should ALWAYS be of type string and contain
+ the JID and node of the item respectively
+ """
+ # JID, node, name, address
+ self.model = gtk.ListStore(str, str, str, str)
+ self.model.set_sort_column_id(3, gtk.SORT_ASCENDING)
+ self.window.services_treeview.set_model(self.model)
+ # Name column
+ col = gtk.TreeViewColumn(_('Name'))
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = 2)
+ self.window.services_treeview.insert_column(col, -1)
+ col.set_resizable(True)
+ # Address column
+ col = gtk.TreeViewColumn(_('JID'))
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = 3)
+ self.window.services_treeview.insert_column(col, -1)
+ col.set_resizable(True)
+ self.window.services_treeview.set_headers_visible(True)
+
+ def _clean_treemodel(self):
+ self.model.clear()
+ for col in self.window.services_treeview.get_columns():
+ self.window.services_treeview.remove_column(col)
+ self.window.services_treeview.set_headers_visible(False)
+
+ def _add_actions(self):
+ """
+ Add the action buttons to the buttonbox for actions the browser can
+ perform
+ """
+ self.browse_button = gtk.Button()
+ image = gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
+ label = gtk.Label(_('_Browse'))
+ label.set_use_underline(True)
+ hbox = gtk.HBox()
+ hbox.pack_start(image, False, True, 6)
+ hbox.pack_end(label, True, True)
+ self.browse_button.add(hbox)
+ self.browse_button.connect('clicked', self.on_browse_button_clicked)
+ self.window.action_buttonbox.add(self.browse_button)
+ self.browse_button.show_all()
+
+ def _clean_actions(self):
+ """
+ Remove the action buttons specific to this browser
+ """
+ if self.browse_button:
+ self.browse_button.destroy()
+ self.browse_button = None
+
+ def _set_title(self, jid, node, identities, features, data):
+ """
+ Set the window title based on agent info
+ """
+ # Set the banner and window title
+ if 'name' in identities[0]:
+ name = identities[0]['name']
+ self.window._set_window_banner_text(self._get_agent_address(), name)
+
+ # Add an icon to the banner.
+ pix = self.cache.get_icon(identities)
+ self.window.banner_icon.set_from_pixbuf(pix)
+ self.window.banner_icon.show()
+
+ def _clean_title(self):
+ # Everything done here is done in window._initial_state
+ # This is for subclasses.
+ pass
+
+ def prepare_window(self, window):
+ """
+ Prepare the service discovery window. Called when a browser is hooked up
+ with a ServiceDiscoveryWindow instance
+ """
+ self.window = window
+ self.cache = window.cache
+
+ self._set_initial_title()
+ self._create_treemodel()
+ self._add_actions()
+
+ # This is a hack. The buttonbox apparently doesn't care about pack_start
+ # or pack_end, so we repack the close button here to make sure it's last
+ close_button = self.window.xml.get_object('close_button')
+ self.window.action_buttonbox.remove(close_button)
+ self.window.action_buttonbox.pack_end(close_button)
+ close_button.show_all()
+
+ self.update_actions()
+
+ self.active = True
+ self.cache.get_info(self.jid, self.node, self._set_title)
+
+ def cleanup(self):
+ """
+ Cleanup when the window intends to switch browsers
+ """
+ self.active = False
+
+ self._clean_actions()
+ self._clean_treemodel()
+ self._clean_title()
+
+ self.window._initial_state()
+
+ def update_theme(self):
+ """
+ Called when the default theme is changed
+ """
+ pass
+
+ def on_browse_button_clicked(self, widget = None):
+ """
+ When we want to browse an agent: open a new services window with a
+ browser for the agent type
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ jid = model[iter_][0].decode('utf-8')
+ if jid:
+ node = model[iter_][1].decode('utf-8')
+ self.window.open(jid, node)
+
+ def update_actions(self):
+ """
+ When we select a row: activate action buttons based on the agent's info
+ """
+ if self.browse_button:
+ self.browse_button.set_sensitive(False)
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ jid = model[iter_][0].decode('utf-8')
+ node = model[iter_][1].decode('utf-8')
+ if jid:
+ self.cache.get_info(jid, node, self._update_actions, nofetch = True)
+
+ def _update_actions(self, jid, node, identities, features, data):
+ """
+ Continuation of update_actions
+ """
+ if not identities or not self.browse_button:
+ return
+ klass = self.cache.get_browser(identities, features)
+ if klass:
+ self.browse_button.set_sensitive(True)
+
+ def default_action(self):
+ """
+ When we double-click a row: perform the default action on the selected
+ item
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ jid = model[iter_][0].decode('utf-8')
+ node = model[iter_][1].decode('utf-8')
+ if jid:
+ self.cache.get_info(jid, node, self._default_action, nofetch = True)
+
+ def _default_action(self, jid, node, identities, features, data):
+ """
+ Continuation of default_action
+ """
+ if self.cache.get_browser(identities, features):
+ # Browse if we can
+ self.on_browse_button_clicked()
+ return True
+ return False
+
+ def browse(self, force = False):
+ """
+ Fill the treeview with agents, fetching the info if necessary
+ """
+ self.model.clear()
+ self._total_items = self._progress = 0
+ self.window.progressbar.show()
+ self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb)
+ self.cache.get_items(self.jid, self.node, self._agent_items,
+ force=force, args=(force,))
+
+ def _pulse_timeout_cb(self, *args):
+ """
+ Simple callback to keep the progressbar pulsing
+ """
+ if not self.active:
+ return False
+ self.window.progressbar.pulse()
+ return True
+
+ def _find_item(self, jid, node):
+ """
+ Check if an item is already in the treeview. Return an iter to it if so,
+ None otherwise
+ """
+ iter_ = self.model.get_iter_root()
+ while iter_:
+ cjid = self.model.get_value(iter_, 0).decode('utf-8')
+ cnode = self.model.get_value(iter_, 1).decode('utf-8')
+ if jid == cjid and node == cnode:
+ break
+ iter_ = self.model.iter_next(iter_)
+ if iter_:
+ return iter_
+ return None
+
+ def _agent_items(self, jid, node, items, force):
+ """
+ Callback for when we receive a list of agent items
+ """
+ self.model.clear()
+ self._total_items = 0
+ gobject.source_remove(self._pulse_timeout)
+ self.window.progressbar.hide()
+ # The server returned an error
+ if items == 0:
+ if not self.window.address_comboboxentry:
+ # We can't travel anywhere else.
+ self.window.destroy()
+ dialogs.ErrorDialog(_('The service is not browsable'),
_('This service does not contain any items to browse.'))
- return
- # We got a list of items
- self.window.services_treeview.set_model(None)
- for item in items:
- jid_ = item['jid']
- node_ = item.get('node', '')
- # If such an item is already here: don't add it
- if self._find_item(jid_, node_):
- continue
- self._total_items += 1
- self._add_item(jid_, node_, node, item, force)
- self.window.services_treeview.set_model(self.model)
-
- def _agent_info(self, jid, node, identities, features, data):
- """
- Callback for when we receive info about an agent's item
- """
- iter_ = self._find_item(jid, node)
- if not iter_:
- # Not in the treeview, stop
- return
- if identities == 0:
- # The server returned an error
- self._update_error(iter_, jid, node)
- else:
- # We got our info
- self._update_info(iter_, jid, node, identities, features, data)
- self.update_actions()
-
- def _add_item(self, jid, node, parent_node, item, force):
- """
- Called when an item should be added to the model. The result of a
- disco#items query
- """
- self.model.append((jid, node, item.get('name', ''),
- get_agent_address(jid, node)))
- self.cache.get_info(jid, node, self._agent_info, force = force)
-
- def _update_item(self, iter_, jid, node, item):
- """
- Called when an item should be updated in the model. The result of a
- disco#items query
- """
- if 'name' in item:
- self.model[iter_][2] = item['name']
-
- def _update_info(self, iter_, jid, node, identities, features, data):
- """
- Called when an item should be updated in the model with further info.
- The result of a disco#info query
- """
- name = identities[0].get('name', '')
- if name:
- self.model[iter_][2] = name
-
- def _update_error(self, iter_, jid, node):
- '''Called when a disco#info query failed for an item.'''
- pass
+ return
+ # We got a list of items
+ self.window.services_treeview.set_model(None)
+ for item in items:
+ jid_ = item['jid']
+ node_ = item.get('node', '')
+ # If such an item is already here: don't add it
+ if self._find_item(jid_, node_):
+ continue
+ self._total_items += 1
+ self._add_item(jid_, node_, node, item, force)
+ self.window.services_treeview.set_model(self.model)
+
+ def _agent_info(self, jid, node, identities, features, data):
+ """
+ Callback for when we receive info about an agent's item
+ """
+ iter_ = self._find_item(jid, node)
+ if not iter_:
+ # Not in the treeview, stop
+ return
+ if identities == 0:
+ # The server returned an error
+ self._update_error(iter_, jid, node)
+ else:
+ # We got our info
+ self._update_info(iter_, jid, node, identities, features, data)
+ self.update_actions()
+
+ def _add_item(self, jid, node, parent_node, item, force):
+ """
+ Called when an item should be added to the model. The result of a
+ disco#items query
+ """
+ self.model.append((jid, node, item.get('name', ''),
+ get_agent_address(jid, node)))
+ self.cache.get_info(jid, node, self._agent_info, force = force)
+
+ def _update_item(self, iter_, jid, node, item):
+ """
+ Called when an item should be updated in the model. The result of a
+ disco#items query
+ """
+ if 'name' in item:
+ self.model[iter_][2] = item['name']
+
+ def _update_info(self, iter_, jid, node, identities, features, data):
+ """
+ Called when an item should be updated in the model with further info.
+ The result of a disco#info query
+ """
+ name = identities[0].get('name', '')
+ if name:
+ self.model[iter_][2] = name
+
+ def _update_error(self, iter_, jid, node):
+ '''Called when a disco#info query failed for an item.'''
+ pass
class ToplevelAgentBrowser(AgentBrowser):
- """
- This browser is used at the top level of a jabber server to browse services
- such as transports, conference servers, etc
- """
-
- def __init__(self, *args):
- AgentBrowser.__init__(self, *args)
- self._progressbar_sourceid = None
- self._renderer = None
- self._progress = 0
- self.tooltip = tooltips.ServiceDiscoveryTooltip()
- self.register_button = None
- self.join_button = None
- self.execute_button = None
- self.search_button = None
- # Keep track of our treeview signals
- self._view_signals = []
- self._scroll_signal = None
-
- def _pixbuf_renderer_data_func(self, col, cell, model, iter_):
- """
- Callback for setting the pixbuf renderer's properties
- """
- jid = model.get_value(iter_, 0)
- if jid:
- pix = model.get_value(iter_, 2)
- cell.set_property('visible', True)
- cell.set_property('pixbuf', pix)
- else:
- cell.set_property('visible', False)
-
- def _text_renderer_data_func(self, col, cell, model, iter_):
- """
- Callback for setting the text renderer's properties
- """
- jid = model.get_value(iter_, 0)
- markup = model.get_value(iter_, 3)
- state = model.get_value(iter_, 4)
- cell.set_property('markup', markup)
- if jid:
- cell.set_property('cell_background_set', False)
- if state > 0:
- # 1 = fetching, 2 = error
- cell.set_property('foreground_set', True)
- else:
- # Normal/succes
- cell.set_property('foreground_set', False)
- else:
- theme = gajim.config.get('roster_theme')
- bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
- if bgcolor:
- cell.set_property('cell_background_set', True)
- cell.set_property('foreground_set', False)
-
- def _treemodel_sort_func(self, model, iter1, iter2):
- """
- Sort function for our treemode
- """
- # Compare state
- statecmp = cmp(model.get_value(iter1, 4), model.get_value(iter2, 4))
- if statecmp == 0:
- # These can be None, apparently
- descr1 = model.get_value(iter1, 3)
- if descr1:
- descr1 = descr1.decode('utf-8')
- descr2 = model.get_value(iter2, 3)
- if descr2:
- descr2 = descr2.decode('utf-8')
- # Compare strings
- return cmp(descr1, descr2)
- return statecmp
-
- def _show_tooltip(self, state):
- view = self.window.services_treeview
- pointer = view.get_pointer()
- props = view.get_path_at_pos(pointer[0], pointer[1])
- # check if the current pointer is at the same path
- # as it was before setting the timeout
- if props and self.tooltip.id == props[0]:
- # bounding rectangle of coordinates for the cell within the treeview
- rect = view.get_cell_area(props[0], props[1])
- # position of the treeview on the screen
- position = view.window.get_origin()
- self.tooltip.show_tooltip(state, rect.height, position[1] + rect.y)
- else:
- self.tooltip.hide_tooltip()
-
- # These are all callbacks to make tooltips work
- def on_treeview_leave_notify_event(self, widget, event):
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id == props[0]:
- self.tooltip.hide_tooltip()
-
- def on_treeview_motion_notify_event(self, widget, event):
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id != props[0]:
- self.tooltip.hide_tooltip()
- if props:
- row = props[0]
- iter_ = None
- try:
- iter_ = self.model.get_iter(row)
- except Exception:
- self.tooltip.hide_tooltip()
- return
- jid = self.model[iter_][0]
- state = self.model[iter_][4]
- # Not a category, and we have something to say about state
- if jid and state > 0 and \
- (self.tooltip.timeout == 0 or self.tooltip.id != props[0]):
- self.tooltip.id = row
- self.tooltip.timeout = gobject.timeout_add(500,
- self._show_tooltip, state)
-
- def on_treeview_event_hide_tooltip(self, widget, event):
- """
- This happens on scroll_event, key_press_event and button_press_event
- """
- self.tooltip.hide_tooltip()
-
- def _create_treemodel(self):
- # JID, node, icon, description, state
- # State means 2 when error, 1 when fetching, 0 when succes.
- view = self.window.services_treeview
- self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int)
- self.model.set_sort_func(4, self._treemodel_sort_func)
- self.model.set_sort_column_id(4, gtk.SORT_ASCENDING)
- view.set_model(self.model)
-
- col = gtk.TreeViewColumn()
- # Icon Renderer
- renderer = gtk.CellRendererPixbuf()
- renderer.set_property('xpad', 6)
- col.pack_start(renderer, expand=False)
- col.set_cell_data_func(renderer, self._pixbuf_renderer_data_func)
- # Text Renderer
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, expand=True)
- col.set_cell_data_func(renderer, self._text_renderer_data_func)
- renderer.set_property('foreground', 'dark gray')
- # Save this so we can go along with theme changes
- self._renderer = renderer
- self.update_theme()
-
- view.insert_column(col, -1)
- col.set_resizable(True)
-
- # Connect signals
- scrollwin = self.window.services_scrollwin
- self._view_signals.append(view.connect('leave-notify-event',
- self.on_treeview_leave_notify_event))
- self._view_signals.append(view.connect('motion-notify-event',
- self.on_treeview_motion_notify_event))
- self._view_signals.append(view.connect('key-press-event',
- self.on_treeview_event_hide_tooltip))
- self._view_signals.append(view.connect('button-press-event',
- self.on_treeview_event_hide_tooltip))
- self._scroll_signal = scrollwin.connect('scroll-event',
- self.on_treeview_event_hide_tooltip)
-
- def _clean_treemodel(self):
- # Disconnect signals
- view = self.window.services_treeview
- for sig in self._view_signals:
- view.disconnect(sig)
- self._view_signals = []
- if self._scroll_signal:
- scrollwin = self.window.services_scrollwin
- scrollwin.disconnect(self._scroll_signal)
- self._scroll_signal = None
- AgentBrowser._clean_treemodel(self)
-
- def _add_actions(self):
- AgentBrowser._add_actions(self)
- self.execute_button = gtk.Button()
- image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
- label = gtk.Label(_('_Execute Command'))
- label.set_use_underline(True)
- hbox = gtk.HBox()
- hbox.pack_start(image, False, True, 6)
- hbox.pack_end(label, True, True)
- self.execute_button.add(hbox)
- self.execute_button.connect('clicked', self.on_execute_button_clicked)
- self.window.action_buttonbox.add(self.execute_button)
- self.execute_button.show_all()
-
- self.register_button = gtk.Button(label=_("Re_gister"),
- use_underline=True)
- self.register_button.connect('clicked', self.on_register_button_clicked)
- self.window.action_buttonbox.add(self.register_button)
- self.register_button.show_all()
-
- self.join_button = gtk.Button()
- image = gtk.image_new_from_stock(gtk.STOCK_CONNECT, gtk.ICON_SIZE_BUTTON)
- label = gtk.Label(_('_Join'))
- label.set_use_underline(True)
- hbox = gtk.HBox()
- hbox.pack_start(image, False, True, 6)
- hbox.pack_end(label, True, True)
- self.join_button.add(hbox)
- self.join_button.connect('clicked', self.on_join_button_clicked)
- self.window.action_buttonbox.add(self.join_button)
- self.join_button.show_all()
-
- self.search_button = gtk.Button()
- image = gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON)
- label = gtk.Label(_('_Search'))
- label.set_use_underline(True)
- hbox = gtk.HBox()
- hbox.pack_start(image, False, True, 6)
- hbox.pack_end(label, True, True)
- self.search_button.add(hbox)
- self.search_button.connect('clicked', self.on_search_button_clicked)
- self.window.action_buttonbox.add(self.search_button)
- self.search_button.show_all()
-
- def _clean_actions(self):
- if self.execute_button:
- self.execute_button.destroy()
- self.execute_button = None
- if self.register_button:
- self.register_button.destroy()
- self.register_button = None
- if self.join_button:
- self.join_button.destroy()
- self.join_button = None
- if self.search_button:
- self.search_button.destroy()
- self.search_button = None
- AgentBrowser._clean_actions(self)
-
- def on_search_button_clicked(self, widget = None):
- """
- When we want to search something: open search window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0].decode('utf-8')
- if service in gajim.interface.instances[self.account]['search']:
- gajim.interface.instances[self.account]['search'][service].window.\
- present()
- else:
- gajim.interface.instances[self.account]['search'][service] = \
- search_window.SearchWindow(self.account, service)
-
- def cleanup(self):
- self.tooltip.hide_tooltip()
- AgentBrowser.cleanup(self)
-
- def update_theme(self):
- theme = gajim.config.get('roster_theme')
- bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
- if bgcolor:
- self._renderer.set_property('cell-background', bgcolor)
- self.window.services_treeview.queue_draw()
-
- def on_execute_button_clicked(self, widget=None):
- """
- When we want to execute a command: open adhoc command window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0].decode('utf-8')
- node = model[iter_][1].decode('utf-8')
- adhoc_commands.CommandWindow(self.account, service, commandnode=node)
-
- def on_register_button_clicked(self, widget = None):
- """
- When we want to register an agent: request information about registering
- with the agent and close the window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0].decode('utf-8')
- if jid:
- gajim.connections[self.account].request_register_agent_info(jid)
- self.window.destroy(chain = True)
-
- def on_join_button_clicked(self, widget):
- """
- When we want to join an IRC room or create a new MUC room: Opens the
- join_groupchat_window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0].decode('utf-8')
- if 'join_gc' not in gajim.interface.instances[self.account]:
- try:
- dialogs.JoinGroupchatWindow(self.account, service)
- except GajimGeneralException:
- pass
- else:
- gajim.interface.instances[self.account]['join_gc'].window.present()
- self.window.destroy(chain = True)
-
- def update_actions(self):
- if self.execute_button:
- self.execute_button.set_sensitive(False)
- if self.register_button:
- self.register_button.set_sensitive(False)
- if self.browse_button:
- self.browse_button.set_sensitive(False)
- if self.join_button:
- self.join_button.set_sensitive(False)
- if self.search_button:
- self.search_button.set_sensitive(False)
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- if not model[iter_][0]:
- # We're on a category row
- return
- if model[iter_][4] != 0:
- # We don't have the info (yet)
- # It's either unknown or a transport, register button should be active
- if self.register_button:
- self.register_button.set_sensitive(True)
- # Guess what kind of service we're dealing with
- if self.browse_button:
- jid = model[iter_][0].decode('utf-8')
- type_ = gajim.get_transport_name_from_jid(jid,
- use_config_setting = False)
- if type_:
- identity = {'category': '_jid', 'type': type_}
- klass = self.cache.get_browser([identity])
- if klass:
- self.browse_button.set_sensitive(True)
- else:
- # We couldn't guess
- self.browse_button.set_sensitive(True)
- else:
- # Normal case, we have info
- AgentBrowser.update_actions(self)
-
- def _update_actions(self, jid, node, identities, features, data):
- AgentBrowser._update_actions(self, jid, node, identities, features, data)
- if self.execute_button and xmpp.NS_COMMANDS in features:
- self.execute_button.set_sensitive(True)
- if self.search_button and xmpp.NS_SEARCH in features:
- self.search_button.set_sensitive(True)
- if self.register_button and xmpp.NS_REGISTER in features:
- # We can register this agent
- registered_transports = []
- jid_list = gajim.contacts.get_jid_list(self.account)
- for jid in jid_list:
- contact = gajim.contacts.get_first_contact_from_jid(
- self.account, jid)
- if _('Transports') in contact.groups:
- registered_transports.append(jid)
- if jid in registered_transports:
- self.register_button.set_label(_('_Edit'))
- else:
- self.register_button.set_label(_('Re_gister'))
- self.register_button.set_sensitive(True)
- if self.join_button and xmpp.NS_MUC in features:
- self.join_button.set_sensitive(True)
-
- def _default_action(self, jid, node, identities, features, data):
- if AgentBrowser._default_action(self, jid, node, identities, features, data):
- return True
- if xmpp.NS_REGISTER in features:
- # Register if we can't browse
- self.on_register_button_clicked()
- return True
- return False
-
- def browse(self, force = False):
- self._progress = 0
- AgentBrowser.browse(self, force = force)
-
- def _expand_all(self):
- """
- Expand all items in the treeview
- """
- # GTK apparently screws up here occasionally. :/
- #def expand_all(*args):
- # self.window.services_treeview.expand_all()
- # self.expanding = False
- # return False
- #self.expanding = True
- #gobject.idle_add(expand_all)
- self.window.services_treeview.expand_all()
-
- def _update_progressbar(self):
- """
- Update the progressbar
- """
- # Refresh this every update
- if self._progressbar_sourceid:
- gobject.source_remove(self._progressbar_sourceid)
-
- fraction = 0
- if self._total_items:
- self.window.progressbar.set_text(_("Scanning %(current)d / %(total)d.."
- ) % {'current': self._progress, 'total': self._total_items})
- fraction = float(self._progress) / float(self._total_items)
- if self._progress >= self._total_items:
- # We show the progressbar for just a bit before hiding it.
- id_ = gobject.timeout_add_seconds(2, self._hide_progressbar_cb)
- self._progressbar_sourceid = id_
- else:
- self.window.progressbar.show()
- # Hide the progressbar if we're timing out anyways. (20 secs)
- id_ = gobject.timeout_add_seconds(20, self._hide_progressbar_cb)
- self._progressbar_sourceid = id_
- self.window.progressbar.set_fraction(fraction)
-
- def _hide_progressbar_cb(self, *args):
- """
- Simple callback to hide the progressbar a second after we finish
- """
- if self.active:
- self.window.progressbar.hide()
- return False
-
- def _friendly_category(self, category, type_=None):
- """
- Get the friendly category name and priority
- """
- cat = None
- if type_:
- # Try type-specific override
- try:
- cat, prio = _cat_to_descr[(category, type_)]
- except KeyError:
- pass
- if not cat:
- try:
- cat, prio = _cat_to_descr[category]
- except KeyError:
- cat, prio = _cat_to_descr['other']
- return cat, prio
-
- def _create_category(self, cat, type_=None):
- """
- Creates a category row
- """
- cat, prio = self._friendly_category(cat, type_)
- return self.model.append(None, ('', '', None, cat, prio))
-
- def _find_category(self, cat, type_=None):
- """
- Looks up a category row and returns the iterator to it, or None
- """
- cat = self._friendly_category(cat, type_)[0]
- iter_ = self.model.get_iter_root()
- while iter_:
- if self.model.get_value(iter_, 3).decode('utf-8') == cat:
- break
- iter_ = self.model.iter_next(iter_)
- if iter_:
- return iter_
- return None
-
- def _find_item(self, jid, node):
- iter_ = None
- cat_iter = self.model.get_iter_root()
- while cat_iter and not iter_:
- iter_ = self.model.iter_children(cat_iter)
- while iter_:
- cjid = self.model.get_value(iter_, 0).decode('utf-8')
- cnode = self.model.get_value(iter_, 1).decode('utf-8')
- if jid == cjid and node == cnode:
- break
- iter_ = self.model.iter_next(iter_)
- cat_iter = self.model.iter_next(cat_iter)
- if iter_:
- return iter_
- return None
-
- def _add_item(self, jid, node, parent_node, item, force):
- # Row text
- addr = get_agent_address(jid, node)
- if 'name' in item:
- descr = "<b>%s</b>\n%s" % (item['name'], addr)
- else:
- descr = "<b>%s</b>" % addr
- # Guess which kind of service this is
- identities = []
- type_ = gajim.get_transport_name_from_jid(jid,
- use_config_setting = False)
- if type_:
- identity = {'category': '_jid', 'type': type_}
- identities.append(identity)
- cat_args = ('_jid', type_)
- else:
- # Put it in the 'other' category for now
- cat_args = ('other',)
- # Set the pixmap for the row
- pix = self.cache.get_icon(identities)
- # Put it in the right category
- cat = self._find_category(*cat_args)
- if not cat:
- cat = self._create_category(*cat_args)
- self.model.append(cat, (jid, node, pix, descr, 1))
- gobject.idle_add(self._expand_all)
- # Grab info on the service
- self.cache.get_info(jid, node, self._agent_info, force=force)
- self._update_progressbar()
-
- def _update_item(self, iter_, jid, node, item):
- addr = get_agent_address(jid, node)
- if 'name' in item:
- descr = "<b>%s</b>\n%s" % (item['name'], addr)
- else:
- descr = "<b>%s</b>" % addr
- self.model[iter_][3] = descr
-
- def _update_info(self, iter_, jid, node, identities, features, data):
- addr = get_agent_address(jid, node)
- name = identities[0].get('name', '')
- if name:
- descr = "<b>%s</b>\n%s" % (name, addr)
- else:
- descr = "<b>%s</b>" % addr
-
- # Update progress
- self._progress += 1
- self._update_progressbar()
-
- # Search for an icon and category we can display
- pix = self.cache.get_icon(identities)
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- except KeyError:
- continue
- break
-
- # Check if we have to move categories
- old_cat_iter = self.model.iter_parent(iter_)
- old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8')
- if self.model.get_value(old_cat_iter, 3) == cat:
- # Already in the right category, just update
- self.model[iter_][2] = pix
- self.model[iter_][3] = descr
- self.model[iter_][4] = 0
- return
- # Not in the right category, move it.
- self.model.remove(iter_)
-
- # Check if the old category is empty
- if not self.model.iter_is_valid(old_cat_iter):
- old_cat_iter = self._find_category(old_cat)
- if not self.model.iter_children(old_cat_iter):
- self.model.remove(old_cat_iter)
-
- cat_iter = self._find_category(cat, type_)
- if not cat_iter:
- cat_iter = self._create_category(cat, type_)
- self.model.append(cat_iter, (jid, node, pix, descr, 0))
- self._expand_all()
-
- def _update_error(self, iter_, jid, node):
- self.model[iter_][4] = 2
- self._progress += 1
- self._update_progressbar()
+ """
+ This browser is used at the top level of a jabber server to browse services
+ such as transports, conference servers, etc
+ """
+
+ def __init__(self, *args):
+ AgentBrowser.__init__(self, *args)
+ self._progressbar_sourceid = None
+ self._renderer = None
+ self._progress = 0
+ self.tooltip = tooltips.ServiceDiscoveryTooltip()
+ self.register_button = None
+ self.join_button = None
+ self.execute_button = None
+ self.search_button = None
+ # Keep track of our treeview signals
+ self._view_signals = []
+ self._scroll_signal = None
+
+ def _pixbuf_renderer_data_func(self, col, cell, model, iter_):
+ """
+ Callback for setting the pixbuf renderer's properties
+ """
+ jid = model.get_value(iter_, 0)
+ if jid:
+ pix = model.get_value(iter_, 2)
+ cell.set_property('visible', True)
+ cell.set_property('pixbuf', pix)
+ else:
+ cell.set_property('visible', False)
+
+ def _text_renderer_data_func(self, col, cell, model, iter_):
+ """
+ Callback for setting the text renderer's properties
+ """
+ jid = model.get_value(iter_, 0)
+ markup = model.get_value(iter_, 3)
+ state = model.get_value(iter_, 4)
+ cell.set_property('markup', markup)
+ if jid:
+ cell.set_property('cell_background_set', False)
+ if state > 0:
+ # 1 = fetching, 2 = error
+ cell.set_property('foreground_set', True)
+ else:
+ # Normal/succes
+ cell.set_property('foreground_set', False)
+ else:
+ theme = gajim.config.get('roster_theme')
+ bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
+ if bgcolor:
+ cell.set_property('cell_background_set', True)
+ cell.set_property('foreground_set', False)
+
+ def _treemodel_sort_func(self, model, iter1, iter2):
+ """
+ Sort function for our treemode
+ """
+ # Compare state
+ statecmp = cmp(model.get_value(iter1, 4), model.get_value(iter2, 4))
+ if statecmp == 0:
+ # These can be None, apparently
+ descr1 = model.get_value(iter1, 3)
+ if descr1:
+ descr1 = descr1.decode('utf-8')
+ descr2 = model.get_value(iter2, 3)
+ if descr2:
+ descr2 = descr2.decode('utf-8')
+ # Compare strings
+ return cmp(descr1, descr2)
+ return statecmp
+
+ def _show_tooltip(self, state):
+ view = self.window.services_treeview
+ pointer = view.get_pointer()
+ props = view.get_path_at_pos(pointer[0], pointer[1])
+ # check if the current pointer is at the same path
+ # as it was before setting the timeout
+ if props and self.tooltip.id == props[0]:
+ # bounding rectangle of coordinates for the cell within the treeview
+ rect = view.get_cell_area(props[0], props[1])
+ # position of the treeview on the screen
+ position = view.window.get_origin()
+ self.tooltip.show_tooltip(state, rect.height, position[1] + rect.y)
+ else:
+ self.tooltip.hide_tooltip()
+
+ # These are all callbacks to make tooltips work
+ def on_treeview_leave_notify_event(self, widget, event):
+ props = widget.get_path_at_pos(int(event.x), int(event.y))
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id == props[0]:
+ self.tooltip.hide_tooltip()
+
+ def on_treeview_motion_notify_event(self, widget, event):
+ props = widget.get_path_at_pos(int(event.x), int(event.y))
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id != props[0]:
+ self.tooltip.hide_tooltip()
+ if props:
+ row = props[0]
+ iter_ = None
+ try:
+ iter_ = self.model.get_iter(row)
+ except Exception:
+ self.tooltip.hide_tooltip()
+ return
+ jid = self.model[iter_][0]
+ state = self.model[iter_][4]
+ # Not a category, and we have something to say about state
+ if jid and state > 0 and \
+ (self.tooltip.timeout == 0 or self.tooltip.id != props[0]):
+ self.tooltip.id = row
+ self.tooltip.timeout = gobject.timeout_add(500,
+ self._show_tooltip, state)
+
+ def on_treeview_event_hide_tooltip(self, widget, event):
+ """
+ This happens on scroll_event, key_press_event and button_press_event
+ """
+ self.tooltip.hide_tooltip()
+
+ def _create_treemodel(self):
+ # JID, node, icon, description, state
+ # State means 2 when error, 1 when fetching, 0 when succes.
+ view = self.window.services_treeview
+ self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int)
+ self.model.set_sort_func(4, self._treemodel_sort_func)
+ self.model.set_sort_column_id(4, gtk.SORT_ASCENDING)
+ view.set_model(self.model)
+
+ col = gtk.TreeViewColumn()
+ # Icon Renderer
+ renderer = gtk.CellRendererPixbuf()
+ renderer.set_property('xpad', 6)
+ col.pack_start(renderer, expand=False)
+ col.set_cell_data_func(renderer, self._pixbuf_renderer_data_func)
+ # Text Renderer
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, expand=True)
+ col.set_cell_data_func(renderer, self._text_renderer_data_func)
+ renderer.set_property('foreground', 'dark gray')
+ # Save this so we can go along with theme changes
+ self._renderer = renderer
+ self.update_theme()
+
+ view.insert_column(col, -1)
+ col.set_resizable(True)
+
+ # Connect signals
+ scrollwin = self.window.services_scrollwin
+ self._view_signals.append(view.connect('leave-notify-event',
+ self.on_treeview_leave_notify_event))
+ self._view_signals.append(view.connect('motion-notify-event',
+ self.on_treeview_motion_notify_event))
+ self._view_signals.append(view.connect('key-press-event',
+ self.on_treeview_event_hide_tooltip))
+ self._view_signals.append(view.connect('button-press-event',
+ self.on_treeview_event_hide_tooltip))
+ self._scroll_signal = scrollwin.connect('scroll-event',
+ self.on_treeview_event_hide_tooltip)
+
+ def _clean_treemodel(self):
+ # Disconnect signals
+ view = self.window.services_treeview
+ for sig in self._view_signals:
+ view.disconnect(sig)
+ self._view_signals = []
+ if self._scroll_signal:
+ scrollwin = self.window.services_scrollwin
+ scrollwin.disconnect(self._scroll_signal)
+ self._scroll_signal = None
+ AgentBrowser._clean_treemodel(self)
+
+ def _add_actions(self):
+ AgentBrowser._add_actions(self)
+ self.execute_button = gtk.Button()
+ image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
+ label = gtk.Label(_('_Execute Command'))
+ label.set_use_underline(True)
+ hbox = gtk.HBox()
+ hbox.pack_start(image, False, True, 6)
+ hbox.pack_end(label, True, True)
+ self.execute_button.add(hbox)
+ self.execute_button.connect('clicked', self.on_execute_button_clicked)
+ self.window.action_buttonbox.add(self.execute_button)
+ self.execute_button.show_all()
+
+ self.register_button = gtk.Button(label=_("Re_gister"),
+ use_underline=True)
+ self.register_button.connect('clicked', self.on_register_button_clicked)
+ self.window.action_buttonbox.add(self.register_button)
+ self.register_button.show_all()
+
+ self.join_button = gtk.Button()
+ image = gtk.image_new_from_stock(gtk.STOCK_CONNECT, gtk.ICON_SIZE_BUTTON)
+ label = gtk.Label(_('_Join'))
+ label.set_use_underline(True)
+ hbox = gtk.HBox()
+ hbox.pack_start(image, False, True, 6)
+ hbox.pack_end(label, True, True)
+ self.join_button.add(hbox)
+ self.join_button.connect('clicked', self.on_join_button_clicked)
+ self.window.action_buttonbox.add(self.join_button)
+ self.join_button.show_all()
+
+ self.search_button = gtk.Button()
+ image = gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON)
+ label = gtk.Label(_('_Search'))
+ label.set_use_underline(True)
+ hbox = gtk.HBox()
+ hbox.pack_start(image, False, True, 6)
+ hbox.pack_end(label, True, True)
+ self.search_button.add(hbox)
+ self.search_button.connect('clicked', self.on_search_button_clicked)
+ self.window.action_buttonbox.add(self.search_button)
+ self.search_button.show_all()
+
+ def _clean_actions(self):
+ if self.execute_button:
+ self.execute_button.destroy()
+ self.execute_button = None
+ if self.register_button:
+ self.register_button.destroy()
+ self.register_button = None
+ if self.join_button:
+ self.join_button.destroy()
+ self.join_button = None
+ if self.search_button:
+ self.search_button.destroy()
+ self.search_button = None
+ AgentBrowser._clean_actions(self)
+
+ def on_search_button_clicked(self, widget = None):
+ """
+ When we want to search something: open search window
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ service = model[iter_][0].decode('utf-8')
+ if service in gajim.interface.instances[self.account]['search']:
+ gajim.interface.instances[self.account]['search'][service].window.\
+ present()
+ else:
+ gajim.interface.instances[self.account]['search'][service] = \
+ search_window.SearchWindow(self.account, service)
+
+ def cleanup(self):
+ self.tooltip.hide_tooltip()
+ AgentBrowser.cleanup(self)
+
+ def update_theme(self):
+ theme = gajim.config.get('roster_theme')
+ bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
+ if bgcolor:
+ self._renderer.set_property('cell-background', bgcolor)
+ self.window.services_treeview.queue_draw()
+
+ def on_execute_button_clicked(self, widget=None):
+ """
+ When we want to execute a command: open adhoc command window
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ service = model[iter_][0].decode('utf-8')
+ node = model[iter_][1].decode('utf-8')
+ adhoc_commands.CommandWindow(self.account, service, commandnode=node)
+
+ def on_register_button_clicked(self, widget = None):
+ """
+ When we want to register an agent: request information about registering
+ with the agent and close the window
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ jid = model[iter_][0].decode('utf-8')
+ if jid:
+ gajim.connections[self.account].request_register_agent_info(jid)
+ self.window.destroy(chain = True)
+
+ def on_join_button_clicked(self, widget):
+ """
+ When we want to join an IRC room or create a new MUC room: Opens the
+ join_groupchat_window
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ service = model[iter_][0].decode('utf-8')
+ if 'join_gc' not in gajim.interface.instances[self.account]:
+ try:
+ dialogs.JoinGroupchatWindow(self.account, service)
+ except GajimGeneralException:
+ pass
+ else:
+ gajim.interface.instances[self.account]['join_gc'].window.present()
+ self.window.destroy(chain = True)
+
+ def update_actions(self):
+ if self.execute_button:
+ self.execute_button.set_sensitive(False)
+ if self.register_button:
+ self.register_button.set_sensitive(False)
+ if self.browse_button:
+ self.browse_button.set_sensitive(False)
+ if self.join_button:
+ self.join_button.set_sensitive(False)
+ if self.search_button:
+ self.search_button.set_sensitive(False)
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ if not model[iter_][0]:
+ # We're on a category row
+ return
+ if model[iter_][4] != 0:
+ # We don't have the info (yet)
+ # It's either unknown or a transport, register button should be active
+ if self.register_button:
+ self.register_button.set_sensitive(True)
+ # Guess what kind of service we're dealing with
+ if self.browse_button:
+ jid = model[iter_][0].decode('utf-8')
+ type_ = gajim.get_transport_name_from_jid(jid,
+ use_config_setting = False)
+ if type_:
+ identity = {'category': '_jid', 'type': type_}
+ klass = self.cache.get_browser([identity])
+ if klass:
+ self.browse_button.set_sensitive(True)
+ else:
+ # We couldn't guess
+ self.browse_button.set_sensitive(True)
+ else:
+ # Normal case, we have info
+ AgentBrowser.update_actions(self)
+
+ def _update_actions(self, jid, node, identities, features, data):
+ AgentBrowser._update_actions(self, jid, node, identities, features, data)
+ if self.execute_button and xmpp.NS_COMMANDS in features:
+ self.execute_button.set_sensitive(True)
+ if self.search_button and xmpp.NS_SEARCH in features:
+ self.search_button.set_sensitive(True)
+ if self.register_button and xmpp.NS_REGISTER in features:
+ # We can register this agent
+ registered_transports = []
+ jid_list = gajim.contacts.get_jid_list(self.account)
+ for jid in jid_list:
+ contact = gajim.contacts.get_first_contact_from_jid(
+ self.account, jid)
+ if _('Transports') in contact.groups:
+ registered_transports.append(jid)
+ if jid in registered_transports:
+ self.register_button.set_label(_('_Edit'))
+ else:
+ self.register_button.set_label(_('Re_gister'))
+ self.register_button.set_sensitive(True)
+ if self.join_button and xmpp.NS_MUC in features:
+ self.join_button.set_sensitive(True)
+
+ def _default_action(self, jid, node, identities, features, data):
+ if AgentBrowser._default_action(self, jid, node, identities, features, data):
+ return True
+ if xmpp.NS_REGISTER in features:
+ # Register if we can't browse
+ self.on_register_button_clicked()
+ return True
+ return False
+
+ def browse(self, force = False):
+ self._progress = 0
+ AgentBrowser.browse(self, force = force)
+
+ def _expand_all(self):
+ """
+ Expand all items in the treeview
+ """
+ # GTK apparently screws up here occasionally. :/
+ #def expand_all(*args):
+ # self.window.services_treeview.expand_all()
+ # self.expanding = False
+ # return False
+ #self.expanding = True
+ #gobject.idle_add(expand_all)
+ self.window.services_treeview.expand_all()
+
+ def _update_progressbar(self):
+ """
+ Update the progressbar
+ """
+ # Refresh this every update
+ if self._progressbar_sourceid:
+ gobject.source_remove(self._progressbar_sourceid)
+
+ fraction = 0
+ if self._total_items:
+ self.window.progressbar.set_text(_("Scanning %(current)d / %(total)d.."
+ ) % {'current': self._progress, 'total': self._total_items})
+ fraction = float(self._progress) / float(self._total_items)
+ if self._progress >= self._total_items:
+ # We show the progressbar for just a bit before hiding it.
+ id_ = gobject.timeout_add_seconds(2, self._hide_progressbar_cb)
+ self._progressbar_sourceid = id_
+ else:
+ self.window.progressbar.show()
+ # Hide the progressbar if we're timing out anyways. (20 secs)
+ id_ = gobject.timeout_add_seconds(20, self._hide_progressbar_cb)
+ self._progressbar_sourceid = id_
+ self.window.progressbar.set_fraction(fraction)
+
+ def _hide_progressbar_cb(self, *args):
+ """
+ Simple callback to hide the progressbar a second after we finish
+ """
+ if self.active:
+ self.window.progressbar.hide()
+ return False
+
+ def _friendly_category(self, category, type_=None):
+ """
+ Get the friendly category name and priority
+ """
+ cat = None
+ if type_:
+ # Try type-specific override
+ try:
+ cat, prio = _cat_to_descr[(category, type_)]
+ except KeyError:
+ pass
+ if not cat:
+ try:
+ cat, prio = _cat_to_descr[category]
+ except KeyError:
+ cat, prio = _cat_to_descr['other']
+ return cat, prio
+
+ def _create_category(self, cat, type_=None):
+ """
+ Creates a category row
+ """
+ cat, prio = self._friendly_category(cat, type_)
+ return self.model.append(None, ('', '', None, cat, prio))
+
+ def _find_category(self, cat, type_=None):
+ """
+ Looks up a category row and returns the iterator to it, or None
+ """
+ cat = self._friendly_category(cat, type_)[0]
+ iter_ = self.model.get_iter_root()
+ while iter_:
+ if self.model.get_value(iter_, 3).decode('utf-8') == cat:
+ break
+ iter_ = self.model.iter_next(iter_)
+ if iter_:
+ return iter_
+ return None
+
+ def _find_item(self, jid, node):
+ iter_ = None
+ cat_iter = self.model.get_iter_root()
+ while cat_iter and not iter_:
+ iter_ = self.model.iter_children(cat_iter)
+ while iter_:
+ cjid = self.model.get_value(iter_, 0).decode('utf-8')
+ cnode = self.model.get_value(iter_, 1).decode('utf-8')
+ if jid == cjid and node == cnode:
+ break
+ iter_ = self.model.iter_next(iter_)
+ cat_iter = self.model.iter_next(cat_iter)
+ if iter_:
+ return iter_
+ return None
+
+ def _add_item(self, jid, node, parent_node, item, force):
+ # Row text
+ addr = get_agent_address(jid, node)
+ if 'name' in item:
+ descr = "<b>%s</b>\n%s" % (item['name'], addr)
+ else:
+ descr = "<b>%s</b>" % addr
+ # Guess which kind of service this is
+ identities = []
+ type_ = gajim.get_transport_name_from_jid(jid,
+ use_config_setting = False)
+ if type_:
+ identity = {'category': '_jid', 'type': type_}
+ identities.append(identity)
+ cat_args = ('_jid', type_)
+ else:
+ # Put it in the 'other' category for now
+ cat_args = ('other',)
+ # Set the pixmap for the row
+ pix = self.cache.get_icon(identities)
+ # Put it in the right category
+ cat = self._find_category(*cat_args)
+ if not cat:
+ cat = self._create_category(*cat_args)
+ self.model.append(cat, (jid, node, pix, descr, 1))
+ gobject.idle_add(self._expand_all)
+ # Grab info on the service
+ self.cache.get_info(jid, node, self._agent_info, force=force)
+ self._update_progressbar()
+
+ def _update_item(self, iter_, jid, node, item):
+ addr = get_agent_address(jid, node)
+ if 'name' in item:
+ descr = "<b>%s</b>\n%s" % (item['name'], addr)
+ else:
+ descr = "<b>%s</b>" % addr
+ self.model[iter_][3] = descr
+
+ def _update_info(self, iter_, jid, node, identities, features, data):
+ addr = get_agent_address(jid, node)
+ name = identities[0].get('name', '')
+ if name:
+ descr = "<b>%s</b>\n%s" % (name, addr)
+ else:
+ descr = "<b>%s</b>" % addr
+
+ # Update progress
+ self._progress += 1
+ self._update_progressbar()
+
+ # Search for an icon and category we can display
+ pix = self.cache.get_icon(identities)
+ for identity in identities:
+ try:
+ cat, type_ = identity['category'], identity['type']
+ except KeyError:
+ continue
+ break
+
+ # Check if we have to move categories
+ old_cat_iter = self.model.iter_parent(iter_)
+ old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8')
+ if self.model.get_value(old_cat_iter, 3) == cat:
+ # Already in the right category, just update
+ self.model[iter_][2] = pix
+ self.model[iter_][3] = descr
+ self.model[iter_][4] = 0
+ return
+ # Not in the right category, move it.
+ self.model.remove(iter_)
+
+ # Check if the old category is empty
+ if not self.model.iter_is_valid(old_cat_iter):
+ old_cat_iter = self._find_category(old_cat)
+ if not self.model.iter_children(old_cat_iter):
+ self.model.remove(old_cat_iter)
+
+ cat_iter = self._find_category(cat, type_)
+ if not cat_iter:
+ cat_iter = self._create_category(cat, type_)
+ self.model.append(cat_iter, (jid, node, pix, descr, 0))
+ self._expand_all()
+
+ def _update_error(self, iter_, jid, node):
+ self.model[iter_][4] = 2
+ self._progress += 1
+ self._update_progressbar()
class MucBrowser(AgentBrowser):
- def __init__(self, *args, **kwargs):
- AgentBrowser.__init__(self, *args, **kwargs)
- self.join_button = None
- self.bookmark_button = None
-
- def _create_treemodel(self):
- # JID, node, name, users_int, users_str, description, fetched
- # This is rather long, I'd rather not use a data_func here though.
- # Users is a string, because want to be able to leave it empty.
- self.model = gtk.ListStore(str, str, str, int, str, str, bool)
- self.model.set_sort_column_id(2, gtk.SORT_ASCENDING)
- self.window.services_treeview.set_model(self.model)
- # Name column
- col = gtk.TreeViewColumn(_('Name'))
- col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
- col.set_fixed_width(100)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = 2)
- col.set_sort_column_id(2)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Users column
- col = gtk.TreeViewColumn(_('Users'))
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = 4)
- col.set_sort_column_id(3)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Description column
- col = gtk.TreeViewColumn(_('Description'))
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = 5)
- col.set_sort_column_id(4)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Id column
- col = gtk.TreeViewColumn(_('Id'))
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = 0)
- col.set_sort_column_id(0)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- self.window.services_treeview.set_headers_visible(True)
- self.window.services_treeview.set_headers_clickable(True)
- # Source id for idle callback used to start disco#info queries.
- self._fetch_source = None
- # Query failure counter
- self._broken = 0
- # Connect to scrollwindow scrolling
- self.vadj = self.window.services_scrollwin.get_property('vadjustment')
- self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll)
- # And to size changes
- self.size_cbid = self.window.services_scrollwin.connect(
- 'size-allocate', self.on_scroll)
-
- def _clean_treemodel(self):
- if self.size_cbid:
- self.window.services_scrollwin.disconnect(self.size_cbid)
- self.size_cbid = None
- if self.vadj_cbid:
- self.vadj.disconnect(self.vadj_cbid)
- self.vadj_cbid = None
- AgentBrowser._clean_treemodel(self)
-
- def _add_actions(self):
- self.bookmark_button = gtk.Button(label=_('_Bookmark'), use_underline=True)
- self.bookmark_button.connect('clicked', self.on_bookmark_button_clicked)
- self.window.action_buttonbox.add(self.bookmark_button)
- self.bookmark_button.show_all()
- self.join_button = gtk.Button(label=_('_Join'), use_underline=True)
- self.join_button.connect('clicked', self.on_join_button_clicked)
- self.window.action_buttonbox.add(self.join_button)
- self.join_button.show_all()
-
- def _clean_actions(self):
- if self.bookmark_button:
- self.bookmark_button.destroy()
- self.bookmark_button = None
- if self.join_button:
- self.join_button.destroy()
- self.join_button = None
-
- def on_bookmark_button_clicked(self, *args):
- model, iter = self.window.services_treeview.get_selection().get_selected()
- if not iter:
- return
- name = gajim.config.get_per('accounts', self.account, 'name')
- room_jid = model[iter][0].decode('utf-8')
- bm = {
- 'name': room_jid.split('@')[0],
- 'jid': room_jid,
- 'autojoin': '0',
- 'minimize': '0',
- 'password': '',
- 'nick': name
- }
-
- for bookmark in gajim.connections[self.account].bookmarks:
- if bookmark['jid'] == bm['jid']:
- dialogs.ErrorDialog(
- _('Bookmark already set'),
- _('Group Chat "%s" is already in your bookmarks.') % bm['jid'])
- return
-
- gajim.connections[self.account].bookmarks.append(bm)
- gajim.connections[self.account].store_bookmarks()
-
- gajim.interface.roster.set_actions_menu_needs_rebuild()
-
- dialogs.InformationDialog(
- _('Bookmark has been added successfully'),
- _('You can manage your bookmarks via Actions menu in your roster.'))
-
- def on_join_button_clicked(self, *args):
- """
- When we want to join a conference: ask specific informations about the
- selected agent and close the window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0].decode('utf-8')
- room = model[iter_][1].decode('utf-8')
- if 'join_gc' not in gajim.interface.instances[self.account]:
- try:
- dialogs.JoinGroupchatWindow(self.account, service)
- except GajimGeneralException:
- pass
- else:
- gajim.interface.instances[self.account]['join_gc'].window.present()
- self.window.destroy(chain = True)
-
- def update_actions(self):
- sens = self.window.services_treeview.get_selection().count_selected_rows()
- if self.bookmark_button:
- self.bookmark_button.set_sensitive(sens > 0)
- if self.join_button:
- self.join_button.set_sensitive(sens > 0)
-
- def default_action(self):
- self.on_join_button_clicked()
-
- def _start_info_query(self):
- """
- Idle callback to start checking for visible rows
- """
- self._fetch_source = None
- self._query_visible()
- return False
-
- def on_scroll(self, *args):
- """
- Scrollwindow callback to trigger new queries on scolling
- """
- # This apparently happens when inactive sometimes
- self._query_visible()
-
- def _query_visible(self):
- """
- Query the next visible row for info
- """
- if self._fetch_source:
- # We're already fetching
- return
- view = self.window.services_treeview
- if not view.flags() & gtk.REALIZED:
- # Prevent a silly warning, try again in a bit.
- self._fetch_source = gobject.timeout_add(100, self._start_info_query)
- return
- # We have to do this in a pygtk <2.8 compatible way :/
- #start, end = self.window.services_treeview.get_visible_range()
- rect = view.get_visible_rect()
- iter_ = end = None
- # Top row
- try:
- sx, sy = view.tree_to_widget_coords(rect.x, rect.y)
- spath = view.get_path_at_pos(sx, sy)[0]
- iter_ = self.model.get_iter(spath)
- except TypeError:
- self._fetch_source = None
- return
- # Bottom row
- # Iter compare is broke, use the path instead
- try:
- ex, ey = view.tree_to_widget_coords(rect.x + rect.height,
- rect.y + rect.height)
- end = view.get_path_at_pos(ex, ey)[0]
- # end is the last visible, we want to query that aswell
- end = (end[0] + 1,)
- except TypeError:
- # We're at the end of the model, we can leave end=None though.
- pass
- while iter_ and self.model.get_path(iter_) != end:
- if not self.model.get_value(iter_, 6):
- jid = self.model.get_value(iter_, 0).decode('utf-8')
- node = self.model.get_value(iter_, 1).decode('utf-8')
- self.cache.get_info(jid, node, self._agent_info)
- self._fetch_source = True
- return
- iter_ = self.model.iter_next(iter_)
- self._fetch_source = None
-
- def _channel_altinfo(self, jid, node, items, name = None):
- """
- Callback for the alternate disco#items query. We try to atleast get the
- amount of users in the room if the service does not support MUC dataforms
- """
- if items == 0:
- # The server returned an error
- self._broken += 1
- if self._broken >= 3:
- # Disable queries completely after 3 failures
- if self.size_cbid:
- self.window.services_scrollwin.disconnect(self.size_cbid)
- self.size_cbid = None
- if self.vadj_cbid:
- self.vadj.disconnect(self.vadj_cbid)
- self.vadj_cbid = None
- self._fetch_source = None
- return
- else:
- iter_ = self._find_item(jid, node)
- if iter_:
- if name:
- self.model[iter_][2] = name
- self.model[iter_][3] = len(items) # The number of users
- self.model[iter_][4] = str(len(items)) # The number of users
- self.model[iter_][6] = True
- self._fetch_source = None
- self._query_visible()
-
- def _add_item(self, jid, node, parent_node, item, force):
- self.model.append((jid, node, item.get('name', ''), -1, '', '', False))
- if not self._fetch_source:
- self._fetch_source = gobject.idle_add(self._start_info_query)
-
- def _update_info(self, iter_, jid, node, identities, features, data):
- name = identities[0].get('name', '')
- for form in data:
- typefield = form.getField('FORM_TYPE')
- if typefield and typefield.getValue() == \
- 'http://jabber.org/protocol/muc#roominfo':
- # Fill model row from the form's fields
- users = form.getField('muc#roominfo_occupants')
- descr = form.getField('muc#roominfo_description')
- if users:
- self.model[iter_][3] = int(users.getValue())
- self.model[iter_][4] = users.getValue()
- if descr:
- self.model[iter_][5] = descr.getValue()
- # Only set these when we find a form with additional info
- # Some servers don't support forms and put extra info in
- # the name attribute, so we preserve it in that case.
- self.model[iter_][2] = name
- self.model[iter_][6] = True
- break
- else:
- # We didn't find a form, switch to alternate query mode
- self.cache.get_items(jid, node, self._channel_altinfo, args = (name,))
- return
- # Continue with the next
- self._fetch_source = None
- self._query_visible()
-
- def _update_error(self, iter_, jid, node):
- # switch to alternate query mode
- self.cache.get_items(jid, node, self._channel_altinfo)
+ def __init__(self, *args, **kwargs):
+ AgentBrowser.__init__(self, *args, **kwargs)
+ self.join_button = None
+ self.bookmark_button = None
+
+ def _create_treemodel(self):
+ # JID, node, name, users_int, users_str, description, fetched
+ # This is rather long, I'd rather not use a data_func here though.
+ # Users is a string, because want to be able to leave it empty.
+ self.model = gtk.ListStore(str, str, str, int, str, str, bool)
+ self.model.set_sort_column_id(2, gtk.SORT_ASCENDING)
+ self.window.services_treeview.set_model(self.model)
+ # Name column
+ col = gtk.TreeViewColumn(_('Name'))
+ col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+ col.set_fixed_width(100)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = 2)
+ col.set_sort_column_id(2)
+ self.window.services_treeview.insert_column(col, -1)
+ col.set_resizable(True)
+ # Users column
+ col = gtk.TreeViewColumn(_('Users'))
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = 4)
+ col.set_sort_column_id(3)
+ self.window.services_treeview.insert_column(col, -1)
+ col.set_resizable(True)
+ # Description column
+ col = gtk.TreeViewColumn(_('Description'))
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = 5)
+ col.set_sort_column_id(4)
+ self.window.services_treeview.insert_column(col, -1)
+ col.set_resizable(True)
+ # Id column
+ col = gtk.TreeViewColumn(_('Id'))
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = 0)
+ col.set_sort_column_id(0)
+ self.window.services_treeview.insert_column(col, -1)
+ col.set_resizable(True)
+ self.window.services_treeview.set_headers_visible(True)
+ self.window.services_treeview.set_headers_clickable(True)
+ # Source id for idle callback used to start disco#info queries.
+ self._fetch_source = None
+ # Query failure counter
+ self._broken = 0
+ # Connect to scrollwindow scrolling
+ self.vadj = self.window.services_scrollwin.get_property('vadjustment')
+ self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll)
+ # And to size changes
+ self.size_cbid = self.window.services_scrollwin.connect(
+ 'size-allocate', self.on_scroll)
+
+ def _clean_treemodel(self):
+ if self.size_cbid:
+ self.window.services_scrollwin.disconnect(self.size_cbid)
+ self.size_cbid = None
+ if self.vadj_cbid:
+ self.vadj.disconnect(self.vadj_cbid)
+ self.vadj_cbid = None
+ AgentBrowser._clean_treemodel(self)
+
+ def _add_actions(self):
+ self.bookmark_button = gtk.Button(label=_('_Bookmark'), use_underline=True)
+ self.bookmark_button.connect('clicked', self.on_bookmark_button_clicked)
+ self.window.action_buttonbox.add(self.bookmark_button)
+ self.bookmark_button.show_all()
+ self.join_button = gtk.Button(label=_('_Join'), use_underline=True)
+ self.join_button.connect('clicked', self.on_join_button_clicked)
+ self.window.action_buttonbox.add(self.join_button)
+ self.join_button.show_all()
+
+ def _clean_actions(self):
+ if self.bookmark_button:
+ self.bookmark_button.destroy()
+ self.bookmark_button = None
+ if self.join_button:
+ self.join_button.destroy()
+ self.join_button = None
+
+ def on_bookmark_button_clicked(self, *args):
+ model, iter = self.window.services_treeview.get_selection().get_selected()
+ if not iter:
+ return
+ name = gajim.config.get_per('accounts', self.account, 'name')
+ room_jid = model[iter][0].decode('utf-8')
+ bm = {
+ 'name': room_jid.split('@')[0],
+ 'jid': room_jid,
+ 'autojoin': '0',
+ 'minimize': '0',
+ 'password': '',
+ 'nick': name
+ }
+
+ for bookmark in gajim.connections[self.account].bookmarks:
+ if bookmark['jid'] == bm['jid']:
+ dialogs.ErrorDialog(
+ _('Bookmark already set'),
+ _('Group Chat "%s" is already in your bookmarks.') % bm['jid'])
+ return
+
+ gajim.connections[self.account].bookmarks.append(bm)
+ gajim.connections[self.account].store_bookmarks()
+
+ gajim.interface.roster.set_actions_menu_needs_rebuild()
+
+ dialogs.InformationDialog(
+ _('Bookmark has been added successfully'),
+ _('You can manage your bookmarks via Actions menu in your roster.'))
+
+ def on_join_button_clicked(self, *args):
+ """
+ When we want to join a conference: ask specific informations about the
+ selected agent and close the window
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ service = model[iter_][0].decode('utf-8')
+ room = model[iter_][1].decode('utf-8')
+ if 'join_gc' not in gajim.interface.instances[self.account]:
+ try:
+ dialogs.JoinGroupchatWindow(self.account, service)
+ except GajimGeneralException:
+ pass
+ else:
+ gajim.interface.instances[self.account]['join_gc'].window.present()
+ self.window.destroy(chain = True)
+
+ def update_actions(self):
+ sens = self.window.services_treeview.get_selection().count_selected_rows()
+ if self.bookmark_button:
+ self.bookmark_button.set_sensitive(sens > 0)
+ if self.join_button:
+ self.join_button.set_sensitive(sens > 0)
+
+ def default_action(self):
+ self.on_join_button_clicked()
+
+ def _start_info_query(self):
+ """
+ Idle callback to start checking for visible rows
+ """
+ self._fetch_source = None
+ self._query_visible()
+ return False
+
+ def on_scroll(self, *args):
+ """
+ Scrollwindow callback to trigger new queries on scolling
+ """
+ # This apparently happens when inactive sometimes
+ self._query_visible()
+
+ def _query_visible(self):
+ """
+ Query the next visible row for info
+ """
+ if self._fetch_source:
+ # We're already fetching
+ return
+ view = self.window.services_treeview
+ if not view.flags() & gtk.REALIZED:
+ # Prevent a silly warning, try again in a bit.
+ self._fetch_source = gobject.timeout_add(100, self._start_info_query)
+ return
+ # We have to do this in a pygtk <2.8 compatible way :/
+ #start, end = self.window.services_treeview.get_visible_range()
+ rect = view.get_visible_rect()
+ iter_ = end = None
+ # Top row
+ try:
+ sx, sy = view.tree_to_widget_coords(rect.x, rect.y)
+ spath = view.get_path_at_pos(sx, sy)[0]
+ iter_ = self.model.get_iter(spath)
+ except TypeError:
+ self._fetch_source = None
+ return
+ # Bottom row
+ # Iter compare is broke, use the path instead
+ try:
+ ex, ey = view.tree_to_widget_coords(rect.x + rect.height,
+ rect.y + rect.height)
+ end = view.get_path_at_pos(ex, ey)[0]
+ # end is the last visible, we want to query that aswell
+ end = (end[0] + 1,)
+ except TypeError:
+ # We're at the end of the model, we can leave end=None though.
+ pass
+ while iter_ and self.model.get_path(iter_) != end:
+ if not self.model.get_value(iter_, 6):
+ jid = self.model.get_value(iter_, 0).decode('utf-8')
+ node = self.model.get_value(iter_, 1).decode('utf-8')
+ self.cache.get_info(jid, node, self._agent_info)
+ self._fetch_source = True
+ return
+ iter_ = self.model.iter_next(iter_)
+ self._fetch_source = None
+
+ def _channel_altinfo(self, jid, node, items, name = None):
+ """
+ Callback for the alternate disco#items query. We try to atleast get the
+ amount of users in the room if the service does not support MUC dataforms
+ """
+ if items == 0:
+ # The server returned an error
+ self._broken += 1
+ if self._broken >= 3:
+ # Disable queries completely after 3 failures
+ if self.size_cbid:
+ self.window.services_scrollwin.disconnect(self.size_cbid)
+ self.size_cbid = None
+ if self.vadj_cbid:
+ self.vadj.disconnect(self.vadj_cbid)
+ self.vadj_cbid = None
+ self._fetch_source = None
+ return
+ else:
+ iter_ = self._find_item(jid, node)
+ if iter_:
+ if name:
+ self.model[iter_][2] = name
+ self.model[iter_][3] = len(items) # The number of users
+ self.model[iter_][4] = str(len(items)) # The number of users
+ self.model[iter_][6] = True
+ self._fetch_source = None
+ self._query_visible()
+
+ def _add_item(self, jid, node, parent_node, item, force):
+ self.model.append((jid, node, item.get('name', ''), -1, '', '', False))
+ if not self._fetch_source:
+ self._fetch_source = gobject.idle_add(self._start_info_query)
+
+ def _update_info(self, iter_, jid, node, identities, features, data):
+ name = identities[0].get('name', '')
+ for form in data:
+ typefield = form.getField('FORM_TYPE')
+ if typefield and typefield.getValue() == \
+ 'http://jabber.org/protocol/muc#roominfo':
+ # Fill model row from the form's fields
+ users = form.getField('muc#roominfo_occupants')
+ descr = form.getField('muc#roominfo_description')
+ if users:
+ self.model[iter_][3] = int(users.getValue())
+ self.model[iter_][4] = users.getValue()
+ if descr:
+ self.model[iter_][5] = descr.getValue()
+ # Only set these when we find a form with additional info
+ # Some servers don't support forms and put extra info in
+ # the name attribute, so we preserve it in that case.
+ self.model[iter_][2] = name
+ self.model[iter_][6] = True
+ break
+ else:
+ # We didn't find a form, switch to alternate query mode
+ self.cache.get_items(jid, node, self._channel_altinfo, args = (name,))
+ return
+ # Continue with the next
+ self._fetch_source = None
+ self._query_visible()
+
+ def _update_error(self, iter_, jid, node):
+ # switch to alternate query mode
+ self.cache.get_items(jid, node, self._channel_altinfo)
def PubSubBrowser(account, jid, node):
- """
- Return an AgentBrowser subclass that will display service discovery for
- particular pubsub service. Different pubsub services may need to present
- different data during browsing
- """
- # for now, only discussion groups are supported...
- # TODO: check if it has appropriate features to be such kind of service
- return DiscussionGroupsBrowser(account, jid, node)
+ """
+ Return an AgentBrowser subclass that will display service discovery for
+ particular pubsub service. Different pubsub services may need to present
+ different data during browsing
+ """
+ # for now, only discussion groups are supported...
+ # TODO: check if it has appropriate features to be such kind of service
+ return DiscussionGroupsBrowser(account, jid, node)
class DiscussionGroupsBrowser(AgentBrowser):
- """
- For browsing pubsub-based discussion groups service
- """
-
- def __init__(self, account, jid, node):
- AgentBrowser.__init__(self, account, jid, node)
-
- # this will become set object when we get subscriptions; None means
- # we don't know yet which groups are subscribed
- self.subscriptions = None
-
- # this will become our action widgets when we create them; None means
- # we don't have them yet (needed for check in callback)
- self.subscribe_button = None
- self.unsubscribe_button = None
-
- gajim.connections[account].send_pb_subscription_query(jid, self._on_pep_subscriptions)
-
- def _create_treemodel(self):
- """
- Create treemodel for the window
- """
- # JID, node, name (with description) - pango markup, dont have info?, subscribed?
- self.model = gtk.TreeStore(str, str, str, bool, bool)
- # sort by name
- self.model.set_sort_column_id(2, gtk.SORT_ASCENDING)
- self.window.services_treeview.set_model(self.model)
-
- # Name column
- # Pango markup for name and description, description printed with
- # <small/> font
- renderer = gtk.CellRendererText()
- col = gtk.TreeViewColumn(_('Name'))
- col.pack_start(renderer)
- col.set_attributes(renderer, markup=2)
- col.set_resizable(True)
- self.window.services_treeview.insert_column(col, -1)
- self.window.services_treeview.set_headers_visible(True)
-
- # Subscription state
- renderer = gtk.CellRendererToggle()
- col = gtk.TreeViewColumn(_('Subscribed'))
- col.pack_start(renderer)
- col.set_attributes(renderer, inconsistent=3, active=4)
- col.set_resizable(False)
- self.window.services_treeview.insert_column(col, -1)
-
- # Node Column
- renderer = gtk.CellRendererText()
- col = gtk.TreeViewColumn(_('Node'))
- col.pack_start(renderer)
- col.set_attributes(renderer, markup=1)
- col.set_resizable(True)
- self.window.services_treeview.insert_column(col, -1)
-
- def _add_items(self, jid, node, items, force):
- for item in items:
- jid_ = item['jid']
- node_ = item.get('node', '')
- self._total_items += 1
- self._add_item(jid_, node_, node, item, force)
-
- def _in_list_foreach(self, model, path, iter_, node):
- if model[path][1] == node:
- self.in_list = True
-
- def _in_list(self, node):
- self.in_list = False
- self.model.foreach(self._in_list_foreach, node)
- return self.in_list
-
- def _add_item(self, jid, node, parent_node, item, force):
- """
- Called when we got basic information about new node from query. Show the
- item
- """
- name = item.get('name', '')
-
- if self.subscriptions is not None:
- dunno = False
- subscribed = node in self.subscriptions
- else:
- dunno = True
- subscribed = False
-
- name = gobject.markup_escape_text(name)
- name = '<b>%s</b>' % name
-
- parent_iter = self._get_iter(parent_node)
- if not self._in_list(node):
- self.model.append(parent_iter, (jid, node, name, dunno, subscribed))
- self.cache.get_items(jid, node, self._add_items, force = force,
- args = (force,))
-
- def _get_child_iter(self, parent_iter, node):
- child_iter = self.model.iter_children(parent_iter)
- while child_iter:
- if self.model[child_iter][1] == node:
- return child_iter
- child_iter = self.model.iter_next(child_iter)
- return None
-
- def _get_iter(self, node):
- ''' Look for an iter with the given node '''
- self.found_iter = None
- def is_node(model, path, iter, node):
- if model[iter][1] == node:
- self.found_iter = iter
- return True
- self.model.foreach(is_node, node)
- return self.found_iter
-
- def _add_actions(self):
- self.post_button = gtk.Button(label=_('New post'), use_underline=True)
- self.post_button.set_sensitive(False)
- self.post_button.connect('clicked', self.on_post_button_clicked)
- self.window.action_buttonbox.add(self.post_button)
- self.post_button.show_all()
-
- self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True)
- self.subscribe_button.set_sensitive(False)
- self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked)
- self.window.action_buttonbox.add(self.subscribe_button)
- self.subscribe_button.show_all()
-
- self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True)
- self.unsubscribe_button.set_sensitive(False)
- self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked)
- self.window.action_buttonbox.add(self.unsubscribe_button)
- self.unsubscribe_button.show_all()
-
- def _clean_actions(self):
- if self.post_button is not None:
- self.post_button.destroy()
- self.post_button = None
-
- if self.subscribe_button is not None:
- self.subscribe_button.destroy()
- self.subscribe_button = None
-
- if self.unsubscribe_button is not None:
- self.unsubscribe_button.destroy()
- self.unsubscribe_button = None
-
- def update_actions(self):
- """
- Called when user selected a row. Make subscribe/unsubscribe buttons
- sensitive appropriatelly
- """
- # we have nothing to do if we don't have buttons...
- if self.subscribe_button is None: return
-
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_ or self.subscriptions is None:
- # no item selected or no subscriptions info, all buttons are insensitive
- self.post_button.set_sensitive(False)
- self.subscribe_button.set_sensitive(False)
- self.unsubscribe_button.set_sensitive(False)
- else:
- subscribed = model.get_value(iter_, 4) # 4 = subscribed?
- self.post_button.set_sensitive(subscribed)
- self.subscribe_button.set_sensitive(not subscribed)
- self.unsubscribe_button.set_sensitive(subscribed)
-
- def on_post_button_clicked(self, widget):
- """
- Called when 'post' button is pressed. Open window to create post
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if iter_ is None: return
-
- groupnode = model.get_value(iter_, 1) # 1 = groupnode
-
- groups.GroupsPostWindow(self.account, self.jid, groupnode)
-
- def on_subscribe_button_clicked(self, widget):
- """
- Called when 'subscribe' button is pressed. Send subscribtion request
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if iter_ is None: return
-
- groupnode = model.get_value(iter_, 1) # 1 = groupnode
-
- gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._on_pep_subscribe, groupnode)
-
- def on_unsubscribe_button_clicked(self, widget):
- """
- Called when 'unsubscribe' button is pressed. Send unsubscription request
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if iter_ is None: return
-
- groupnode = model.get_value(iter_, 1) # 1 = groupnode
-
- gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._on_pep_unsubscribe, groupnode)
-
- def _on_pep_subscriptions(self, conn, request):
- """
- We got the subscribed groups list stanza. Now, if we already have items
- on the list, we should actualize them
- """
- try:
- subscriptions = request.getTag('pubsub').getTag('subscriptions')
- except Exception:
- return
-
- groups = set()
- for child in subscriptions.getTags('subscription'):
- groups.add(child['node'])
-
- self.subscriptions = groups
-
- # try to setup existing items in model
- model = self.window.services_treeview.get_model()
- for row in model:
- # 1 = group node
- # 3 = insensitive checkbox for subscribed
- # 4 = subscribed?
- groupnode = row[1]
- row[3] = False
- row[4] = groupnode in groups
-
- # we now know subscriptions, update button states
- self.update_actions()
-
- raise xmpp.NodeProcessed
-
- def _on_pep_subscribe(self, conn, request, groupnode):
- """
- We have just subscribed to a node. Update UI
- """
- self.subscriptions.add(groupnode)
-
- model = self.window.services_treeview.get_model()
- for row in model:
- if row[1] == groupnode: # 1 = groupnode
- row[4] = True
- break
-
- self.update_actions()
-
- raise xmpp.NodeProcessed
-
- def _on_pep_unsubscribe(self, conn, request, groupnode):
- """
- We have just unsubscribed from a node. Update UI
- """
- self.subscriptions.remove(groupnode)
-
- model = self.window.services_treeview.get_model()
- for row in model:
- if row[1] == groupnode: # 1 = groupnode
- row[4]=False
- break
-
- self.update_actions()
-
- raise xmpp.NodeProcessed
+ """
+ For browsing pubsub-based discussion groups service
+ """
+
+ def __init__(self, account, jid, node):
+ AgentBrowser.__init__(self, account, jid, node)
+
+ # this will become set object when we get subscriptions; None means
+ # we don't know yet which groups are subscribed
+ self.subscriptions = None
+
+ # this will become our action widgets when we create them; None means
+ # we don't have them yet (needed for check in callback)
+ self.subscribe_button = None
+ self.unsubscribe_button = None
+
+ gajim.connections[account].send_pb_subscription_query(jid, self._on_pep_subscriptions)
+
+ def _create_treemodel(self):
+ """
+ Create treemodel for the window
+ """
+ # JID, node, name (with description) - pango markup, dont have info?, subscribed?
+ self.model = gtk.TreeStore(str, str, str, bool, bool)
+ # sort by name
+ self.model.set_sort_column_id(2, gtk.SORT_ASCENDING)
+ self.window.services_treeview.set_model(self.model)
+
+ # Name column
+ # Pango markup for name and description, description printed with
+ # <small/> font
+ renderer = gtk.CellRendererText()
+ col = gtk.TreeViewColumn(_('Name'))
+ col.pack_start(renderer)
+ col.set_attributes(renderer, markup=2)
+ col.set_resizable(True)
+ self.window.services_treeview.insert_column(col, -1)
+ self.window.services_treeview.set_headers_visible(True)
+
+ # Subscription state
+ renderer = gtk.CellRendererToggle()
+ col = gtk.TreeViewColumn(_('Subscribed'))
+ col.pack_start(renderer)
+ col.set_attributes(renderer, inconsistent=3, active=4)
+ col.set_resizable(False)
+ self.window.services_treeview.insert_column(col, -1)
+
+ # Node Column
+ renderer = gtk.CellRendererText()
+ col = gtk.TreeViewColumn(_('Node'))
+ col.pack_start(renderer)
+ col.set_attributes(renderer, markup=1)
+ col.set_resizable(True)
+ self.window.services_treeview.insert_column(col, -1)
+
+ def _add_items(self, jid, node, items, force):
+ for item in items:
+ jid_ = item['jid']
+ node_ = item.get('node', '')
+ self._total_items += 1
+ self._add_item(jid_, node_, node, item, force)
+
+ def _in_list_foreach(self, model, path, iter_, node):
+ if model[path][1] == node:
+ self.in_list = True
+
+ def _in_list(self, node):
+ self.in_list = False
+ self.model.foreach(self._in_list_foreach, node)
+ return self.in_list
+
+ def _add_item(self, jid, node, parent_node, item, force):
+ """
+ Called when we got basic information about new node from query. Show the
+ item
+ """
+ name = item.get('name', '')
+
+ if self.subscriptions is not None:
+ dunno = False
+ subscribed = node in self.subscriptions
+ else:
+ dunno = True
+ subscribed = False
+
+ name = gobject.markup_escape_text(name)
+ name = '<b>%s</b>' % name
+
+ parent_iter = self._get_iter(parent_node)
+ if not self._in_list(node):
+ self.model.append(parent_iter, (jid, node, name, dunno, subscribed))
+ self.cache.get_items(jid, node, self._add_items, force = force,
+ args = (force,))
+
+ def _get_child_iter(self, parent_iter, node):
+ child_iter = self.model.iter_children(parent_iter)
+ while child_iter:
+ if self.model[child_iter][1] == node:
+ return child_iter
+ child_iter = self.model.iter_next(child_iter)
+ return None
+
+ def _get_iter(self, node):
+ ''' Look for an iter with the given node '''
+ self.found_iter = None
+ def is_node(model, path, iter, node):
+ if model[iter][1] == node:
+ self.found_iter = iter
+ return True
+ self.model.foreach(is_node, node)
+ return self.found_iter
+
+ def _add_actions(self):
+ self.post_button = gtk.Button(label=_('New post'), use_underline=True)
+ self.post_button.set_sensitive(False)
+ self.post_button.connect('clicked', self.on_post_button_clicked)
+ self.window.action_buttonbox.add(self.post_button)
+ self.post_button.show_all()
+
+ self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True)
+ self.subscribe_button.set_sensitive(False)
+ self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked)
+ self.window.action_buttonbox.add(self.subscribe_button)
+ self.subscribe_button.show_all()
+
+ self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True)
+ self.unsubscribe_button.set_sensitive(False)
+ self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked)
+ self.window.action_buttonbox.add(self.unsubscribe_button)
+ self.unsubscribe_button.show_all()
+
+ def _clean_actions(self):
+ if self.post_button is not None:
+ self.post_button.destroy()
+ self.post_button = None
+
+ if self.subscribe_button is not None:
+ self.subscribe_button.destroy()
+ self.subscribe_button = None
+
+ if self.unsubscribe_button is not None:
+ self.unsubscribe_button.destroy()
+ self.unsubscribe_button = None
+
+ def update_actions(self):
+ """
+ Called when user selected a row. Make subscribe/unsubscribe buttons
+ sensitive appropriatelly
+ """
+ # we have nothing to do if we don't have buttons...
+ if self.subscribe_button is None: return
+
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if not iter_ or self.subscriptions is None:
+ # no item selected or no subscriptions info, all buttons are insensitive
+ self.post_button.set_sensitive(False)
+ self.subscribe_button.set_sensitive(False)
+ self.unsubscribe_button.set_sensitive(False)
+ else:
+ subscribed = model.get_value(iter_, 4) # 4 = subscribed?
+ self.post_button.set_sensitive(subscribed)
+ self.subscribe_button.set_sensitive(not subscribed)
+ self.unsubscribe_button.set_sensitive(subscribed)
+
+ def on_post_button_clicked(self, widget):
+ """
+ Called when 'post' button is pressed. Open window to create post
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if iter_ is None: return
+
+ groupnode = model.get_value(iter_, 1) # 1 = groupnode
+
+ groups.GroupsPostWindow(self.account, self.jid, groupnode)
+
+ def on_subscribe_button_clicked(self, widget):
+ """
+ Called when 'subscribe' button is pressed. Send subscribtion request
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if iter_ is None: return
+
+ groupnode = model.get_value(iter_, 1) # 1 = groupnode
+
+ gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._on_pep_subscribe, groupnode)
+
+ def on_unsubscribe_button_clicked(self, widget):
+ """
+ Called when 'unsubscribe' button is pressed. Send unsubscription request
+ """
+ model, iter_ = self.window.services_treeview.get_selection().get_selected()
+ if iter_ is None: return
+
+ groupnode = model.get_value(iter_, 1) # 1 = groupnode
+
+ gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._on_pep_unsubscribe, groupnode)
+
+ def _on_pep_subscriptions(self, conn, request):
+ """
+ We got the subscribed groups list stanza. Now, if we already have items
+ on the list, we should actualize them
+ """
+ try:
+ subscriptions = request.getTag('pubsub').getTag('subscriptions')
+ except Exception:
+ return
+
+ groups = set()
+ for child in subscriptions.getTags('subscription'):
+ groups.add(child['node'])
+
+ self.subscriptions = groups
+
+ # try to setup existing items in model
+ model = self.window.services_treeview.get_model()
+ for row in model:
+ # 1 = group node
+ # 3 = insensitive checkbox for subscribed
+ # 4 = subscribed?
+ groupnode = row[1]
+ row[3] = False
+ row[4] = groupnode in groups
+
+ # we now know subscriptions, update button states
+ self.update_actions()
+
+ raise xmpp.NodeProcessed
+
+ def _on_pep_subscribe(self, conn, request, groupnode):
+ """
+ We have just subscribed to a node. Update UI
+ """
+ self.subscriptions.add(groupnode)
+
+ model = self.window.services_treeview.get_model()
+ for row in model:
+ if row[1] == groupnode: # 1 = groupnode
+ row[4] = True
+ break
+
+ self.update_actions()
+
+ raise xmpp.NodeProcessed
+
+ def _on_pep_unsubscribe(self, conn, request, groupnode):
+ """
+ We have just unsubscribed from a node. Update UI
+ """
+ self.subscriptions.remove(groupnode)
+
+ model = self.window.services_treeview.get_model()
+ for row in model:
+ if row[1] == groupnode: # 1 = groupnode
+ row[4]=False
+ break
+
+ self.update_actions()
+
+ raise xmpp.NodeProcessed
# Fill the global agent type info dictionary
_agent_type_info = _gen_agent_type_info()
-
-# vim: se ts=3:
diff --git a/src/features_window.py b/src/features_window.py
index b737d4e37..e15c9b21d 100644
--- a/src/features_window.py
+++ b/src/features_window.py
@@ -33,226 +33,224 @@ from common import helpers
from common import kwalletbinding
class FeaturesWindow:
- """
- Class for features window
- """
+ """
+ Class for features window
+ """
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('features_window.ui')
- self.window = self.xml.get_object('features_window')
- treeview = self.xml.get_object('features_treeview')
- self.desc_label = self.xml.get_object('feature_desc_label')
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('features_window.ui')
+ self.window = self.xml.get_object('features_window')
+ treeview = self.xml.get_object('features_treeview')
+ self.desc_label = self.xml.get_object('feature_desc_label')
- # {name: (available_function, unix_text, windows_text)}
- self.features = {
- _('SSL certificat validation'): (self.pyopenssl_available,
- _('A library used to validate server certificates to ensure a secure connection.'),
- _('Requires python-pyopenssl.'),
- _('Requires python-pyopenssl.')),
- _('Bonjour / Zeroconf'): (self.zeroconf_available,
- _('Serverless chatting with autodetected clients in a local network.'),
- _('Requires python-avahi.'),
- _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')),
- _('Command line'): (self.dbus_available,
- _('A script to control Gajim via commandline.'),
- _('Requires python-dbus.'),
- _('Feature not available under Windows.')),
- _('OpenGPG message encryption'): (self.gpg_available,
- _('Encrypting chat messages with gpg keys.'),
- _('Requires gpg and python-GnuPGInterface.'),
- _('Feature not available under Windows.')),
- _('Network-manager'): (self.network_manager_available,
- _('Autodetection of network status.'),
- _('Requires gnome-network-manager and python-dbus.'),
- _('Feature not available under Windows.')),
- _('Session Management'): (self.session_management_available,
- _('Gajim session is stored on logout and restored on login.'),
- _('Requires python-gnome2.'),
- _('Feature not available under Windows.')),
- _('Password encryption'): (self.some_keyring_available,
- _('Passwords can be stored securely and not just in plaintext.'),
- _('Requires gnome-keyring and python-gnome2-desktop, or kwalletcli.'),
- _('Feature not available under Windows.')),
- _('SRV'): (self.srv_available,
- _('Ability to connect to servers which are using SRV records.'),
- _('Requires dnsutils.'),
- _('Requires nslookup to use SRV records.')),
- _('Spell Checker'): (self.speller_available,
- _('Spellchecking of composed messages.'),
- _('Requires libgtkspell.'),
- _('Feature not available under Windows.')),
- _('Notification'): (self.notification_available,
- _('Passive popups notifying for new events.'),
- _('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'),
- _('Feature not available under Windows.')),
- _('Automatic status'): (self.idle_available,
- _('Ability to measure idle time, in order to set auto status.'),
- _('Requires libxss library.'),
- _('Requires python2.5.')),
- _('LaTeX'): (self.latex_available,
- _('Transform LaTeX expressions between $$ $$.'),
- _('Requires texlive-latex-base and dvipng. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'),
- _('Requires texlive-latex-base and dvipng (All is in MikTeX). You have to set \'use_latex\' to True in the Advanced Configuration Editor.')),
- _('End to End message encryption'): (self.pycrypto_available,
- _('Encrypting chat messages.'),
- _('Requires python-crypto.'),
- _('Requires python-crypto.')),
- _('RST Generator'): (self.docutils_available,
- _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
- _('Requires python-docutils.'),
- _('Requires python-docutils.')),
- _('Audio / Video'): (self.farsight_available,
- _('Ability to start audio and video chat.'),
- _('Requires python-farsight.'),
- _('Feature not available under Windows.')),
- }
+ # {name: (available_function, unix_text, windows_text)}
+ self.features = {
+ _('SSL certificat validation'): (self.pyopenssl_available,
+ _('A library used to validate server certificates to ensure a secure connection.'),
+ _('Requires python-pyopenssl.'),
+ _('Requires python-pyopenssl.')),
+ _('Bonjour / Zeroconf'): (self.zeroconf_available,
+ _('Serverless chatting with autodetected clients in a local network.'),
+ _('Requires python-avahi.'),
+ _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')),
+ _('Command line'): (self.dbus_available,
+ _('A script to control Gajim via commandline.'),
+ _('Requires python-dbus.'),
+ _('Feature not available under Windows.')),
+ _('OpenGPG message encryption'): (self.gpg_available,
+ _('Encrypting chat messages with gpg keys.'),
+ _('Requires gpg and python-GnuPGInterface.'),
+ _('Feature not available under Windows.')),
+ _('Network-manager'): (self.network_manager_available,
+ _('Autodetection of network status.'),
+ _('Requires gnome-network-manager and python-dbus.'),
+ _('Feature not available under Windows.')),
+ _('Session Management'): (self.session_management_available,
+ _('Gajim session is stored on logout and restored on login.'),
+ _('Requires python-gnome2.'),
+ _('Feature not available under Windows.')),
+ _('Password encryption'): (self.some_keyring_available,
+ _('Passwords can be stored securely and not just in plaintext.'),
+ _('Requires gnome-keyring and python-gnome2-desktop, or kwalletcli.'),
+ _('Feature not available under Windows.')),
+ _('SRV'): (self.srv_available,
+ _('Ability to connect to servers which are using SRV records.'),
+ _('Requires dnsutils.'),
+ _('Requires nslookup to use SRV records.')),
+ _('Spell Checker'): (self.speller_available,
+ _('Spellchecking of composed messages.'),
+ _('Requires libgtkspell.'),
+ _('Feature not available under Windows.')),
+ _('Notification'): (self.notification_available,
+ _('Passive popups notifying for new events.'),
+ _('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'),
+ _('Feature not available under Windows.')),
+ _('Automatic status'): (self.idle_available,
+ _('Ability to measure idle time, in order to set auto status.'),
+ _('Requires libxss library.'),
+ _('Requires python2.5.')),
+ _('LaTeX'): (self.latex_available,
+ _('Transform LaTeX expressions between $$ $$.'),
+ _('Requires texlive-latex-base and dvipng. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'),
+ _('Requires texlive-latex-base and dvipng (All is in MikTeX). You have to set \'use_latex\' to True in the Advanced Configuration Editor.')),
+ _('End to End message encryption'): (self.pycrypto_available,
+ _('Encrypting chat messages.'),
+ _('Requires python-crypto.'),
+ _('Requires python-crypto.')),
+ _('RST Generator'): (self.docutils_available,
+ _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
+ _('Requires python-docutils.'),
+ _('Requires python-docutils.')),
+ _('Audio / Video'): (self.farsight_available,
+ _('Ability to start audio and video chat.'),
+ _('Requires python-farsight.'),
+ _('Feature not available under Windows.')),
+ }
- # name, supported
- self.model = gtk.ListStore(str, bool)
- treeview.set_model(self.model)
+ # name, supported
+ self.model = gtk.ListStore(str, bool)
+ treeview.set_model(self.model)
- col = gtk.TreeViewColumn(_('Available'))
- treeview.append_column(col)
- cell = gtk.CellRendererToggle()
- cell.set_property('radio', True)
- col.pack_start(cell)
- col.set_attributes(cell, active = 1)
+ col = gtk.TreeViewColumn(_('Available'))
+ treeview.append_column(col)
+ cell = gtk.CellRendererToggle()
+ cell.set_property('radio', True)
+ col.pack_start(cell)
+ col.set_attributes(cell, active = 1)
- col = gtk.TreeViewColumn(_('Feature'))
- treeview.append_column(col)
- cell = gtk.CellRendererText()
- col.pack_start(cell, expand = True)
- col.add_attribute(cell, 'text', 0)
+ col = gtk.TreeViewColumn(_('Feature'))
+ treeview.append_column(col)
+ cell = gtk.CellRendererText()
+ col.pack_start(cell, expand = True)
+ col.add_attribute(cell, 'text', 0)
- # Fill model
- for feature in self.features:
- func = self.features[feature][0]
- rep = func()
- self.model.append([feature, rep])
+ # Fill model
+ for feature in self.features:
+ func = self.features[feature][0]
+ rep = func()
+ self.model.append([feature, rep])
- self.model.set_sort_column_id(0, gtk.SORT_ASCENDING)
+ self.model.set_sort_column_id(0, gtk.SORT_ASCENDING)
- self.xml.connect_signals(self)
- self.window.show_all()
- self.xml.get_object('close_button').grab_focus()
+ self.xml.connect_signals(self)
+ self.window.show_all()
+ self.xml.get_object('close_button').grab_focus()
- def on_close_button_clicked(self, widget):
- self.window.destroy()
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
- def on_features_treeview_cursor_changed(self, widget):
- selection = widget.get_selection()
- if not selection:
- return
- rows = selection.get_selected_rows()[1]
- if not rows:
- return
- path = rows[0]
- feature = self.model[path][0].decode('utf-8')
- text = self.features[feature][1] + '\n'
- if os.name == 'nt':
- text = text + self.features[feature][3]
- else:
- text = text + self.features[feature][2]
- self.desc_label.set_text(text)
+ def on_features_treeview_cursor_changed(self, widget):
+ selection = widget.get_selection()
+ if not selection:
+ return
+ rows = selection.get_selected_rows()[1]
+ if not rows:
+ return
+ path = rows[0]
+ feature = self.model[path][0].decode('utf-8')
+ text = self.features[feature][1] + '\n'
+ if os.name == 'nt':
+ text = text + self.features[feature][3]
+ else:
+ text = text + self.features[feature][2]
+ self.desc_label.set_text(text)
- def pyopenssl_available(self):
- try:
- import OpenSSL.SSL
- import OpenSSL.crypto
- except Exception:
- return False
- return True
+ def pyopenssl_available(self):
+ try:
+ import OpenSSL.SSL
+ import OpenSSL.crypto
+ except Exception:
+ return False
+ return True
- def zeroconf_available(self):
- try:
- import avahi
- except Exception:
- try:
- import pybonjour
- except Exception:
- return False
- return True
+ def zeroconf_available(self):
+ try:
+ import avahi
+ except Exception:
+ try:
+ import pybonjour
+ except Exception:
+ return False
+ return True
- def dbus_available(self):
- if os.name == 'nt':
- return False
- from common import dbus_support
- return dbus_support.supported
+ def dbus_available(self):
+ if os.name == 'nt':
+ return False
+ from common import dbus_support
+ return dbus_support.supported
- def gpg_available(self):
- if os.name == 'nt':
- return False
- return gajim.HAVE_GPG
+ def gpg_available(self):
+ if os.name == 'nt':
+ return False
+ return gajim.HAVE_GPG
- def network_manager_available(self):
- if os.name == 'nt':
- return False
- import network_manager_listener
- return network_manager_listener.supported
+ def network_manager_available(self):
+ if os.name == 'nt':
+ return False
+ import network_manager_listener
+ return network_manager_listener.supported
- def session_management_available(self):
- if os.name == 'nt':
- return False
- try:
- import gnome.ui
- except Exception:
- return False
- return True
+ def session_management_available(self):
+ if os.name == 'nt':
+ return False
+ try:
+ import gnome.ui
+ except Exception:
+ return False
+ return True
- def some_keyring_available(self):
- if os.name == 'nt':
- return False
- if kwalletbinding.kwallet_available():
- return True
- try:
- import gnomekeyring
- except Exception:
- return False
- return True
+ def some_keyring_available(self):
+ if os.name == 'nt':
+ return False
+ if kwalletbinding.kwallet_available():
+ return True
+ try:
+ import gnomekeyring
+ except Exception:
+ return False
+ return True
- def srv_available(self):
- return helpers.is_in_path('nslookup')
+ def srv_available(self):
+ return helpers.is_in_path('nslookup')
- def speller_available(self):
- if os.name == 'nt':
- return False
- try:
- import gtkspell
- except ImportError:
- return False
- return True
+ def speller_available(self):
+ if os.name == 'nt':
+ return False
+ try:
+ import gtkspell
+ except ImportError:
+ return False
+ return True
- def notification_available(self):
- if os.name == 'nt':
- return False
- from common import dbus_support
- if self.dbus_available() and dbus_support.get_notifications_interface():
- return True
- try:
- import pynotify
- except Exception:
- return False
- return True
+ def notification_available(self):
+ if os.name == 'nt':
+ return False
+ from common import dbus_support
+ if self.dbus_available() and dbus_support.get_notifications_interface():
+ return True
+ try:
+ import pynotify
+ except Exception:
+ return False
+ return True
- def idle_available(self):
- from common import sleepy
- return sleepy.SUPPORTED
+ def idle_available(self):
+ from common import sleepy
+ return sleepy.SUPPORTED
- def latex_available(self):
- from common import latex
- return latex.check_for_latex_support()
+ def latex_available(self):
+ from common import latex
+ return latex.check_for_latex_support()
- def pycrypto_available(self):
- return gajim.HAVE_PYCRYPTO
+ def pycrypto_available(self):
+ return gajim.HAVE_PYCRYPTO
- def docutils_available(self):
- try:
- import docutils
- except Exception:
- return False
- return True
+ def docutils_available(self):
+ try:
+ import docutils
+ except Exception:
+ return False
+ return True
- def farsight_available(self):
- return gajim.HAVE_FARSIGHT
-
-# vim: se ts=3:
+ def farsight_available(self):
+ return gajim.HAVE_FARSIGHT
diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py
index b1ec59f1d..66a70fcaf 100644
--- a/src/filetransfers_window.py
+++ b/src/filetransfers_window.py
@@ -34,7 +34,7 @@ import dialogs
from common import gajim
from common import helpers
from common.protocol.bytestream import (is_transfer_active, is_transfer_paused,
- is_transfer_stopped)
+ is_transfer_stopped)
C_IMAGE = 0
C_LABELS = 1
@@ -46,918 +46,916 @@ C_SID = 6
class FileTransfersWindow:
- def __init__(self):
- self.files_props = {'r' : {}, 's': {}}
- self.height_diff = 0
- self.xml = gtkgui_helpers.get_gtk_builder('filetransfers.ui')
- self.window = self.xml.get_object('file_transfers_window')
- self.tree = self.xml.get_object('transfers_list')
- self.cancel_button = self.xml.get_object('cancel_button')
- self.pause_button = self.xml.get_object('pause_restore_button')
- self.cleanup_button = self.xml.get_object('cleanup_button')
- self.notify_ft_checkbox = self.xml.get_object(
- 'notify_ft_complete_checkbox')
-
- shall_notify = gajim.config.get('notify_on_file_complete')
- self.notify_ft_checkbox.set_active(shall_notify
- )
- self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str)
- self.tree.set_model(self.model)
- col = gtk.TreeViewColumn()
-
- render_pixbuf = gtk.CellRendererPixbuf()
-
- col.pack_start(render_pixbuf, expand=True)
- render_pixbuf.set_property('xpad', 3)
- render_pixbuf.set_property('ypad', 3)
- render_pixbuf.set_property('yalign', .0)
- col.add_attribute(render_pixbuf, 'pixbuf', 0)
- self.tree.append_column(col)
-
- col = gtk.TreeViewColumn(_('File'))
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, expand=False)
- col.add_attribute(renderer, 'markup' , C_LABELS)
- renderer.set_property('yalign', 0.)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, expand=True)
- col.add_attribute(renderer, 'markup' , C_FILE)
- renderer.set_property('xalign', 0.)
- renderer.set_property('yalign', 0.)
- renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
- col.set_resizable(True)
- col.set_expand(True)
- self.tree.append_column(col)
-
- col = gtk.TreeViewColumn(_('Time'))
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, expand=False)
- col.add_attribute(renderer, 'markup' , C_TIME)
- renderer.set_property('yalign', 0.5)
- renderer.set_property('xalign', 0.5)
- renderer = gtk.CellRendererText()
- renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
- col.set_resizable(True)
- col.set_expand(False)
- self.tree.append_column(col)
-
- col = gtk.TreeViewColumn(_('Progress'))
- renderer = gtk.CellRendererProgress()
- renderer.set_property('yalign', 0.5)
- renderer.set_property('xalign', 0.5)
- col.pack_start(renderer, expand=False)
- col.add_attribute(renderer, 'text' , C_PROGRESS)
- col.add_attribute(renderer, 'value' , C_PERCENT)
- col.set_resizable(True)
- col.set_expand(False)
- self.tree.append_column(col)
-
- self.images = {}
- self.icons = {
- 'upload': gtk.STOCK_GO_UP,
- 'download': gtk.STOCK_GO_DOWN,
- 'stop': gtk.STOCK_STOP,
- 'waiting': gtk.STOCK_REFRESH,
- 'pause': gtk.STOCK_MEDIA_PAUSE,
- 'continue': gtk.STOCK_MEDIA_PLAY,
- 'ok': gtk.STOCK_APPLY,
- }
-
- self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
- self.tree.get_selection().connect('changed', self.selection_changed)
- self.tooltip = tooltips.FileTransfersTooltip()
- self.file_transfers_menu = self.xml.get_object('file_transfers_menu')
- self.open_folder_menuitem = self.xml.get_object('open_folder_menuitem')
- self.cancel_menuitem = self.xml.get_object('cancel_menuitem')
- self.pause_menuitem = self.xml.get_object('pause_menuitem')
- self.continue_menuitem = self.xml.get_object('continue_menuitem')
- self.remove_menuitem = self.xml.get_object('remove_menuitem')
- self.xml.connect_signals(self)
-
- def find_transfer_by_jid(self, account, jid):
- """
- Find all transfers with peer 'jid' that belong to 'account'
- """
- active_transfers = [[], []] # ['senders', 'receivers']
-
- # 'account' is the sender
- for file_props in self.files_props['s'].values():
- if file_props['tt_account'] == account:
- receiver_jid = unicode(file_props['receiver']).split('/')[0]
- if jid == receiver_jid:
- if not is_transfer_stopped(file_props):
- active_transfers[0].append(file_props)
-
- # 'account' is the recipient
- for file_props in self.files_props['r'].values():
- if file_props['tt_account'] == account:
- sender_jid = unicode(file_props['sender']).split('/')[0]
- if jid == sender_jid:
- if not is_transfer_stopped(file_props):
- active_transfers[1].append(file_props)
- return active_transfers
-
- def show_completed(self, jid, file_props):
- """
- Show a dialog saying that file (file_props) has been transferred
- """
- def on_open(widget, file_props):
- dialog.destroy()
- if 'file-name' not in file_props:
- return
- path = os.path.split(file_props['file-name'])[0]
- if os.path.exists(path) and os.path.isdir(path):
- helpers.launch_file_manager(path)
- self.tree.get_selection().unselect_all()
-
- if file_props['type'] == 'r':
- # file path is used below in 'Save in'
- (file_path, file_name) = os.path.split(file_props['file-name'])
- else:
- file_name = file_props['name']
- sectext = '\t' + _('Filename: %s') % file_name
- sectext += '\n\t' + _('Size: %s') % \
- helpers.convert_bytes(file_props['size'])
- if file_props['type'] == 'r':
- jid = unicode(file_props['sender']).split('/')[0]
- sender_name = gajim.contacts.get_first_contact_from_jid(
- file_props['tt_account'], jid).get_shown_name()
- sender = sender_name
- else:
- #You is a reply of who sent a file
- sender = _('You')
- sectext += '\n\t' + _('Sender: %s') % sender
- sectext += '\n\t' + _('Recipient: ')
- if file_props['type'] == 's':
- jid = unicode(file_props['receiver']).split('/')[0]
- receiver_name = gajim.contacts.get_first_contact_from_jid(
- file_props['tt_account'], jid).get_shown_name()
- recipient = receiver_name
- else:
- #You is a reply of who received a file
- recipient = _('You')
- sectext += recipient
- if file_props['type'] == 'r':
- sectext += '\n\t' + _('Saved in: %s') % file_path
- dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
- _('File transfer completed'), sectext)
- if file_props['type'] == 'r':
- button = gtk.Button(_('_Open Containing Folder'))
- button.connect('clicked', on_open, file_props)
- dialog.action_area.pack_start(button)
- ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
- def on_ok(widget):
- dialog.destroy()
- ok_button.connect('clicked', on_ok)
- dialog.show_all()
-
- def show_request_error(self, file_props):
- """
- Show error dialog to the recipient saying that transfer has been canceled
- """
- dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.'))
- self.tree.get_selection().unselect_all()
-
- def show_send_error(self, file_props):
- """
- Show error dialog to the sender saying that transfer has been canceled
- """
- dialogs.InformationDialog(_('File transfer cancelled'),
- _('Connection with peer cannot be established.'))
- self.tree.get_selection().unselect_all()
-
- def show_stopped(self, jid, file_props, error_msg=''):
- if file_props['type'] == 'r':
- file_name = os.path.basename(file_props['file-name'])
- else:
- file_name = file_props['name']
- sectext = '\t' + _('Filename: %s') % file_name
- sectext += '\n\t' + _('Recipient: %s') % jid
- if error_msg:
- sectext += '\n\t' + _('Error message: %s') % error_msg
- dialogs.ErrorDialog(_('File transfer stopped'), sectext)
- self.tree.get_selection().unselect_all()
-
- def show_file_send_request(self, account, contact):
- desc_entry = gtk.Entry()
-
- def on_ok(widget):
- file_dir = None
- files_path_list = dialog.get_filenames()
- files_path_list = gtkgui_helpers.decode_filechooser_file_paths(
- files_path_list)
- desc = desc_entry.get_text()
- for file_path in files_path_list:
- if self.send_file(account, contact, file_path, desc) and file_dir is None:
- file_dir = os.path.dirname(file_path)
- if file_dir:
- gajim.config.set('last_send_dir', file_dir)
- dialog.destroy()
-
- dialog = dialogs.FileChooserDialog(_('Choose File to Send...'),
- gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
- gtk.RESPONSE_OK,
- True, # select multiple true as we can select many files to send
- gajim.config.get('last_send_dir'),
- on_response_ok=on_ok,
- on_response_cancel=lambda e:dialog.destroy()
- )
-
- btn = gtk.Button(_('_Send'))
- btn.set_property('can-default', True)
- # FIXME: add send icon to this button (JUMP_TO)
- dialog.add_action_widget(btn, gtk.RESPONSE_OK)
- dialog.set_default_response(gtk.RESPONSE_OK)
-
- desc_hbox = gtk.HBox(False, 5)
- desc_hbox.pack_start(gtk.Label(_('Description: ')), False, False, 0)
- desc_hbox.pack_start(desc_entry, True, True, 0)
-
- dialog.vbox.pack_start(desc_hbox, False, False, 0)
-
- btn.show()
- desc_hbox.show_all()
-
- def send_file(self, account, contact, file_path, file_desc=''):
- """
- Start the real transfer(upload) of the file
- """
- if gtkgui_helpers.file_is_locked(file_path):
- pritext = _('Gajim cannot access this file')
- sextext = _('This file is being used by another process.')
- dialogs.ErrorDialog(pritext, sextext)
- return
-
- if isinstance(contact, str):
- if contact.find('/') == -1:
- return
- (jid, resource) = contact.split('/', 1)
- contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource)
- file_name = os.path.split(file_path)[1]
- file_props = self.get_send_file_props(account, contact,
- file_path, file_name, file_desc)
- if file_props is None:
- return False
- self.add_transfer(account, contact, file_props)
- gajim.connections[account].send_file_request(file_props)
- return True
-
- def _start_receive(self, file_path, account, contact, file_props):
- file_dir = os.path.dirname(file_path)
- if file_dir:
- gajim.config.set('last_save_dir', file_dir)
- file_props['file-name'] = file_path
- self.add_transfer(account, contact, file_props)
- gajim.connections[account].send_file_approval(file_props)
-
- def show_file_request(self, account, contact, file_props):
- """
- Show dialog asking for comfirmation and store location of new file
- requested by a contact
- """
- if file_props is None or 'name' not in file_props:
- return
- sec_text = '\t' + _('File: %s') % gobject.markup_escape_text(
- file_props['name'])
- if 'size' in file_props:
- sec_text += '\n\t' + _('Size: %s') % \
- helpers.convert_bytes(file_props['size'])
- if 'mime-type' in file_props:
- sec_text += '\n\t' + _('Type: %s') % file_props['mime-type']
- if 'desc' in file_props:
- sec_text += '\n\t' + _('Description: %s') % file_props['desc']
- prim_text = _('%s wants to send you a file:') % contact.jid
- dialog = None
-
- def on_response_ok(account, contact, file_props):
-
- def on_ok(widget, account, contact, file_props):
- file_path = dialog2.get_filename()
- file_path = gtkgui_helpers.decode_filechooser_file_paths(
- (file_path,))[0]
- if os.path.exists(file_path):
- # check if we have write permissions
- if not os.access(file_path, os.W_OK):
- file_name = os.path.basename(file_path)
- dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % file_name),
- _('A file with this name already exists and you do not have permission to overwrite it.'))
- return
- stat = os.stat(file_path)
- dl_size = stat.st_size
- file_size = file_props['size']
- dl_finished = dl_size >= file_size
-
- def on_response(response):
- if response < 0:
- return
- elif response == 100:
- file_props['offset'] = dl_size
- dialog2.destroy()
- self._start_receive(file_path, account, contact, file_props)
-
- dialog = dialogs.FTOverwriteConfirmationDialog(
- _('This file already exists'), _('What do you want to do?'),
- propose_resume=not dl_finished, on_response=on_response)
- dialog.set_transient_for(dialog2)
- dialog.set_destroy_with_parent(True)
- return
- else:
- dirname = os.path.dirname(file_path)
- if not os.access(dirname, os.W_OK) and os.name != 'nt':
- # read-only bit is used to mark special folder under windows,
- # not to mark that a folder is read-only. See ticket #3587
- dialogs.ErrorDialog(_('Directory "%s" is not writable') % dirname, _('You do not have permission to create files in this directory.'))
- return
- dialog2.destroy()
- self._start_receive(file_path, account, contact, file_props)
-
- def on_cancel(widget, account, contact, file_props):
- dialog2.destroy()
- gajim.connections[account].send_file_rejection(file_props)
-
- dialog2 = dialogs.FileChooserDialog(
- title_text=_('Save File as...'),
- action=gtk.FILE_CHOOSER_ACTION_SAVE,
- buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
- gtk.STOCK_SAVE, gtk.RESPONSE_OK),
- default_response=gtk.RESPONSE_OK,
- current_folder=gajim.config.get('last_save_dir'),
- on_response_ok=(on_ok, account, contact, file_props),
- on_response_cancel=(on_cancel, account, contact, file_props))
-
- dialog2.set_current_name(file_props['name'])
- dialog2.connect('delete-event', lambda widget, event:
- on_cancel(widget, account, contact, file_props))
-
- def on_response_cancel(account, file_props):
- gajim.connections[account].send_file_rejection(file_props)
-
- dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
- on_response_ok=(on_response_ok, account, contact, file_props),
- on_response_cancel=(on_response_cancel, account, file_props))
- dialog.connect('delete-event', lambda widget, event:
- on_response_cancel(widget, account, file_props))
- dialog.popup()
-
- def get_icon(self, ident):
- return self.images.setdefault(ident,
- self.window.render_icon(self.icons[ident], gtk.ICON_SIZE_MENU))
-
- def set_status(self, typ, sid, status):
- """
- Change the status of a transfer to state 'status'
- """
- iter_ = self.get_iter_by_sid(typ, sid)
- if iter_ is None:
- return
- sid = self.model[iter_][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- if status == 'stop':
- file_props['stopped'] = True
- elif status == 'ok':
- file_props['completed'] = True
- self.model.set(iter_, C_IMAGE, self.get_icon(status))
- path = self.model.get_path(iter_)
- self.select_func(path)
-
- def _format_percent(self, percent):
- """
- Add extra spaces from both sides of the percent, so that progress string
- has always a fixed size
- """
- _str = ' '
- if percent != 100.:
- _str += ' '
- if percent < 10:
- _str += ' '
- _str += unicode(percent) + '% \n'
- return _str
-
- def _format_time(self, _time):
- times = { 'hours': 0, 'minutes': 0, 'seconds': 0 }
- _time = int(_time)
- times['seconds'] = _time % 60
- if _time >= 60:
- _time /= 60
- times['minutes'] = _time % 60
- if _time >= 60:
- times['hours'] = _time / 60
-
- #Print remaining time in format 00:00:00
- #You can change the places of (hours), (minutes), (seconds) -
- #they are not translatable.
- return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times
-
- def _get_eta_and_speed(self, full_size, transfered_size, file_props):
- if len(file_props['transfered_size']) == 0:
- return 0., 0.
- elif len(file_props['transfered_size']) == 1:
- speed = round(float(transfered_size) / file_props['elapsed-time'])
- else:
- # first and last are (time, transfered_size)
- first = file_props['transfered_size'][0]
- last = file_props['transfered_size'][-1]
- transfered = last[1] - first[1]
- tim = last[0] - first[0]
- if tim == 0:
- return 0., 0.
- speed = round(float(transfered) / tim)
- if speed == 0.:
- return 0., 0.
- remaining_size = full_size - transfered_size
- eta = remaining_size / speed
- return eta, speed
-
- def _remove_transfer(self, iter_, sid, file_props):
- self.model.remove(iter_)
- if 'tt_account' in file_props:
- # file transfer is set
- account = file_props['tt_account']
- if account in gajim.connections:
- # there is a connection to the account
- gajim.connections[account].remove_transfer(file_props)
- if file_props['type'] == 'r': # we receive a file
- other = file_props['sender']
- else: # we send a file
- other = file_props['receiver']
- if isinstance(other, unicode):
- jid = gajim.get_jid_without_resource(other)
- else: # It's a Contact instance
- jid = other.jid
- for ev_type in ('file-error', 'file-completed', 'file-request-error',
- 'file-send-error', 'file-stopped'):
- for event in gajim.events.get_events(account, jid, [ev_type]):
- if event.parameters['sid'] == file_props['sid']:
- gajim.events.remove_events(account, jid, event)
- gajim.interface.roster.draw_contact(jid, account)
- gajim.interface.roster.show_title()
- del(self.files_props[sid[0]][sid[1:]])
- del(file_props)
-
- def set_progress(self, typ, sid, transfered_size, iter_=None):
- """
- Change the progress of a transfer with new transfered size
- """
- if sid not in self.files_props[typ]:
- return
- file_props = self.files_props[typ][sid]
- full_size = int(file_props['size'])
- if full_size == 0:
- percent = 0
- else:
- percent = round(float(transfered_size) / full_size * 100, 1)
- if iter_ is None:
- iter_ = self.get_iter_by_sid(typ, sid)
- if iter_ is not None:
- just_began = False
- if self.model[iter_][C_PERCENT] == 0 and int(percent > 0):
- just_began = True
- text = self._format_percent(percent)
- if transfered_size == 0:
- text += '0'
- else:
- text += helpers.convert_bytes(transfered_size)
- text += '/' + helpers.convert_bytes(full_size)
- # Kb/s
-
- # remaining time
- if 'offset' in file_props and file_props['offset']:
- transfered_size -= file_props['offset']
- full_size -= file_props['offset']
-
- if file_props['elapsed-time'] > 0:
- file_props['transfered_size'].append((file_props['last-time'], transfered_size))
- if len(file_props['transfered_size']) > 6:
- file_props['transfered_size'].pop(0)
- eta, speed = self._get_eta_and_speed(full_size, transfered_size,
- file_props)
-
- self.model.set(iter_, C_PROGRESS, text)
- self.model.set(iter_, C_PERCENT, int(percent))
- text = self._format_time(eta)
- text += '\n'
- #This should make the string Kb/s,
- #where 'Kb' part is taken from %s.
- #Only the 's' after / (which means second) should be translated.
- text += _('(%(filesize_unit)s/s)') % {'filesize_unit':
- helpers.convert_bytes(speed)}
- self.model.set(iter_, C_TIME, text)
-
- # try to guess what should be the status image
- if file_props['type'] == 'r':
- status = 'download'
- else:
- status = 'upload'
- if 'paused' in file_props and file_props['paused'] == True:
- status = 'pause'
- elif 'stalled' in file_props and file_props['stalled'] == True:
- status = 'waiting'
- if 'connected' in file_props and file_props['connected'] == False:
- status = 'stop'
- self.model.set(iter_, 0, self.get_icon(status))
- if transfered_size == full_size:
- self.set_status(typ, sid, 'ok')
- elif just_began:
- path = self.model.get_path(iter_)
- self.select_func(path)
-
- def get_iter_by_sid(self, typ, sid):
- """
- Return iter to the row, which holds file transfer, identified by the
- session id
- """
- iter_ = self.model.get_iter_root()
- while iter_:
- if typ + sid == self.model[iter_][C_SID].decode('utf-8'):
- return iter_
- iter_ = self.model.iter_next(iter_)
-
- def get_send_file_props(self, account, contact, file_path, file_name,
- file_desc=''):
- """
- Create new file_props dict and set initial file transfer properties in it
- """
- file_props = {'file-name' : file_path, 'name' : file_name,
- 'type' : 's', 'desc' : file_desc}
- if os.path.isfile(file_path):
- stat = os.stat(file_path)
- else:
- dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path)
- return None
- if stat[6] == 0:
- dialogs.ErrorDialog(_('Invalid File'),
- _('It is not possible to send empty files'))
- return None
- file_props['elapsed-time'] = 0
- file_props['size'] = unicode(stat[6])
- file_props['sid'] = helpers.get_random_string_16()
- file_props['completed'] = False
- file_props['started'] = False
- file_props['sender'] = account
- file_props['receiver'] = contact
- file_props['tt_account'] = account
- # keep the last time: transfered_size to compute transfer speed
- file_props['transfered_size'] = []
- return file_props
-
- def add_transfer(self, account, contact, file_props):
- """
- Add new transfer to FT window and show the FT window
- """
- self.on_transfers_list_leave_notify_event(None)
- if file_props is None:
- return
- file_props['elapsed-time'] = 0
- self.files_props[file_props['type']][file_props['sid']] = file_props
- iter_ = self.model.prepend()
- text_labels = '<b>' + _('Name: ') + '</b>\n'
- if file_props['type'] == 'r':
- text_labels += '<b>' + _('Sender: ') + '</b>'
- else:
- text_labels += '<b>' + _('Recipient: ') + '</b>'
-
- if file_props['type'] == 'r':
- file_name = os.path.split(file_props['file-name'])[1]
- else:
- file_name = file_props['name']
- text_props = gobject.markup_escape_text(file_name) + '\n'
- text_props += contact.get_shown_name()
- self.model.set(iter_, 1, text_labels, 2, text_props, C_SID,
- file_props['type'] + file_props['sid'])
- self.set_progress(file_props['type'], file_props['sid'], 0, iter_)
- if 'started' in file_props and file_props['started'] is False:
- status = 'waiting'
- elif file_props['type'] == 'r':
- status = 'download'
- else:
- status = 'upload'
- file_props['tt_account'] = account
- self.set_status(file_props['type'], file_props['sid'], status)
- self.set_cleanup_sensitivity()
- self.window.show_all()
-
- def on_transfers_list_motion_notify_event(self, widget, event):
- pointer = self.tree.get_pointer()
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- self.height_diff = pointer[1] - int(event.y)
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id != props[0]:
- self.tooltip.hide_tooltip()
- if props:
- row = props[0]
- iter_ = None
- try:
- iter_ = self.model.get_iter(row)
- except Exception:
- self.tooltip.hide_tooltip()
- return
- sid = self.model[iter_][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- if file_props is not None:
- if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
- self.tooltip.id = row
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, widget)
-
- def on_transfers_list_leave_notify_event(self, widget=None, event=None):
- if event is not None:
- self.height_diff = int(event.y)
- elif self.height_diff is 0:
- return
- pointer = self.tree.get_pointer()
- props = self.tree.get_path_at_pos(pointer[0], pointer[1] - self.height_diff)
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id == props[0]:
- self.tooltip.hide_tooltip()
-
- def on_transfers_list_row_activated(self, widget, path, col):
- # try to open the containing folder
- self.on_open_folder_menuitem_activate(widget)
-
- def set_cleanup_sensitivity(self):
- """
- Check if there are transfer rows and set cleanup_button sensitive, or
- insensitive if model is empty
- """
- if len(self.model) == 0:
- self.cleanup_button.set_sensitive(False)
- else:
- self.cleanup_button.set_sensitive(True)
-
- def set_all_insensitive(self):
- """
- Make all buttons/menuitems insensitive
- """
- self.pause_button.set_sensitive(False)
- self.pause_menuitem.set_sensitive(False)
- self.continue_menuitem.set_sensitive(False)
- self.remove_menuitem.set_sensitive(False)
- self.cancel_button.set_sensitive(False)
- self.cancel_menuitem.set_sensitive(False)
- self.open_folder_menuitem.set_sensitive(False)
- self.set_cleanup_sensitivity()
-
- def set_buttons_sensitive(self, path, is_row_selected):
- """
- Make buttons/menuitems sensitive as appropriate to the state of file
- transfer located at path 'path'
- """
- if path is None:
- self.set_all_insensitive()
- return
- current_iter = self.model.get_iter(path)
- sid = self.model[current_iter][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- self.remove_menuitem.set_sensitive(is_row_selected)
- self.open_folder_menuitem.set_sensitive(is_row_selected)
- is_stopped = False
- if is_transfer_stopped(file_props):
- is_stopped = True
- self.cancel_button.set_sensitive(not is_stopped)
- self.cancel_menuitem.set_sensitive(not is_stopped)
- if not is_row_selected:
- # no selection, disable the buttons
- self.set_all_insensitive()
- elif not is_stopped:
- if is_transfer_active(file_props):
- # file transfer is active
- self.toggle_pause_continue(True)
- self.pause_button.set_sensitive(True)
- elif is_transfer_paused(file_props):
- # file transfer is paused
- self.toggle_pause_continue(False)
- self.pause_button.set_sensitive(True)
- else:
- self.pause_button.set_sensitive(False)
- self.pause_menuitem.set_sensitive(False)
- self.continue_menuitem.set_sensitive(False)
- else:
- self.pause_button.set_sensitive(False)
- self.pause_menuitem.set_sensitive(False)
- self.continue_menuitem.set_sensitive(False)
- return True
-
- def selection_changed(self, args):
- """
- Selection has changed - change the sensitivity of the buttons/menuitems
- """
- selection = args
- selected = selection.get_selected_rows()
- if selected[1] != []:
- selected_path = selected[1][0]
- self.select_func(selected_path)
- else:
- self.set_all_insensitive()
-
- def select_func(self, path):
- is_selected = False
- selected = self.tree.get_selection().get_selected_rows()
- if selected[1] != []:
- selected_path = selected[1][0]
- if selected_path == path:
- is_selected = True
- self.set_buttons_sensitive(path, is_selected)
- self.set_cleanup_sensitivity()
- return True
-
- def on_cleanup_button_clicked(self, widget):
- i = len(self.model) - 1
- while i >= 0:
- iter_ = self.model.get_iter((i))
- sid = self.model[iter_][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- if is_transfer_stopped(file_props):
- self._remove_transfer(iter_, sid, file_props)
- i -= 1
- self.tree.get_selection().unselect_all()
- self.set_all_insensitive()
-
- def toggle_pause_continue(self, status):
- if status:
- label = _('Pause')
- self.pause_button.set_label(label)
- self.pause_button.set_image(gtk.image_new_from_stock(
- gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU))
-
- self.pause_menuitem.set_sensitive(True)
- self.pause_menuitem.set_no_show_all(False)
- self.continue_menuitem.hide()
- self.continue_menuitem.set_no_show_all(True)
-
- else:
- label = _('_Continue')
- self.pause_button.set_label(label)
- self.pause_button.set_image(gtk.image_new_from_stock(
- gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU))
- self.pause_menuitem.hide()
- self.pause_menuitem.set_no_show_all(True)
- self.continue_menuitem.set_sensitive(True)
- self.continue_menuitem.set_no_show_all(False)
-
- def on_pause_restore_button_clicked(self, widget):
- selected = self.tree.get_selection().get_selected()
- if selected is None or selected[1] is None:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- if self.is_transfer_paused(file_props):
- file_props['last-time'] = time.time()
- file_props['paused'] = False
- types = {'r' : 'download', 's' : 'upload'}
- self.set_status(file_props['type'], file_props['sid'], types[sid[0]])
- self.toggle_pause_continue(True)
- file_props['continue_cb']()
- elif is_transfer_active(file_props):
- file_props['paused'] = True
- self.set_status(file_props['type'], file_props['sid'], 'pause')
- # reset that to compute speed only when we resume
- file_props['transfered_size'] = []
- self.toggle_pause_continue(False)
-
- def on_cancel_button_clicked(self, widget):
- selected = self.tree.get_selection().get_selected()
- if selected is None or selected[1] is None:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- if 'tt_account' not in file_props:
- return
- account = file_props['tt_account']
- if account not in gajim.connections:
- return
- gajim.connections[account].disconnect_transfer(file_props)
- self.set_status(file_props['type'], file_props['sid'], 'stop')
-
- def show_tooltip(self, widget):
- if self.height_diff == 0:
- self.tooltip.hide_tooltip()
- return
- pointer = self.tree.get_pointer()
- props = self.tree.get_path_at_pos(pointer[0],
- pointer[1] - self.height_diff)
- # check if the current pointer is at the same path
- # as it was before setting the timeout
- if props and self.tooltip.id == props[0]:
- iter_ = self.model.get_iter(props[0])
- sid = self.model[iter_][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- # bounding rectangle of coordinates for the cell within the treeview
- rect = self.tree.get_cell_area(props[0], props[1])
- # position of the treeview on the screen
- position = widget.window.get_origin()
- self.tooltip.show_tooltip(file_props , rect.height,
- position[1] + rect.y + self.height_diff)
- else:
- self.tooltip.hide_tooltip()
-
- def on_notify_ft_complete_checkbox_toggled(self, widget):
- gajim.config.set('notify_on_file_complete',
- widget.get_active())
-
- def on_file_transfers_dialog_delete_event(self, widget, event):
- self.on_transfers_list_leave_notify_event(widget, None)
- self.window.hide()
- return True # do NOT destory window
-
- def on_close_button_clicked(self, widget):
- self.window.hide()
-
- def show_context_menu(self, event, iter_):
- # change the sensitive propery of the buttons and menuitems
- if iter_:
- path = self.model.get_path(iter_)
- self.set_buttons_sensitive(path, True)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
- self.file_transfers_menu.show_all()
- self.file_transfers_menu.popup(None, self.tree, None,
- event_button, event.time)
-
- def on_transfers_list_key_press_event(self, widget, event):
- """
- When a key is pressed in the treeviews
- """
- self.tooltip.hide_tooltip()
- iter_ = None
- try:
- iter_ = self.tree.get_selection().get_selected()[1]
- except TypeError:
- self.tree.get_selection().unselect_all()
-
- if iter_ is not None:
- path = self.model.get_path(iter_)
- self.tree.get_selection().select_path(path)
-
- if event.keyval == gtk.keysyms.Menu:
- self.show_context_menu(event, iter_)
- return True
-
-
- def on_transfers_list_button_release_event(self, widget, event):
- # hide tooltip, no matter the button is pressed
- self.tooltip.hide_tooltip()
- path = None
- try:
- path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
- except TypeError:
- self.tree.get_selection().unselect_all()
- if path is None:
- self.set_all_insensitive()
- else:
- self.select_func(path)
-
- def on_transfers_list_button_press_event(self, widget, event):
- # hide tooltip, no matter the button is pressed
- self.tooltip.hide_tooltip()
- path, iter_ = None, None
- try:
- path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
- except TypeError:
- self.tree.get_selection().unselect_all()
- if event.button == 3: # Right click
- if path:
- self.tree.get_selection().select_path(path)
- iter_ = self.model.get_iter(path)
- self.show_context_menu(event, iter_)
- if path:
- return True
-
- def on_open_folder_menuitem_activate(self, widget):
- selected = self.tree.get_selection().get_selected()
- if not selected or not selected[1]:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- if 'file-name' not in file_props:
- return
- path = os.path.split(file_props['file-name'])[0]
- if os.path.exists(path) and os.path.isdir(path):
- helpers.launch_file_manager(path)
-
- def on_cancel_menuitem_activate(self, widget):
- self.on_cancel_button_clicked(widget)
-
- def on_continue_menuitem_activate(self, widget):
- self.on_pause_restore_button_clicked(widget)
-
- def on_pause_menuitem_activate(self, widget):
- self.on_pause_restore_button_clicked(widget)
-
- def on_remove_menuitem_activate(self, widget):
- selected = self.tree.get_selection().get_selected()
- if not selected or not selected[1]:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][C_SID].decode('utf-8')
- file_props = self.files_props[sid[0]][sid[1:]]
- self._remove_transfer(s_iter, sid, file_props)
- self.set_all_insensitive()
-
- def on_file_transfers_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape: # ESCAPE
- self.window.hide()
-
-# vim: se ts=3:
+ def __init__(self):
+ self.files_props = {'r' : {}, 's': {}}
+ self.height_diff = 0
+ self.xml = gtkgui_helpers.get_gtk_builder('filetransfers.ui')
+ self.window = self.xml.get_object('file_transfers_window')
+ self.tree = self.xml.get_object('transfers_list')
+ self.cancel_button = self.xml.get_object('cancel_button')
+ self.pause_button = self.xml.get_object('pause_restore_button')
+ self.cleanup_button = self.xml.get_object('cleanup_button')
+ self.notify_ft_checkbox = self.xml.get_object(
+ 'notify_ft_complete_checkbox')
+
+ shall_notify = gajim.config.get('notify_on_file_complete')
+ self.notify_ft_checkbox.set_active(shall_notify
+ )
+ self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, int, str)
+ self.tree.set_model(self.model)
+ col = gtk.TreeViewColumn()
+
+ render_pixbuf = gtk.CellRendererPixbuf()
+
+ col.pack_start(render_pixbuf, expand=True)
+ render_pixbuf.set_property('xpad', 3)
+ render_pixbuf.set_property('ypad', 3)
+ render_pixbuf.set_property('yalign', .0)
+ col.add_attribute(render_pixbuf, 'pixbuf', 0)
+ self.tree.append_column(col)
+
+ col = gtk.TreeViewColumn(_('File'))
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, expand=False)
+ col.add_attribute(renderer, 'markup' , C_LABELS)
+ renderer.set_property('yalign', 0.)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, expand=True)
+ col.add_attribute(renderer, 'markup' , C_FILE)
+ renderer.set_property('xalign', 0.)
+ renderer.set_property('yalign', 0.)
+ renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
+ col.set_resizable(True)
+ col.set_expand(True)
+ self.tree.append_column(col)
+
+ col = gtk.TreeViewColumn(_('Time'))
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, expand=False)
+ col.add_attribute(renderer, 'markup' , C_TIME)
+ renderer.set_property('yalign', 0.5)
+ renderer.set_property('xalign', 0.5)
+ renderer = gtk.CellRendererText()
+ renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
+ col.set_resizable(True)
+ col.set_expand(False)
+ self.tree.append_column(col)
+
+ col = gtk.TreeViewColumn(_('Progress'))
+ renderer = gtk.CellRendererProgress()
+ renderer.set_property('yalign', 0.5)
+ renderer.set_property('xalign', 0.5)
+ col.pack_start(renderer, expand=False)
+ col.add_attribute(renderer, 'text' , C_PROGRESS)
+ col.add_attribute(renderer, 'value' , C_PERCENT)
+ col.set_resizable(True)
+ col.set_expand(False)
+ self.tree.append_column(col)
+
+ self.images = {}
+ self.icons = {
+ 'upload': gtk.STOCK_GO_UP,
+ 'download': gtk.STOCK_GO_DOWN,
+ 'stop': gtk.STOCK_STOP,
+ 'waiting': gtk.STOCK_REFRESH,
+ 'pause': gtk.STOCK_MEDIA_PAUSE,
+ 'continue': gtk.STOCK_MEDIA_PLAY,
+ 'ok': gtk.STOCK_APPLY,
+ }
+
+ self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
+ self.tree.get_selection().connect('changed', self.selection_changed)
+ self.tooltip = tooltips.FileTransfersTooltip()
+ self.file_transfers_menu = self.xml.get_object('file_transfers_menu')
+ self.open_folder_menuitem = self.xml.get_object('open_folder_menuitem')
+ self.cancel_menuitem = self.xml.get_object('cancel_menuitem')
+ self.pause_menuitem = self.xml.get_object('pause_menuitem')
+ self.continue_menuitem = self.xml.get_object('continue_menuitem')
+ self.remove_menuitem = self.xml.get_object('remove_menuitem')
+ self.xml.connect_signals(self)
+
+ def find_transfer_by_jid(self, account, jid):
+ """
+ Find all transfers with peer 'jid' that belong to 'account'
+ """
+ active_transfers = [[], []] # ['senders', 'receivers']
+
+ # 'account' is the sender
+ for file_props in self.files_props['s'].values():
+ if file_props['tt_account'] == account:
+ receiver_jid = unicode(file_props['receiver']).split('/')[0]
+ if jid == receiver_jid:
+ if not is_transfer_stopped(file_props):
+ active_transfers[0].append(file_props)
+
+ # 'account' is the recipient
+ for file_props in self.files_props['r'].values():
+ if file_props['tt_account'] == account:
+ sender_jid = unicode(file_props['sender']).split('/')[0]
+ if jid == sender_jid:
+ if not is_transfer_stopped(file_props):
+ active_transfers[1].append(file_props)
+ return active_transfers
+
+ def show_completed(self, jid, file_props):
+ """
+ Show a dialog saying that file (file_props) has been transferred
+ """
+ def on_open(widget, file_props):
+ dialog.destroy()
+ if 'file-name' not in file_props:
+ return
+ path = os.path.split(file_props['file-name'])[0]
+ if os.path.exists(path) and os.path.isdir(path):
+ helpers.launch_file_manager(path)
+ self.tree.get_selection().unselect_all()
+
+ if file_props['type'] == 'r':
+ # file path is used below in 'Save in'
+ (file_path, file_name) = os.path.split(file_props['file-name'])
+ else:
+ file_name = file_props['name']
+ sectext = '\t' + _('Filename: %s') % file_name
+ sectext += '\n\t' + _('Size: %s') % \
+ helpers.convert_bytes(file_props['size'])
+ if file_props['type'] == 'r':
+ jid = unicode(file_props['sender']).split('/')[0]
+ sender_name = gajim.contacts.get_first_contact_from_jid(
+ file_props['tt_account'], jid).get_shown_name()
+ sender = sender_name
+ else:
+ #You is a reply of who sent a file
+ sender = _('You')
+ sectext += '\n\t' + _('Sender: %s') % sender
+ sectext += '\n\t' + _('Recipient: ')
+ if file_props['type'] == 's':
+ jid = unicode(file_props['receiver']).split('/')[0]
+ receiver_name = gajim.contacts.get_first_contact_from_jid(
+ file_props['tt_account'], jid).get_shown_name()
+ recipient = receiver_name
+ else:
+ #You is a reply of who received a file
+ recipient = _('You')
+ sectext += recipient
+ if file_props['type'] == 'r':
+ sectext += '\n\t' + _('Saved in: %s') % file_path
+ dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
+ _('File transfer completed'), sectext)
+ if file_props['type'] == 'r':
+ button = gtk.Button(_('_Open Containing Folder'))
+ button.connect('clicked', on_open, file_props)
+ dialog.action_area.pack_start(button)
+ ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
+ def on_ok(widget):
+ dialog.destroy()
+ ok_button.connect('clicked', on_ok)
+ dialog.show_all()
+
+ def show_request_error(self, file_props):
+ """
+ Show error dialog to the recipient saying that transfer has been canceled
+ """
+ dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.'))
+ self.tree.get_selection().unselect_all()
+
+ def show_send_error(self, file_props):
+ """
+ Show error dialog to the sender saying that transfer has been canceled
+ """
+ dialogs.InformationDialog(_('File transfer cancelled'),
+ _('Connection with peer cannot be established.'))
+ self.tree.get_selection().unselect_all()
+
+ def show_stopped(self, jid, file_props, error_msg=''):
+ if file_props['type'] == 'r':
+ file_name = os.path.basename(file_props['file-name'])
+ else:
+ file_name = file_props['name']
+ sectext = '\t' + _('Filename: %s') % file_name
+ sectext += '\n\t' + _('Recipient: %s') % jid
+ if error_msg:
+ sectext += '\n\t' + _('Error message: %s') % error_msg
+ dialogs.ErrorDialog(_('File transfer stopped'), sectext)
+ self.tree.get_selection().unselect_all()
+
+ def show_file_send_request(self, account, contact):
+ desc_entry = gtk.Entry()
+
+ def on_ok(widget):
+ file_dir = None
+ files_path_list = dialog.get_filenames()
+ files_path_list = gtkgui_helpers.decode_filechooser_file_paths(
+ files_path_list)
+ desc = desc_entry.get_text()
+ for file_path in files_path_list:
+ if self.send_file(account, contact, file_path, desc) and file_dir is None:
+ file_dir = os.path.dirname(file_path)
+ if file_dir:
+ gajim.config.set('last_send_dir', file_dir)
+ dialog.destroy()
+
+ dialog = dialogs.FileChooserDialog(_('Choose File to Send...'),
+ gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+ gtk.RESPONSE_OK,
+ True, # select multiple true as we can select many files to send
+ gajim.config.get('last_send_dir'),
+ on_response_ok=on_ok,
+ on_response_cancel=lambda e:dialog.destroy()
+ )
+
+ btn = gtk.Button(_('_Send'))
+ btn.set_property('can-default', True)
+ # FIXME: add send icon to this button (JUMP_TO)
+ dialog.add_action_widget(btn, gtk.RESPONSE_OK)
+ dialog.set_default_response(gtk.RESPONSE_OK)
+
+ desc_hbox = gtk.HBox(False, 5)
+ desc_hbox.pack_start(gtk.Label(_('Description: ')), False, False, 0)
+ desc_hbox.pack_start(desc_entry, True, True, 0)
+
+ dialog.vbox.pack_start(desc_hbox, False, False, 0)
+
+ btn.show()
+ desc_hbox.show_all()
+
+ def send_file(self, account, contact, file_path, file_desc=''):
+ """
+ Start the real transfer(upload) of the file
+ """
+ if gtkgui_helpers.file_is_locked(file_path):
+ pritext = _('Gajim cannot access this file')
+ sextext = _('This file is being used by another process.')
+ dialogs.ErrorDialog(pritext, sextext)
+ return
+
+ if isinstance(contact, str):
+ if contact.find('/') == -1:
+ return
+ (jid, resource) = contact.split('/', 1)
+ contact = gajim.contacts.create_contact(jid=jid, account=account, resource=resource)
+ file_name = os.path.split(file_path)[1]
+ file_props = self.get_send_file_props(account, contact,
+ file_path, file_name, file_desc)
+ if file_props is None:
+ return False
+ self.add_transfer(account, contact, file_props)
+ gajim.connections[account].send_file_request(file_props)
+ return True
+
+ def _start_receive(self, file_path, account, contact, file_props):
+ file_dir = os.path.dirname(file_path)
+ if file_dir:
+ gajim.config.set('last_save_dir', file_dir)
+ file_props['file-name'] = file_path
+ self.add_transfer(account, contact, file_props)
+ gajim.connections[account].send_file_approval(file_props)
+
+ def show_file_request(self, account, contact, file_props):
+ """
+ Show dialog asking for comfirmation and store location of new file
+ requested by a contact
+ """
+ if file_props is None or 'name' not in file_props:
+ return
+ sec_text = '\t' + _('File: %s') % gobject.markup_escape_text(
+ file_props['name'])
+ if 'size' in file_props:
+ sec_text += '\n\t' + _('Size: %s') % \
+ helpers.convert_bytes(file_props['size'])
+ if 'mime-type' in file_props:
+ sec_text += '\n\t' + _('Type: %s') % file_props['mime-type']
+ if 'desc' in file_props:
+ sec_text += '\n\t' + _('Description: %s') % file_props['desc']
+ prim_text = _('%s wants to send you a file:') % contact.jid
+ dialog = None
+
+ def on_response_ok(account, contact, file_props):
+
+ def on_ok(widget, account, contact, file_props):
+ file_path = dialog2.get_filename()
+ file_path = gtkgui_helpers.decode_filechooser_file_paths(
+ (file_path,))[0]
+ if os.path.exists(file_path):
+ # check if we have write permissions
+ if not os.access(file_path, os.W_OK):
+ file_name = os.path.basename(file_path)
+ dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % file_name),
+ _('A file with this name already exists and you do not have permission to overwrite it.'))
+ return
+ stat = os.stat(file_path)
+ dl_size = stat.st_size
+ file_size = file_props['size']
+ dl_finished = dl_size >= file_size
+
+ def on_response(response):
+ if response < 0:
+ return
+ elif response == 100:
+ file_props['offset'] = dl_size
+ dialog2.destroy()
+ self._start_receive(file_path, account, contact, file_props)
+
+ dialog = dialogs.FTOverwriteConfirmationDialog(
+ _('This file already exists'), _('What do you want to do?'),
+ propose_resume=not dl_finished, on_response=on_response)
+ dialog.set_transient_for(dialog2)
+ dialog.set_destroy_with_parent(True)
+ return
+ else:
+ dirname = os.path.dirname(file_path)
+ if not os.access(dirname, os.W_OK) and os.name != 'nt':
+ # read-only bit is used to mark special folder under windows,
+ # not to mark that a folder is read-only. See ticket #3587
+ dialogs.ErrorDialog(_('Directory "%s" is not writable') % dirname, _('You do not have permission to create files in this directory.'))
+ return
+ dialog2.destroy()
+ self._start_receive(file_path, account, contact, file_props)
+
+ def on_cancel(widget, account, contact, file_props):
+ dialog2.destroy()
+ gajim.connections[account].send_file_rejection(file_props)
+
+ dialog2 = dialogs.FileChooserDialog(
+ title_text=_('Save File as...'),
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE, gtk.RESPONSE_OK),
+ default_response=gtk.RESPONSE_OK,
+ current_folder=gajim.config.get('last_save_dir'),
+ on_response_ok=(on_ok, account, contact, file_props),
+ on_response_cancel=(on_cancel, account, contact, file_props))
+
+ dialog2.set_current_name(file_props['name'])
+ dialog2.connect('delete-event', lambda widget, event:
+ on_cancel(widget, account, contact, file_props))
+
+ def on_response_cancel(account, file_props):
+ gajim.connections[account].send_file_rejection(file_props)
+
+ dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
+ on_response_ok=(on_response_ok, account, contact, file_props),
+ on_response_cancel=(on_response_cancel, account, file_props))
+ dialog.connect('delete-event', lambda widget, event:
+ on_response_cancel(widget, account, file_props))
+ dialog.popup()
+
+ def get_icon(self, ident):
+ return self.images.setdefault(ident,
+ self.window.render_icon(self.icons[ident], gtk.ICON_SIZE_MENU))
+
+ def set_status(self, typ, sid, status):
+ """
+ Change the status of a transfer to state 'status'
+ """
+ iter_ = self.get_iter_by_sid(typ, sid)
+ if iter_ is None:
+ return
+ sid = self.model[iter_][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ if status == 'stop':
+ file_props['stopped'] = True
+ elif status == 'ok':
+ file_props['completed'] = True
+ self.model.set(iter_, C_IMAGE, self.get_icon(status))
+ path = self.model.get_path(iter_)
+ self.select_func(path)
+
+ def _format_percent(self, percent):
+ """
+ Add extra spaces from both sides of the percent, so that progress string
+ has always a fixed size
+ """
+ _str = ' '
+ if percent != 100.:
+ _str += ' '
+ if percent < 10:
+ _str += ' '
+ _str += unicode(percent) + '% \n'
+ return _str
+
+ def _format_time(self, _time):
+ times = { 'hours': 0, 'minutes': 0, 'seconds': 0 }
+ _time = int(_time)
+ times['seconds'] = _time % 60
+ if _time >= 60:
+ _time /= 60
+ times['minutes'] = _time % 60
+ if _time >= 60:
+ times['hours'] = _time / 60
+
+ #Print remaining time in format 00:00:00
+ #You can change the places of (hours), (minutes), (seconds) -
+ #they are not translatable.
+ return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times
+
+ def _get_eta_and_speed(self, full_size, transfered_size, file_props):
+ if len(file_props['transfered_size']) == 0:
+ return 0., 0.
+ elif len(file_props['transfered_size']) == 1:
+ speed = round(float(transfered_size) / file_props['elapsed-time'])
+ else:
+ # first and last are (time, transfered_size)
+ first = file_props['transfered_size'][0]
+ last = file_props['transfered_size'][-1]
+ transfered = last[1] - first[1]
+ tim = last[0] - first[0]
+ if tim == 0:
+ return 0., 0.
+ speed = round(float(transfered) / tim)
+ if speed == 0.:
+ return 0., 0.
+ remaining_size = full_size - transfered_size
+ eta = remaining_size / speed
+ return eta, speed
+
+ def _remove_transfer(self, iter_, sid, file_props):
+ self.model.remove(iter_)
+ if 'tt_account' in file_props:
+ # file transfer is set
+ account = file_props['tt_account']
+ if account in gajim.connections:
+ # there is a connection to the account
+ gajim.connections[account].remove_transfer(file_props)
+ if file_props['type'] == 'r': # we receive a file
+ other = file_props['sender']
+ else: # we send a file
+ other = file_props['receiver']
+ if isinstance(other, unicode):
+ jid = gajim.get_jid_without_resource(other)
+ else: # It's a Contact instance
+ jid = other.jid
+ for ev_type in ('file-error', 'file-completed', 'file-request-error',
+ 'file-send-error', 'file-stopped'):
+ for event in gajim.events.get_events(account, jid, [ev_type]):
+ if event.parameters['sid'] == file_props['sid']:
+ gajim.events.remove_events(account, jid, event)
+ gajim.interface.roster.draw_contact(jid, account)
+ gajim.interface.roster.show_title()
+ del(self.files_props[sid[0]][sid[1:]])
+ del(file_props)
+
+ def set_progress(self, typ, sid, transfered_size, iter_=None):
+ """
+ Change the progress of a transfer with new transfered size
+ """
+ if sid not in self.files_props[typ]:
+ return
+ file_props = self.files_props[typ][sid]
+ full_size = int(file_props['size'])
+ if full_size == 0:
+ percent = 0
+ else:
+ percent = round(float(transfered_size) / full_size * 100, 1)
+ if iter_ is None:
+ iter_ = self.get_iter_by_sid(typ, sid)
+ if iter_ is not None:
+ just_began = False
+ if self.model[iter_][C_PERCENT] == 0 and int(percent > 0):
+ just_began = True
+ text = self._format_percent(percent)
+ if transfered_size == 0:
+ text += '0'
+ else:
+ text += helpers.convert_bytes(transfered_size)
+ text += '/' + helpers.convert_bytes(full_size)
+ # Kb/s
+
+ # remaining time
+ if 'offset' in file_props and file_props['offset']:
+ transfered_size -= file_props['offset']
+ full_size -= file_props['offset']
+
+ if file_props['elapsed-time'] > 0:
+ file_props['transfered_size'].append((file_props['last-time'], transfered_size))
+ if len(file_props['transfered_size']) > 6:
+ file_props['transfered_size'].pop(0)
+ eta, speed = self._get_eta_and_speed(full_size, transfered_size,
+ file_props)
+
+ self.model.set(iter_, C_PROGRESS, text)
+ self.model.set(iter_, C_PERCENT, int(percent))
+ text = self._format_time(eta)
+ text += '\n'
+ #This should make the string Kb/s,
+ #where 'Kb' part is taken from %s.
+ #Only the 's' after / (which means second) should be translated.
+ text += _('(%(filesize_unit)s/s)') % {'filesize_unit':
+ helpers.convert_bytes(speed)}
+ self.model.set(iter_, C_TIME, text)
+
+ # try to guess what should be the status image
+ if file_props['type'] == 'r':
+ status = 'download'
+ else:
+ status = 'upload'
+ if 'paused' in file_props and file_props['paused'] == True:
+ status = 'pause'
+ elif 'stalled' in file_props and file_props['stalled'] == True:
+ status = 'waiting'
+ if 'connected' in file_props and file_props['connected'] == False:
+ status = 'stop'
+ self.model.set(iter_, 0, self.get_icon(status))
+ if transfered_size == full_size:
+ self.set_status(typ, sid, 'ok')
+ elif just_began:
+ path = self.model.get_path(iter_)
+ self.select_func(path)
+
+ def get_iter_by_sid(self, typ, sid):
+ """
+ Return iter to the row, which holds file transfer, identified by the
+ session id
+ """
+ iter_ = self.model.get_iter_root()
+ while iter_:
+ if typ + sid == self.model[iter_][C_SID].decode('utf-8'):
+ return iter_
+ iter_ = self.model.iter_next(iter_)
+
+ def get_send_file_props(self, account, contact, file_path, file_name,
+ file_desc=''):
+ """
+ Create new file_props dict and set initial file transfer properties in it
+ """
+ file_props = {'file-name' : file_path, 'name' : file_name,
+ 'type' : 's', 'desc' : file_desc}
+ if os.path.isfile(file_path):
+ stat = os.stat(file_path)
+ else:
+ dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path)
+ return None
+ if stat[6] == 0:
+ dialogs.ErrorDialog(_('Invalid File'),
+ _('It is not possible to send empty files'))
+ return None
+ file_props['elapsed-time'] = 0
+ file_props['size'] = unicode(stat[6])
+ file_props['sid'] = helpers.get_random_string_16()
+ file_props['completed'] = False
+ file_props['started'] = False
+ file_props['sender'] = account
+ file_props['receiver'] = contact
+ file_props['tt_account'] = account
+ # keep the last time: transfered_size to compute transfer speed
+ file_props['transfered_size'] = []
+ return file_props
+
+ def add_transfer(self, account, contact, file_props):
+ """
+ Add new transfer to FT window and show the FT window
+ """
+ self.on_transfers_list_leave_notify_event(None)
+ if file_props is None:
+ return
+ file_props['elapsed-time'] = 0
+ self.files_props[file_props['type']][file_props['sid']] = file_props
+ iter_ = self.model.prepend()
+ text_labels = '<b>' + _('Name: ') + '</b>\n'
+ if file_props['type'] == 'r':
+ text_labels += '<b>' + _('Sender: ') + '</b>'
+ else:
+ text_labels += '<b>' + _('Recipient: ') + '</b>'
+
+ if file_props['type'] == 'r':
+ file_name = os.path.split(file_props['file-name'])[1]
+ else:
+ file_name = file_props['name']
+ text_props = gobject.markup_escape_text(file_name) + '\n'
+ text_props += contact.get_shown_name()
+ self.model.set(iter_, 1, text_labels, 2, text_props, C_SID,
+ file_props['type'] + file_props['sid'])
+ self.set_progress(file_props['type'], file_props['sid'], 0, iter_)
+ if 'started' in file_props and file_props['started'] is False:
+ status = 'waiting'
+ elif file_props['type'] == 'r':
+ status = 'download'
+ else:
+ status = 'upload'
+ file_props['tt_account'] = account
+ self.set_status(file_props['type'], file_props['sid'], status)
+ self.set_cleanup_sensitivity()
+ self.window.show_all()
+
+ def on_transfers_list_motion_notify_event(self, widget, event):
+ pointer = self.tree.get_pointer()
+ props = widget.get_path_at_pos(int(event.x), int(event.y))
+ self.height_diff = pointer[1] - int(event.y)
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id != props[0]:
+ self.tooltip.hide_tooltip()
+ if props:
+ row = props[0]
+ iter_ = None
+ try:
+ iter_ = self.model.get_iter(row)
+ except Exception:
+ self.tooltip.hide_tooltip()
+ return
+ sid = self.model[iter_][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ if file_props is not None:
+ if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
+ self.tooltip.id = row
+ self.tooltip.timeout = gobject.timeout_add(500,
+ self.show_tooltip, widget)
+
+ def on_transfers_list_leave_notify_event(self, widget=None, event=None):
+ if event is not None:
+ self.height_diff = int(event.y)
+ elif self.height_diff is 0:
+ return
+ pointer = self.tree.get_pointer()
+ props = self.tree.get_path_at_pos(pointer[0], pointer[1] - self.height_diff)
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id == props[0]:
+ self.tooltip.hide_tooltip()
+
+ def on_transfers_list_row_activated(self, widget, path, col):
+ # try to open the containing folder
+ self.on_open_folder_menuitem_activate(widget)
+
+ def set_cleanup_sensitivity(self):
+ """
+ Check if there are transfer rows and set cleanup_button sensitive, or
+ insensitive if model is empty
+ """
+ if len(self.model) == 0:
+ self.cleanup_button.set_sensitive(False)
+ else:
+ self.cleanup_button.set_sensitive(True)
+
+ def set_all_insensitive(self):
+ """
+ Make all buttons/menuitems insensitive
+ """
+ self.pause_button.set_sensitive(False)
+ self.pause_menuitem.set_sensitive(False)
+ self.continue_menuitem.set_sensitive(False)
+ self.remove_menuitem.set_sensitive(False)
+ self.cancel_button.set_sensitive(False)
+ self.cancel_menuitem.set_sensitive(False)
+ self.open_folder_menuitem.set_sensitive(False)
+ self.set_cleanup_sensitivity()
+
+ def set_buttons_sensitive(self, path, is_row_selected):
+ """
+ Make buttons/menuitems sensitive as appropriate to the state of file
+ transfer located at path 'path'
+ """
+ if path is None:
+ self.set_all_insensitive()
+ return
+ current_iter = self.model.get_iter(path)
+ sid = self.model[current_iter][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ self.remove_menuitem.set_sensitive(is_row_selected)
+ self.open_folder_menuitem.set_sensitive(is_row_selected)
+ is_stopped = False
+ if is_transfer_stopped(file_props):
+ is_stopped = True
+ self.cancel_button.set_sensitive(not is_stopped)
+ self.cancel_menuitem.set_sensitive(not is_stopped)
+ if not is_row_selected:
+ # no selection, disable the buttons
+ self.set_all_insensitive()
+ elif not is_stopped:
+ if is_transfer_active(file_props):
+ # file transfer is active
+ self.toggle_pause_continue(True)
+ self.pause_button.set_sensitive(True)
+ elif is_transfer_paused(file_props):
+ # file transfer is paused
+ self.toggle_pause_continue(False)
+ self.pause_button.set_sensitive(True)
+ else:
+ self.pause_button.set_sensitive(False)
+ self.pause_menuitem.set_sensitive(False)
+ self.continue_menuitem.set_sensitive(False)
+ else:
+ self.pause_button.set_sensitive(False)
+ self.pause_menuitem.set_sensitive(False)
+ self.continue_menuitem.set_sensitive(False)
+ return True
+
+ def selection_changed(self, args):
+ """
+ Selection has changed - change the sensitivity of the buttons/menuitems
+ """
+ selection = args
+ selected = selection.get_selected_rows()
+ if selected[1] != []:
+ selected_path = selected[1][0]
+ self.select_func(selected_path)
+ else:
+ self.set_all_insensitive()
+
+ def select_func(self, path):
+ is_selected = False
+ selected = self.tree.get_selection().get_selected_rows()
+ if selected[1] != []:
+ selected_path = selected[1][0]
+ if selected_path == path:
+ is_selected = True
+ self.set_buttons_sensitive(path, is_selected)
+ self.set_cleanup_sensitivity()
+ return True
+
+ def on_cleanup_button_clicked(self, widget):
+ i = len(self.model) - 1
+ while i >= 0:
+ iter_ = self.model.get_iter((i))
+ sid = self.model[iter_][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ if is_transfer_stopped(file_props):
+ self._remove_transfer(iter_, sid, file_props)
+ i -= 1
+ self.tree.get_selection().unselect_all()
+ self.set_all_insensitive()
+
+ def toggle_pause_continue(self, status):
+ if status:
+ label = _('Pause')
+ self.pause_button.set_label(label)
+ self.pause_button.set_image(gtk.image_new_from_stock(
+ gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU))
+
+ self.pause_menuitem.set_sensitive(True)
+ self.pause_menuitem.set_no_show_all(False)
+ self.continue_menuitem.hide()
+ self.continue_menuitem.set_no_show_all(True)
+
+ else:
+ label = _('_Continue')
+ self.pause_button.set_label(label)
+ self.pause_button.set_image(gtk.image_new_from_stock(
+ gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU))
+ self.pause_menuitem.hide()
+ self.pause_menuitem.set_no_show_all(True)
+ self.continue_menuitem.set_sensitive(True)
+ self.continue_menuitem.set_no_show_all(False)
+
+ def on_pause_restore_button_clicked(self, widget):
+ selected = self.tree.get_selection().get_selected()
+ if selected is None or selected[1] is None:
+ return
+ s_iter = selected[1]
+ sid = self.model[s_iter][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ if self.is_transfer_paused(file_props):
+ file_props['last-time'] = time.time()
+ file_props['paused'] = False
+ types = {'r' : 'download', 's' : 'upload'}
+ self.set_status(file_props['type'], file_props['sid'], types[sid[0]])
+ self.toggle_pause_continue(True)
+ file_props['continue_cb']()
+ elif is_transfer_active(file_props):
+ file_props['paused'] = True
+ self.set_status(file_props['type'], file_props['sid'], 'pause')
+ # reset that to compute speed only when we resume
+ file_props['transfered_size'] = []
+ self.toggle_pause_continue(False)
+
+ def on_cancel_button_clicked(self, widget):
+ selected = self.tree.get_selection().get_selected()
+ if selected is None or selected[1] is None:
+ return
+ s_iter = selected[1]
+ sid = self.model[s_iter][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ if 'tt_account' not in file_props:
+ return
+ account = file_props['tt_account']
+ if account not in gajim.connections:
+ return
+ gajim.connections[account].disconnect_transfer(file_props)
+ self.set_status(file_props['type'], file_props['sid'], 'stop')
+
+ def show_tooltip(self, widget):
+ if self.height_diff == 0:
+ self.tooltip.hide_tooltip()
+ return
+ pointer = self.tree.get_pointer()
+ props = self.tree.get_path_at_pos(pointer[0],
+ pointer[1] - self.height_diff)
+ # check if the current pointer is at the same path
+ # as it was before setting the timeout
+ if props and self.tooltip.id == props[0]:
+ iter_ = self.model.get_iter(props[0])
+ sid = self.model[iter_][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ # bounding rectangle of coordinates for the cell within the treeview
+ rect = self.tree.get_cell_area(props[0], props[1])
+ # position of the treeview on the screen
+ position = widget.window.get_origin()
+ self.tooltip.show_tooltip(file_props , rect.height,
+ position[1] + rect.y + self.height_diff)
+ else:
+ self.tooltip.hide_tooltip()
+
+ def on_notify_ft_complete_checkbox_toggled(self, widget):
+ gajim.config.set('notify_on_file_complete',
+ widget.get_active())
+
+ def on_file_transfers_dialog_delete_event(self, widget, event):
+ self.on_transfers_list_leave_notify_event(widget, None)
+ self.window.hide()
+ return True # do NOT destory window
+
+ def on_close_button_clicked(self, widget):
+ self.window.hide()
+
+ def show_context_menu(self, event, iter_):
+ # change the sensitive propery of the buttons and menuitems
+ if iter_:
+ path = self.model.get_path(iter_)
+ self.set_buttons_sensitive(path, True)
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+ self.file_transfers_menu.show_all()
+ self.file_transfers_menu.popup(None, self.tree, None,
+ event_button, event.time)
+
+ def on_transfers_list_key_press_event(self, widget, event):
+ """
+ When a key is pressed in the treeviews
+ """
+ self.tooltip.hide_tooltip()
+ iter_ = None
+ try:
+ iter_ = self.tree.get_selection().get_selected()[1]
+ except TypeError:
+ self.tree.get_selection().unselect_all()
+
+ if iter_ is not None:
+ path = self.model.get_path(iter_)
+ self.tree.get_selection().select_path(path)
+
+ if event.keyval == gtk.keysyms.Menu:
+ self.show_context_menu(event, iter_)
+ return True
+
+
+ def on_transfers_list_button_release_event(self, widget, event):
+ # hide tooltip, no matter the button is pressed
+ self.tooltip.hide_tooltip()
+ path = None
+ try:
+ path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
+ except TypeError:
+ self.tree.get_selection().unselect_all()
+ if path is None:
+ self.set_all_insensitive()
+ else:
+ self.select_func(path)
+
+ def on_transfers_list_button_press_event(self, widget, event):
+ # hide tooltip, no matter the button is pressed
+ self.tooltip.hide_tooltip()
+ path, iter_ = None, None
+ try:
+ path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
+ except TypeError:
+ self.tree.get_selection().unselect_all()
+ if event.button == 3: # Right click
+ if path:
+ self.tree.get_selection().select_path(path)
+ iter_ = self.model.get_iter(path)
+ self.show_context_menu(event, iter_)
+ if path:
+ return True
+
+ def on_open_folder_menuitem_activate(self, widget):
+ selected = self.tree.get_selection().get_selected()
+ if not selected or not selected[1]:
+ return
+ s_iter = selected[1]
+ sid = self.model[s_iter][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ if 'file-name' not in file_props:
+ return
+ path = os.path.split(file_props['file-name'])[0]
+ if os.path.exists(path) and os.path.isdir(path):
+ helpers.launch_file_manager(path)
+
+ def on_cancel_menuitem_activate(self, widget):
+ self.on_cancel_button_clicked(widget)
+
+ def on_continue_menuitem_activate(self, widget):
+ self.on_pause_restore_button_clicked(widget)
+
+ def on_pause_menuitem_activate(self, widget):
+ self.on_pause_restore_button_clicked(widget)
+
+ def on_remove_menuitem_activate(self, widget):
+ selected = self.tree.get_selection().get_selected()
+ if not selected or not selected[1]:
+ return
+ s_iter = selected[1]
+ sid = self.model[s_iter][C_SID].decode('utf-8')
+ file_props = self.files_props[sid[0]][sid[1:]]
+ self._remove_transfer(s_iter, sid, file_props)
+ self.set_all_insensitive()
+
+ def on_file_transfers_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape: # ESCAPE
+ self.window.hide()
diff --git a/src/gajim-remote.py b/src/gajim-remote.py
index 5368ed978..86feb6564 100644
--- a/src/gajim-remote.py
+++ b/src/gajim-remote.py
@@ -36,24 +36,24 @@ from common import exceptions
from common import i18n # This installs _() function
try:
- PREFERRED_ENCODING = locale.getpreferredencoding()
+ PREFERRED_ENCODING = locale.getpreferredencoding()
except Exception:
- PREFERRED_ENCODING = 'UTF-8'
+ PREFERRED_ENCODING = 'UTF-8'
def send_error(error_message):
- '''Writes error message to stderr and exits'''
- print >> sys.stderr, error_message.encode(PREFERRED_ENCODING)
- sys.exit(1)
+ '''Writes error message to stderr and exits'''
+ print >> sys.stderr, error_message.encode(PREFERRED_ENCODING)
+ sys.exit(1)
try:
- import dbus
- import dbus.service
- import dbus.glib
- # test if dbus-x11 is installed
- bus = dbus.SessionBus()
+ import dbus
+ import dbus.service
+ import dbus.glib
+ # test if dbus-x11 is installed
+ bus = dbus.SessionBus()
except Exception:
- print str(exceptions.DbusNotSupported())
- sys.exit(1)
+ print str(exceptions.DbusNotSupported())
+ sys.exit(1)
OBJ_PATH = '/org/gajim/dbus/RemoteObject'
INTERFACE = 'org.gajim.dbus.RemoteInterface'
@@ -62,537 +62,535 @@ BASENAME = 'gajim-remote'
class GajimRemote:
- def __init__(self):
- self.argv_len = len(sys.argv)
- # define commands dict. Prototype :
- # {
- # 'command': [comment, [list of arguments] ]
- # }
- #
- # each argument is defined as a tuple:
- # (argument name, help on argument, is mandatory)
- #
- self.commands = {
- 'help':[
- _('Shows a help on specific command'),
- [
- #User gets help for the command, specified by this parameter
- (_('command'),
- _('show help on command'), False)
- ]
- ],
- 'toggle_roster_appearance' : [
- _('Shows or hides the roster window'),
- []
- ],
- 'show_next_pending_event': [
- _('Pops up a window with the next pending event'),
- []
- ],
- 'list_contacts': [
- _('Prints a list of all contacts in the roster. Each contact '
- 'appears on a separate line'),
- [
- (_('account'), _('show only contacts of the given account'),
- False)
- ]
-
- ],
- 'list_accounts': [
- _('Prints a list of registered accounts'),
- []
- ],
- 'change_status': [
- _('Changes the status of account or accounts'),
- [
+ def __init__(self):
+ self.argv_len = len(sys.argv)
+ # define commands dict. Prototype :
+ # {
+ # 'command': [comment, [list of arguments] ]
+ # }
+ #
+ # each argument is defined as a tuple:
+ # (argument name, help on argument, is mandatory)
+ #
+ self.commands = {
+ 'help':[
+ _('Shows a help on specific command'),
+ [
+ #User gets help for the command, specified by this parameter
+ (_('command'),
+ _('show help on command'), False)
+ ]
+ ],
+ 'toggle_roster_appearance' : [
+ _('Shows or hides the roster window'),
+ []
+ ],
+ 'show_next_pending_event': [
+ _('Pops up a window with the next pending event'),
+ []
+ ],
+ 'list_contacts': [
+ _('Prints a list of all contacts in the roster. Each contact '
+ 'appears on a separate line'),
+ [
+ (_('account'), _('show only contacts of the given account'),
+ False)
+ ]
+
+ ],
+ 'list_accounts': [
+ _('Prints a list of registered accounts'),
+ []
+ ],
+ 'change_status': [
+ _('Changes the status of account or accounts'),
+ [
#offline, online, chat, away, xa, dnd, invisible should not be translated
- (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible. If not set, use accoun\'t previous status'), False),
- (_('message'), _('status message'), False),
- (_('account'), _('change status of account "account". '
- 'If not specified, try to change status of all accounts that have '
- '"sync with global status" option set'), False)
- ]
- ],
- 'set_priority': [
- _('Changes the priority of account or accounts'),
- [
- (_('priority'), _('priority you want to give to the account'),
- True),
- (_('account'), _('change the priority of the given account. '
- 'If not specified, change status of all accounts that have'
- ' "sync with global status" option set'), False)
- ]
- ],
- 'open_chat': [
- _('Shows the chat dialog so that you can send messages to a contact'),
- [
- ('jid', _('JID of the contact that you want to chat with'),
- True),
- (_('account'), _('if specified, contact is taken from the '
- 'contact list of this account'), False),
- (_('message'),
- _('message content. The account must be specified or ""'),
- False)
- ]
- ],
- 'send_chat_message':[
- _('Sends new chat message to a contact in the roster. Both OpenPGP key '
- 'and account are optional. If you want to set only \'account\', '
- 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
- [
- ('jid', _('JID of the contact that will receive the message'), True),
- (_('message'), _('message contents'), True),
- (_('pgp key'), _('if specified, the message will be encrypted '
- 'using this public key'), False),
- (_('account'), _('if specified, the message will be sent '
- 'using this account'), False),
- ]
- ],
- 'send_single_message':[
- _('Sends new single message to a contact in the roster. Both OpenPGP key '
- 'and account are optional. If you want to set only \'account\', '
- 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
- [
- ('jid', _('JID of the contact that will receive the message'), True),
- (_('subject'), _('message subject'), True),
- (_('message'), _('message contents'), True),
- (_('pgp key'), _('if specified, the message will be encrypted '
- 'using this public key'), False),
- (_('account'), _('if specified, the message will be sent '
- 'using this account'), False),
- ]
- ],
- 'send_groupchat_message':[
- _('Sends new message to a groupchat you\'ve joined.'),
- [
- ('room_jid', _('JID of the room that will receive the message'), True),
- (_('message'), _('message contents'), True),
- (_('account'), _('if specified, the message will be sent '
- 'using this account'), False),
- ]
- ],
- 'contact_info': [
- _('Gets detailed info on a contact'),
- [
- ('jid', _('JID of the contact'), True)
- ]
- ],
- 'account_info': [
- _('Gets detailed info on a account'),
- [
- ('account', _('Name of the account'), True)
- ]
- ],
- 'send_file': [
- _('Sends file to a contact'),
- [
- (_('file'), _('File path'), True),
- ('jid', _('JID of the contact'), True),
- (_('account'), _('if specified, file will be sent using this '
- 'account'), False)
- ]
- ],
- 'prefs_list': [
- _('Lists all preferences and their values'),
- [ ]
- ],
- 'prefs_put': [
- _('Sets value of \'key\' to \'value\'.'),
- [
- (_('key=value'), _('\'key\' is the name of the preference, '
- '\'value\' is the value to set it to'), True)
- ]
- ],
- 'prefs_del': [
- _('Deletes a preference item'),
- [
- (_('key'), _('name of the preference to be deleted'), True)
- ]
- ],
- 'prefs_store': [
- _('Writes the current state of Gajim preferences to the .config '
- 'file'),
- [ ]
- ],
- 'remove_contact': [
- _('Removes contact from roster'),
- [
- ('jid', _('JID of the contact'), True),
- (_('account'), _('if specified, contact is taken from the '
- 'contact list of this account'), False)
-
- ]
- ],
- 'add_contact': [
- _('Adds contact to roster'),
- [
- (_('jid'), _('JID of the contact'), True),
- (_('account'), _('Adds new contact to this account'), False)
- ]
- ],
-
- 'get_status': [
- _('Returns current status (the global one unless account is specified)'),
- [
- (_('account'), '', False)
- ]
- ],
-
- 'get_status_message': [
- _('Returns current status message (the global one unless account is specified)'),
- [
- (_('account'), '', False)
- ]
- ],
-
- 'get_unread_msgs_number': [
- _('Returns number of unread messages'),
- [ ]
- ],
- 'start_chat': [
- _('Opens \'Start Chat\' dialog'),
- [
- (_('account'), _('Starts chat, using this account'), True)
- ]
- ],
- 'send_xml': [
- _('Sends custom XML'),
- [
- ('xml', _('XML to send'), True),
- ('account', _('Account in which the xml will be sent; '
- 'if not specified, xml will be sent to all accounts'),
- False)
- ]
- ],
- 'change_avatar': [
- _('Change the avatar'),
- [
- ('picture', _('Picture to use'), True),
- ('account', _('Account in which the avatar will be set; '
- 'if not specified, the avatar will be set for all accounts'),
- False)
- ]
- ],
- 'handle_uri': [
- _('Handle a xmpp:/ uri'),
- [
- (_('uri'), _('URI to handle'), True),
- (_('account'), _('Account in which you want to handle it'),
- False),
- (_('message'), _('Message content'), False)
- ]
- ],
- 'join_room': [
- _('Join a MUC room'),
- [
- (_('room'), _('Room JID'), True),
- (_('nick'), _('Nickname to use'), False),
- (_('password'), _('Password to enter the room'), False),
- (_('account'), _('Account from which you want to enter the '
- 'room'), False)
- ]
- ],
- 'check_gajim_running':[
- _('Check if Gajim is running'),
- []
- ],
- 'toggle_ipython' : [
- _('Shows or hides the ipython window'),
- []
- ],
-
- }
-
- self.sbus = None
- if self.argv_len < 2 or sys.argv[1] not in self.commands.keys():
- # no args or bad args
- send_error(self.compose_help())
- self.command = sys.argv[1]
- if self.command == 'help':
- if self.argv_len == 3:
- print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING)
- else:
- print self.compose_help().encode(PREFERRED_ENCODING)
- sys.exit(0)
- if self.command == 'handle_uri':
- self.handle_uri()
- if self.command == 'check_gajim_running':
- print self.check_gajim_running()
- sys.exit(0)
- self.init_connection()
- self.check_arguments()
-
- if self.command == 'contact_info':
- if self.argv_len < 3:
- send_error(_('Missing argument "contact_jid"'))
-
- try:
- res = self.call_remote_method()
- except exceptions.ServiceNotAvailable:
- # At this point an error message has already been displayed
- sys.exit(1)
- else:
- self.print_result(res)
-
- def print_result(self, res):
- """
- Print retrieved result to the output
- """
- if res is not None:
- if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'):
- if self.command in ('send_message', 'send_single_message'):
- self.argv_len -= 2
-
- if res is False:
- if self.argv_len < 4:
- send_error(_('\'%s\' is not in your roster.\n'
- 'Please specify account for sending the message.') % sys.argv[2])
- else:
- send_error(_('You have no active account'))
- elif self.command == 'list_accounts':
- if isinstance(res, list):
- for account in res:
- if isinstance(account, unicode):
- print account.encode(PREFERRED_ENCODING)
- else:
- print account
- elif self.command == 'account_info':
- if res:
- print self.print_info(0, res, True)
- elif self.command == 'list_contacts':
- for account_dict in res:
- print self.print_info(0, account_dict, True)
- elif self.command == 'prefs_list':
- pref_keys = sorted(res.keys())
- for pref_key in pref_keys:
- result = '%s = %s' % (pref_key, res[pref_key])
- if isinstance(result, unicode):
- print result.encode(PREFERRED_ENCODING)
- else:
- print result
- elif self.command == 'contact_info':
- print self.print_info(0, res, True)
- elif res:
- print unicode(res).encode(PREFERRED_ENCODING)
-
- def check_gajim_running(self):
- if not self.sbus:
- try:
- self.sbus = dbus.SessionBus()
- except Exception:
- raise exceptions.SessionBusNotPresent
-
- test = False
- if hasattr(self.sbus, 'name_has_owner'):
- if self.sbus.name_has_owner(SERVICE):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(),
- SERVICE):
- test = True
- return test
-
- def init_connection(self):
- """
- Create the onnection to the session dbus, or exit if it is not possible
- """
- try:
- self.sbus = dbus.SessionBus()
- except Exception:
- raise exceptions.SessionBusNotPresent
-
- if not self.check_gajim_running():
- send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.'))
- obj = self.sbus.get_object(SERVICE, OBJ_PATH)
- interface = dbus.Interface(obj, INTERFACE)
-
- # get the function asked
- self.method = interface.__getattr__(self.command)
-
- def make_arguments_row(self, args):
- """
- Return arguments list. Mandatory arguments are enclosed with:
- '<', '>', optional arguments - with '[', ']'
- """
- s = ''
- for arg in args:
- if arg[2]:
- s += ' <' + arg[0] + '>'
- else:
- s += ' [' + arg[0] + ']'
- return s
-
- def help_on_command(self, command):
- """
- Return help message for a given command
- """
- if command in self.commands:
- command_props = self.commands[command]
- arguments_str = self.make_arguments_row(command_props[1])
- str_ = _('Usage: %(basename)s %(command)s %(arguments)s \n\t %(help)s')\
- % {'basename': BASENAME, 'command': command,
- 'arguments': arguments_str, 'help': command_props[0]}
- if len(command_props[1]) > 0:
- str_ += '\n\n' + _('Arguments:') + '\n'
- for argument in command_props[1]:
- str_ += ' ' + argument[0] + ' - ' + argument[1] + '\n'
- return str_
- send_error(_('%s not found') % command)
-
- def compose_help(self):
- """
- Print usage, and list available commands
- """
- s = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME
- for command in sorted(self.commands):
- s += ' ' + command
- for arg in self.commands[command][1]:
- if arg[2]:
- s += ' <' + arg[0] + '>'
- else:
- s += ' [' + arg[0] + ']'
- s += '\n'
- return s
-
- def print_info(self, level, prop_dict, encode_return = False):
- """
- Return formated string from data structure
- """
- if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)):
- return ''
- ret_str = ''
- if isinstance(prop_dict, (list, tuple)):
- ret_str = ''
- spacing = ' ' * level * 4
- for val in prop_dict:
- if val is None:
- ret_str +='\t'
- elif isinstance(val, int):
- ret_str +='\t' + str(val)
- elif isinstance(val, (str, unicode)):
- ret_str +='\t' + val
- elif isinstance(val, (list, tuple)):
- res = ''
- for items in val:
- res += self.print_info(level+1, items)
- if res != '':
- ret_str += '\t' + res
- elif isinstance(val, dict):
- ret_str += self.print_info(level+1, val)
- ret_str = '%s(%s)\n' % (spacing, ret_str[1:])
- elif isinstance(prop_dict, dict):
- for key in prop_dict.keys():
- val = prop_dict[key]
- spacing = ' ' * level * 4
- if isinstance(val, (unicode, int, str)):
- if val is not None:
- val = val.strip()
- ret_str += '%s%-10s: %s\n' % (spacing, key, val)
- elif isinstance(val, (list, tuple)):
- res = ''
- for items in val:
- res += self.print_info(level+1, items)
- if res != '':
- ret_str += '%s%s: \n%s' % (spacing, key, res)
- elif isinstance(val, dict):
- res = self.print_info(level+1, val)
- if res != '':
- ret_str += '%s%s: \n%s' % (spacing, key, res)
- if (encode_return):
- try:
- ret_str = ret_str.encode(PREFERRED_ENCODING)
- except Exception:
- pass
- return ret_str
-
- def check_arguments(self):
- """
- Make check if all necessary arguments are given
- """
- argv_len = self.argv_len - 2
- args = self.commands[self.command][1]
- if len(args) < argv_len:
- send_error(_('Too many arguments. \n'
- 'Type "%(basename)s help %(command)s" for more info') % {
- 'basename': BASENAME, 'command': self.command})
- if len(args) > argv_len:
- if args[argv_len][2]:
- send_error(_('Argument "%(arg)s" is not specified. \n'
- 'Type "%(basename)s help %(command)s" for more info') %
- {'arg': args[argv_len][0], 'basename': BASENAME,
- 'command': self.command})
- self.arguments = []
- i = 0
- for arg in sys.argv[2:]:
- i += 1
- if i < len(args):
- self.arguments.append(arg)
- else:
- # it's latest argument with spaces
- self.arguments.append(' '.join(sys.argv[i+1:]))
- break
- # add empty string for missing args
- self.arguments += ['']*(len(args)-i)
-
- def handle_uri(self):
- if not sys.argv[2].startswith('xmpp:'):
- send_error(_('Wrong uri'))
- sys.argv[2] = sys.argv[2][5:]
- uri = sys.argv[2]
- if not '?' in uri:
- self.command = sys.argv[1] = 'open_chat'
- return
- if 'body=' in uri:
- # Open chat window and paste the text in the input message dialog
- self.command = sys.argv[1] = 'open_chat'
- message = uri.split('body=')
- message = message[1].split(';')[0]
- try:
- message = urllib.unquote(message)
- except UnicodeDecodeError:
- pass
- sys.argv[2] = uri.split('?')[0]
- if len(sys.argv) == 4:
- # jid in the sys.argv
- sys.argv.append(message)
- else:
- sys.argv.append('')
- sys.argv.append(message)
- sys.argv[3] = ''
- sys.argv[4] = message
- return
- (jid, action) = uri.split('?', 1)
- try:
- jid = urllib.unquote(jid)
- except UnicodeDecodeError:
- pass
- sys.argv[2] = jid
- if action == 'join':
- self.command = sys.argv[1] = 'join_room'
- # Move account parameter from position 3 to 5
- sys.argv.append('')
- sys.argv.append(sys.argv[3])
- sys.argv[3] = ''
- return
- if action.startswith('roster'):
- # Add contact to roster
- self.command = sys.argv[1] = 'add_contact'
- return
- sys.exit(0)
-
- def call_remote_method(self):
- """
- Calls self.method with arguments from sys.argv[2:]
- """
- args = [i.decode(PREFERRED_ENCODING) for i in self.arguments]
- args = [dbus.String(i) for i in args]
- try:
- res = self.method(*args)
- return res
- except Exception:
- raise exceptions.ServiceNotAvailable
- return None
+ (_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible. If not set, use accoun\'t previous status'), False),
+ (_('message'), _('status message'), False),
+ (_('account'), _('change status of account "account". '
+ 'If not specified, try to change status of all accounts that have '
+ '"sync with global status" option set'), False)
+ ]
+ ],
+ 'set_priority': [
+ _('Changes the priority of account or accounts'),
+ [
+ (_('priority'), _('priority you want to give to the account'),
+ True),
+ (_('account'), _('change the priority of the given account. '
+ 'If not specified, change status of all accounts that have'
+ ' "sync with global status" option set'), False)
+ ]
+ ],
+ 'open_chat': [
+ _('Shows the chat dialog so that you can send messages to a contact'),
+ [
+ ('jid', _('JID of the contact that you want to chat with'),
+ True),
+ (_('account'), _('if specified, contact is taken from the '
+ 'contact list of this account'), False),
+ (_('message'),
+ _('message content. The account must be specified or ""'),
+ False)
+ ]
+ ],
+ 'send_chat_message':[
+ _('Sends new chat message to a contact in the roster. Both OpenPGP key '
+ 'and account are optional. If you want to set only \'account\', '
+ 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
+ [
+ ('jid', _('JID of the contact that will receive the message'), True),
+ (_('message'), _('message contents'), True),
+ (_('pgp key'), _('if specified, the message will be encrypted '
+ 'using this public key'), False),
+ (_('account'), _('if specified, the message will be sent '
+ 'using this account'), False),
+ ]
+ ],
+ 'send_single_message':[
+ _('Sends new single message to a contact in the roster. Both OpenPGP key '
+ 'and account are optional. If you want to set only \'account\', '
+ 'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
+ [
+ ('jid', _('JID of the contact that will receive the message'), True),
+ (_('subject'), _('message subject'), True),
+ (_('message'), _('message contents'), True),
+ (_('pgp key'), _('if specified, the message will be encrypted '
+ 'using this public key'), False),
+ (_('account'), _('if specified, the message will be sent '
+ 'using this account'), False),
+ ]
+ ],
+ 'send_groupchat_message':[
+ _('Sends new message to a groupchat you\'ve joined.'),
+ [
+ ('room_jid', _('JID of the room that will receive the message'), True),
+ (_('message'), _('message contents'), True),
+ (_('account'), _('if specified, the message will be sent '
+ 'using this account'), False),
+ ]
+ ],
+ 'contact_info': [
+ _('Gets detailed info on a contact'),
+ [
+ ('jid', _('JID of the contact'), True)
+ ]
+ ],
+ 'account_info': [
+ _('Gets detailed info on a account'),
+ [
+ ('account', _('Name of the account'), True)
+ ]
+ ],
+ 'send_file': [
+ _('Sends file to a contact'),
+ [
+ (_('file'), _('File path'), True),
+ ('jid', _('JID of the contact'), True),
+ (_('account'), _('if specified, file will be sent using this '
+ 'account'), False)
+ ]
+ ],
+ 'prefs_list': [
+ _('Lists all preferences and their values'),
+ [ ]
+ ],
+ 'prefs_put': [
+ _('Sets value of \'key\' to \'value\'.'),
+ [
+ (_('key=value'), _('\'key\' is the name of the preference, '
+ '\'value\' is the value to set it to'), True)
+ ]
+ ],
+ 'prefs_del': [
+ _('Deletes a preference item'),
+ [
+ (_('key'), _('name of the preference to be deleted'), True)
+ ]
+ ],
+ 'prefs_store': [
+ _('Writes the current state of Gajim preferences to the .config '
+ 'file'),
+ [ ]
+ ],
+ 'remove_contact': [
+ _('Removes contact from roster'),
+ [
+ ('jid', _('JID of the contact'), True),
+ (_('account'), _('if specified, contact is taken from the '
+ 'contact list of this account'), False)
+
+ ]
+ ],
+ 'add_contact': [
+ _('Adds contact to roster'),
+ [
+ (_('jid'), _('JID of the contact'), True),
+ (_('account'), _('Adds new contact to this account'), False)
+ ]
+ ],
+
+ 'get_status': [
+ _('Returns current status (the global one unless account is specified)'),
+ [
+ (_('account'), '', False)
+ ]
+ ],
+
+ 'get_status_message': [
+ _('Returns current status message (the global one unless account is specified)'),
+ [
+ (_('account'), '', False)
+ ]
+ ],
+
+ 'get_unread_msgs_number': [
+ _('Returns number of unread messages'),
+ [ ]
+ ],
+ 'start_chat': [
+ _('Opens \'Start Chat\' dialog'),
+ [
+ (_('account'), _('Starts chat, using this account'), True)
+ ]
+ ],
+ 'send_xml': [
+ _('Sends custom XML'),
+ [
+ ('xml', _('XML to send'), True),
+ ('account', _('Account in which the xml will be sent; '
+ 'if not specified, xml will be sent to all accounts'),
+ False)
+ ]
+ ],
+ 'change_avatar': [
+ _('Change the avatar'),
+ [
+ ('picture', _('Picture to use'), True),
+ ('account', _('Account in which the avatar will be set; '
+ 'if not specified, the avatar will be set for all accounts'),
+ False)
+ ]
+ ],
+ 'handle_uri': [
+ _('Handle a xmpp:/ uri'),
+ [
+ (_('uri'), _('URI to handle'), True),
+ (_('account'), _('Account in which you want to handle it'),
+ False),
+ (_('message'), _('Message content'), False)
+ ]
+ ],
+ 'join_room': [
+ _('Join a MUC room'),
+ [
+ (_('room'), _('Room JID'), True),
+ (_('nick'), _('Nickname to use'), False),
+ (_('password'), _('Password to enter the room'), False),
+ (_('account'), _('Account from which you want to enter the '
+ 'room'), False)
+ ]
+ ],
+ 'check_gajim_running':[
+ _('Check if Gajim is running'),
+ []
+ ],
+ 'toggle_ipython' : [
+ _('Shows or hides the ipython window'),
+ []
+ ],
+
+ }
+
+ self.sbus = None
+ if self.argv_len < 2 or sys.argv[1] not in self.commands.keys():
+ # no args or bad args
+ send_error(self.compose_help())
+ self.command = sys.argv[1]
+ if self.command == 'help':
+ if self.argv_len == 3:
+ print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING)
+ else:
+ print self.compose_help().encode(PREFERRED_ENCODING)
+ sys.exit(0)
+ if self.command == 'handle_uri':
+ self.handle_uri()
+ if self.command == 'check_gajim_running':
+ print self.check_gajim_running()
+ sys.exit(0)
+ self.init_connection()
+ self.check_arguments()
+
+ if self.command == 'contact_info':
+ if self.argv_len < 3:
+ send_error(_('Missing argument "contact_jid"'))
+
+ try:
+ res = self.call_remote_method()
+ except exceptions.ServiceNotAvailable:
+ # At this point an error message has already been displayed
+ sys.exit(1)
+ else:
+ self.print_result(res)
+
+ def print_result(self, res):
+ """
+ Print retrieved result to the output
+ """
+ if res is not None:
+ if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'):
+ if self.command in ('send_message', 'send_single_message'):
+ self.argv_len -= 2
+
+ if res is False:
+ if self.argv_len < 4:
+ send_error(_('\'%s\' is not in your roster.\n'
+ 'Please specify account for sending the message.') % sys.argv[2])
+ else:
+ send_error(_('You have no active account'))
+ elif self.command == 'list_accounts':
+ if isinstance(res, list):
+ for account in res:
+ if isinstance(account, unicode):
+ print account.encode(PREFERRED_ENCODING)
+ else:
+ print account
+ elif self.command == 'account_info':
+ if res:
+ print self.print_info(0, res, True)
+ elif self.command == 'list_contacts':
+ for account_dict in res:
+ print self.print_info(0, account_dict, True)
+ elif self.command == 'prefs_list':
+ pref_keys = sorted(res.keys())
+ for pref_key in pref_keys:
+ result = '%s = %s' % (pref_key, res[pref_key])
+ if isinstance(result, unicode):
+ print result.encode(PREFERRED_ENCODING)
+ else:
+ print result
+ elif self.command == 'contact_info':
+ print self.print_info(0, res, True)
+ elif res:
+ print unicode(res).encode(PREFERRED_ENCODING)
+
+ def check_gajim_running(self):
+ if not self.sbus:
+ try:
+ self.sbus = dbus.SessionBus()
+ except Exception:
+ raise exceptions.SessionBusNotPresent
+
+ test = False
+ if hasattr(self.sbus, 'name_has_owner'):
+ if self.sbus.name_has_owner(SERVICE):
+ test = True
+ elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(),
+ SERVICE):
+ test = True
+ return test
+
+ def init_connection(self):
+ """
+ Create the onnection to the session dbus, or exit if it is not possible
+ """
+ try:
+ self.sbus = dbus.SessionBus()
+ except Exception:
+ raise exceptions.SessionBusNotPresent
+
+ if not self.check_gajim_running():
+ send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.'))
+ obj = self.sbus.get_object(SERVICE, OBJ_PATH)
+ interface = dbus.Interface(obj, INTERFACE)
+
+ # get the function asked
+ self.method = interface.__getattr__(self.command)
+
+ def make_arguments_row(self, args):
+ """
+ Return arguments list. Mandatory arguments are enclosed with:
+ '<', '>', optional arguments - with '[', ']'
+ """
+ s = ''
+ for arg in args:
+ if arg[2]:
+ s += ' <' + arg[0] + '>'
+ else:
+ s += ' [' + arg[0] + ']'
+ return s
+
+ def help_on_command(self, command):
+ """
+ Return help message for a given command
+ """
+ if command in self.commands:
+ command_props = self.commands[command]
+ arguments_str = self.make_arguments_row(command_props[1])
+ str_ = _('Usage: %(basename)s %(command)s %(arguments)s \n\t %(help)s')\
+ % {'basename': BASENAME, 'command': command,
+ 'arguments': arguments_str, 'help': command_props[0]}
+ if len(command_props[1]) > 0:
+ str_ += '\n\n' + _('Arguments:') + '\n'
+ for argument in command_props[1]:
+ str_ += ' ' + argument[0] + ' - ' + argument[1] + '\n'
+ return str_
+ send_error(_('%s not found') % command)
+
+ def compose_help(self):
+ """
+ Print usage, and list available commands
+ """
+ s = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME
+ for command in sorted(self.commands):
+ s += ' ' + command
+ for arg in self.commands[command][1]:
+ if arg[2]:
+ s += ' <' + arg[0] + '>'
+ else:
+ s += ' [' + arg[0] + ']'
+ s += '\n'
+ return s
+
+ def print_info(self, level, prop_dict, encode_return = False):
+ """
+ Return formated string from data structure
+ """
+ if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)):
+ return ''
+ ret_str = ''
+ if isinstance(prop_dict, (list, tuple)):
+ ret_str = ''
+ spacing = ' ' * level * 4
+ for val in prop_dict:
+ if val is None:
+ ret_str +='\t'
+ elif isinstance(val, int):
+ ret_str +='\t' + str(val)
+ elif isinstance(val, (str, unicode)):
+ ret_str +='\t' + val
+ elif isinstance(val, (list, tuple)):
+ res = ''
+ for items in val:
+ res += self.print_info(level+1, items)
+ if res != '':
+ ret_str += '\t' + res
+ elif isinstance(val, dict):
+ ret_str += self.print_info(level+1, val)
+ ret_str = '%s(%s)\n' % (spacing, ret_str[1:])
+ elif isinstance(prop_dict, dict):
+ for key in prop_dict.keys():
+ val = prop_dict[key]
+ spacing = ' ' * level * 4
+ if isinstance(val, (unicode, int, str)):
+ if val is not None:
+ val = val.strip()
+ ret_str += '%s%-10s: %s\n' % (spacing, key, val)
+ elif isinstance(val, (list, tuple)):
+ res = ''
+ for items in val:
+ res += self.print_info(level+1, items)
+ if res != '':
+ ret_str += '%s%s: \n%s' % (spacing, key, res)
+ elif isinstance(val, dict):
+ res = self.print_info(level+1, val)
+ if res != '':
+ ret_str += '%s%s: \n%s' % (spacing, key, res)
+ if (encode_return):
+ try:
+ ret_str = ret_str.encode(PREFERRED_ENCODING)
+ except Exception:
+ pass
+ return ret_str
+
+ def check_arguments(self):
+ """
+ Make check if all necessary arguments are given
+ """
+ argv_len = self.argv_len - 2
+ args = self.commands[self.command][1]
+ if len(args) < argv_len:
+ send_error(_('Too many arguments. \n'
+ 'Type "%(basename)s help %(command)s" for more info') % {
+ 'basename': BASENAME, 'command': self.command})
+ if len(args) > argv_len:
+ if args[argv_len][2]:
+ send_error(_('Argument "%(arg)s" is not specified. \n'
+ 'Type "%(basename)s help %(command)s" for more info') %
+ {'arg': args[argv_len][0], 'basename': BASENAME,
+ 'command': self.command})
+ self.arguments = []
+ i = 0
+ for arg in sys.argv[2:]:
+ i += 1
+ if i < len(args):
+ self.arguments.append(arg)
+ else:
+ # it's latest argument with spaces
+ self.arguments.append(' '.join(sys.argv[i+1:]))
+ break
+ # add empty string for missing args
+ self.arguments += ['']*(len(args)-i)
+
+ def handle_uri(self):
+ if not sys.argv[2].startswith('xmpp:'):
+ send_error(_('Wrong uri'))
+ sys.argv[2] = sys.argv[2][5:]
+ uri = sys.argv[2]
+ if not '?' in uri:
+ self.command = sys.argv[1] = 'open_chat'
+ return
+ if 'body=' in uri:
+ # Open chat window and paste the text in the input message dialog
+ self.command = sys.argv[1] = 'open_chat'
+ message = uri.split('body=')
+ message = message[1].split(';')[0]
+ try:
+ message = urllib.unquote(message)
+ except UnicodeDecodeError:
+ pass
+ sys.argv[2] = uri.split('?')[0]
+ if len(sys.argv) == 4:
+ # jid in the sys.argv
+ sys.argv.append(message)
+ else:
+ sys.argv.append('')
+ sys.argv.append(message)
+ sys.argv[3] = ''
+ sys.argv[4] = message
+ return
+ (jid, action) = uri.split('?', 1)
+ try:
+ jid = urllib.unquote(jid)
+ except UnicodeDecodeError:
+ pass
+ sys.argv[2] = jid
+ if action == 'join':
+ self.command = sys.argv[1] = 'join_room'
+ # Move account parameter from position 3 to 5
+ sys.argv.append('')
+ sys.argv.append(sys.argv[3])
+ sys.argv[3] = ''
+ return
+ if action.startswith('roster'):
+ # Add contact to roster
+ self.command = sys.argv[1] = 'add_contact'
+ return
+ sys.exit(0)
+
+ def call_remote_method(self):
+ """
+ Calls self.method with arguments from sys.argv[2:]
+ """
+ args = [i.decode(PREFERRED_ENCODING) for i in self.arguments]
+ args = [dbus.String(i) for i in args]
+ try:
+ res = self.method(*args)
+ return res
+ except Exception:
+ raise exceptions.ServiceNotAvailable
+ return None
if __name__ == '__main__':
- GajimRemote()
-
-# vim: se ts=3:
+ GajimRemote()
diff --git a/src/gajim.py b/src/gajim.py
index bff8085a5..e3c9bb5a2 100644
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -40,24 +40,24 @@ import sys
import warnings
if os.name == 'nt':
- warnings.filterwarnings(action='ignore')
-
- if os.path.isdir('gtk'):
- # Used to create windows installer with GTK included
- paths = os.environ['PATH']
- list_ = paths.split(';')
- new_list = []
- for p in list_:
- if p.find('gtk') < 0 and p.find('GTK') < 0:
- new_list.append(p)
- new_list.insert(0, 'gtk/lib')
- new_list.insert(0, 'gtk/bin')
- os.environ['PATH'] = ';'.join(new_list)
- os.environ['GTK_BASEPATH'] = 'gtk'
+ warnings.filterwarnings(action='ignore')
+
+ if os.path.isdir('gtk'):
+ # Used to create windows installer with GTK included
+ paths = os.environ['PATH']
+ list_ = paths.split(';')
+ new_list = []
+ for p in list_:
+ if p.find('gtk') < 0 and p.find('GTK') < 0:
+ new_list.append(p)
+ new_list.insert(0, 'gtk/lib')
+ new_list.insert(0, 'gtk/bin')
+ os.environ['PATH'] = ';'.join(new_list)
+ os.environ['GTK_BASEPATH'] = 'gtk'
if os.name == 'nt':
- # needed for docutils
- sys.path.append('.')
+ # needed for docutils
+ sys.path.append('.')
from common import logging_helpers
logging_helpers.init('TERM' in os.environ)
@@ -70,32 +70,32 @@ import getopt
from common import i18n
def parseOpts():
- profile = ''
- config_path = None
-
- try:
- shortargs = 'hqvl:p:c:'
- longargs = 'help quiet verbose loglevel= profile= config_path='
- opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
- except getopt.error, msg:
- print msg
- print 'for help use --help'
- sys.exit(2)
- for o, a in opts:
- if o in ('-h', '--help'):
- print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]'
- sys.exit()
- elif o in ('-q', '--quiet'):
- logging_helpers.set_quiet()
- elif o in ('-v', '--verbose'):
- logging_helpers.set_verbose()
- elif o in ('-p', '--profile'): # gajim --profile name
- profile = a
- elif o in ('-l', '--loglevel'):
- logging_helpers.set_loglevels(a)
- elif o in ('-c', '--config-path'):
- config_path = a
- return profile, config_path
+ profile = ''
+ config_path = None
+
+ try:
+ shortargs = 'hqvl:p:c:'
+ longargs = 'help quiet verbose loglevel= profile= config_path='
+ opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
+ except getopt.error, msg:
+ print msg
+ print 'for help use --help'
+ sys.exit(2)
+ for o, a in opts:
+ if o in ('-h', '--help'):
+ print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]'
+ sys.exit()
+ elif o in ('-q', '--quiet'):
+ logging_helpers.set_quiet()
+ elif o in ('-v', '--verbose'):
+ logging_helpers.set_verbose()
+ elif o in ('-p', '--profile'): # gajim --profile name
+ profile = a
+ elif o in ('-l', '--loglevel'):
+ logging_helpers.set_loglevels(a)
+ elif o in ('-c', '--config-path'):
+ config_path = a
+ return profile, config_path
profile, config_path = parseOpts()
del parseOpts
@@ -110,102 +110,102 @@ common.configpaths.gajimpaths.init_profile(profile)
del profile
if os.name == 'nt':
- class MyStderr(object):
- _file = None
- _error = None
- def write(self, text):
- fname=os.path.join(common.configpaths.gajimpaths.cache_root,
- os.path.split(sys.executable)[1]+'.log')
- if self._file is None and self._error is None:
- try:
- self._file = open(fname, 'a')
- except Exception, details:
- self._error = details
- if self._file is not None:
- self._file.write(text)
- self._file.flush()
- def flush(self):
- if self._file is not None:
- self._file.flush()
-
- sys.stderr = MyStderr()
+ class MyStderr(object):
+ _file = None
+ _error = None
+ def write(self, text):
+ fname=os.path.join(common.configpaths.gajimpaths.cache_root,
+ os.path.split(sys.executable)[1]+'.log')
+ if self._file is None and self._error is None:
+ try:
+ self._file = open(fname, 'a')
+ except Exception, details:
+ self._error = details
+ if self._file is not None:
+ self._file.write(text)
+ self._file.flush()
+ def flush(self):
+ if self._file is not None:
+ self._file.flush()
+
+ sys.stderr = MyStderr()
# PyGTK2.10+ only throws a warning
warnings.filterwarnings('error', module='gtk')
try:
- import gtk
+ import gtk
except Warning, msg:
- if str(msg) == 'could not open display':
- print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
- else:
- print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg)
- sys.exit()
+ if str(msg) == 'could not open display':
+ print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
+ else:
+ print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg)
+ sys.exit()
warnings.resetwarnings()
if os.name == 'nt':
- warnings.filterwarnings(action='ignore')
+ warnings.filterwarnings(action='ignore')
pritext = ''
from common import exceptions
try:
- from common import gajim
+ from common import gajim
except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH
else:
- from common import dbus_support
- if dbus_support.supported:
- from music_track_listener import MusicTrackListener
- import dbus
-
- from ctypes import CDLL
- from ctypes.util import find_library
- import platform
-
- sysname = platform.system()
- if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'):
- libc = CDLL(find_library('c'))
-
- # The constant defined in <linux/prctl.h> which is used to set the name of
- # the process.
- PR_SET_NAME = 15
-
- if sysname == 'Linux':
- libc.prctl(PR_SET_NAME, 'gajim')
- elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'):
- libc.setproctitle('gajim')
-
- if gtk.pygtk_version < (2, 16, 0):
- pritext = _('Gajim needs PyGTK 2.16 or above')
- sectext = _('Gajim needs PyGTK 2.16 or above to run. Quiting...')
- elif gtk.gtk_version < (2, 16, 0):
- pritext = _('Gajim needs GTK 2.16 or above')
- sectext = _('Gajim needs GTK 2.16 or above to run. Quiting...')
-
- try:
- from common import check_paths
- except exceptions.PysqliteNotAvailable, e:
- pritext = _('Gajim needs PySQLite2 to run')
- sectext = str(e)
-
- if os.name == 'nt':
- try:
- import winsound # windows-only built-in module for playing wav
- import win32api # do NOT remove. we req this module
- except Exception:
- pritext = _('Gajim needs pywin32 to run')
- sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018'
+ from common import dbus_support
+ if dbus_support.supported:
+ from music_track_listener import MusicTrackListener
+ import dbus
+
+ from ctypes import CDLL
+ from ctypes.util import find_library
+ import platform
+
+ sysname = platform.system()
+ if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'):
+ libc = CDLL(find_library('c'))
+
+ # The constant defined in <linux/prctl.h> which is used to set the name of
+ # the process.
+ PR_SET_NAME = 15
+
+ if sysname == 'Linux':
+ libc.prctl(PR_SET_NAME, 'gajim')
+ elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'):
+ libc.setproctitle('gajim')
+
+ if gtk.pygtk_version < (2, 16, 0):
+ pritext = _('Gajim needs PyGTK 2.16 or above')
+ sectext = _('Gajim needs PyGTK 2.16 or above to run. Quiting...')
+ elif gtk.gtk_version < (2, 16, 0):
+ pritext = _('Gajim needs GTK 2.16 or above')
+ sectext = _('Gajim needs GTK 2.16 or above to run. Quiting...')
+
+ try:
+ from common import check_paths
+ except exceptions.PysqliteNotAvailable, e:
+ pritext = _('Gajim needs PySQLite2 to run')
+ sectext = str(e)
+
+ if os.name == 'nt':
+ try:
+ import winsound # windows-only built-in module for playing wav
+ import win32api # do NOT remove. we req this module
+ except Exception:
+ pritext = _('Gajim needs pywin32 to run')
+ sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018'
if pritext:
- dlg = gtk.MessageDialog(None,
- gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
- gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
+ dlg = gtk.MessageDialog(None,
+ gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
- dlg.format_secondary_text(sectext)
- dlg.run()
- dlg.destroy()
- sys.exit()
+ dlg.format_secondary_text(sectext)
+ dlg.run()
+ dlg.destroy()
+ sys.exit()
del pritext
@@ -213,9 +213,9 @@ import gtkexcepthook
import gobject
if not hasattr(gobject, 'timeout_add_seconds'):
- def timeout_add_seconds_fake(time_sec, *args):
- return gobject.timeout_add(time_sec * 1000, *args)
- gobject.timeout_add_seconds = timeout_add_seconds_fake
+ def timeout_add_seconds_fake(time_sec, *args):
+ return gobject.timeout_add(time_sec * 1000, *args)
+ gobject.timeout_add_seconds = timeout_add_seconds_fake
import signal
@@ -231,184 +231,182 @@ import errno
import dialogs
def pid_alive():
- try:
- pf = open(pid_filename)
- except IOError:
- # probably file not found
- return False
-
- try:
- pid = int(pf.read().strip())
- pf.close()
- except Exception:
- traceback.print_exc()
- # PID file exists, but something happened trying to read PID
- # Could be 0.10 style empty PID file, so assume Gajim is running
- return True
-
- if os.name == 'nt':
- try:
- from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, )
- except Exception:
- return True
-
- class PROCESSENTRY32(Structure):
- _fields_ = [
- ('dwSize', c_ulong, ),
- ('cntUsage', c_ulong, ),
- ('th32ProcessID', c_ulong, ),
- ('th32DefaultHeapID', c_ulong, ),
- ('th32ModuleID', c_ulong, ),
- ('cntThreads', c_ulong, ),
- ('th32ParentProcessID', c_ulong, ),
- ('pcPriClassBase', c_ulong, ),
- ('dwFlags', c_ulong, ),
- ('szExeFile', c_char*512, ),
- ]
- def __init__(self):
- Structure.__init__(self, 512+9*4)
-
- k = windll.kernel32
- k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong,
- k.CreateToolhelp32Snapshot.restype = c_int
- k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32),
- k.Process32First.restype = c_int
- k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32),
- k.Process32Next.restype = c_int
-
- def get_p(p):
- h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS
- assert h > 0, 'CreateToolhelp32Snapshot failed'
- b = pointer(PROCESSENTRY32())
- f = k.Process32First(h, b)
- while f:
- if b.contents.th32ProcessID == p:
- return b.contents.szExeFile
- f = k.Process32Next(h, b)
-
- if get_p(pid) in ('python.exe', 'gajim.exe'):
- return True
- return False
- try:
- if not os.path.exists('/proc'):
- return True # no /proc, assume Gajim is running
-
- try:
- f = open('/proc/%d/cmdline'% pid)
- except IOError, e:
- if e.errno == errno.ENOENT:
- return False # file/pid does not exist
- raise
-
- n = f.read().lower()
- f.close()
- if n.find('gajim') < 0:
- return False
- return True # Running Gajim found at pid
- except Exception:
- traceback.print_exc()
-
- # If we are here, pidfile exists, but some unexpected error occured.
- # Assume Gajim is running.
- return True
+ try:
+ pf = open(pid_filename)
+ except IOError:
+ # probably file not found
+ return False
+
+ try:
+ pid = int(pf.read().strip())
+ pf.close()
+ except Exception:
+ traceback.print_exc()
+ # PID file exists, but something happened trying to read PID
+ # Could be 0.10 style empty PID file, so assume Gajim is running
+ return True
+
+ if os.name == 'nt':
+ try:
+ from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, )
+ except Exception:
+ return True
+
+ class PROCESSENTRY32(Structure):
+ _fields_ = [
+ ('dwSize', c_ulong, ),
+ ('cntUsage', c_ulong, ),
+ ('th32ProcessID', c_ulong, ),
+ ('th32DefaultHeapID', c_ulong, ),
+ ('th32ModuleID', c_ulong, ),
+ ('cntThreads', c_ulong, ),
+ ('th32ParentProcessID', c_ulong, ),
+ ('pcPriClassBase', c_ulong, ),
+ ('dwFlags', c_ulong, ),
+ ('szExeFile', c_char*512, ),
+ ]
+ def __init__(self):
+ Structure.__init__(self, 512+9*4)
+
+ k = windll.kernel32
+ k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong,
+ k.CreateToolhelp32Snapshot.restype = c_int
+ k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32),
+ k.Process32First.restype = c_int
+ k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32),
+ k.Process32Next.restype = c_int
+
+ def get_p(p):
+ h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS
+ assert h > 0, 'CreateToolhelp32Snapshot failed'
+ b = pointer(PROCESSENTRY32())
+ f = k.Process32First(h, b)
+ while f:
+ if b.contents.th32ProcessID == p:
+ return b.contents.szExeFile
+ f = k.Process32Next(h, b)
+
+ if get_p(pid) in ('python.exe', 'gajim.exe'):
+ return True
+ return False
+ try:
+ if not os.path.exists('/proc'):
+ return True # no /proc, assume Gajim is running
+
+ try:
+ f = open('/proc/%d/cmdline'% pid)
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ return False # file/pid does not exist
+ raise
+
+ n = f.read().lower()
+ f.close()
+ if n.find('gajim') < 0:
+ return False
+ return True # Running Gajim found at pid
+ except Exception:
+ traceback.print_exc()
+
+ # If we are here, pidfile exists, but some unexpected error occured.
+ # Assume Gajim is running.
+ return True
if pid_alive():
- pix = gtkgui_helpers.get_icon_pixmap('gajim', 48)
- gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
- pritext = _('Gajim is already running')
- sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
- dialog = dialogs.YesNoDialog(pritext, sectext)
- dialog.popup()
- if dialog.run() != gtk.RESPONSE_YES:
- sys.exit(3)
- dialog.destroy()
- # run anyway, delete pid and useless global vars
- if os.path.exists(pid_filename):
- os.remove(pid_filename)
- del path_to_file
- del pix
- del pritext
- del sectext
- dialog.destroy()
+ pix = gtkgui_helpers.get_icon_pixmap('gajim', 48)
+ gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
+ pritext = _('Gajim is already running')
+ sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
+ dialog = dialogs.YesNoDialog(pritext, sectext)
+ dialog.popup()
+ if dialog.run() != gtk.RESPONSE_YES:
+ sys.exit(3)
+ dialog.destroy()
+ # run anyway, delete pid and useless global vars
+ if os.path.exists(pid_filename):
+ os.remove(pid_filename)
+ del path_to_file
+ del pix
+ del pritext
+ del sectext
+ dialog.destroy()
# Create .gajim dir
pid_dir = os.path.dirname(pid_filename)
if not os.path.exists(pid_dir):
- check_paths.create_path(pid_dir)
+ check_paths.create_path(pid_dir)
# Create pid file
try:
- f = open(pid_filename, 'w')
- f.write(str(os.getpid()))
- f.close()
+ f = open(pid_filename, 'w')
+ f.write(str(os.getpid()))
+ f.close()
except IOError, e:
- dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e))
- dlg.run()
- dlg.destroy()
- sys.exit()
+ dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e))
+ dlg.run()
+ dlg.destroy()
+ sys.exit()
del pid_dir
del f
def on_exit():
- # delete pid file on normal exit
- if os.path.exists(pid_filename):
- os.remove(pid_filename)
- # Shutdown GUI and save config
- if hasattr(gajim.interface, 'roster'):
- gajim.interface.roster.prepare_quit()
+ # delete pid file on normal exit
+ if os.path.exists(pid_filename):
+ os.remove(pid_filename)
+ # Shutdown GUI and save config
+ if hasattr(gajim.interface, 'roster'):
+ gajim.interface.roster.prepare_quit()
import atexit
atexit.register(on_exit)
-
+
from gui_interface import Interface
if __name__ == '__main__':
- def sigint_cb(num, stack):
- sys.exit(5)
- # ^C exits the application normally to delete pid file
- signal.signal(signal.SIGINT, sigint_cb)
-
- log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \
- sys.getfilesystemencoding(), locale.getpreferredencoding())
-
- if os.name != 'nt':
- # Session Management support
- try:
- import gnome.ui
- raise ImportError
- except ImportError:
- pass
- else:
- def die_cb(cli):
- gajim.interface.roster.quit_gtkgui_interface()
- gnome.program_init('gajim', gajim.version)
- cli = gnome.ui.master_client()
- cli.connect('die', die_cb)
-
- path_to_gajim_script = gtkgui_helpers.get_abspath_for_script(
- 'gajim')
-
- if path_to_gajim_script:
- argv = [path_to_gajim_script]
- # FIXME: remove this typeerror catch when gnome python is old and
- # not bad patched by distro men [2.12.0 + should not need all that
- # NORMALLY]
- try:
- cli.set_restart_command(argv)
- except AttributeError:
- cli.set_restart_command(len(argv), argv)
-
- check_paths.check_and_possibly_create_paths()
-
- interface = Interface()
- interface.run()
-
- try:
- if os.name != 'nt':
- # This makes Gajim unusable under windows, and threads are used only
- # for GPG, so not under windows
- gtk.gdk.threads_init()
- gtk.main()
- except KeyboardInterrupt:
- print >> sys.stderr, 'KeyboardInterrupt'
-
-# vim: se ts=3:
+ def sigint_cb(num, stack):
+ sys.exit(5)
+ # ^C exits the application normally to delete pid file
+ signal.signal(signal.SIGINT, sigint_cb)
+
+ log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \
+ sys.getfilesystemencoding(), locale.getpreferredencoding())
+
+ if os.name != 'nt':
+ # Session Management support
+ try:
+ import gnome.ui
+ raise ImportError
+ except ImportError:
+ pass
+ else:
+ def die_cb(cli):
+ gajim.interface.roster.quit_gtkgui_interface()
+ gnome.program_init('gajim', gajim.version)
+ cli = gnome.ui.master_client()
+ cli.connect('die', die_cb)
+
+ path_to_gajim_script = gtkgui_helpers.get_abspath_for_script(
+ 'gajim')
+
+ if path_to_gajim_script:
+ argv = [path_to_gajim_script]
+ # FIXME: remove this typeerror catch when gnome python is old and
+ # not bad patched by distro men [2.12.0 + should not need all that
+ # NORMALLY]
+ try:
+ cli.set_restart_command(argv)
+ except AttributeError:
+ cli.set_restart_command(len(argv), argv)
+
+ check_paths.check_and_possibly_create_paths()
+
+ interface = Interface()
+ interface.run()
+
+ try:
+ if os.name != 'nt':
+ # This makes Gajim unusable under windows, and threads are used only
+ # for GPG, so not under windows
+ gtk.gdk.threads_init()
+ gtk.main()
+ except KeyboardInterrupt:
+ print >> sys.stderr, 'KeyboardInterrupt'
diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py
index b754b7226..5d83de43b 100644
--- a/src/gajim_themes_window.py
+++ b/src/gajim_themes_window.py
@@ -31,378 +31,376 @@ from common import gajim
class GajimThemesWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('gajim_themes_window.ui')
- self.window = self.xml.get_object('gajim_themes_window')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- self.options = ['account', 'group', 'contact', 'banner']
- self.options_combobox = self.xml.get_object('options_combobox')
- self.textcolor_checkbutton = self.xml.get_object('textcolor_checkbutton')
- self.background_checkbutton = self.xml.get_object('background_checkbutton')
- self.textfont_checkbutton = self.xml.get_object('textfont_checkbutton')
- self.text_colorbutton = self.xml.get_object('text_colorbutton')
- self.background_colorbutton = self.xml.get_object('background_colorbutton')
- self.text_fontbutton = self.xml.get_object('text_fontbutton')
- self.bold_togglebutton = self.xml.get_object('bold_togglebutton')
- self.italic_togglebutton = self.xml.get_object('italic_togglebutton')
- self.themes_tree = self.xml.get_object('themes_treeview')
- self.theme_options_vbox = self.xml.get_object('theme_options_vbox')
- self.theme_options_table = self.xml.get_object('theme_options_table')
- self.colorbuttons = {}
- for chatstate in ('inactive', 'composing', 'paused', 'gone',
- 'muc_msg', 'muc_directed_msg'):
- self.colorbuttons[chatstate] = self.xml.get_object(chatstate + \
- '_colorbutton')
- model = gtk.ListStore(str)
- self.themes_tree.set_model(model)
- col = gtk.TreeViewColumn(_('Theme'))
- self.themes_tree.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.set_attributes(renderer, text = 0)
- renderer.connect('edited', self.on_theme_cell_edited)
- renderer.set_property('editable', True)
- self.current_theme = gajim.config.get('roster_theme')
- self.no_update = False
- self.fill_themes_treeview()
- self.select_active_theme()
- self.current_option = self.options[0]
- self.set_theme_options(self.current_theme, self.current_option)
-
- self.xml.connect_signals(self)
- self.window.connect('delete-event', self.on_themese_window_delete_event)
- self.themes_tree.get_selection().connect('changed',
- self.selection_changed)
- self.window.show_all()
-
- def on_themese_window_delete_event(self, widget, event):
- self.window.hide()
- return True # do NOT destroy the window
-
- def on_close_button_clicked(self, widget):
- if 'preferences' in gajim.interface.instances:
- gajim.interface.instances['preferences'].update_theme_list()
- self.window.hide()
-
- def on_theme_cell_edited(self, cell, row, new_name):
- model = self.themes_tree.get_model()
- iter_ = model.get_iter_from_string(row)
- old_name = model.get_value(iter_, 0).decode('utf-8')
- new_name = new_name.decode('utf-8')
- if old_name == new_name:
- return
- if old_name == 'default':
- dialogs.ErrorDialog(
- _('You cannot make changes to the default theme'),
- _('Please create a clean new theme with your desired name.'))
- return
- new_config_name = new_name.replace(' ', '_')
- if new_config_name in gajim.config.get_per('themes'):
- return
- gajim.config.add_per('themes', new_config_name)
- # Copy old theme values
- old_config_name = old_name.replace(' ', '_')
- properties = ['textcolor', 'bgcolor', 'font', 'fontattrs']
- gajim.config.add_per('themes', new_config_name)
- for option in self.options:
- for property_ in properties:
- option_name = option + property_
- gajim.config.set_per('themes', new_config_name, option_name,
- gajim.config.get_per('themes', old_config_name, option_name))
- gajim.config.del_per('themes', old_config_name)
- if old_config_name == gajim.config.get('roster_theme'):
- gajim.config.set('roster_theme', new_config_name)
- model.set_value(iter_, 0, new_name)
- self.current_theme = new_name
-
- def fill_themes_treeview(self):
- model = self.themes_tree.get_model()
- model.clear()
- for config_theme in gajim.config.get_per('themes'):
- theme = config_theme.replace('_', ' ')
- model.append([theme])
-
- def select_active_theme(self):
- model = self.themes_tree.get_model()
- iter_ = model.get_iter_root()
- active_theme = gajim.config.get('roster_theme').replace('_', ' ')
- while iter_:
- theme = model[iter_][0]
- if theme == active_theme:
- self.themes_tree.get_selection().select_iter(iter_)
- if active_theme == 'default':
- self.xml.get_object('remove_button').set_sensitive(False)
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- else:
- self.xml.get_object('remove_button').set_sensitive(True)
- self.theme_options_vbox.set_sensitive(True)
- self.theme_options_table.set_sensitive(True)
- break
- iter_ = model.iter_next(iter_)
-
- def selection_changed(self, widget = None):
- (model, iter_) = self.themes_tree.get_selection().get_selected()
- selected = self.themes_tree.get_selection().get_selected_rows()
- if not iter_ or selected[1] == []:
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- return
- self.current_theme = model.get_value(iter_, 0).decode('utf-8')
- self.current_theme = self.current_theme.replace(' ', '_')
- self.set_theme_options(self.current_theme)
- if self.current_theme == 'default':
- self.xml.get_object('remove_button').set_sensitive(False)
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- else:
- self.xml.get_object('remove_button').set_sensitive(True)
- self.theme_options_vbox.set_sensitive(True)
- self.theme_options_table.set_sensitive(True)
-
- def on_add_button_clicked(self, widget):
- model = self.themes_tree.get_model()
- iter_ = model.append()
- i = 0
- # don't confuse translators
- theme_name = _('theme name')
- theme_name_ns = theme_name.replace(' ', '_')
- while theme_name_ns + unicode(i) in gajim.config.get_per('themes'):
- i += 1
- model.set_value(iter_, 0, theme_name + unicode(i))
- gajim.config.add_per('themes', theme_name_ns + unicode(i))
- self.themes_tree.get_selection().select_iter(iter_)
- col = self.themes_tree.get_column(0)
- path = model.get_path(iter_)
- self.themes_tree.set_cursor(path, col, True)
-
- def on_remove_button_clicked(self, widget):
- (model, iter_) = self.themes_tree.get_selection().get_selected()
- if not iter_:
- return
- if self.current_theme == gajim.config.get('roster_theme'):
- dialogs.ErrorDialog(
- _('You cannot delete your current theme'),
- _('Please first choose another for your current theme.'))
- return
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- self.xml.get_object('remove_button').set_sensitive(False)
- gajim.config.del_per('themes', self.current_theme)
- model.remove(iter_)
-
- def set_theme_options(self, theme, option = 'account'):
- self.no_update = True
- self.options_combobox.set_active(self.options.index(option))
- textcolor = gajim.config.get_per('themes', theme, option + 'textcolor')
- if textcolor:
- state = True
- self.text_colorbutton.set_color(gtk.gdk.color_parse(textcolor))
- else:
- state = False
- self.textcolor_checkbutton.set_active(state)
- self.text_colorbutton.set_sensitive(state)
- bgcolor = gajim.config.get_per('themes', theme, option + 'bgcolor')
- if bgcolor:
- state = True
- self.background_colorbutton.set_color(gtk.gdk.color_parse(
- bgcolor))
- else:
- state = False
- self.background_checkbutton.set_active(state)
- self.background_colorbutton.set_sensitive(state)
-
- # get the font name before we set widgets and it will not be overriden
- font_name = gajim.config.get_per('themes', theme, option + 'font')
- font_attrs = gajim.config.get_per('themes', theme, option + 'fontattrs')
- self._set_font_widgets(font_attrs)
- if font_name:
- state = True
- self.text_fontbutton.set_font_name(font_name)
- else:
- state = False
- self.textfont_checkbutton.set_active(state)
- self.text_fontbutton.set_sensitive(state)
- self.no_update = False
- gajim.interface.roster.change_roster_style(None)
-
- for chatstate in ('inactive', 'composing', 'paused', 'gone',
- 'muc_msg', 'muc_directed_msg'):
- color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \
- '_color')
- self.colorbuttons[chatstate].set_color(gtk.gdk.color_parse(color))
-
- def on_textcolor_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.text_colorbutton.set_sensitive(state)
- self._set_color(state, self.text_colorbutton,
- 'textcolor')
-
- def on_background_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.background_colorbutton.set_sensitive(state)
- self._set_color(state, self.background_colorbutton,
- 'bgcolor')
-
- def on_textfont_checkbutton_toggled(self, widget):
- self.text_fontbutton.set_sensitive(widget.get_active())
- self._set_font()
-
- def on_text_colorbutton_color_set(self, widget):
- self._set_color(True, widget, 'textcolor')
-
- def on_background_colorbutton_color_set(self, widget):
- self._set_color(True, widget, 'bgcolor')
-
- def on_text_fontbutton_font_set(self, widget):
- self._set_font()
-
- def on_options_combobox_changed(self, widget):
- index = self.options_combobox.get_active()
- if index == -1:
- return
- self.current_option = self.options[index]
- self.set_theme_options(self.current_theme,
- self.current_option)
-
- def on_bold_togglebutton_toggled(self, widget):
- if not self.no_update:
- self._set_font()
-
- def on_italic_togglebutton_toggled(self, widget):
- if not self.no_update:
- self._set_font()
-
- def _set_color(self, state, widget, option):
- """
- Set color value in prefs and update the UI
- """
- if state:
- color = widget.get_color()
- color_string = gtkgui_helpers.make_color_string(color)
- else:
- color_string = ''
- begin_option = ''
- if not option.startswith('state'):
- begin_option = self.current_option
- gajim.config.set_per('themes', self.current_theme,
- begin_option + option, color_string)
- # use faster functions for this
- if self.current_option == 'banner':
- gajim.interface.roster.repaint_themed_widgets()
- gajim.interface.save_config()
- return
- if self.no_update:
- return
- gajim.interface.roster.change_roster_style(self.current_option)
- gajim.interface.save_config()
-
- def _set_font(self):
- """
- Set font value in prefs and update the UI
- """
- state = self.textfont_checkbutton.get_active()
- if state:
- font_string = self.text_fontbutton.get_font_name()
- else:
- font_string = ''
- gajim.config.set_per('themes', self.current_theme,
- self.current_option + 'font', font_string)
- font_attrs = self._get_font_attrs()
- gajim.config.set_per('themes', self.current_theme,
- self.current_option + 'fontattrs', font_attrs)
- # use faster functions for this
- if self.current_option == 'banner':
- gajim.interface.roster.repaint_themed_widgets()
- if self.no_update:
- return
- gajim.interface.roster.change_roster_style(self.current_option)
- gajim.interface.save_config()
-
- def _toggle_font_widgets(self, font_props):
- """
- Toggle font buttons with the bool values of font_props tuple
- """
- self.bold_togglebutton.set_active(font_props[0])
- self.italic_togglebutton.set_active(font_props[1])
-
- def _get_font_description(self):
- """
- Return a FontDescription from togglebuttons states
- """
- fd = pango.FontDescription()
- if self.bold_togglebutton.get_active():
- fd.set_weight(pango.WEIGHT_BOLD)
- if self.italic_togglebutton.get_active():
- fd.set_style(pango.STYLE_ITALIC)
- return fd
-
- def _set_font_widgets(self, font_attrs):
- """
- Set the correct toggle state of font style buttons by a font string of
- type 'BI'
- """
- font_props = [False, False, False]
- if font_attrs:
- if font_attrs.find('B') != -1:
- font_props[0] = True
- if font_attrs.find('I') != -1:
- font_props[1] = True
- self._toggle_font_widgets(font_props)
-
- def _get_font_attrs(self):
- """
- Get a string with letters of font attribures: 'BI'
- """
- attrs = ''
- if self.bold_togglebutton.get_active():
- attrs += 'B'
- if self.italic_togglebutton.get_active():
- attrs += 'I'
- return attrs
-
-
- def _get_font_props(self, font_name):
- """
- Get tuple of font properties: weight, style
- """
- font_props = [False, False, False]
- font_description = pango.FontDescription(font_name)
- if font_description.get_weight() != pango.WEIGHT_NORMAL:
- font_props[0] = True
- if font_description.get_style() != pango.STYLE_ITALIC:
- font_props[1] = True
- return font_props
-
- def on_inactive_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_inactive_color')
- self.no_update = False
-
- def on_composing_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_composing_color')
- self.no_update = False
-
- def on_paused_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_paused_color')
- self.no_update = False
-
- def on_gone_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_gone_color')
- self.no_update = False
-
- def on_muc_msg_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_muc_msg_color')
- self.no_update = False
-
- def on_muc_directed_msg_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_muc_directed_msg_color')
- self.no_update = False
-
-# vim: se ts=3:
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_gtk_builder('gajim_themes_window.ui')
+ self.window = self.xml.get_object('gajim_themes_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+
+ self.options = ['account', 'group', 'contact', 'banner']
+ self.options_combobox = self.xml.get_object('options_combobox')
+ self.textcolor_checkbutton = self.xml.get_object('textcolor_checkbutton')
+ self.background_checkbutton = self.xml.get_object('background_checkbutton')
+ self.textfont_checkbutton = self.xml.get_object('textfont_checkbutton')
+ self.text_colorbutton = self.xml.get_object('text_colorbutton')
+ self.background_colorbutton = self.xml.get_object('background_colorbutton')
+ self.text_fontbutton = self.xml.get_object('text_fontbutton')
+ self.bold_togglebutton = self.xml.get_object('bold_togglebutton')
+ self.italic_togglebutton = self.xml.get_object('italic_togglebutton')
+ self.themes_tree = self.xml.get_object('themes_treeview')
+ self.theme_options_vbox = self.xml.get_object('theme_options_vbox')
+ self.theme_options_table = self.xml.get_object('theme_options_table')
+ self.colorbuttons = {}
+ for chatstate in ('inactive', 'composing', 'paused', 'gone',
+ 'muc_msg', 'muc_directed_msg'):
+ self.colorbuttons[chatstate] = self.xml.get_object(chatstate + \
+ '_colorbutton')
+ model = gtk.ListStore(str)
+ self.themes_tree.set_model(model)
+ col = gtk.TreeViewColumn(_('Theme'))
+ self.themes_tree.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text = 0)
+ renderer.connect('edited', self.on_theme_cell_edited)
+ renderer.set_property('editable', True)
+ self.current_theme = gajim.config.get('roster_theme')
+ self.no_update = False
+ self.fill_themes_treeview()
+ self.select_active_theme()
+ self.current_option = self.options[0]
+ self.set_theme_options(self.current_theme, self.current_option)
+
+ self.xml.connect_signals(self)
+ self.window.connect('delete-event', self.on_themese_window_delete_event)
+ self.themes_tree.get_selection().connect('changed',
+ self.selection_changed)
+ self.window.show_all()
+
+ def on_themese_window_delete_event(self, widget, event):
+ self.window.hide()
+ return True # do NOT destroy the window
+
+ def on_close_button_clicked(self, widget):
+ if 'preferences' in gajim.interface.instances:
+ gajim.interface.instances['preferences'].update_theme_list()
+ self.window.hide()
+
+ def on_theme_cell_edited(self, cell, row, new_name):
+ model = self.themes_tree.get_model()
+ iter_ = model.get_iter_from_string(row)
+ old_name = model.get_value(iter_, 0).decode('utf-8')
+ new_name = new_name.decode('utf-8')
+ if old_name == new_name:
+ return
+ if old_name == 'default':
+ dialogs.ErrorDialog(
+ _('You cannot make changes to the default theme'),
+ _('Please create a clean new theme with your desired name.'))
+ return
+ new_config_name = new_name.replace(' ', '_')
+ if new_config_name in gajim.config.get_per('themes'):
+ return
+ gajim.config.add_per('themes', new_config_name)
+ # Copy old theme values
+ old_config_name = old_name.replace(' ', '_')
+ properties = ['textcolor', 'bgcolor', 'font', 'fontattrs']
+ gajim.config.add_per('themes', new_config_name)
+ for option in self.options:
+ for property_ in properties:
+ option_name = option + property_
+ gajim.config.set_per('themes', new_config_name, option_name,
+ gajim.config.get_per('themes', old_config_name, option_name))
+ gajim.config.del_per('themes', old_config_name)
+ if old_config_name == gajim.config.get('roster_theme'):
+ gajim.config.set('roster_theme', new_config_name)
+ model.set_value(iter_, 0, new_name)
+ self.current_theme = new_name
+
+ def fill_themes_treeview(self):
+ model = self.themes_tree.get_model()
+ model.clear()
+ for config_theme in gajim.config.get_per('themes'):
+ theme = config_theme.replace('_', ' ')
+ model.append([theme])
+
+ def select_active_theme(self):
+ model = self.themes_tree.get_model()
+ iter_ = model.get_iter_root()
+ active_theme = gajim.config.get('roster_theme').replace('_', ' ')
+ while iter_:
+ theme = model[iter_][0]
+ if theme == active_theme:
+ self.themes_tree.get_selection().select_iter(iter_)
+ if active_theme == 'default':
+ self.xml.get_object('remove_button').set_sensitive(False)
+ self.theme_options_vbox.set_sensitive(False)
+ self.theme_options_table.set_sensitive(False)
+ else:
+ self.xml.get_object('remove_button').set_sensitive(True)
+ self.theme_options_vbox.set_sensitive(True)
+ self.theme_options_table.set_sensitive(True)
+ break
+ iter_ = model.iter_next(iter_)
+
+ def selection_changed(self, widget = None):
+ (model, iter_) = self.themes_tree.get_selection().get_selected()
+ selected = self.themes_tree.get_selection().get_selected_rows()
+ if not iter_ or selected[1] == []:
+ self.theme_options_vbox.set_sensitive(False)
+ self.theme_options_table.set_sensitive(False)
+ return
+ self.current_theme = model.get_value(iter_, 0).decode('utf-8')
+ self.current_theme = self.current_theme.replace(' ', '_')
+ self.set_theme_options(self.current_theme)
+ if self.current_theme == 'default':
+ self.xml.get_object('remove_button').set_sensitive(False)
+ self.theme_options_vbox.set_sensitive(False)
+ self.theme_options_table.set_sensitive(False)
+ else:
+ self.xml.get_object('remove_button').set_sensitive(True)
+ self.theme_options_vbox.set_sensitive(True)
+ self.theme_options_table.set_sensitive(True)
+
+ def on_add_button_clicked(self, widget):
+ model = self.themes_tree.get_model()
+ iter_ = model.append()
+ i = 0
+ # don't confuse translators
+ theme_name = _('theme name')
+ theme_name_ns = theme_name.replace(' ', '_')
+ while theme_name_ns + unicode(i) in gajim.config.get_per('themes'):
+ i += 1
+ model.set_value(iter_, 0, theme_name + unicode(i))
+ gajim.config.add_per('themes', theme_name_ns + unicode(i))
+ self.themes_tree.get_selection().select_iter(iter_)
+ col = self.themes_tree.get_column(0)
+ path = model.get_path(iter_)
+ self.themes_tree.set_cursor(path, col, True)
+
+ def on_remove_button_clicked(self, widget):
+ (model, iter_) = self.themes_tree.get_selection().get_selected()
+ if not iter_:
+ return
+ if self.current_theme == gajim.config.get('roster_theme'):
+ dialogs.ErrorDialog(
+ _('You cannot delete your current theme'),
+ _('Please first choose another for your current theme.'))
+ return
+ self.theme_options_vbox.set_sensitive(False)
+ self.theme_options_table.set_sensitive(False)
+ self.xml.get_object('remove_button').set_sensitive(False)
+ gajim.config.del_per('themes', self.current_theme)
+ model.remove(iter_)
+
+ def set_theme_options(self, theme, option = 'account'):
+ self.no_update = True
+ self.options_combobox.set_active(self.options.index(option))
+ textcolor = gajim.config.get_per('themes', theme, option + 'textcolor')
+ if textcolor:
+ state = True
+ self.text_colorbutton.set_color(gtk.gdk.color_parse(textcolor))
+ else:
+ state = False
+ self.textcolor_checkbutton.set_active(state)
+ self.text_colorbutton.set_sensitive(state)
+ bgcolor = gajim.config.get_per('themes', theme, option + 'bgcolor')
+ if bgcolor:
+ state = True
+ self.background_colorbutton.set_color(gtk.gdk.color_parse(
+ bgcolor))
+ else:
+ state = False
+ self.background_checkbutton.set_active(state)
+ self.background_colorbutton.set_sensitive(state)
+
+ # get the font name before we set widgets and it will not be overriden
+ font_name = gajim.config.get_per('themes', theme, option + 'font')
+ font_attrs = gajim.config.get_per('themes', theme, option + 'fontattrs')
+ self._set_font_widgets(font_attrs)
+ if font_name:
+ state = True
+ self.text_fontbutton.set_font_name(font_name)
+ else:
+ state = False
+ self.textfont_checkbutton.set_active(state)
+ self.text_fontbutton.set_sensitive(state)
+ self.no_update = False
+ gajim.interface.roster.change_roster_style(None)
+
+ for chatstate in ('inactive', 'composing', 'paused', 'gone',
+ 'muc_msg', 'muc_directed_msg'):
+ color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \
+ '_color')
+ self.colorbuttons[chatstate].set_color(gtk.gdk.color_parse(color))
+
+ def on_textcolor_checkbutton_toggled(self, widget):
+ state = widget.get_active()
+ self.text_colorbutton.set_sensitive(state)
+ self._set_color(state, self.text_colorbutton,
+ 'textcolor')
+
+ def on_background_checkbutton_toggled(self, widget):
+ state = widget.get_active()
+ self.background_colorbutton.set_sensitive(state)
+ self._set_color(state, self.background_colorbutton,
+ 'bgcolor')
+
+ def on_textfont_checkbutton_toggled(self, widget):
+ self.text_fontbutton.set_sensitive(widget.get_active())
+ self._set_font()
+
+ def on_text_colorbutton_color_set(self, widget):
+ self._set_color(True, widget, 'textcolor')
+
+ def on_background_colorbutton_color_set(self, widget):
+ self._set_color(True, widget, 'bgcolor')
+
+ def on_text_fontbutton_font_set(self, widget):
+ self._set_font()
+
+ def on_options_combobox_changed(self, widget):
+ index = self.options_combobox.get_active()
+ if index == -1:
+ return
+ self.current_option = self.options[index]
+ self.set_theme_options(self.current_theme,
+ self.current_option)
+
+ def on_bold_togglebutton_toggled(self, widget):
+ if not self.no_update:
+ self._set_font()
+
+ def on_italic_togglebutton_toggled(self, widget):
+ if not self.no_update:
+ self._set_font()
+
+ def _set_color(self, state, widget, option):
+ """
+ Set color value in prefs and update the UI
+ """
+ if state:
+ color = widget.get_color()
+ color_string = gtkgui_helpers.make_color_string(color)
+ else:
+ color_string = ''
+ begin_option = ''
+ if not option.startswith('state'):
+ begin_option = self.current_option
+ gajim.config.set_per('themes', self.current_theme,
+ begin_option + option, color_string)
+ # use faster functions for this
+ if self.current_option == 'banner':
+ gajim.interface.roster.repaint_themed_widgets()
+ gajim.interface.save_config()
+ return
+ if self.no_update:
+ return
+ gajim.interface.roster.change_roster_style(self.current_option)
+ gajim.interface.save_config()
+
+ def _set_font(self):
+ """
+ Set font value in prefs and update the UI
+ """
+ state = self.textfont_checkbutton.get_active()
+ if state:
+ font_string = self.text_fontbutton.get_font_name()
+ else:
+ font_string = ''
+ gajim.config.set_per('themes', self.current_theme,
+ self.current_option + 'font', font_string)
+ font_attrs = self._get_font_attrs()
+ gajim.config.set_per('themes', self.current_theme,
+ self.current_option + 'fontattrs', font_attrs)
+ # use faster functions for this
+ if self.current_option == 'banner':
+ gajim.interface.roster.repaint_themed_widgets()
+ if self.no_update:
+ return
+ gajim.interface.roster.change_roster_style(self.current_option)
+ gajim.interface.save_config()
+
+ def _toggle_font_widgets(self, font_props):
+ """
+ Toggle font buttons with the bool values of font_props tuple
+ """
+ self.bold_togglebutton.set_active(font_props[0])
+ self.italic_togglebutton.set_active(font_props[1])
+
+ def _get_font_description(self):
+ """
+ Return a FontDescription from togglebuttons states
+ """
+ fd = pango.FontDescription()
+ if self.bold_togglebutton.get_active():
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if self.italic_togglebutton.get_active():
+ fd.set_style(pango.STYLE_ITALIC)
+ return fd
+
+ def _set_font_widgets(self, font_attrs):
+ """
+ Set the correct toggle state of font style buttons by a font string of
+ type 'BI'
+ """
+ font_props = [False, False, False]
+ if font_attrs:
+ if font_attrs.find('B') != -1:
+ font_props[0] = True
+ if font_attrs.find('I') != -1:
+ font_props[1] = True
+ self._toggle_font_widgets(font_props)
+
+ def _get_font_attrs(self):
+ """
+ Get a string with letters of font attribures: 'BI'
+ """
+ attrs = ''
+ if self.bold_togglebutton.get_active():
+ attrs += 'B'
+ if self.italic_togglebutton.get_active():
+ attrs += 'I'
+ return attrs
+
+
+ def _get_font_props(self, font_name):
+ """
+ Get tuple of font properties: weight, style
+ """
+ font_props = [False, False, False]
+ font_description = pango.FontDescription(font_name)
+ if font_description.get_weight() != pango.WEIGHT_NORMAL:
+ font_props[0] = True
+ if font_description.get_style() != pango.STYLE_ITALIC:
+ font_props[1] = True
+ return font_props
+
+ def on_inactive_colorbutton_color_set(self, widget):
+ self.no_update = True
+ self._set_color(True, widget, 'state_inactive_color')
+ self.no_update = False
+
+ def on_composing_colorbutton_color_set(self, widget):
+ self.no_update = True
+ self._set_color(True, widget, 'state_composing_color')
+ self.no_update = False
+
+ def on_paused_colorbutton_color_set(self, widget):
+ self.no_update = True
+ self._set_color(True, widget, 'state_paused_color')
+ self.no_update = False
+
+ def on_gone_colorbutton_color_set(self, widget):
+ self.no_update = True
+ self._set_color(True, widget, 'state_gone_color')
+ self.no_update = False
+
+ def on_muc_msg_colorbutton_color_set(self, widget):
+ self.no_update = True
+ self._set_color(True, widget, 'state_muc_msg_color')
+ self.no_update = False
+
+ def on_muc_directed_msg_colorbutton_color_set(self, widget):
+ self.no_update = True
+ self._set_color(True, widget, 'state_muc_directed_msg_color')
+ self.no_update = False
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index cd8b7d905..65fbacff2 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -64,2349 +64,2347 @@ C_AVATAR, # avatar of the contact
) = range(5)
def set_renderer_color(treeview, renderer, set_background=True):
- """
- Set style for group row, using PRELIGHT system color
- """
- if set_background:
- bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT]
- renderer.set_property('cell-background-gdk', bgcolor)
- else:
- fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT]
- renderer.set_property('foreground-gdk', fgcolor)
+ """
+ Set style for group row, using PRELIGHT system color
+ """
+ if set_background:
+ bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT]
+ renderer.set_property('cell-background-gdk', bgcolor)
+ else:
+ fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT]
+ renderer.set_property('foreground-gdk', fgcolor)
def tree_cell_data_func(column, renderer, model, iter_, tv=None):
- # cell data func is global, because we don't want it to keep
- # reference to GroupchatControl instance (self)
- theme = gajim.config.get('roster_theme')
- # allocate space for avatar only if needed
- parent_iter = model.iter_parent(iter_)
- if isinstance(renderer, gtk.CellRendererPixbuf):
- avatar_position = gajim.config.get('avatar_position_in_roster')
- if avatar_position == 'right':
- renderer.set_property('xalign', 1) # align pixbuf to the right
- else:
- renderer.set_property('xalign', 0.5)
- if parent_iter and (model[iter_][C_AVATAR] or avatar_position == 'left'):
- renderer.set_property('visible', True)
- renderer.set_property('width', gajim.config.get('roster_avatar_width'))
- else:
- renderer.set_property('visible', False)
- if parent_iter:
- bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor')
- if bgcolor:
- renderer.set_property('cell-background', bgcolor)
- else:
- renderer.set_property('cell-background', None)
- if isinstance(renderer, gtk.CellRendererText):
- # foreground property is only with CellRendererText
- color = gajim.config.get_per('themes', theme, 'contacttextcolor')
- if color:
- renderer.set_property('foreground', color)
- else:
- renderer.set_property('foreground', None)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
- else: # it is root (eg. group)
- bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
- if bgcolor:
- renderer.set_property('cell-background', bgcolor)
- else:
- set_renderer_color(tv, renderer)
- if isinstance(renderer, gtk.CellRendererText):
- # foreground property is only with CellRendererText
- color = gajim.config.get_per('themes', theme, 'grouptextcolor')
- if color:
- renderer.set_property('foreground', color)
- else:
- set_renderer_color(tv, renderer, False)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
+ # cell data func is global, because we don't want it to keep
+ # reference to GroupchatControl instance (self)
+ theme = gajim.config.get('roster_theme')
+ # allocate space for avatar only if needed
+ parent_iter = model.iter_parent(iter_)
+ if isinstance(renderer, gtk.CellRendererPixbuf):
+ avatar_position = gajim.config.get('avatar_position_in_roster')
+ if avatar_position == 'right':
+ renderer.set_property('xalign', 1) # align pixbuf to the right
+ else:
+ renderer.set_property('xalign', 0.5)
+ if parent_iter and (model[iter_][C_AVATAR] or avatar_position == 'left'):
+ renderer.set_property('visible', True)
+ renderer.set_property('width', gajim.config.get('roster_avatar_width'))
+ else:
+ renderer.set_property('visible', False)
+ if parent_iter:
+ bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor')
+ if bgcolor:
+ renderer.set_property('cell-background', bgcolor)
+ else:
+ renderer.set_property('cell-background', None)
+ if isinstance(renderer, gtk.CellRendererText):
+ # foreground property is only with CellRendererText
+ color = gajim.config.get_per('themes', theme, 'contacttextcolor')
+ if color:
+ renderer.set_property('foreground', color)
+ else:
+ renderer.set_property('foreground', None)
+ renderer.set_property('font',
+ gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
+ else: # it is root (eg. group)
+ bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
+ if bgcolor:
+ renderer.set_property('cell-background', bgcolor)
+ else:
+ set_renderer_color(tv, renderer)
+ if isinstance(renderer, gtk.CellRendererText):
+ # foreground property is only with CellRendererText
+ color = gajim.config.get_per('themes', theme, 'grouptextcolor')
+ if color:
+ renderer.set_property('foreground', color)
+ else:
+ set_renderer_color(tv, renderer, False)
+ renderer.set_property('font',
+ gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
class PrivateChatControl(ChatControl):
- TYPE_ID = message_control.TYPE_PM
-
- # Set a command host to bound to. Every command given through a private chat
- # will be processed with this command host.
- COMMAND_HOST = PrivateChatCommands
-
- def __init__(self, parent_win, gc_contact, contact, account, session):
- room_jid = gc_contact.room_jid
- room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account)
- if room_jid in gajim.interface.minimized_controls[account]:
- room_ctrl = gajim.interface.minimized_controls[account][room_jid]
- if room_ctrl:
- self.room_name = room_ctrl.name
- else:
- self.room_name = room_jid
- self.gc_contact = gc_contact
- ChatControl.__init__(self, parent_win, contact, account, session)
- self.TYPE_ID = 'pm'
-
- def send_message(self, message, xhtml=None, process_commands=True):
- """
- Call this method to send the message
- """
- message = helpers.remove_invalid_xml_chars(message)
- if not message:
- return
-
- # We need to make sure that we can still send through the room and that
- # the recipient did not go away
- contact = gajim.contacts.get_first_contact_from_jid(self.account,
- self.contact.jid)
- if not contact:
- # contact was from pm in MUC
- room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
- gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
- if not gc_contact:
- dialogs.ErrorDialog(
- _('Sending private message failed'),
- #in second %s code replaces with nickname
- _('You are no longer in group chat "%(room)s" or "%(nick)s" has '
- 'left.') % {'room': room, 'nick': nick})
- return
-
- ChatControl.send_message(self, message, xhtml=xhtml,
- process_commands=process_commands)
-
- def update_ui(self):
- if self.contact.show == 'offline':
- self.got_disconnected()
- else:
- self.got_connected()
- ChatControl.update_ui(self)
-
- def update_contact(self):
- self.contact = self.gc_contact.as_contact()
-
- def begin_e2e_negotiation(self):
- self.no_autonegotiation = True
-
- if not self.session:
- fjid = self.gc_contact.get_full_jid()
- new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id)
- self.set_session(new_sess)
-
- self.session.negotiate_e2e(False)
+ TYPE_ID = message_control.TYPE_PM
+
+ # Set a command host to bound to. Every command given through a private chat
+ # will be processed with this command host.
+ COMMAND_HOST = PrivateChatCommands
+
+ def __init__(self, parent_win, gc_contact, contact, account, session):
+ room_jid = gc_contact.room_jid
+ room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account)
+ if room_jid in gajim.interface.minimized_controls[account]:
+ room_ctrl = gajim.interface.minimized_controls[account][room_jid]
+ if room_ctrl:
+ self.room_name = room_ctrl.name
+ else:
+ self.room_name = room_jid
+ self.gc_contact = gc_contact
+ ChatControl.__init__(self, parent_win, contact, account, session)
+ self.TYPE_ID = 'pm'
+
+ def send_message(self, message, xhtml=None, process_commands=True):
+ """
+ Call this method to send the message
+ """
+ message = helpers.remove_invalid_xml_chars(message)
+ if not message:
+ return
+
+ # We need to make sure that we can still send through the room and that
+ # the recipient did not go away
+ contact = gajim.contacts.get_first_contact_from_jid(self.account,
+ self.contact.jid)
+ if not contact:
+ # contact was from pm in MUC
+ room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
+ gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
+ if not gc_contact:
+ dialogs.ErrorDialog(
+ _('Sending private message failed'),
+ #in second %s code replaces with nickname
+ _('You are no longer in group chat "%(room)s" or "%(nick)s" has '
+ 'left.') % {'room': room, 'nick': nick})
+ return
+
+ ChatControl.send_message(self, message, xhtml=xhtml,
+ process_commands=process_commands)
+
+ def update_ui(self):
+ if self.contact.show == 'offline':
+ self.got_disconnected()
+ else:
+ self.got_connected()
+ ChatControl.update_ui(self)
+
+ def update_contact(self):
+ self.contact = self.gc_contact.as_contact()
+
+ def begin_e2e_negotiation(self):
+ self.no_autonegotiation = True
+
+ if not self.session:
+ fjid = self.gc_contact.get_full_jid()
+ new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id)
+ self.set_session(new_sess)
+
+ self.session.negotiate_e2e(False)
class GroupchatControl(ChatControlBase):
- TYPE_ID = message_control.TYPE_GC
-
- # Set a command host to bound to. Every command given through a group chat
- # will be processed with this command host.
- COMMAND_HOST = GroupChatCommands
-
- def __init__(self, parent_win, contact, acct, is_continued=False):
- ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
- 'groupchat_control', contact, acct)
-
- self.is_continued=is_continued
- self.is_anonymous = True
-
- # Controls the state of autorejoin.
- # None - autorejoin is neutral.
- # False - autorejoin is to be prevented (gets reset to initial state in
- # got_connected()).
- # int - autorejoin is being active and working (gets reset to initial
- # state in got_connected()).
- self.autorejoin = None
-
- self.actions_button = self.xml.get_object('muc_window_actions_button')
- id_ = self.actions_button.connect('clicked',
- self.on_actions_button_clicked)
- self.handlers[id_] = self.actions_button
-
- widget = self.xml.get_object('change_nick_button')
- id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('change_subject_button')
- id_ = widget.connect('clicked', self._on_change_subject_menuitem_activate)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('bookmark_button')
- for bm in gajim.connections[self.account].bookmarks:
- if bm['jid'] == self.contact.jid:
- widget.hide()
- break
- else:
- id_ = widget.connect('clicked',
- self._on_bookmark_room_menuitem_activate)
- self.handlers[id_] = widget
- widget.show()
-
- widget = self.xml.get_object('list_treeview')
- id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
- self.handlers[id_] = widget
-
- id_ = widget.connect('row_collapsed', self.on_list_treeview_row_collapsed)
- self.handlers[id_] = widget
-
- id_ = widget.connect('row_activated',
- self.on_list_treeview_row_activated)
- self.handlers[id_] = widget
-
- id_ = widget.connect('button_press_event',
- self.on_list_treeview_button_press_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('key_press_event',
- self.on_list_treeview_key_press_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('motion_notify_event',
- self.on_list_treeview_motion_notify_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('leave_notify_event',
- self.on_list_treeview_leave_notify_event)
- self.handlers[id_] = widget
-
- self.room_jid = self.contact.jid
- self.nick = contact.name.decode('utf-8')
- self.new_nick = ''
- self.name = ''
- for bm in gajim.connections[self.account].bookmarks:
- if bm['jid'] == self.room_jid:
- self.name = bm['name']
- break
- if not self.name:
- self.name = self.room_jid.split('@')[0]
-
- compact_view = gajim.config.get('compact_view')
- self.chat_buttons_set_visible(compact_view)
- self.widget_set_visible(self.xml.get_object('banner_eventbox'),
- gajim.config.get('hide_groupchat_banner'))
- self.widget_set_visible(self.xml.get_object('list_scrolledwindow'),
- gajim.config.get('hide_groupchat_occupants_list'))
-
- self._last_selected_contact = None # None or holds jid, account tuple
-
- # muc attention flag (when we are mentioned in a muc)
- # if True, the room has mentioned us
- self.attention_flag = False
-
- # sorted list of nicks who mentioned us (last at the end)
- self.attention_list = []
- self.room_creation = int(time.time()) # Use int to reduce mem usage
- self.nick_hits = []
- self.last_key_tabs = False
-
- self.subject = ''
-
- self.tooltip = tooltips.GCTooltip()
-
- # nickname coloring
- self.gc_count_nicknames_colors = 0
- self.gc_custom_colors = {}
- self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
- split(':'))
-
- self.name_label = self.xml.get_object('banner_name_label')
- self.event_box = self.xml.get_object('banner_eventbox')
-
- # set the position of the current hpaned
- hpaned_position = gajim.config.get('gc-hpaned-position')
- self.hpaned = self.xml.get_object('hpaned')
- self.hpaned.set_position(hpaned_position)
-
- self.list_treeview = self.xml.get_object('list_treeview')
- selection = self.list_treeview.get_selection()
- id_ = selection.connect('changed',
- self.on_list_treeview_selection_changed)
- self.handlers[id_] = selection
- id_ = self.list_treeview.connect('style-set',
- self.on_list_treeview_style_set)
- self.handlers[id_] = self.list_treeview
- self.resize_from_another_muc = False
- # we want to know when the the widget resizes, because that is
- # an indication that the hpaned has moved...
- # FIXME: Find a better indicator that the hpaned has moved.
- id_ = self.list_treeview.connect('size-allocate',
- self.on_treeview_size_allocate)
- self.handlers[id_] = self.list_treeview
- #status_image, shown_nick, type, nickname, avatar
- store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf)
- store.set_sort_func(C_NICK, self.tree_compare_iters)
- store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING)
- self.list_treeview.set_model(store)
-
- # columns
-
- # this col has 3 cells:
- # first one img, second one text, third is sec pixbuf
- column = gtk.TreeViewColumn()
-
- def add_avatar_renderer():
- renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image
- column.pack_start(renderer_pixbuf, expand=False)
- column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR)
- column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func,
- self.list_treeview)
-
- if gajim.config.get('avatar_position_in_roster') == 'left':
- add_avatar_renderer()
-
- renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img
- renderer_image.set_property('width', 26)
- column.pack_start(renderer_image, expand=False)
- column.add_attribute(renderer_image, 'image', C_IMG)
- column.set_cell_data_func(renderer_image, tree_cell_data_func,
- self.list_treeview)
-
- renderer_text = gtk.CellRendererText() # nickname
- column.pack_start(renderer_text, expand=True)
- column.add_attribute(renderer_text, 'markup', C_TEXT)
- renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END)
- column.set_cell_data_func(renderer_text, tree_cell_data_func,
- self.list_treeview)
-
- if gajim.config.get('avatar_position_in_roster') == 'right':
- add_avatar_renderer()
-
- self.list_treeview.append_column(column)
-
- # workaround to avoid gtk arrows to be shown
- column = gtk.TreeViewColumn() # 2nd COLUMN
- renderer = gtk.CellRendererPixbuf()
- column.pack_start(renderer, expand=False)
- self.list_treeview.append_column(column)
- column.set_visible(False)
- self.list_treeview.set_expander_column(column)
-
- gajim.gc_connected[self.account][self.room_jid] = False
- # disable win, we are not connected yet
- ChatControlBase.got_disconnected(self)
-
- self.update_ui()
- self.conv_textview.tv.grab_focus()
- self.widget.show_all()
-
- def tree_compare_iters(self, model, iter1, iter2):
- """
- Compare two iters to sort them
- """
- type1 = model[iter1][C_TYPE]
- type2 = model[iter2][C_TYPE]
- if not type1 or not type2:
- return 0
- nick1 = model[iter1][C_NICK]
- nick2 = model[iter2][C_NICK]
- if not nick1 or not nick2:
- return 0
- nick1 = nick1.decode('utf-8')
- nick2 = nick2.decode('utf-8')
- if type1 == 'role':
- return locale.strcoll(nick1, nick2)
- if type1 == 'contact':
- gc_contact1 = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, nick1)
- if not gc_contact1:
- return 0
- if type2 == 'contact':
- gc_contact2 = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, nick2)
- if not gc_contact2:
- return 0
- if type1 == 'contact' and type2 == 'contact' and \
- gajim.config.get('sort_by_show_in_muc'):
- cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
- 'invisible': 5, 'offline': 6, 'error': 7}
- show1 = cshow[gc_contact1.show]
- show2 = cshow[gc_contact2.show]
- if show1 < show2:
- return -1
- elif show1 > show2:
- return 1
- # We compare names
- name1 = gc_contact1.get_shown_name()
- name2 = gc_contact2.get_shown_name()
- return locale.strcoll(name1.lower(), name2.lower())
-
- def on_msg_textview_populate_popup(self, textview, menu):
- """
- Override the default context menu and we prepend Clear
- and the ability to insert a nick
- """
- ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
- item = gtk.SeparatorMenuItem()
- menu.prepend(item)
-
- item = gtk.MenuItem(_('Insert Nickname'))
- menu.prepend(item)
- submenu = gtk.Menu()
- item.set_submenu(submenu)
-
- for nick in sorted(gajim.contacts.get_nick_list(self.account,
- self.room_jid)):
- item = gtk.MenuItem(nick, use_underline=False)
- submenu.append(item)
- id_ = item.connect('activate', self.append_nick_in_msg_textview, nick)
- self.handlers[id_] = item
-
- menu.show_all()
-
- def resize_occupant_treeview(self, position):
- self.resize_from_another_muc = True
- self.hpaned.set_position(position)
- def reset_flag():
- self.resize_from_another_muc = False
- # Reset the flag when everything will be redrawn, and in particular when
- # on_treeview_size_allocate will have been called.
- gobject.idle_add(reset_flag)
-
- def on_treeview_size_allocate(self, widget, allocation):
- """
- The MUC treeview has resized. Move the hpaned in all tabs to match
- """
- if self.resize_from_another_muc:
- # Don't send the event to other MUC
- return
- hpaned_position = self.hpaned.get_position()
- for account in gajim.gc_connected:
- for room_jid in [i for i in gajim.gc_connected[account] if \
- gajim.gc_connected[account][i] and i != self.room_jid]:
- ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account)
- if not ctrl:
- ctrl = gajim.interface.minimized_controls[account][room_jid]
- if ctrl:
- ctrl.resize_occupant_treeview(hpaned_position)
-
- def iter_contact_rows(self):
- """
- Iterate over all contact rows in the tree model
- """
- model = self.list_treeview.get_model()
- role_iter = model.get_iter_root()
- while role_iter:
- contact_iter = model.iter_children(role_iter)
- while contact_iter:
- yield model[contact_iter]
- contact_iter = model.iter_next(contact_iter)
- role_iter = model.iter_next(role_iter)
-
- def on_list_treeview_style_set(self, treeview, style):
- """
- When style (theme) changes, redraw all contacts
- """
- # Get the room_jid from treeview
- for contact in self.iter_contact_rows():
- nick = contact[C_NICK].decode('utf-8')
- self.draw_contact(nick)
-
- def on_list_treeview_selection_changed(self, selection):
- model, selected_iter = selection.get_selected()
- self.draw_contact(self.nick)
- if self._last_selected_contact is not None:
- self.draw_contact(self._last_selected_contact)
- if selected_iter is None:
- self._last_selected_contact = None
- return
- contact = model[selected_iter]
- nick = contact[C_NICK].decode('utf-8')
- self._last_selected_contact = nick
- if contact[C_TYPE] != 'contact':
- return
- self.draw_contact(nick, selected=True, focus=True)
-
- def get_tab_label(self, chatstate):
- """
- Markup the label if necessary. Returns a tuple such as: (new_label_str,
- color) either of which can be None if chatstate is given that means we
- have HE SENT US a chatstate
- """
-
- has_focus = self.parent_win.window.get_property('has-toplevel-focus')
- current_tab = self.parent_win.get_active_control() == self
- color_name = None
- color = None
- theme = gajim.config.get('roster_theme')
- if chatstate == 'attention' and (not has_focus or not current_tab):
- self.attention_flag = True
- color_name = gajim.config.get_per('themes', theme,
- 'state_muc_directed_msg_color')
- elif chatstate:
- if chatstate == 'active' or (current_tab and has_focus):
- self.attention_flag = False
- # get active color from gtk
- color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
- elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\
- not self.attention_flag:
- color_name = gajim.config.get_per('themes', theme,
- 'state_muc_msg_color')
- if color_name:
- color = gtk.gdk.colormap_get_system().alloc_color(color_name)
-
- if self.is_continued:
- # if this is a continued conversation
- label_str = self.get_continued_conversation_name()
- else:
- label_str = self.name
-
- # count waiting highlighted messages
- unread = ''
- num_unread = self.get_nb_unread()
- if num_unread == 1:
- unread = '*'
- elif num_unread > 1:
- unread = '[' + unicode(num_unread) + ']'
- label_str = unread + label_str
- return (label_str, color)
-
- def get_tab_image(self, count_unread=True):
- # Set tab image (always 16x16)
- tab_image = None
- if gajim.gc_connected[self.account][self.room_jid]:
- tab_image = gtkgui_helpers.load_icon('muc_active')
- else:
- tab_image = gtkgui_helpers.load_icon('muc_inactive')
- return tab_image
-
- def update_ui(self):
- ChatControlBase.update_ui(self)
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- self.draw_contact(nick)
-
- def _change_style(self, model, path, iter_):
- model[iter_][C_NICK] = model[iter_][C_NICK]
-
- def change_roster_style(self):
- model = self.list_treeview.get_model()
- model.foreach(self._change_style)
-
- def repaint_themed_widgets(self):
- ChatControlBase.repaint_themed_widgets(self)
- self.change_roster_style()
-
- def _update_banner_state_image(self):
- banner_status_img = self.xml.get_object('gc_banner_status_image')
- images = gajim.interface.jabber_state_images
- if self.room_jid in gajim.gc_connected[self.account] and \
- gajim.gc_connected[self.account][self.room_jid]:
- image = 'muc_active'
- else:
- image = 'muc_inactive'
- if '32' in images and image in images['32']:
- muc_icon = images['32'][image]
- if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY:
- pix = muc_icon.get_pixbuf()
- banner_status_img.set_from_pixbuf(pix)
- return
- # we need to scale 16x16 to 32x32
- muc_icon = images['16'][image]
- pix = muc_icon.get_pixbuf()
- scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
- banner_status_img.set_from_pixbuf(scaled_pix)
-
- def get_continued_conversation_name(self):
- """
- Get the name of a continued conversation. Will return Continued
- Conversation if there isn't any other contact in the room
- """
- nicks = []
- for nick in gajim.contacts.get_nick_list(self.account,
- self.room_jid):
- if nick != self.nick:
- nicks.append(nick)
- if nicks != []:
- title = ', '
- title = _('Conversation with ') + title.join(nicks)
- else:
- title = _('Continued conversation')
- return title
-
- def draw_banner_text(self):
- """
- Draw the text in the fat line at the top of the window that houses the
- room jid, subject
- """
- self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
- self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
- font_attrs, font_attrs_small = self.get_font_attrs()
- if self.is_continued:
- name = self.get_continued_conversation_name()
- else:
- name = self.room_jid
- text = '<span %s>%s</span>' % (font_attrs, name)
- self.name_label.set_markup(text)
-
- if self.subject:
- subject = helpers.reduce_chars_newlines(self.subject, max_lines=2)
- subject = gobject.markup_escape_text(subject)
- subject_text = self.urlfinder.sub(self.make_href, subject)
- subject_text = '<span %s>%s</span>' % (font_attrs_small, subject_text)
-
- # tooltip must always hold ALL the subject
- self.event_box.set_tooltip_text(self.subject)
- self.banner_status_label.set_no_show_all(False)
- self.banner_status_label.show()
- else:
- subject_text = ''
- self.event_box.set_has_tooltip(False)
- self.banner_status_label.hide()
- self.banner_status_label.set_no_show_all(True)
-
- self.banner_status_label.set_markup(subject_text)
-
- def prepare_context_menu(self, hide_buttonbar_items=False):
- """
- Set sensitivity state for configure_room
- """
- xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui')
- menu = xml.get_object('gc_control_popup_menu')
-
- bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem')
- change_nick_menuitem = xml.get_object('change_nick_menuitem')
- configure_room_menuitem = xml.get_object('configure_room_menuitem')
- destroy_room_menuitem = xml.get_object('destroy_room_menuitem')
- change_subject_menuitem = xml.get_object('change_subject_menuitem')
- history_menuitem = xml.get_object('history_menuitem')
- minimize_menuitem = xml.get_object('minimize_menuitem')
- bookmark_separator = xml.get_object('bookmark_separator')
- separatormenuitem2 = xml.get_object('separatormenuitem2')
-
- if hide_buttonbar_items:
- change_nick_menuitem.hide()
- change_subject_menuitem.hide()
- bookmark_room_menuitem.hide()
- history_menuitem.hide()
- bookmark_separator.hide()
- separatormenuitem2.hide()
- else:
- change_nick_menuitem.show()
- change_subject_menuitem.show()
- bookmark_room_menuitem.show()
- history_menuitem.show()
- bookmark_separator.show()
- separatormenuitem2.show()
- for bm in gajim.connections[self.account].bookmarks:
- if bm['jid'] == self.room_jid:
- bookmark_room_menuitem.hide()
- bookmark_separator.hide()
- break
-
- ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
- change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n,
- gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE)
- change_subject_menuitem.add_accelerator('activate', ag,
- gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE)
- bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b,
- gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
- history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h,
- gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
-
- if self.contact.jid in gajim.config.get_per('accounts', self.account,
- 'minimized_gc').split(' '):
- minimize_menuitem.set_active(True)
- if not gajim.connections[self.account].private_storage_supported:
- bookmark_room_menuitem.set_sensitive(False)
- if gajim.gc_connected[self.account][self.room_jid]:
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- self.nick)
- if c.affiliation not in ('owner', 'admin'):
- configure_room_menuitem.set_sensitive(False)
- else:
- configure_room_menuitem.set_sensitive(True)
- if c.affiliation != 'owner':
- destroy_room_menuitem.set_sensitive(False)
- else:
- destroy_room_menuitem.set_sensitive(True)
- change_subject_menuitem.set_sensitive(True)
- change_nick_menuitem.set_sensitive(True)
- else:
- # We are not connected to this groupchat, disable unusable menuitems
- configure_room_menuitem.set_sensitive(False)
- destroy_room_menuitem.set_sensitive(False)
- change_subject_menuitem.set_sensitive(False)
- change_nick_menuitem.set_sensitive(False)
-
- # connect the menuitems to their respective functions
- id_ = bookmark_room_menuitem.connect('activate',
- self._on_bookmark_room_menuitem_activate)
- self.handlers[id_] = bookmark_room_menuitem
-
- id_ = change_nick_menuitem.connect('activate',
- self._on_change_nick_menuitem_activate)
- self.handlers[id_] = change_nick_menuitem
-
- id_ = configure_room_menuitem.connect('activate',
- self._on_configure_room_menuitem_activate)
- self.handlers[id_] = configure_room_menuitem
-
- id_ = destroy_room_menuitem.connect('activate',
- self._on_destroy_room_menuitem_activate)
- self.handlers[id_] = destroy_room_menuitem
-
- id_ = change_subject_menuitem.connect('activate',
- self._on_change_subject_menuitem_activate)
- self.handlers[id_] = change_subject_menuitem
-
- id_ = history_menuitem.connect('activate',
- self._on_history_menuitem_activate)
- self.handlers[id_] = history_menuitem
-
- id_ = minimize_menuitem.connect('toggled',
- self.on_minimize_menuitem_toggled)
- self.handlers[id_] = minimize_menuitem
-
- menu.connect('selection-done', self.destroy_menu,
- change_nick_menuitem, change_subject_menuitem,
- bookmark_room_menuitem, history_menuitem)
- return menu
-
- def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
- bookmark_room_menuitem, history_menuitem):
- # destroy accelerators
- ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
- change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n,
- gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK)
- change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t,
- gtk.gdk.MOD1_MASK)
- bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b,
- gtk.gdk.CONTROL_MASK)
- history_menuitem.remove_accelerator(ag, gtk.keysyms.h,
- gtk.gdk.CONTROL_MASK)
- # destroy menu
- menu.destroy()
-
- def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None,
- status_code=[]):
- if '100' in status_code:
- # Room is not anonymous
- self.is_anonymous = False
- if not nick:
- # message from server
- self.print_conversation(msg, tim=tim, xhtml=xhtml)
- else:
- # message from someone
- if has_timestamp:
- # don't print xhtml if it's an old message.
- # Like that xhtml messages are grayed too.
- self.print_old_conversation(msg, nick, tim, None)
- else:
- self.print_conversation(msg, nick, tim, xhtml)
-
- def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None,
- encrypted=False):
- # Do we have a queue?
- fjid = self.room_jid + '/' + nick
- no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
-
- event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
- encrypted, '', msg_id, xhtml, session))
- gajim.events.add_event(self.account, fjid, event)
-
- autopopup = gajim.config.get('autopopup')
- autopopupaway = gajim.config.get('autopopupaway')
- iter_ = self.get_contact_iter(nick)
- path = self.list_treeview.get_model().get_path(iter_)
- if not autopopup or (not autopopupaway and \
- gajim.connections[self.account].connected > 2):
- if no_queue: # We didn't have a queue: we change icons
- model = self.list_treeview.get_model()
- state_images =\
- gajim.interface.roster.get_appropriate_state_images(
- self.room_jid, icon_name='event')
- image = state_images['event']
- model[iter_][C_IMG] = image
- if self.parent_win:
- self.parent_win.show_title()
- self.parent_win.redraw_tab(self)
- else:
- self._start_private_message(nick)
- # Scroll to line
- self.list_treeview.expand_row(path[0:1], False)
- self.list_treeview.scroll_to_cell(path)
- self.list_treeview.set_cursor(path)
- contact = gajim.contacts.get_contact_with_highest_priority(self.account, \
- self.room_jid)
- if contact:
- gajim.interface.roster.draw_contact(self.room_jid, self.account)
-
- def get_contact_iter(self, nick):
- model = self.list_treeview.get_model()
- role_iter = model.get_iter_root()
- while role_iter:
- user_iter = model.iter_children(role_iter)
- while user_iter:
- if nick == model[user_iter][C_NICK].decode('utf-8'):
- return user_iter
- else:
- user_iter = model.iter_next(user_iter)
- role_iter = model.iter_next(role_iter)
- return None
-
- def print_old_conversation(self, text, contact='', tim=None, xhtml = None):
- if isinstance(text, str):
- text = unicode(text, 'utf-8')
- if contact:
- if contact == self.nick: # it's us
- kind = 'outgoing'
- else:
- kind = 'incoming'
- else:
- kind = 'status'
- if gajim.config.get('restored_messages_small'):
- small_attr = ['small']
- else:
- small_attr = []
- ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
- small_attr, small_attr + ['restored_message'],
- small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml)
-
- def print_conversation(self, text, contact='', tim=None, xhtml=None,
- graphics=True):
- """
- Print a line in the conversation
-
- If contact is set: it's a message from someone or an info message
- (contact = 'info' in such a case).
- If contact is not set: it's a message from the server or help.
- """
- if isinstance(text, str):
- text = unicode(text, 'utf-8')
- other_tags_for_name = []
- other_tags_for_text = []
- if contact:
- if contact == self.nick: # it's us
- kind = 'outgoing'
- elif contact == 'info':
- kind = 'info'
- contact = None
- else:
- kind = 'incoming'
- # muc-specific chatstate
- if self.parent_win:
- self.parent_win.redraw_tab(self, 'newmsg')
- else:
- kind = 'status'
-
- if kind == 'incoming': # it's a message NOT from us
- # highlighting and sounds
- (highlight, sound) = self.highlighting_for_message(text, tim)
- if contact in self.gc_custom_colors:
- other_tags_for_name.append('gc_nickname_color_' + \
- str(self.gc_custom_colors[contact]))
- else:
- self.gc_count_nicknames_colors += 1
- if self.gc_count_nicknames_colors == self.number_of_colors:
- self.gc_count_nicknames_colors = 0
- self.gc_custom_colors[contact] = \
- self.gc_count_nicknames_colors
- other_tags_for_name.append('gc_nickname_color_' + \
- str(self.gc_count_nicknames_colors))
- if highlight:
- # muc-specific chatstate
- if self.parent_win:
- self.parent_win.redraw_tab(self, 'attention')
- else:
- self.attention_flag = True
- other_tags_for_name.append('bold')
- other_tags_for_text.append('marked')
-
- if contact in self.attention_list:
- self.attention_list.remove(contact)
- elif len(self.attention_list) > 6:
- self.attention_list.pop(0) # remove older
- self.attention_list.append(contact)
-
- if sound == 'received':
- helpers.play_sound('muc_message_received')
- elif sound == 'highlight':
- helpers.play_sound('muc_message_highlight')
- if text.startswith('/me ') or text.startswith('/me\n'):
- other_tags_for_text.append('gc_nickname_color_' + \
- str(self.gc_custom_colors[contact]))
-
- self.check_and_possibly_add_focus_out_line()
-
- ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
- other_tags_for_name, [], other_tags_for_text, xhtml=xhtml,
- graphics=graphics)
-
- def get_nb_unread(self):
- type_events = ['printed_marked_gc_msg']
- if gajim.config.get('notify_on_all_muc_messages'):
- type_events.append('printed_gc_msg')
- nb = len(gajim.events.get_events(self.account, self.room_jid,
- type_events))
- nb += self.get_nb_unread_pm()
- return nb
-
- def get_nb_unread_pm(self):
- nb = 0
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \
- nick, ['pm']))
- return nb
-
- def highlighting_for_message(self, text, tim):
- """
- Returns a 2-Tuple. The first says whether or not to highlight the text,
- the second, what sound to play
- """
- highlight, sound = (None, None)
-
- # Are any of the defined highlighting words in the text?
- if self.needs_visual_notification(text):
- highlight = True
- if gajim.config.get_per('soundevents', 'muc_message_highlight',
- 'enabled'):
- sound = 'highlight'
-
- # Do we play a sound on every muc message?
- elif gajim.config.get_per('soundevents', 'muc_message_received', \
- 'enabled'):
- sound = 'received'
-
- # Is it a history message? Don't want sound-floods when we join.
- if tim != time.localtime():
- sound = None
-
- return (highlight, sound)
-
- def check_and_possibly_add_focus_out_line(self):
- """
- Check and possibly add focus out line for room_jid if it needs it and
- does not already have it as last event. If it goes to add this line
- - remove previous line first
- """
- win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account)
- if win and self.room_jid == win.get_active_jid() and\
- win.window.get_property('has-toplevel-focus') and\
- self.parent_win.get_active_control() == self:
- # it's the current room and it's the focused window.
- # we have full focus (we are reading it!)
- return
-
- self.conv_textview.show_focus_out_line()
-
- def needs_visual_notification(self, text):
- """
- Check text to see whether any of the words in (muc_highlight_words and
- nick) appear
- """
- special_words = gajim.config.get('muc_highlight_words').split(';')
- special_words.append(self.nick)
- # Strip empties: ''.split(';') == [''] and would highlight everything.
- # Also lowercase everything for case insensitive compare.
- special_words = [word.lower() for word in special_words if word]
- text = text.lower()
-
- for special_word in special_words:
- found_here = text.find(special_word)
- while(found_here > -1):
- end_here = found_here + len(special_word)
- if (found_here == 0 or not text[found_here - 1].isalpha()) and \
- (end_here == len(text) or not text[end_here].isalpha()):
- # It is beginning of text or char before is not alpha AND
- # it is end of text or char after is not alpha
- return True
- # continue searching
- start = found_here + 1
- found_here = text.find(special_word, start)
- return False
-
- def set_subject(self, subject):
- self.subject = subject
- self.draw_banner_text()
-
- def got_connected(self):
- # Make autorejoin stop.
- if self.autorejoin:
- gobject.source_remove(self.autorejoin)
- self.autorejoin = None
-
- gajim.gc_connected[self.account][self.room_jid] = True
- ChatControlBase.got_connected(self)
- # We don't redraw the whole banner here, because only icon change
- self._update_banner_state_image()
- if self.parent_win:
- self.parent_win.redraw_tab(self)
-
- def got_disconnected(self):
- self.list_treeview.get_model().clear()
- nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
- for nick in nick_list:
- # Update pm chat window
- fjid = self.room_jid + '/' + nick
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
-
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
- if ctrl:
- gc_contact.show = 'offline'
- gc_contact.status = ''
- ctrl.update_ui()
- if ctrl.parent_win:
- ctrl.parent_win.redraw_tab(ctrl)
-
- gajim.contacts.remove_gc_contact(self.account, gc_contact)
- gajim.gc_connected[self.account][self.room_jid] = False
- ChatControlBase.got_disconnected(self)
- # Tell connection to note the date we disconnect to avoid duplicate logs
- gajim.connections[self.account].gc_got_disconnected(self.room_jid)
- # We don't redraw the whole banner here, because only icon change
- self._update_banner_state_image()
- if self.parent_win:
- self.parent_win.redraw_tab(self)
-
- # Autorejoin stuff goes here.
- # Notice that we don't need to activate autorejoin if connection is lost
- # or in progress.
- if self.autorejoin is None and gajim.account_is_connected(self.account):
- ar_to = gajim.config.get('muc_autorejoin_timeout')
- if ar_to:
- self.autorejoin = gobject.timeout_add_seconds(ar_to, self.rejoin)
-
- def rejoin(self):
- if not self.autorejoin:
- return False
- password = gajim.gc_passwords.get(self.room_jid, '')
- gajim.connections[self.account].join_gc(self.nick, self.room_jid,
- password)
- return True
-
- def draw_roster(self):
- self.list_treeview.get_model().clear()
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role,
- gc_contact.affiliation, gc_contact.status, gc_contact.jid)
- self.draw_all_roles()
- # Recalculate column width for ellipsizin
- self.list_treeview.columns_autosize()
-
- def on_send_pm(self, widget=None, model=None, iter_=None, nick=None,
- msg=None):
- """
- Open a chat window and if msg is not None - send private message to a
- contact in a room
- """
- if nick is None:
- nick = model[iter_][C_NICK].decode('utf-8')
-
- ctrl = self._start_private_message(nick)
- if ctrl and msg:
- ctrl.send_message(msg)
-
- def on_send_file(self, widget, gc_contact):
- """
- Send a file to a contact in the room
- """
- self._on_send_file(gc_contact)
-
- def draw_contact(self, nick, selected=False, focus=False):
- iter_ = self.get_contact_iter(nick)
- if not iter_:
- return
- model = self.list_treeview.get_model()
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- state_images = gajim.interface.jabber_state_images['16']
- if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
- image = state_images['event']
- else:
- image = state_images[gc_contact.show]
-
- name = gobject.markup_escape_text(gc_contact.name)
-
- # Strike name if blocked
- fjid = self.room_jid + '/' + nick
- if helpers.jid_is_blocked(self.account, fjid):
- name = '<span strikethrough="true">%s</span>' % name
-
- status = gc_contact.status
- # add status msg, if not empty, under contact name in the treeview
- if status and gajim.config.get('show_status_msgs_in_roster'):
- status = status.strip()
- if status != '':
- status = helpers.reduce_chars_newlines(status, max_lines=1)
- # escape markup entities and make them small italic and fg color
- color = gtkgui_helpers._get_fade_color(self.list_treeview,
- selected, focus)
- colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue)
- name += ('\n<span size="small" style="italic" foreground="%s">'
- '%s</span>') % (colorstring, gobject.markup_escape_text(status))
-
- if image.get_storage_type() == gtk.IMAGE_PIXBUF and \
- gc_contact.affiliation != 'none' and gajim.config.get(
- 'show_affiliation_in_groupchat'):
- pixbuf1 = image.get_pixbuf().copy()
- pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 4, 4)
- if gc_contact.affiliation == 'owner':
- pixbuf2.fill(0xff0000ff) # Red
- elif gc_contact.affiliation == 'admin':
- pixbuf2.fill(0xffb200ff) # Oragne
- elif gc_contact.affiliation == 'member':
- pixbuf2.fill(0x00ff00ff) # Green
- pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'),
- pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
- gtk.gdk.INTERP_HYPER, 127)
- image = gtk.image_new_from_pixbuf(pixbuf1)
- model[iter_][C_IMG] = image
- model[iter_][C_TEXT] = name
-
- def draw_avatar(self, nick):
- if not gajim.config.get('show_avatars_in_roster'):
- return
- model = self.list_treeview.get_model()
- iter_ = self.get_contact_iter(nick)
- if not iter_:
- return
- fake_jid = self.room_jid + '/' + nick
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
- if pixbuf in ('ask', None):
- scaled_pixbuf = None
- else:
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
- model[iter_][C_AVATAR] = scaled_pixbuf
-
- def draw_role(self, role):
- role_iter = self.get_role_iter(role)
- if not role_iter:
- return
- model = self.list_treeview.get_model()
- role_name = helpers.get_uf_role(role, plural=True)
- if gajim.config.get('show_contacts_number'):
- nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts(
- self.account, self.room_jid, role)
- role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total))
- model[role_iter][C_TEXT] = role_name
-
- def draw_all_roles(self):
- for role in ('visitor', 'participant', 'moderator'):
- self.draw_role(role)
-
- def chg_contact_status(self, nick, show, status, role, affiliation, jid,
- reason, actor, statusCode, new_nick, avatar_sha, tim=None):
- """
- When an occupant changes his or her status
- """
- if show == 'invisible':
- return
-
- if not role:
- role = 'visitor'
- if not affiliation:
- affiliation = 'none'
- fake_jid = self.room_jid + '/' + nick
- newly_created = False
- nick_jid = nick
-
- # Set to true if role or affiliation have changed
- right_changed = False
-
- if jid:
- # delete ressource
- simple_jid = gajim.get_jid_without_resource(jid)
- nick_jid += ' (%s)' % simple_jid
-
- # statusCode
- # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init
- if statusCode:
- if '100' in statusCode:
- # Can be a message (see handle_event_gc_config_change in gajim.py)
- self.print_conversation(\
- _('Any occupant is allowed to see your full JID'))
- if '170' in statusCode:
- # Can be a message (see handle_event_gc_config_change in gajim.py)
- self.print_conversation(_('Room logging is enabled'))
- if '201' in statusCode:
- self.print_conversation(_('A new room has been created'))
- if '210' in statusCode:
- self.print_conversation(\
- _('The server has assigned or modified your roomnick'))
-
- if show in ('offline', 'error'):
- if statusCode:
- if '307' in statusCode:
- if actor is None: # do not print 'kicked by None'
- s = _('%(nick)s has been kicked: %(reason)s') % {
- 'nick': nick,
- 'reason': reason }
- else:
- s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % {
- 'nick': nick,
- 'who': actor,
- 'reason': reason }
- self.print_conversation(s, 'info', tim=tim, graphics=False)
- if nick == self.nick and not gajim.config.get(
- 'muc_autorejoin_on_kick'):
- self.autorejoin = False
- elif '301' in statusCode:
- if actor is None: # do not print 'banned by None'
- s = _('%(nick)s has been banned: %(reason)s') % {
- 'nick': nick,
- 'reason': reason }
- else:
- s = _('%(nick)s has been banned by %(who)s: %(reason)s') % {
- 'nick': nick,
- 'who': actor,
- 'reason': reason }
- self.print_conversation(s, 'info', tim=tim, graphics=False)
- if nick == self.nick:
- self.autorejoin = False
- elif '303' in statusCode: # Someone changed his or her nick
- if new_nick == self.new_nick or nick == self.nick:
- # We changed our nick
- self.nick = new_nick
- self.new_nick = ''
- s = _('You are now known as %s') % new_nick
- # Stop all E2E sessions
- nick_list = gajim.contacts.get_nick_list(self.account,
- self.room_jid)
- for nick_ in nick_list:
- fjid_ = self.room_jid + '/' + nick_
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid_,
- self.account)
- if ctrl and ctrl.session and \
- ctrl.session.enable_encryption:
- thread_id = ctrl.session.thread_id
- ctrl.session.terminate_e2e()
- gajim.connections[self.account].delete_session(fjid_,
- thread_id)
- ctrl.no_autonegotiation = False
- else:
- s = _('%(nick)s is now known as %(new_nick)s') % {
- 'nick': nick, 'new_nick': new_nick}
- # We add new nick to muc roster here, so we don't see
- # that "new_nick has joined the room" when he just changed nick.
- # add_contact_to_roster will be called a second time
- # after that, but that doesn't hurt
- self.add_contact_to_roster(new_nick, show, role, affiliation,
- status, jid)
- if nick in self.attention_list:
- self.attention_list.remove(nick)
- # keep nickname color
- if nick in self.gc_custom_colors:
- self.gc_custom_colors[new_nick] = \
- self.gc_custom_colors[nick]
- # rename vcard / avatar
- puny_jid = helpers.sanitize_filename(self.room_jid)
- puny_nick = helpers.sanitize_filename(nick)
- puny_new_nick = helpers.sanitize_filename(new_nick)
- old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- new_path = os.path.join(gajim.VCARD_PATH, puny_jid,
- puny_new_nick)
- files = {old_path: new_path}
- path = os.path.join(gajim.AVATAR_PATH, puny_jid)
- # possible extensions
- for ext in ('.png', '.jpeg', '_notif_size_bw.png',
- '_notif_size_colored.png'):
- files[os.path.join(path, puny_nick + ext)] = \
- os.path.join(path, puny_new_nick + ext)
- for old_file in files:
- if os.path.exists(old_file) and old_file != files[old_file]:
- if os.path.exists(files[old_file]) and helpers.windowsify(
- old_file) != helpers.windowsify(files[old_file]):
- # Windows require this, but os.remove('test') will also
- # remove 'TEST'
- os.remove(files[old_file])
- os.rename(old_file, files[old_file])
- self.print_conversation(s, 'info', tim=tim, graphics=False)
- elif '321' in statusCode:
- s = _('%(nick)s has been removed from the room (%(reason)s)') % {
- 'nick': nick, 'reason': _('affiliation changed') }
- self.print_conversation(s, 'info', tim=tim, graphics=False)
- elif '322' in statusCode:
- s = _('%(nick)s has been removed from the room (%(reason)s)') % {
- 'nick': nick,
- 'reason': _('room configuration changed to members-only') }
- self.print_conversation(s, 'info', tim=tim, graphics=False)
- elif '332' in statusCode:
- s = _('%(nick)s has been removed from the room (%(reason)s)') % {
- 'nick': nick,
- 'reason': _('system shutdown') }
- self.print_conversation(s, 'info', tim=tim, graphics=False)
- # Room has been destroyed.
- elif 'destroyed' in statusCode:
- self.autorejoin = False
- self.print_conversation(reason, 'info', tim, graphics=False)
-
- if len(gajim.events.get_events(self.account, jid=fake_jid,
- types=['pm'])) == 0:
- self.remove_contact(nick)
- self.draw_all_roles()
- else:
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
- c.show = show
- c.status = status
- if nick == self.nick and (not statusCode or \
- '303' not in statusCode): # We became offline
- self.got_disconnected()
- contact = gajim.contacts.\
- get_contact_with_highest_priority(self.account, self.room_jid)
- if contact:
- gajim.interface.roster.draw_contact(self.room_jid, self.account)
- if self.parent_win:
- self.parent_win.redraw_tab(self)
- else:
- iter_ = self.get_contact_iter(nick)
- if not iter_:
- if '210' in statusCode:
- # Server changed our nick
- self.nick = nick
- s = _('You are now known as %s') % nick
- self.print_conversation(s, 'info', tim=tim, graphics=False)
- iter_ = self.add_contact_to_roster(nick, show, role, affiliation,
- status, jid)
- newly_created = True
- self.draw_all_roles()
- if statusCode and '201' in statusCode: # We just created the room
- gajim.connections[self.account].request_gc_config(self.room_jid)
- else:
- gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- if not gc_c:
- log.error('%s has an iter, but no gc_contact instance')
- return
- # Re-get vcard if avatar has changed
- # We do that here because we may request it to the real JID if we
- # knows it. connections.py doesn't know it.
- con = gajim.connections[self.account]
- if gc_c and gc_c.jid:
- real_jid = gc_c.jid
- if gc_c.resource:
- real_jid += '/' + gc_c.resource
- else:
- real_jid = fake_jid
- if fake_jid in con.vcard_shas:
- if avatar_sha != con.vcard_shas[fake_jid]:
- server = gajim.get_server_from_jid(self.room_jid)
- if not server.startswith('irc'):
- con.request_vcard(real_jid, fake_jid)
- else:
- cached_vcard = con.get_cached_vcard(fake_jid, True)
- if cached_vcard and 'PHOTO' in cached_vcard and \
- 'SHA' in cached_vcard['PHOTO']:
- cached_sha = cached_vcard['PHOTO']['SHA']
- else:
- cached_sha = ''
- if cached_sha != avatar_sha:
- # avatar has been updated
- # sha in mem will be updated later
- server = gajim.get_server_from_jid(self.room_jid)
- if not server.startswith('irc'):
- con.request_vcard(real_jid, fake_jid)
- else:
- # save sha in mem NOW
- con.vcard_shas[fake_jid] = avatar_sha
-
- actual_affiliation = gc_c.affiliation
- if affiliation != actual_affiliation:
- if actor:
- st = _('** Affiliation of %(nick)s has been set to '
- '%(affiliation)s by %(actor)s') % {'nick': nick_jid,
- 'affiliation': affiliation, 'actor': actor}
- else:
- st = _('** Affiliation of %(nick)s has been set to '
- '%(affiliation)s') % {'nick': nick_jid,
- 'affiliation': affiliation}
- if reason:
- st += ' (%s)' % reason
- self.print_conversation(st, tim=tim, graphics=False)
- right_changed = True
- actual_role = self.get_role(nick)
- if role != actual_role:
- self.remove_contact(nick)
- self.add_contact_to_roster(nick, show, role,
- affiliation, status, jid)
- self.draw_role(actual_role)
- self.draw_role(role)
- if actor:
- st = _('** Role of %(nick)s has been set to %(role)s by '
- '%(actor)s') % {'nick': nick_jid, 'role': role,
- 'actor': actor}
- else:
- st = _('** Role of %(nick)s has been set to %(role)s') % {
- 'nick': nick_jid, 'role': role}
- if reason:
- st += ' (%s)' % reason
- self.print_conversation(st, tim=tim, graphics=False)
- right_changed = True
- else:
- if gc_c.show == show and gc_c.status == status and \
- gc_c.affiliation == affiliation: # no change
- return
- gc_c.show = show
- gc_c.affiliation = affiliation
- gc_c.status = status
- self.draw_contact(nick)
- if (time.time() - self.room_creation) > 30 and nick != self.nick and \
- (not statusCode or '303' not in statusCode) and not right_changed:
- st = ''
- print_status = None
- for bookmark in gajim.connections[self.account].bookmarks:
- if bookmark['jid'] == self.room_jid:
- print_status = bookmark.get('print_status', None)
- break
- if not print_status:
- print_status = gajim.config.get('print_status_in_muc')
- if show == 'offline':
- if nick in self.attention_list:
- self.attention_list.remove(nick)
- if show == 'offline' and print_status in ('all', 'in_and_out') and \
- (not statusCode or '307' not in statusCode):
- st = _('%s has left') % nick_jid
- if reason:
- st += ' [%s]' % reason
- else:
- if newly_created and print_status in ('all', 'in_and_out'):
- st = _('%s has joined the group chat') % nick_jid
- elif print_status == 'all':
- st = _('%(nick)s is now %(status)s') % {'nick': nick_jid,
- 'status': helpers.get_uf_show(show)}
- if st:
- if status:
- st += ' (' + status + ')'
- self.print_conversation(st, tim=tim, graphics=False)
-
- def add_contact_to_roster(self, nick, show, role, affiliation, status,
- jid=''):
- model = self.list_treeview.get_model()
- role_name = helpers.get_uf_role(role, plural=True)
-
- resource = ''
- if jid:
- jids = jid.split('/', 1)
- j = jids[0]
- if len(jids) > 1:
- resource = jids[1]
- else:
- j = ''
-
- name = nick
-
- role_iter = self.get_role_iter(role)
- if not role_iter:
- role_iter = model.append(None,
- (gajim.interface.jabber_state_images['16']['closed'], role,
- 'role', role_name, None))
- self.draw_all_roles()
- iter_ = model.append(role_iter, (None, nick, 'contact', name, None))
- if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account,
- name=nick, show=show, status=status, role=role,
- affiliation=affiliation, jid=j, resource=resource)
- gajim.contacts.add_gc_contact(self.account, gc_contact)
- self.draw_contact(nick)
- self.draw_avatar(nick)
- # Do not ask avatar to irc rooms as irc transports reply with messages
- server = gajim.get_server_from_jid(self.room_jid)
- if gajim.config.get('ask_avatars_on_startup') and \
- not server.startswith('irc'):
- fake_jid = self.room_jid + '/' + nick
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
- if pixbuf == 'ask':
- if j:
- fjid = j
- if resource:
- fjid += '/' + resource
- gajim.connections[self.account].request_vcard(fjid, fake_jid)
- else:
- gajim.connections[self.account].request_vcard(fake_jid, fake_jid)
- if nick == self.nick: # we became online
- self.got_connected()
- self.list_treeview.expand_row((model.get_path(role_iter)), False)
- if self.is_continued:
- self.draw_banner_text()
- return iter_
-
- def get_role_iter(self, role):
- model = self.list_treeview.get_model()
- role_iter = model.get_iter_root()
- while role_iter:
- role_name = model[role_iter][C_NICK].decode('utf-8')
- if role == role_name:
- return role_iter
- role_iter = model.iter_next(role_iter)
- return None
-
- def remove_contact(self, nick):
- """
- Remove a user from the contacts_list
- """
- model = self.list_treeview.get_model()
- iter_ = self.get_contact_iter(nick)
- if not iter_:
- return
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- if gc_contact:
- gajim.contacts.remove_gc_contact(self.account, gc_contact)
- parent_iter = model.iter_parent(iter_)
- model.remove(iter_)
- if model.iter_n_children(parent_iter) == 0:
- model.remove(parent_iter)
-
- def send_message(self, message, xhtml=None, process_commands=True):
- """
- Call this function to send our message
- """
- if not message:
- return
-
- if process_commands and self.process_as_command(message):
- return
-
- message = helpers.remove_invalid_xml_chars(message)
-
- if not message:
- return
-
- if message != '' or message != '\n':
- self.save_sent_message(message)
-
- # Send the message
- gajim.connections[self.account].send_gc_message(self.room_jid,
- message, xhtml=xhtml)
- self.msg_textview.get_buffer().set_text('')
- self.msg_textview.grab_focus()
-
- def get_role(self, nick):
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- if gc_contact:
- return gc_contact.role
- else:
- return 'visitor'
-
- def minimizable(self):
- if self.contact.jid in gajim.config.get_per('accounts', self.account,
- 'minimized_gc').split(' '):
- return True
- return False
-
- def minimize(self, status='offline'):
- # Minimize it
- win = gajim.interface.msg_win_mgr.get_window(self.contact.jid,
- self.account)
- ctrl = win.get_control(self.contact.jid, self.account)
-
- ctrl_page = win.notebook.page_num(ctrl.widget)
- control = win.notebook.get_nth_page(ctrl_page)
-
- win.notebook.remove_page(ctrl_page)
- control.unparent()
- ctrl.parent_win = None
-
- gajim.interface.roster.add_groupchat(self.contact.jid, self.account,
- status = self.subject)
-
- del win._controls[self.account][self.contact.jid]
-
- def shutdown(self, status='offline'):
- # Preventing autorejoin from being activated
- self.autorejoin = False
-
- if self.room_jid in gajim.gc_connected[self.account] and \
- gajim.gc_connected[self.account][self.room_jid]:
- # Tell connection to note the date we disconnect to avoid duplicate
- # logs. We do it only when connected because if connection was lost
- # there may be new messages since disconnection.
- gajim.connections[self.account].gc_got_disconnected(self.room_jid)
- gajim.connections[self.account].send_gc_status(self.nick, self.room_jid,
- show='offline', status=status)
- nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
- for nick in nick_list:
- # Update pm chat window
- fjid = self.room_jid + '/' + nick
- ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, self.account)
- if ctrl:
- contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- contact.show = 'offline'
- contact.status = ''
- ctrl.update_ui()
- ctrl.parent_win.redraw_tab(ctrl)
- for sess in gajim.connections[self.account].get_sessions(fjid):
- if sess.control:
- sess.control.no_autonegotiation = False
- if sess.enable_encryption:
- sess.terminate_e2e()
- gajim.connections[self.account].delete_session(fjid,
- sess.thread_id)
- # They can already be removed by the destroy function
- if self.room_jid in gajim.contacts.get_gc_list(self.account):
- gajim.contacts.remove_room(self.account, self.room_jid)
- del gajim.gc_connected[self.account][self.room_jid]
- # Save hpaned position
- gajim.config.set('gc-hpaned-position', self.hpaned.get_position())
- # remove all register handlers on wigets, created by self.xml
- # to prevent circular references among objects
- for i in self.handlers.keys():
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers[i]
- # Remove unread events from systray
- gajim.events.remove_events(self.account, self.room_jid)
-
- def safe_shutdown(self):
- if self.minimizable():
- return True
- includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
- excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
- # whether to ask for comfirmation before closing muc
- if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \
- and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\
- in excludes:
- return False
- return True
-
- def allow_shutdown(self, method, on_yes, on_no, on_minimize):
- if self.minimizable():
- on_minimize(self)
- return
- if method == self.parent_win.CLOSE_ESC:
- iter_ = self.list_treeview.get_selection().get_selected()[1]
- if iter_:
- self.list_treeview.get_selection().unselect_all()
- on_no(self)
- return
- includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
- excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
- # whether to ask for comfirmation before closing muc
- if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \
- and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\
- in excludes:
-
- def on_ok(clicked):
- if clicked:
- # user does not want to be asked again
- gajim.config.set('confirm_close_muc', False)
- on_yes(self)
-
- def on_cancel(clicked):
- if clicked:
- # user does not want to be asked again
- gajim.config.set('confirm_close_muc', False)
- on_no(self)
-
- pritext = _('Are you sure you want to leave group chat "%s"?')\
- % self.name
- sectext = _('If you close this window, you will be disconnected '
- 'from this group chat.')
-
- dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('Do _not ask me again'), on_response_ok=on_ok,
- on_response_cancel=on_cancel)
- return
-
- on_yes(self)
-
- def set_control_active(self, state):
- self.conv_textview.allow_focus_out_line = True
- self.attention_flag = False
- ChatControlBase.set_control_active(self, state)
- if not state:
- # add the focus-out line to the tab we are leaving
- self.check_and_possibly_add_focus_out_line()
- # Sending active to undo unread state
- self.parent_win.redraw_tab(self, 'active')
-
- def get_specific_unread(self):
- # returns the number of the number of unread msgs
- # for room_jid & number of unread private msgs with each contact
- # that we have
- nb = 0
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- fjid = self.room_jid + '/' + nick
- nb += len(gajim.events.get_events(self.account, fjid))
- # gc can only have messages as event
- return nb
-
- def _on_change_subject_menuitem_activate(self, widget):
- def on_ok(subject):
- # Note, we don't update self.subject since we don't know whether it
- # will work yet
- gajim.connections[self.account].send_gc_subject(self.room_jid, subject)
-
- dialogs.InputTextDialog(_('Changing Subject'),
- _('Please specify the new subject:'), input_str=self.subject,
- ok_handler=on_ok)
-
- def _on_change_nick_menuitem_activate(self, widget):
- if 'change_nick_dialog' in gajim.interface.instances:
- gajim.interface.instances['change_nick_dialog'].present()
- else:
- title = _('Changing Nickname')
- prompt = _('Please specify the new nickname you want to use:')
- gajim.interface.instances['change_nick_dialog'] = \
- dialogs.ChangeNickDialog(self.account, self.room_jid, title,
- prompt)
-
- def _on_configure_room_menuitem_activate(self, widget):
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick)
- if c.affiliation == 'owner':
- gajim.connections[self.account].request_gc_config(self.room_jid)
- elif c.affiliation == 'admin':
- if self.room_jid not in gajim.interface.instances[self.account][
- 'gc_config']:
- gajim.interface.instances[self.account]['gc_config'][self.room_jid]\
- = config.GroupchatConfigWindow(self.account, self.room_jid)
-
- def _on_destroy_room_menuitem_activate(self, widget):
- def on_ok(reason, jid):
- if jid:
- # Test jid
- try:
- jid = helpers.parse_jid(jid)
- except Exception:
- dialogs.ErrorDialog(_('Invalid group chat Jabber ID'),
- _('The group chat Jabber ID has not allowed characters.'))
- return
- gajim.connections[self.account].destroy_gc_room(self.room_jid, reason,
- jid)
-
- # Ask for a reason
- dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid,
- _('You are going to definitively destroy this room.\n'
- 'You may specify a reason below:'),
- _('You may also enter an alternate venue:'), ok_handler=on_ok)
-
- def _on_bookmark_room_menuitem_activate(self, widget):
- """
- Bookmark the room, without autojoin and not minimized
- """
- password = gajim.gc_passwords.get(self.room_jid, '')
- gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \
- '0', '0', password, self.nick)
-
- def _on_drag_data_received(self, widget, context, x, y, selection,
- target_type, timestamp):
- # Invite contact to groupchat
- treeview = gajim.interface.roster.tree
- model = treeview.get_model()
- if not selection.data or target_type == 80:
- # target_type = 80 means a file is dropped
- return
- data = selection.data
- path = treeview.get_selection().get_selected_rows()[1][0]
- iter_ = model.get_iter(path)
- type_ = model[iter_][2]
- if type_ != 'contact': # source is not a contact
- return
- contact_jid = data.decode('utf-8')
- gajim.connections[self.account].send_invite(self.room_jid, contact_jid)
-
- def handle_message_textview_mykey_press(self, widget, event_keyval,
- event_keymod):
- # NOTE: handles mykeypress which is custom signal connected to this
- # CB in new_room(). for this singal see message_textview.py
-
- # construct event instance from binding
- event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
- event.keyval = event_keyval
- event.state = event_keymod
- event.time = 0 # assign current time
-
- message_buffer = widget.get_buffer()
- start_iter, end_iter = message_buffer.get_bounds()
-
- if event.keyval == gtk.keysyms.Tab: # TAB
- cursor_position = message_buffer.get_insert()
- end_iter = message_buffer.get_iter_at_mark(cursor_position)
- text = message_buffer.get_text(start_iter, end_iter, False).decode(
- 'utf-8')
-
- splitted_text = text.split()
-
- # HACK: Not the best soltution.
- if (text.startswith(self.COMMAND_PREFIX) and not
- text.startswith(self.COMMAND_PREFIX * 2) and len(splitted_text) == 1):
- return super(GroupchatControl,
- self).handle_message_textview_mykey_press(widget, event_keyval,
- event_keymod)
-
- # nick completion
- # check if tab is pressed with empty message
- if len(splitted_text): # if there are any words
- begin = splitted_text[-1] # last word we typed
- else:
- begin = ''
-
- gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
- with_refer_to_nick_char = False
- after_nick_len = 1 # the space that is printed after we type [Tab]
-
- # first part of this if : works fine even if refer_to_nick_char
- if gc_refer_to_nick_char and text.endswith(
- gc_refer_to_nick_char + ' '):
- with_refer_to_nick_char = True
- after_nick_len = len(gc_refer_to_nick_char + ' ')
- if len(self.nick_hits) and self.last_key_tabs and \
- text[:-after_nick_len].endswith(self.nick_hits[0]):
- # we should cycle
- # Previous nick in list may had a space inside, so we check text and
- # not splitted_text and store it into 'begin' var
- self.nick_hits.append(self.nick_hits[0])
- begin = self.nick_hits.pop(0)
- else:
- self.nick_hits = [] # clear the hit list
- list_nick = gajim.contacts.get_nick_list(self.account,
- self.room_jid)
- list_nick.sort(key=unicode.lower) # case-insensitive sort
- if begin == '':
- # empty message, show lasts nicks that highlighted us first
- for nick in self.attention_list:
- if nick in list_nick:
- list_nick.remove(nick)
- list_nick.insert(0, nick)
-
- list_nick.remove(self.nick) # Skip self
- for nick in list_nick:
- if nick.lower().startswith(begin.lower()):
- # the word is the begining of a nick
- self.nick_hits.append(nick)
- if len(self.nick_hits):
- if len(splitted_text) < 2 or with_refer_to_nick_char:
- # This is the 1st word of the line or no word or we are cycling
- # at the beginning, possibly with a space in one nick
- add = gc_refer_to_nick_char + ' '
- else:
- add = ' '
- start_iter = end_iter.copy()
- if self.last_key_tabs and with_refer_to_nick_char or (text and \
- text[-1] == ' '):
- # have to accomodate for the added space from last
- # completion
- # gc_refer_to_nick_char may be more than one char!
- start_iter.backward_chars(len(begin) + len(add))
- elif self.last_key_tabs and not gajim.config.get(
- 'shell_like_completion'):
- # have to accomodate for the added space from last
- # completion
- start_iter.backward_chars(len(begin) + \
- len(gc_refer_to_nick_char))
- else:
- start_iter.backward_chars(len(begin))
-
- message_buffer.delete(start_iter, end_iter)
- # get a shell-like completion
- # if there's more than one nick for this completion, complete only
- # the part that all these nicks have in common
- if gajim.config.get('shell_like_completion') and \
- len(self.nick_hits) > 1:
- end = False
- completion = ''
- add = "" # if nick is not complete, don't add anything
- while not end and len(completion) < len(self.nick_hits[0]):
- completion = self.nick_hits[0][:len(completion)+1]
- for nick in self.nick_hits:
- if completion.lower() not in nick.lower():
- end = True
- completion = completion[:-1]
- break
- # if the current nick matches a COMPLETE existing nick,
- # and if the user tab TWICE, complete that nick (with the "add")
- if self.last_key_tabs:
- for nick in self.nick_hits:
- if nick == completion:
- # The user seems to want this nick, so
- # complete it as if it were the only nick
- # available
- add = gc_refer_to_nick_char + ' '
- else:
- completion = self.nick_hits[0]
- message_buffer.insert_at_cursor(completion + add)
- self.last_key_tabs = True
- return True
- self.last_key_tabs = False
-
- def on_list_treeview_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- selection = widget.get_selection()
- iter_ = selection.get_selected()[1]
- if iter_:
- widget.get_selection().unselect_all()
- return True
-
- def on_list_treeview_row_expanded(self, widget, iter_, path):
- """
- When a row is expanded: change the icon of the arrow
- """
- model = widget.get_model()
- image = gajim.interface.jabber_state_images['16']['opened']
- model[iter_][C_IMG] = image
-
- def on_list_treeview_row_collapsed(self, widget, iter_, path):
- """
- When a row is collapsed: change the icon of the arrow
- """
- model = widget.get_model()
- image = gajim.interface.jabber_state_images['16']['closed']
- model[iter_][C_IMG] = image
-
- def kick(self, widget, nick):
- """
- Kick a user
- """
- def on_ok(reason):
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'none', reason)
-
- # ask for reason
- dialogs.InputDialog(_('Kicking %s') % nick,
- _('You may specify a reason below:'), ok_handler=on_ok)
-
- def mk_menu(self, event, iter_):
- """
- Make contact's popup menu
- """
- model = self.list_treeview.get_model()
- nick = model[iter_][C_NICK].decode('utf-8')
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
- fjid = self.room_jid + '/' + nick
- jid = c.jid
- target_affiliation = c.affiliation
- target_role = c.role
-
- # looking for user's affiliation and role
- user_nick = self.nick
- user_affiliation = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, user_nick).affiliation
- user_role = self.get_role(user_nick)
-
- # making menu from gtk builder
- xml = gtkgui_helpers.get_gtk_builder('gc_occupants_menu.ui')
-
- # these conditions were taken from JEP 0045
- item = xml.get_object('kick_menuitem')
- if user_role != 'moderator' or \
- (user_affiliation == 'admin' and target_affiliation == 'owner') or \
- (user_affiliation == 'member' and target_affiliation in ('admin',
- 'owner')) or (user_affiliation == 'none' and target_affiliation != \
- 'none'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.kick, nick)
- self.handlers[id_] = item
-
- item = xml.get_object('voice_checkmenuitem')
- item.set_active(target_role != 'visitor')
- if user_role != 'moderator' or \
- user_affiliation == 'none' or \
- (user_affiliation=='member' and target_affiliation!='none') or \
- target_affiliation in ('admin', 'owner'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_voice_checkmenuitem_activate,
- nick)
- self.handlers[id_] = item
-
- item = xml.get_object('moderator_checkmenuitem')
- item.set_active(target_role == 'moderator')
- if not user_affiliation in ('admin', 'owner') or \
- target_affiliation in ('admin', 'owner'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate,
- nick)
- self.handlers[id_] = item
-
- item = xml.get_object('ban_menuitem')
- if not user_affiliation in ('admin', 'owner') or \
- (target_affiliation in ('admin', 'owner') and\
- user_affiliation != 'owner'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.ban, jid)
- self.handlers[id_] = item
-
- item = xml.get_object('member_checkmenuitem')
- item.set_active(target_affiliation != 'none')
- if not user_affiliation in ('admin', 'owner') or \
- (user_affiliation != 'owner' and target_affiliation in ('admin','owner')):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid)
- self.handlers[id_] = item
-
- item = xml.get_object('admin_checkmenuitem')
- item.set_active(target_affiliation in ('admin', 'owner'))
- if not user_affiliation == 'owner':
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid)
- self.handlers[id_] = item
-
- item = xml.get_object('owner_checkmenuitem')
- item.set_active(target_affiliation == 'owner')
- if not user_affiliation == 'owner':
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid)
- self.handlers[id_] = item
-
- item = xml.get_object('information_menuitem')
- id_ = item.connect('activate', self.on_info, nick)
- self.handlers[id_] = item
-
- item = xml.get_object('history_menuitem')
- id_ = item.connect('activate', self.on_history, nick)
- self.handlers[id_] = item
-
- item = xml.get_object('add_to_roster_menuitem')
- our_jid = gajim.get_jid_from_account(self.account)
- if not jid or jid == our_jid:
- item.set_sensitive(False)
- else:
- id_ = item.connect('activate', self.on_add_to_roster, jid)
- self.handlers[id_] = item
-
- item = xml.get_object('block_menuitem')
- item2 = xml.get_object('unblock_menuitem')
- if helpers.jid_is_blocked(self.account, fjid):
- item.set_no_show_all(True)
- item.hide()
- id_ = item2.connect('activate', self.on_unblock, nick)
- self.handlers[id_] = item2
- else:
- id_ = item.connect('activate', self.on_block, nick)
- self.handlers[id_] = item
- item2.set_no_show_all(True)
- item2.hide()
-
- item = xml.get_object('send_private_message_menuitem')
- id_ = item.connect('activate', self.on_send_pm, model, iter_)
- self.handlers[id_] = item
-
- item = xml.get_object('send_file_menuitem')
- # add a special img for send file menuitem
- path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
- img = gtk.Image()
- img.set_from_file(path_to_upload_img)
- item.set_image(img)
-
- if not c.resource:
- item.set_sensitive(False)
- else:
- id_ = item.connect('activate', self.on_send_file, c)
- self.handlers[id_] = item
-
- # show the popup now!
- menu = xml.get_object('gc_occupants_menu')
- menu.show_all()
- menu.popup(None, None, None, event.button, event.time)
-
- def _start_private_message(self, nick):
- gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
- nick_jid = gc_c.get_full_jid()
-
- ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account)
- if not ctrl:
- ctrl = gajim.interface.new_private_chat(gc_c, self.account)
-
- if ctrl:
- ctrl.parent_win.set_active_tab(ctrl)
-
- return ctrl
-
- def on_row_activated(self, widget, path):
- """
- When an iter is activated (dubblick or single click if gnome is set this
- way
- """
- model = widget.get_model()
- if len(path) == 1: # It's a group
- if (widget.row_expanded(path)):
- widget.collapse_row(path)
- else:
- widget.expand_row(path, False)
- else: # We want to send a private message
- nick = model[path][C_NICK].decode('utf-8')
- self._start_private_message(nick)
-
- def on_list_treeview_row_activated(self, widget, path, col=0):
- """
- When an iter is double clicked: open the chat window
- """
- if not gajim.single_click:
- self.on_row_activated(widget, path)
-
- def on_list_treeview_button_press_event(self, widget, event):
- """
- Popup user's group's or agent menu
- """
- # hide tooltip, no matter the button is pressed
- self.tooltip.hide_tooltip()
- try:
- pos = widget.get_path_at_pos(int(event.x), int(event.y))
- path, x = pos[0], pos[2]
- except TypeError:
- widget.get_selection().unselect_all()
- return
- if event.button == 3: # right click
- widget.get_selection().select_path(path)
- model = widget.get_model()
- iter_ = model.get_iter(path)
- if len(path) == 2:
- self.mk_menu(event, iter_)
- return True
-
- elif event.button == 2: # middle click
- widget.get_selection().select_path(path)
- model = widget.get_model()
- iter_ = model.get_iter(path)
- if len(path) == 2:
- nick = model[iter_][C_NICK].decode('utf-8')
- self._start_private_message(nick)
- return True
-
- elif event.button == 1: # left click
- if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK:
- self.on_row_activated(widget, path)
- return True
- else:
- model = widget.get_model()
- iter_ = model.get_iter(path)
- nick = model[iter_][C_NICK].decode('utf-8')
- if not nick in gajim.contacts.get_nick_list(self.account,
- self.room_jid):
- # it's a group
- if x < 27:
- if (widget.row_expanded(path)):
- widget.collapse_row(path)
- else:
- widget.expand_row(path, False)
- elif event.state & gtk.gdk.SHIFT_MASK:
- self.append_nick_in_msg_textview(self.msg_textview, nick)
- self.msg_textview.grab_focus()
- return True
-
- def append_nick_in_msg_textview(self, widget, nick):
- message_buffer = self.msg_textview.get_buffer()
- start_iter, end_iter = message_buffer.get_bounds()
- cursor_position = message_buffer.get_insert()
- end_iter = message_buffer.get_iter_at_mark(cursor_position)
- text = message_buffer.get_text(start_iter, end_iter, False)
- start = ''
- if text: # Cursor is not at first position
- if not text[-1] in (' ', '\n', '\t'):
- start = ' '
- add = ' '
- else:
- gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
- add = gc_refer_to_nick_char + ' '
- message_buffer.insert_at_cursor(start + nick + add)
-
- def on_list_treeview_motion_notify_event(self, widget, event):
- model = widget.get_model()
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id != props[0]:
- self.tooltip.hide_tooltip()
- if props:
- [row, col, x, y] = props
- iter_ = None
- try:
- iter_ = model.get_iter(row)
- except Exception:
- self.tooltip.hide_tooltip()
- return
- typ = model[iter_][C_TYPE].decode('utf-8')
- if typ == 'contact':
- account = self.account
-
- if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
- self.tooltip.id = row
- nick = model[iter_][C_NICK].decode('utf-8')
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, gajim.contacts.get_gc_contact(account,
- self.room_jid, nick))
-
- def on_list_treeview_leave_notify_event(self, widget, event):
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id == props[0]:
- self.tooltip.hide_tooltip()
-
- def show_tooltip(self, contact):
- if not self.list_treeview.window:
- # control has been destroyed since tooltip was requested
- return
- pointer = self.list_treeview.get_pointer()
- props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1])
- # check if the current pointer is at the same path
- # as it was before setting the timeout
- if props and self.tooltip.id == props[0]:
- rect = self.list_treeview.get_cell_area(props[0],props[1])
- position = self.list_treeview.window.get_origin()
- self.tooltip.show_tooltip(contact, rect.height,
- position[1] + rect.y)
- else:
- self.tooltip.hide_tooltip()
-
- def grant_voice(self, widget, nick):
- """
- Grant voice privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'participant')
-
- def revoke_voice(self, widget, nick):
- """
- Revoke voice privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'visitor')
-
- def grant_moderator(self, widget, nick):
- """
- Grant moderator privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'moderator')
-
- def revoke_moderator(self, widget, nick):
- """
- Revoke moderator privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'participant')
-
- def ban(self, widget, jid):
- """
- Ban a user
- """
- def on_ok(reason):
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'outcast', reason)
-
- # to ban we know the real jid. so jid is not fakejid
- nick = gajim.get_nick_from_jid(jid)
- # ask for reason
- dialogs.InputDialog(_('Banning %s') % nick,
- _('You may specify a reason below:'), ok_handler=on_ok)
-
- def grant_membership(self, widget, jid):
- """
- Grant membership privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'member')
-
- def revoke_membership(self, widget, jid):
- """
- Revoke membership privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'none')
-
- def grant_admin(self, widget, jid):
- """
- Grant administrative privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'admin')
-
- def revoke_admin(self, widget, jid):
- """
- Revoke administrative privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'member')
-
- def grant_owner(self, widget, jid):
- """
- Grant owner privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'owner')
-
- def revoke_owner(self, widget, jid):
- """
- Revoke owner privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'admin')
-
- def on_info(self, widget, nick):
- """
- Call vcard_information_window class to display user's information
- """
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
- contact = gc_contact.as_contact()
- if contact.jid in gajim.interface.instances[self.account]['infos']:
- gajim.interface.instances[self.account]['infos'][contact.jid].window.\
- present()
- else:
- gajim.interface.instances[self.account]['infos'][contact.jid] = \
- vcard.VcardWindow(contact, self.account, gc_contact)
-
- def on_history(self, widget, nick):
- jid = gajim.construct_fjid(self.room_jid, nick)
- self._on_history_menuitem_activate(widget=widget, jid=jid)
-
- def on_add_to_roster(self, widget, jid):
- dialogs.AddNewContactWindow(self.account, jid)
-
- def on_block(self, widget, nick):
- fjid = self.room_jid + '/' + nick
- connection = gajim.connections[self.account]
- if fjid in connection.blocked_contacts:
- return
- new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny',
- 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']}
- connection.blocked_list.append(new_rule)
- connection.blocked_contacts.append(fjid)
- self.draw_contact(nick)
- connection.set_privacy_list('block', connection.blocked_list)
- if len(connection.blocked_list) == 1:
- connection.set_active_list('block')
- connection.set_default_list('block')
- connection.get_privacy_list('block')
-
- def on_unblock(self, widget, nick):
- fjid = self.room_jid + '/' + nick
- connection = gajim.connections[self.account]
- connection.new_blocked_list = []
- # needed for draw_contact:
- if fjid in connection.blocked_contacts:
- connection.blocked_contacts.remove(fjid)
- self.draw_contact(nick)
- for rule in connection.blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'jid' \
- or rule['value'] != fjid:
- connection.new_blocked_list.append(rule)
-
- connection.set_privacy_list('block', connection.new_blocked_list)
- connection.get_privacy_list('block')
- if len(connection.new_blocked_list) == 0:
- connection.blocked_list = []
- connection.blocked_contacts = []
- connection.blocked_groups = []
- connection.set_default_list('')
- connection.set_active_list('')
- connection.del_privacy_list('block')
- if 'blocked_contacts' in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account]['blocked_contacts'].\
- privacy_list_received([])
-
- def on_voice_checkmenuitem_activate(self, widget, nick):
- if widget.get_active():
- self.grant_voice(widget, nick)
- else:
- self.revoke_voice(widget, nick)
-
- def on_moderator_checkmenuitem_activate(self, widget, nick):
- if widget.get_active():
- self.grant_moderator(widget, nick)
- else:
- self.revoke_moderator(widget, nick)
-
- def on_member_checkmenuitem_activate(self, widget, jid):
- if widget.get_active():
- self.grant_membership(widget, jid)
- else:
- self.revoke_membership(widget, jid)
-
- def on_admin_checkmenuitem_activate(self, widget, jid):
- if widget.get_active():
- self.grant_admin(widget, jid)
- else:
- self.revoke_admin(widget, jid)
-
- def on_owner_checkmenuitem_activate(self, widget, jid):
- if widget.get_active():
- self.grant_owner(widget, jid)
- else:
- self.revoke_owner(widget, jid)
-
-# vim: se ts=3:
+ TYPE_ID = message_control.TYPE_GC
+
+ # Set a command host to bound to. Every command given through a group chat
+ # will be processed with this command host.
+ COMMAND_HOST = GroupChatCommands
+
+ def __init__(self, parent_win, contact, acct, is_continued=False):
+ ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
+ 'groupchat_control', contact, acct)
+
+ self.is_continued=is_continued
+ self.is_anonymous = True
+
+ # Controls the state of autorejoin.
+ # None - autorejoin is neutral.
+ # False - autorejoin is to be prevented (gets reset to initial state in
+ # got_connected()).
+ # int - autorejoin is being active and working (gets reset to initial
+ # state in got_connected()).
+ self.autorejoin = None
+
+ self.actions_button = self.xml.get_object('muc_window_actions_button')
+ id_ = self.actions_button.connect('clicked',
+ self.on_actions_button_clicked)
+ self.handlers[id_] = self.actions_button
+
+ widget = self.xml.get_object('change_nick_button')
+ id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
+ self.handlers[id_] = widget
+
+ widget = self.xml.get_object('change_subject_button')
+ id_ = widget.connect('clicked', self._on_change_subject_menuitem_activate)
+ self.handlers[id_] = widget
+
+ widget = self.xml.get_object('bookmark_button')
+ for bm in gajim.connections[self.account].bookmarks:
+ if bm['jid'] == self.contact.jid:
+ widget.hide()
+ break
+ else:
+ id_ = widget.connect('clicked',
+ self._on_bookmark_room_menuitem_activate)
+ self.handlers[id_] = widget
+ widget.show()
+
+ widget = self.xml.get_object('list_treeview')
+ id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('row_collapsed', self.on_list_treeview_row_collapsed)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('row_activated',
+ self.on_list_treeview_row_activated)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('button_press_event',
+ self.on_list_treeview_button_press_event)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('key_press_event',
+ self.on_list_treeview_key_press_event)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('motion_notify_event',
+ self.on_list_treeview_motion_notify_event)
+ self.handlers[id_] = widget
+
+ id_ = widget.connect('leave_notify_event',
+ self.on_list_treeview_leave_notify_event)
+ self.handlers[id_] = widget
+
+ self.room_jid = self.contact.jid
+ self.nick = contact.name.decode('utf-8')
+ self.new_nick = ''
+ self.name = ''
+ for bm in gajim.connections[self.account].bookmarks:
+ if bm['jid'] == self.room_jid:
+ self.name = bm['name']
+ break
+ if not self.name:
+ self.name = self.room_jid.split('@')[0]
+
+ compact_view = gajim.config.get('compact_view')
+ self.chat_buttons_set_visible(compact_view)
+ self.widget_set_visible(self.xml.get_object('banner_eventbox'),
+ gajim.config.get('hide_groupchat_banner'))
+ self.widget_set_visible(self.xml.get_object('list_scrolledwindow'),
+ gajim.config.get('hide_groupchat_occupants_list'))
+
+ self._last_selected_contact = None # None or holds jid, account tuple
+
+ # muc attention flag (when we are mentioned in a muc)
+ # if True, the room has mentioned us
+ self.attention_flag = False
+
+ # sorted list of nicks who mentioned us (last at the end)
+ self.attention_list = []
+ self.room_creation = int(time.time()) # Use int to reduce mem usage
+ self.nick_hits = []
+ self.last_key_tabs = False
+
+ self.subject = ''
+
+ self.tooltip = tooltips.GCTooltip()
+
+ # nickname coloring
+ self.gc_count_nicknames_colors = 0
+ self.gc_custom_colors = {}
+ self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
+ split(':'))
+
+ self.name_label = self.xml.get_object('banner_name_label')
+ self.event_box = self.xml.get_object('banner_eventbox')
+
+ # set the position of the current hpaned
+ hpaned_position = gajim.config.get('gc-hpaned-position')
+ self.hpaned = self.xml.get_object('hpaned')
+ self.hpaned.set_position(hpaned_position)
+
+ self.list_treeview = self.xml.get_object('list_treeview')
+ selection = self.list_treeview.get_selection()
+ id_ = selection.connect('changed',
+ self.on_list_treeview_selection_changed)
+ self.handlers[id_] = selection
+ id_ = self.list_treeview.connect('style-set',
+ self.on_list_treeview_style_set)
+ self.handlers[id_] = self.list_treeview
+ self.resize_from_another_muc = False
+ # we want to know when the the widget resizes, because that is
+ # an indication that the hpaned has moved...
+ # FIXME: Find a better indicator that the hpaned has moved.
+ id_ = self.list_treeview.connect('size-allocate',
+ self.on_treeview_size_allocate)
+ self.handlers[id_] = self.list_treeview
+ #status_image, shown_nick, type, nickname, avatar
+ store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf)
+ store.set_sort_func(C_NICK, self.tree_compare_iters)
+ store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING)
+ self.list_treeview.set_model(store)
+
+ # columns
+
+ # this col has 3 cells:
+ # first one img, second one text, third is sec pixbuf
+ column = gtk.TreeViewColumn()
+
+ def add_avatar_renderer():
+ renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image
+ column.pack_start(renderer_pixbuf, expand=False)
+ column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR)
+ column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func,
+ self.list_treeview)
+
+ if gajim.config.get('avatar_position_in_roster') == 'left':
+ add_avatar_renderer()
+
+ renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img
+ renderer_image.set_property('width', 26)
+ column.pack_start(renderer_image, expand=False)
+ column.add_attribute(renderer_image, 'image', C_IMG)
+ column.set_cell_data_func(renderer_image, tree_cell_data_func,
+ self.list_treeview)
+
+ renderer_text = gtk.CellRendererText() # nickname
+ column.pack_start(renderer_text, expand=True)
+ column.add_attribute(renderer_text, 'markup', C_TEXT)
+ renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END)
+ column.set_cell_data_func(renderer_text, tree_cell_data_func,
+ self.list_treeview)
+
+ if gajim.config.get('avatar_position_in_roster') == 'right':
+ add_avatar_renderer()
+
+ self.list_treeview.append_column(column)
+
+ # workaround to avoid gtk arrows to be shown
+ column = gtk.TreeViewColumn() # 2nd COLUMN
+ renderer = gtk.CellRendererPixbuf()
+ column.pack_start(renderer, expand=False)
+ self.list_treeview.append_column(column)
+ column.set_visible(False)
+ self.list_treeview.set_expander_column(column)
+
+ gajim.gc_connected[self.account][self.room_jid] = False
+ # disable win, we are not connected yet
+ ChatControlBase.got_disconnected(self)
+
+ self.update_ui()
+ self.conv_textview.tv.grab_focus()
+ self.widget.show_all()
+
+ def tree_compare_iters(self, model, iter1, iter2):
+ """
+ Compare two iters to sort them
+ """
+ type1 = model[iter1][C_TYPE]
+ type2 = model[iter2][C_TYPE]
+ if not type1 or not type2:
+ return 0
+ nick1 = model[iter1][C_NICK]
+ nick2 = model[iter2][C_NICK]
+ if not nick1 or not nick2:
+ return 0
+ nick1 = nick1.decode('utf-8')
+ nick2 = nick2.decode('utf-8')
+ if type1 == 'role':
+ return locale.strcoll(nick1, nick2)
+ if type1 == 'contact':
+ gc_contact1 = gajim.contacts.get_gc_contact(self.account,
+ self.room_jid, nick1)
+ if not gc_contact1:
+ return 0
+ if type2 == 'contact':
+ gc_contact2 = gajim.contacts.get_gc_contact(self.account,
+ self.room_jid, nick2)
+ if not gc_contact2:
+ return 0
+ if type1 == 'contact' and type2 == 'contact' and \
+ gajim.config.get('sort_by_show_in_muc'):
+ cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
+ 'invisible': 5, 'offline': 6, 'error': 7}
+ show1 = cshow[gc_contact1.show]
+ show2 = cshow[gc_contact2.show]
+ if show1 < show2:
+ return -1
+ elif show1 > show2:
+ return 1
+ # We compare names
+ name1 = gc_contact1.get_shown_name()
+ name2 = gc_contact2.get_shown_name()
+ return locale.strcoll(name1.lower(), name2.lower())
+
+ def on_msg_textview_populate_popup(self, textview, menu):
+ """
+ Override the default context menu and we prepend Clear
+ and the ability to insert a nick
+ """
+ ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
+ item = gtk.SeparatorMenuItem()
+ menu.prepend(item)
+
+ item = gtk.MenuItem(_('Insert Nickname'))
+ menu.prepend(item)
+ submenu = gtk.Menu()
+ item.set_submenu(submenu)
+
+ for nick in sorted(gajim.contacts.get_nick_list(self.account,
+ self.room_jid)):
+ item = gtk.MenuItem(nick, use_underline=False)
+ submenu.append(item)
+ id_ = item.connect('activate', self.append_nick_in_msg_textview, nick)
+ self.handlers[id_] = item
+
+ menu.show_all()
+
+ def resize_occupant_treeview(self, position):
+ self.resize_from_another_muc = True
+ self.hpaned.set_position(position)
+ def reset_flag():
+ self.resize_from_another_muc = False
+ # Reset the flag when everything will be redrawn, and in particular when
+ # on_treeview_size_allocate will have been called.
+ gobject.idle_add(reset_flag)
+
+ def on_treeview_size_allocate(self, widget, allocation):
+ """
+ The MUC treeview has resized. Move the hpaned in all tabs to match
+ """
+ if self.resize_from_another_muc:
+ # Don't send the event to other MUC
+ return
+ hpaned_position = self.hpaned.get_position()
+ for account in gajim.gc_connected:
+ for room_jid in [i for i in gajim.gc_connected[account] if \
+ gajim.gc_connected[account][i] and i != self.room_jid]:
+ ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid, account)
+ if not ctrl:
+ ctrl = gajim.interface.minimized_controls[account][room_jid]
+ if ctrl:
+ ctrl.resize_occupant_treeview(hpaned_position)
+
+ def iter_contact_rows(self):
+ """
+ Iterate over all contact rows in the tree model
+ """
+ model = self.list_treeview.get_model()
+ role_iter = model.get_iter_root()
+ while role_iter:
+ contact_iter = model.iter_children(role_iter)
+ while contact_iter:
+ yield model[contact_iter]
+ contact_iter = model.iter_next(contact_iter)
+ role_iter = model.iter_next(role_iter)
+
+ def on_list_treeview_style_set(self, treeview, style):
+ """
+ When style (theme) changes, redraw all contacts
+ """
+ # Get the room_jid from treeview
+ for contact in self.iter_contact_rows():
+ nick = contact[C_NICK].decode('utf-8')
+ self.draw_contact(nick)
+
+ def on_list_treeview_selection_changed(self, selection):
+ model, selected_iter = selection.get_selected()
+ self.draw_contact(self.nick)
+ if self._last_selected_contact is not None:
+ self.draw_contact(self._last_selected_contact)
+ if selected_iter is None:
+ self._last_selected_contact = None
+ return
+ contact = model[selected_iter]
+ nick = contact[C_NICK].decode('utf-8')
+ self._last_selected_contact = nick
+ if contact[C_TYPE] != 'contact':
+ return
+ self.draw_contact(nick, selected=True, focus=True)
+
+ def get_tab_label(self, chatstate):
+ """
+ Markup the label if necessary. Returns a tuple such as: (new_label_str,
+ color) either of which can be None if chatstate is given that means we
+ have HE SENT US a chatstate
+ """
+
+ has_focus = self.parent_win.window.get_property('has-toplevel-focus')
+ current_tab = self.parent_win.get_active_control() == self
+ color_name = None
+ color = None
+ theme = gajim.config.get('roster_theme')
+ if chatstate == 'attention' and (not has_focus or not current_tab):
+ self.attention_flag = True
+ color_name = gajim.config.get_per('themes', theme,
+ 'state_muc_directed_msg_color')
+ elif chatstate:
+ if chatstate == 'active' or (current_tab and has_focus):
+ self.attention_flag = False
+ # get active color from gtk
+ color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
+ elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\
+ not self.attention_flag:
+ color_name = gajim.config.get_per('themes', theme,
+ 'state_muc_msg_color')
+ if color_name:
+ color = gtk.gdk.colormap_get_system().alloc_color(color_name)
+
+ if self.is_continued:
+ # if this is a continued conversation
+ label_str = self.get_continued_conversation_name()
+ else:
+ label_str = self.name
+
+ # count waiting highlighted messages
+ unread = ''
+ num_unread = self.get_nb_unread()
+ if num_unread == 1:
+ unread = '*'
+ elif num_unread > 1:
+ unread = '[' + unicode(num_unread) + ']'
+ label_str = unread + label_str
+ return (label_str, color)
+
+ def get_tab_image(self, count_unread=True):
+ # Set tab image (always 16x16)
+ tab_image = None
+ if gajim.gc_connected[self.account][self.room_jid]:
+ tab_image = gtkgui_helpers.load_icon('muc_active')
+ else:
+ tab_image = gtkgui_helpers.load_icon('muc_inactive')
+ return tab_image
+
+ def update_ui(self):
+ ChatControlBase.update_ui(self)
+ for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
+ self.draw_contact(nick)
+
+ def _change_style(self, model, path, iter_):
+ model[iter_][C_NICK] = model[iter_][C_NICK]
+
+ def change_roster_style(self):
+ model = self.list_treeview.get_model()
+ model.foreach(self._change_style)
+
+ def repaint_themed_widgets(self):
+ ChatControlBase.repaint_themed_widgets(self)
+ self.change_roster_style()
+
+ def _update_banner_state_image(self):
+ banner_status_img = self.xml.get_object('gc_banner_status_image')
+ images = gajim.interface.jabber_state_images
+ if self.room_jid in gajim.gc_connected[self.account] and \
+ gajim.gc_connected[self.account][self.room_jid]:
+ image = 'muc_active'
+ else:
+ image = 'muc_inactive'
+ if '32' in images and image in images['32']:
+ muc_icon = images['32'][image]
+ if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY:
+ pix = muc_icon.get_pixbuf()
+ banner_status_img.set_from_pixbuf(pix)
+ return
+ # we need to scale 16x16 to 32x32
+ muc_icon = images['16'][image]
+ pix = muc_icon.get_pixbuf()
+ scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
+ banner_status_img.set_from_pixbuf(scaled_pix)
+
+ def get_continued_conversation_name(self):
+ """
+ Get the name of a continued conversation. Will return Continued
+ Conversation if there isn't any other contact in the room
+ """
+ nicks = []
+ for nick in gajim.contacts.get_nick_list(self.account,
+ self.room_jid):
+ if nick != self.nick:
+ nicks.append(nick)
+ if nicks != []:
+ title = ', '
+ title = _('Conversation with ') + title.join(nicks)
+ else:
+ title = _('Continued conversation')
+ return title
+
+ def draw_banner_text(self):
+ """
+ Draw the text in the fat line at the top of the window that houses the
+ room jid, subject
+ """
+ self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
+ self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
+ font_attrs, font_attrs_small = self.get_font_attrs()
+ if self.is_continued:
+ name = self.get_continued_conversation_name()
+ else:
+ name = self.room_jid
+ text = '<span %s>%s</span>' % (font_attrs, name)
+ self.name_label.set_markup(text)
+
+ if self.subject:
+ subject = helpers.reduce_chars_newlines(self.subject, max_lines=2)
+ subject = gobject.markup_escape_text(subject)
+ subject_text = self.urlfinder.sub(self.make_href, subject)
+ subject_text = '<span %s>%s</span>' % (font_attrs_small, subject_text)
+
+ # tooltip must always hold ALL the subject
+ self.event_box.set_tooltip_text(self.subject)
+ self.banner_status_label.set_no_show_all(False)
+ self.banner_status_label.show()
+ else:
+ subject_text = ''
+ self.event_box.set_has_tooltip(False)
+ self.banner_status_label.hide()
+ self.banner_status_label.set_no_show_all(True)
+
+ self.banner_status_label.set_markup(subject_text)
+
+ def prepare_context_menu(self, hide_buttonbar_items=False):
+ """
+ Set sensitivity state for configure_room
+ """
+ xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui')
+ menu = xml.get_object('gc_control_popup_menu')
+
+ bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem')
+ change_nick_menuitem = xml.get_object('change_nick_menuitem')
+ configure_room_menuitem = xml.get_object('configure_room_menuitem')
+ destroy_room_menuitem = xml.get_object('destroy_room_menuitem')
+ change_subject_menuitem = xml.get_object('change_subject_menuitem')
+ history_menuitem = xml.get_object('history_menuitem')
+ minimize_menuitem = xml.get_object('minimize_menuitem')
+ bookmark_separator = xml.get_object('bookmark_separator')
+ separatormenuitem2 = xml.get_object('separatormenuitem2')
+
+ if hide_buttonbar_items:
+ change_nick_menuitem.hide()
+ change_subject_menuitem.hide()
+ bookmark_room_menuitem.hide()
+ history_menuitem.hide()
+ bookmark_separator.hide()
+ separatormenuitem2.hide()
+ else:
+ change_nick_menuitem.show()
+ change_subject_menuitem.show()
+ bookmark_room_menuitem.show()
+ history_menuitem.show()
+ bookmark_separator.show()
+ separatormenuitem2.show()
+ for bm in gajim.connections[self.account].bookmarks:
+ if bm['jid'] == self.room_jid:
+ bookmark_room_menuitem.hide()
+ bookmark_separator.hide()
+ break
+
+ ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
+ change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n,
+ gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE)
+ change_subject_menuitem.add_accelerator('activate', ag,
+ gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE)
+ bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b,
+ gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
+ history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h,
+ gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
+
+ if self.contact.jid in gajim.config.get_per('accounts', self.account,
+ 'minimized_gc').split(' '):
+ minimize_menuitem.set_active(True)
+ if not gajim.connections[self.account].private_storage_supported:
+ bookmark_room_menuitem.set_sensitive(False)
+ if gajim.gc_connected[self.account][self.room_jid]:
+ c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ self.nick)
+ if c.affiliation not in ('owner', 'admin'):
+ configure_room_menuitem.set_sensitive(False)
+ else:
+ configure_room_menuitem.set_sensitive(True)
+ if c.affiliation != 'owner':
+ destroy_room_menuitem.set_sensitive(False)
+ else:
+ destroy_room_menuitem.set_sensitive(True)
+ change_subject_menuitem.set_sensitive(True)
+ change_nick_menuitem.set_sensitive(True)
+ else:
+ # We are not connected to this groupchat, disable unusable menuitems
+ configure_room_menuitem.set_sensitive(False)
+ destroy_room_menuitem.set_sensitive(False)
+ change_subject_menuitem.set_sensitive(False)
+ change_nick_menuitem.set_sensitive(False)
+
+ # connect the menuitems to their respective functions
+ id_ = bookmark_room_menuitem.connect('activate',
+ self._on_bookmark_room_menuitem_activate)
+ self.handlers[id_] = bookmark_room_menuitem
+
+ id_ = change_nick_menuitem.connect('activate',
+ self._on_change_nick_menuitem_activate)
+ self.handlers[id_] = change_nick_menuitem
+
+ id_ = configure_room_menuitem.connect('activate',
+ self._on_configure_room_menuitem_activate)
+ self.handlers[id_] = configure_room_menuitem
+
+ id_ = destroy_room_menuitem.connect('activate',
+ self._on_destroy_room_menuitem_activate)
+ self.handlers[id_] = destroy_room_menuitem
+
+ id_ = change_subject_menuitem.connect('activate',
+ self._on_change_subject_menuitem_activate)
+ self.handlers[id_] = change_subject_menuitem
+
+ id_ = history_menuitem.connect('activate',
+ self._on_history_menuitem_activate)
+ self.handlers[id_] = history_menuitem
+
+ id_ = minimize_menuitem.connect('toggled',
+ self.on_minimize_menuitem_toggled)
+ self.handlers[id_] = minimize_menuitem
+
+ menu.connect('selection-done', self.destroy_menu,
+ change_nick_menuitem, change_subject_menuitem,
+ bookmark_room_menuitem, history_menuitem)
+ return menu
+
+ def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
+ bookmark_room_menuitem, history_menuitem):
+ # destroy accelerators
+ ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
+ change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n,
+ gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK)
+ change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t,
+ gtk.gdk.MOD1_MASK)
+ bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b,
+ gtk.gdk.CONTROL_MASK)
+ history_menuitem.remove_accelerator(ag, gtk.keysyms.h,
+ gtk.gdk.CONTROL_MASK)
+ # destroy menu
+ menu.destroy()
+
+ def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None,
+ status_code=[]):
+ if '100' in status_code:
+ # Room is not anonymous
+ self.is_anonymous = False
+ if not nick:
+ # message from server
+ self.print_conversation(msg, tim=tim, xhtml=xhtml)
+ else:
+ # message from someone
+ if has_timestamp:
+ # don't print xhtml if it's an old message.
+ # Like that xhtml messages are grayed too.
+ self.print_old_conversation(msg, nick, tim, None)
+ else:
+ self.print_conversation(msg, nick, tim, xhtml)
+
+ def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None,
+ encrypted=False):
+ # Do we have a queue?
+ fjid = self.room_jid + '/' + nick
+ no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
+
+ event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
+ encrypted, '', msg_id, xhtml, session))
+ gajim.events.add_event(self.account, fjid, event)
+
+ autopopup = gajim.config.get('autopopup')
+ autopopupaway = gajim.config.get('autopopupaway')
+ iter_ = self.get_contact_iter(nick)
+ path = self.list_treeview.get_model().get_path(iter_)
+ if not autopopup or (not autopopupaway and \
+ gajim.connections[self.account].connected > 2):
+ if no_queue: # We didn't have a queue: we change icons
+ model = self.list_treeview.get_model()
+ state_images =\
+ gajim.interface.roster.get_appropriate_state_images(
+ self.room_jid, icon_name='event')
+ image = state_images['event']
+ model[iter_][C_IMG] = image
+ if self.parent_win:
+ self.parent_win.show_title()
+ self.parent_win.redraw_tab(self)
+ else:
+ self._start_private_message(nick)
+ # Scroll to line
+ self.list_treeview.expand_row(path[0:1], False)
+ self.list_treeview.scroll_to_cell(path)
+ self.list_treeview.set_cursor(path)
+ contact = gajim.contacts.get_contact_with_highest_priority(self.account, \
+ self.room_jid)
+ if contact:
+ gajim.interface.roster.draw_contact(self.room_jid, self.account)
+
+ def get_contact_iter(self, nick):
+ model = self.list_treeview.get_model()
+ role_iter = model.get_iter_root()
+ while role_iter:
+ user_iter = model.iter_children(role_iter)
+ while user_iter:
+ if nick == model[user_iter][C_NICK].decode('utf-8'):
+ return user_iter
+ else:
+ user_iter = model.iter_next(user_iter)
+ role_iter = model.iter_next(role_iter)
+ return None
+
+ def print_old_conversation(self, text, contact='', tim=None, xhtml = None):
+ if isinstance(text, str):
+ text = unicode(text, 'utf-8')
+ if contact:
+ if contact == self.nick: # it's us
+ kind = 'outgoing'
+ else:
+ kind = 'incoming'
+ else:
+ kind = 'status'
+ if gajim.config.get('restored_messages_small'):
+ small_attr = ['small']
+ else:
+ small_attr = []
+ ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
+ small_attr, small_attr + ['restored_message'],
+ small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml)
+
+ def print_conversation(self, text, contact='', tim=None, xhtml=None,
+ graphics=True):
+ """
+ Print a line in the conversation
+
+ If contact is set: it's a message from someone or an info message
+ (contact = 'info' in such a case).
+ If contact is not set: it's a message from the server or help.
+ """
+ if isinstance(text, str):
+ text = unicode(text, 'utf-8')
+ other_tags_for_name = []
+ other_tags_for_text = []
+ if contact:
+ if contact == self.nick: # it's us
+ kind = 'outgoing'
+ elif contact == 'info':
+ kind = 'info'
+ contact = None
+ else:
+ kind = 'incoming'
+ # muc-specific chatstate
+ if self.parent_win:
+ self.parent_win.redraw_tab(self, 'newmsg')
+ else:
+ kind = 'status'
+
+ if kind == 'incoming': # it's a message NOT from us
+ # highlighting and sounds
+ (highlight, sound) = self.highlighting_for_message(text, tim)
+ if contact in self.gc_custom_colors:
+ other_tags_for_name.append('gc_nickname_color_' + \
+ str(self.gc_custom_colors[contact]))
+ else:
+ self.gc_count_nicknames_colors += 1
+ if self.gc_count_nicknames_colors == self.number_of_colors:
+ self.gc_count_nicknames_colors = 0
+ self.gc_custom_colors[contact] = \
+ self.gc_count_nicknames_colors
+ other_tags_for_name.append('gc_nickname_color_' + \
+ str(self.gc_count_nicknames_colors))
+ if highlight:
+ # muc-specific chatstate
+ if self.parent_win:
+ self.parent_win.redraw_tab(self, 'attention')
+ else:
+ self.attention_flag = True
+ other_tags_for_name.append('bold')
+ other_tags_for_text.append('marked')
+
+ if contact in self.attention_list:
+ self.attention_list.remove(contact)
+ elif len(self.attention_list) > 6:
+ self.attention_list.pop(0) # remove older
+ self.attention_list.append(contact)
+
+ if sound == 'received':
+ helpers.play_sound('muc_message_received')
+ elif sound == 'highlight':
+ helpers.play_sound('muc_message_highlight')
+ if text.startswith('/me ') or text.startswith('/me\n'):
+ other_tags_for_text.append('gc_nickname_color_' + \
+ str(self.gc_custom_colors[contact]))
+
+ self.check_and_possibly_add_focus_out_line()
+
+ ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
+ other_tags_for_name, [], other_tags_for_text, xhtml=xhtml,
+ graphics=graphics)
+
+ def get_nb_unread(self):
+ type_events = ['printed_marked_gc_msg']
+ if gajim.config.get('notify_on_all_muc_messages'):
+ type_events.append('printed_gc_msg')
+ nb = len(gajim.events.get_events(self.account, self.room_jid,
+ type_events))
+ nb += self.get_nb_unread_pm()
+ return nb
+
+ def get_nb_unread_pm(self):
+ nb = 0
+ for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
+ nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \
+ nick, ['pm']))
+ return nb
+
+ def highlighting_for_message(self, text, tim):
+ """
+ Returns a 2-Tuple. The first says whether or not to highlight the text,
+ the second, what sound to play
+ """
+ highlight, sound = (None, None)
+
+ # Are any of the defined highlighting words in the text?
+ if self.needs_visual_notification(text):
+ highlight = True
+ if gajim.config.get_per('soundevents', 'muc_message_highlight',
+ 'enabled'):
+ sound = 'highlight'
+
+ # Do we play a sound on every muc message?
+ elif gajim.config.get_per('soundevents', 'muc_message_received', \
+ 'enabled'):
+ sound = 'received'
+
+ # Is it a history message? Don't want sound-floods when we join.
+ if tim != time.localtime():
+ sound = None
+
+ return (highlight, sound)
+
+ def check_and_possibly_add_focus_out_line(self):
+ """
+ Check and possibly add focus out line for room_jid if it needs it and
+ does not already have it as last event. If it goes to add this line
+ - remove previous line first
+ """
+ win = gajim.interface.msg_win_mgr.get_window(self.room_jid, self.account)
+ if win and self.room_jid == win.get_active_jid() and\
+ win.window.get_property('has-toplevel-focus') and\
+ self.parent_win.get_active_control() == self:
+ # it's the current room and it's the focused window.
+ # we have full focus (we are reading it!)
+ return
+
+ self.conv_textview.show_focus_out_line()
+
+ def needs_visual_notification(self, text):
+ """
+ Check text to see whether any of the words in (muc_highlight_words and
+ nick) appear
+ """
+ special_words = gajim.config.get('muc_highlight_words').split(';')
+ special_words.append(self.nick)
+ # Strip empties: ''.split(';') == [''] and would highlight everything.
+ # Also lowercase everything for case insensitive compare.
+ special_words = [word.lower() for word in special_words if word]
+ text = text.lower()
+
+ for special_word in special_words:
+ found_here = text.find(special_word)
+ while(found_here > -1):
+ end_here = found_here + len(special_word)
+ if (found_here == 0 or not text[found_here - 1].isalpha()) and \
+ (end_here == len(text) or not text[end_here].isalpha()):
+ # It is beginning of text or char before is not alpha AND
+ # it is end of text or char after is not alpha
+ return True
+ # continue searching
+ start = found_here + 1
+ found_here = text.find(special_word, start)
+ return False
+
+ def set_subject(self, subject):
+ self.subject = subject
+ self.draw_banner_text()
+
+ def got_connected(self):
+ # Make autorejoin stop.
+ if self.autorejoin:
+ gobject.source_remove(self.autorejoin)
+ self.autorejoin = None
+
+ gajim.gc_connected[self.account][self.room_jid] = True
+ ChatControlBase.got_connected(self)
+ # We don't redraw the whole banner here, because only icon change
+ self._update_banner_state_image()
+ if self.parent_win:
+ self.parent_win.redraw_tab(self)
+
+ def got_disconnected(self):
+ self.list_treeview.get_model().clear()
+ nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
+ for nick in nick_list:
+ # Update pm chat window
+ fjid = self.room_jid + '/' + nick
+ gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ nick)
+
+ ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
+ if ctrl:
+ gc_contact.show = 'offline'
+ gc_contact.status = ''
+ ctrl.update_ui()
+ if ctrl.parent_win:
+ ctrl.parent_win.redraw_tab(ctrl)
+
+ gajim.contacts.remove_gc_contact(self.account, gc_contact)
+ gajim.gc_connected[self.account][self.room_jid] = False
+ ChatControlBase.got_disconnected(self)
+ # Tell connection to note the date we disconnect to avoid duplicate logs
+ gajim.connections[self.account].gc_got_disconnected(self.room_jid)
+ # We don't redraw the whole banner here, because only icon change
+ self._update_banner_state_image()
+ if self.parent_win:
+ self.parent_win.redraw_tab(self)
+
+ # Autorejoin stuff goes here.
+ # Notice that we don't need to activate autorejoin if connection is lost
+ # or in progress.
+ if self.autorejoin is None and gajim.account_is_connected(self.account):
+ ar_to = gajim.config.get('muc_autorejoin_timeout')
+ if ar_to:
+ self.autorejoin = gobject.timeout_add_seconds(ar_to, self.rejoin)
+
+ def rejoin(self):
+ if not self.autorejoin:
+ return False
+ password = gajim.gc_passwords.get(self.room_jid, '')
+ gajim.connections[self.account].join_gc(self.nick, self.room_jid,
+ password)
+ return True
+
+ def draw_roster(self):
+ self.list_treeview.get_model().clear()
+ for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
+ gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ nick)
+ self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role,
+ gc_contact.affiliation, gc_contact.status, gc_contact.jid)
+ self.draw_all_roles()
+ # Recalculate column width for ellipsizin
+ self.list_treeview.columns_autosize()
+
+ def on_send_pm(self, widget=None, model=None, iter_=None, nick=None,
+ msg=None):
+ """
+ Open a chat window and if msg is not None - send private message to a
+ contact in a room
+ """
+ if nick is None:
+ nick = model[iter_][C_NICK].decode('utf-8')
+
+ ctrl = self._start_private_message(nick)
+ if ctrl and msg:
+ ctrl.send_message(msg)
+
+ def on_send_file(self, widget, gc_contact):
+ """
+ Send a file to a contact in the room
+ """
+ self._on_send_file(gc_contact)
+
+ def draw_contact(self, nick, selected=False, focus=False):
+ iter_ = self.get_contact_iter(nick)
+ if not iter_:
+ return
+ model = self.list_treeview.get_model()
+ gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ nick)
+ state_images = gajim.interface.jabber_state_images['16']
+ if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
+ image = state_images['event']
+ else:
+ image = state_images[gc_contact.show]
+
+ name = gobject.markup_escape_text(gc_contact.name)
+
+ # Strike name if blocked
+ fjid = self.room_jid + '/' + nick
+ if helpers.jid_is_blocked(self.account, fjid):
+ name = '<span strikethrough="true">%s</span>' % name
+
+ status = gc_contact.status
+ # add status msg, if not empty, under contact name in the treeview
+ if status and gajim.config.get('show_status_msgs_in_roster'):
+ status = status.strip()
+ if status != '':
+ status = helpers.reduce_chars_newlines(status, max_lines=1)
+ # escape markup entities and make them small italic and fg color
+ color = gtkgui_helpers._get_fade_color(self.list_treeview,
+ selected, focus)
+ colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue)
+ name += ('\n<span size="small" style="italic" foreground="%s">'
+ '%s</span>') % (colorstring, gobject.markup_escape_text(status))
+
+ if image.get_storage_type() == gtk.IMAGE_PIXBUF and \
+ gc_contact.affiliation != 'none' and gajim.config.get(
+ 'show_affiliation_in_groupchat'):
+ pixbuf1 = image.get_pixbuf().copy()
+ pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 4, 4)
+ if gc_contact.affiliation == 'owner':
+ pixbuf2.fill(0xff0000ff) # Red
+ elif gc_contact.affiliation == 'admin':
+ pixbuf2.fill(0xffb200ff) # Oragne
+ elif gc_contact.affiliation == 'member':
+ pixbuf2.fill(0x00ff00ff) # Green
+ pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'),
+ pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
+ gtk.gdk.INTERP_HYPER, 127)
+ image = gtk.image_new_from_pixbuf(pixbuf1)
+ model[iter_][C_IMG] = image
+ model[iter_][C_TEXT] = name
+
+ def draw_avatar(self, nick):
+ if not gajim.config.get('show_avatars_in_roster'):
+ return
+ model = self.list_treeview.get_model()
+ iter_ = self.get_contact_iter(nick)
+ if not iter_:
+ return
+ fake_jid = self.room_jid + '/' + nick
+ pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
+ if pixbuf in ('ask', None):
+ scaled_pixbuf = None
+ else:
+ scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
+ model[iter_][C_AVATAR] = scaled_pixbuf
+
+ def draw_role(self, role):
+ role_iter = self.get_role_iter(role)
+ if not role_iter:
+ return
+ model = self.list_treeview.get_model()
+ role_name = helpers.get_uf_role(role, plural=True)
+ if gajim.config.get('show_contacts_number'):
+ nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts(
+ self.account, self.room_jid, role)
+ role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total))
+ model[role_iter][C_TEXT] = role_name
+
+ def draw_all_roles(self):
+ for role in ('visitor', 'participant', 'moderator'):
+ self.draw_role(role)
+
+ def chg_contact_status(self, nick, show, status, role, affiliation, jid,
+ reason, actor, statusCode, new_nick, avatar_sha, tim=None):
+ """
+ When an occupant changes his or her status
+ """
+ if show == 'invisible':
+ return
+
+ if not role:
+ role = 'visitor'
+ if not affiliation:
+ affiliation = 'none'
+ fake_jid = self.room_jid + '/' + nick
+ newly_created = False
+ nick_jid = nick
+
+ # Set to true if role or affiliation have changed
+ right_changed = False
+
+ if jid:
+ # delete ressource
+ simple_jid = gajim.get_jid_without_resource(jid)
+ nick_jid += ' (%s)' % simple_jid
+
+ # statusCode
+ # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init
+ if statusCode:
+ if '100' in statusCode:
+ # Can be a message (see handle_event_gc_config_change in gajim.py)
+ self.print_conversation(\
+ _('Any occupant is allowed to see your full JID'))
+ if '170' in statusCode:
+ # Can be a message (see handle_event_gc_config_change in gajim.py)
+ self.print_conversation(_('Room logging is enabled'))
+ if '201' in statusCode:
+ self.print_conversation(_('A new room has been created'))
+ if '210' in statusCode:
+ self.print_conversation(\
+ _('The server has assigned or modified your roomnick'))
+
+ if show in ('offline', 'error'):
+ if statusCode:
+ if '307' in statusCode:
+ if actor is None: # do not print 'kicked by None'
+ s = _('%(nick)s has been kicked: %(reason)s') % {
+ 'nick': nick,
+ 'reason': reason }
+ else:
+ s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % {
+ 'nick': nick,
+ 'who': actor,
+ 'reason': reason }
+ self.print_conversation(s, 'info', tim=tim, graphics=False)
+ if nick == self.nick and not gajim.config.get(
+ 'muc_autorejoin_on_kick'):
+ self.autorejoin = False
+ elif '301' in statusCode:
+ if actor is None: # do not print 'banned by None'
+ s = _('%(nick)s has been banned: %(reason)s') % {
+ 'nick': nick,
+ 'reason': reason }
+ else:
+ s = _('%(nick)s has been banned by %(who)s: %(reason)s') % {
+ 'nick': nick,
+ 'who': actor,
+ 'reason': reason }
+ self.print_conversation(s, 'info', tim=tim, graphics=False)
+ if nick == self.nick:
+ self.autorejoin = False
+ elif '303' in statusCode: # Someone changed his or her nick
+ if new_nick == self.new_nick or nick == self.nick:
+ # We changed our nick
+ self.nick = new_nick
+ self.new_nick = ''
+ s = _('You are now known as %s') % new_nick
+ # Stop all E2E sessions
+ nick_list = gajim.contacts.get_nick_list(self.account,
+ self.room_jid)
+ for nick_ in nick_list:
+ fjid_ = self.room_jid + '/' + nick_
+ ctrl = gajim.interface.msg_win_mgr.get_control(fjid_,
+ self.account)
+ if ctrl and ctrl.session and \
+ ctrl.session.enable_encryption:
+ thread_id = ctrl.session.thread_id
+ ctrl.session.terminate_e2e()
+ gajim.connections[self.account].delete_session(fjid_,
+ thread_id)
+ ctrl.no_autonegotiation = False
+ else:
+ s = _('%(nick)s is now known as %(new_nick)s') % {
+ 'nick': nick, 'new_nick': new_nick}
+ # We add new nick to muc roster here, so we don't see
+ # that "new_nick has joined the room" when he just changed nick.
+ # add_contact_to_roster will be called a second time
+ # after that, but that doesn't hurt
+ self.add_contact_to_roster(new_nick, show, role, affiliation,
+ status, jid)
+ if nick in self.attention_list:
+ self.attention_list.remove(nick)
+ # keep nickname color
+ if nick in self.gc_custom_colors:
+ self.gc_custom_colors[new_nick] = \
+ self.gc_custom_colors[nick]
+ # rename vcard / avatar
+ puny_jid = helpers.sanitize_filename(self.room_jid)
+ puny_nick = helpers.sanitize_filename(nick)
+ puny_new_nick = helpers.sanitize_filename(new_nick)
+ old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+ new_path = os.path.join(gajim.VCARD_PATH, puny_jid,
+ puny_new_nick)
+ files = {old_path: new_path}
+ path = os.path.join(gajim.AVATAR_PATH, puny_jid)
+ # possible extensions
+ for ext in ('.png', '.jpeg', '_notif_size_bw.png',
+ '_notif_size_colored.png'):
+ files[os.path.join(path, puny_nick + ext)] = \
+ os.path.join(path, puny_new_nick + ext)
+ for old_file in files:
+ if os.path.exists(old_file) and old_file != files[old_file]:
+ if os.path.exists(files[old_file]) and helpers.windowsify(
+ old_file) != helpers.windowsify(files[old_file]):
+ # Windows require this, but os.remove('test') will also
+ # remove 'TEST'
+ os.remove(files[old_file])
+ os.rename(old_file, files[old_file])
+ self.print_conversation(s, 'info', tim=tim, graphics=False)
+ elif '321' in statusCode:
+ s = _('%(nick)s has been removed from the room (%(reason)s)') % {
+ 'nick': nick, 'reason': _('affiliation changed') }
+ self.print_conversation(s, 'info', tim=tim, graphics=False)
+ elif '322' in statusCode:
+ s = _('%(nick)s has been removed from the room (%(reason)s)') % {
+ 'nick': nick,
+ 'reason': _('room configuration changed to members-only') }
+ self.print_conversation(s, 'info', tim=tim, graphics=False)
+ elif '332' in statusCode:
+ s = _('%(nick)s has been removed from the room (%(reason)s)') % {
+ 'nick': nick,
+ 'reason': _('system shutdown') }
+ self.print_conversation(s, 'info', tim=tim, graphics=False)
+ # Room has been destroyed.
+ elif 'destroyed' in statusCode:
+ self.autorejoin = False
+ self.print_conversation(reason, 'info', tim, graphics=False)
+
+ if len(gajim.events.get_events(self.account, jid=fake_jid,
+ types=['pm'])) == 0:
+ self.remove_contact(nick)
+ self.draw_all_roles()
+ else:
+ c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+ c.show = show
+ c.status = status
+ if nick == self.nick and (not statusCode or \
+ '303' not in statusCode): # We became offline
+ self.got_disconnected()
+ contact = gajim.contacts.\
+ get_contact_with_highest_priority(self.account, self.room_jid)
+ if contact:
+ gajim.interface.roster.draw_contact(self.room_jid, self.account)
+ if self.parent_win:
+ self.parent_win.redraw_tab(self)
+ else:
+ iter_ = self.get_contact_iter(nick)
+ if not iter_:
+ if '210' in statusCode:
+ # Server changed our nick
+ self.nick = nick
+ s = _('You are now known as %s') % nick
+ self.print_conversation(s, 'info', tim=tim, graphics=False)
+ iter_ = self.add_contact_to_roster(nick, show, role, affiliation,
+ status, jid)
+ newly_created = True
+ self.draw_all_roles()
+ if statusCode and '201' in statusCode: # We just created the room
+ gajim.connections[self.account].request_gc_config(self.room_jid)
+ else:
+ gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ nick)
+ if not gc_c:
+ log.error('%s has an iter, but no gc_contact instance')
+ return
+ # Re-get vcard if avatar has changed
+ # We do that here because we may request it to the real JID if we
+ # knows it. connections.py doesn't know it.
+ con = gajim.connections[self.account]
+ if gc_c and gc_c.jid:
+ real_jid = gc_c.jid
+ if gc_c.resource:
+ real_jid += '/' + gc_c.resource
+ else:
+ real_jid = fake_jid
+ if fake_jid in con.vcard_shas:
+ if avatar_sha != con.vcard_shas[fake_jid]:
+ server = gajim.get_server_from_jid(self.room_jid)
+ if not server.startswith('irc'):
+ con.request_vcard(real_jid, fake_jid)
+ else:
+ cached_vcard = con.get_cached_vcard(fake_jid, True)
+ if cached_vcard and 'PHOTO' in cached_vcard and \
+ 'SHA' in cached_vcard['PHOTO']:
+ cached_sha = cached_vcard['PHOTO']['SHA']
+ else:
+ cached_sha = ''
+ if cached_sha != avatar_sha:
+ # avatar has been updated
+ # sha in mem will be updated later
+ server = gajim.get_server_from_jid(self.room_jid)
+ if not server.startswith('irc'):
+ con.request_vcard(real_jid, fake_jid)
+ else:
+ # save sha in mem NOW
+ con.vcard_shas[fake_jid] = avatar_sha
+
+ actual_affiliation = gc_c.affiliation
+ if affiliation != actual_affiliation:
+ if actor:
+ st = _('** Affiliation of %(nick)s has been set to '
+ '%(affiliation)s by %(actor)s') % {'nick': nick_jid,
+ 'affiliation': affiliation, 'actor': actor}
+ else:
+ st = _('** Affiliation of %(nick)s has been set to '
+ '%(affiliation)s') % {'nick': nick_jid,
+ 'affiliation': affiliation}
+ if reason:
+ st += ' (%s)' % reason
+ self.print_conversation(st, tim=tim, graphics=False)
+ right_changed = True
+ actual_role = self.get_role(nick)
+ if role != actual_role:
+ self.remove_contact(nick)
+ self.add_contact_to_roster(nick, show, role,
+ affiliation, status, jid)
+ self.draw_role(actual_role)
+ self.draw_role(role)
+ if actor:
+ st = _('** Role of %(nick)s has been set to %(role)s by '
+ '%(actor)s') % {'nick': nick_jid, 'role': role,
+ 'actor': actor}
+ else:
+ st = _('** Role of %(nick)s has been set to %(role)s') % {
+ 'nick': nick_jid, 'role': role}
+ if reason:
+ st += ' (%s)' % reason
+ self.print_conversation(st, tim=tim, graphics=False)
+ right_changed = True
+ else:
+ if gc_c.show == show and gc_c.status == status and \
+ gc_c.affiliation == affiliation: # no change
+ return
+ gc_c.show = show
+ gc_c.affiliation = affiliation
+ gc_c.status = status
+ self.draw_contact(nick)
+ if (time.time() - self.room_creation) > 30 and nick != self.nick and \
+ (not statusCode or '303' not in statusCode) and not right_changed:
+ st = ''
+ print_status = None
+ for bookmark in gajim.connections[self.account].bookmarks:
+ if bookmark['jid'] == self.room_jid:
+ print_status = bookmark.get('print_status', None)
+ break
+ if not print_status:
+ print_status = gajim.config.get('print_status_in_muc')
+ if show == 'offline':
+ if nick in self.attention_list:
+ self.attention_list.remove(nick)
+ if show == 'offline' and print_status in ('all', 'in_and_out') and \
+ (not statusCode or '307' not in statusCode):
+ st = _('%s has left') % nick_jid
+ if reason:
+ st += ' [%s]' % reason
+ else:
+ if newly_created and print_status in ('all', 'in_and_out'):
+ st = _('%s has joined the group chat') % nick_jid
+ elif print_status == 'all':
+ st = _('%(nick)s is now %(status)s') % {'nick': nick_jid,
+ 'status': helpers.get_uf_show(show)}
+ if st:
+ if status:
+ st += ' (' + status + ')'
+ self.print_conversation(st, tim=tim, graphics=False)
+
+ def add_contact_to_roster(self, nick, show, role, affiliation, status,
+ jid=''):
+ model = self.list_treeview.get_model()
+ role_name = helpers.get_uf_role(role, plural=True)
+
+ resource = ''
+ if jid:
+ jids = jid.split('/', 1)
+ j = jids[0]
+ if len(jids) > 1:
+ resource = jids[1]
+ else:
+ j = ''
+
+ name = nick
+
+ role_iter = self.get_role_iter(role)
+ if not role_iter:
+ role_iter = model.append(None,
+ (gajim.interface.jabber_state_images['16']['closed'], role,
+ 'role', role_name, None))
+ self.draw_all_roles()
+ iter_ = model.append(role_iter, (None, nick, 'contact', name, None))
+ if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
+ gc_contact = gajim.contacts.create_gc_contact(room_jid=self.room_jid, account=self.account,
+ name=nick, show=show, status=status, role=role,
+ affiliation=affiliation, jid=j, resource=resource)
+ gajim.contacts.add_gc_contact(self.account, gc_contact)
+ self.draw_contact(nick)
+ self.draw_avatar(nick)
+ # Do not ask avatar to irc rooms as irc transports reply with messages
+ server = gajim.get_server_from_jid(self.room_jid)
+ if gajim.config.get('ask_avatars_on_startup') and \
+ not server.startswith('irc'):
+ fake_jid = self.room_jid + '/' + nick
+ pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
+ if pixbuf == 'ask':
+ if j:
+ fjid = j
+ if resource:
+ fjid += '/' + resource
+ gajim.connections[self.account].request_vcard(fjid, fake_jid)
+ else:
+ gajim.connections[self.account].request_vcard(fake_jid, fake_jid)
+ if nick == self.nick: # we became online
+ self.got_connected()
+ self.list_treeview.expand_row((model.get_path(role_iter)), False)
+ if self.is_continued:
+ self.draw_banner_text()
+ return iter_
+
+ def get_role_iter(self, role):
+ model = self.list_treeview.get_model()
+ role_iter = model.get_iter_root()
+ while role_iter:
+ role_name = model[role_iter][C_NICK].decode('utf-8')
+ if role == role_name:
+ return role_iter
+ role_iter = model.iter_next(role_iter)
+ return None
+
+ def remove_contact(self, nick):
+ """
+ Remove a user from the contacts_list
+ """
+ model = self.list_treeview.get_model()
+ iter_ = self.get_contact_iter(nick)
+ if not iter_:
+ return
+ gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ nick)
+ if gc_contact:
+ gajim.contacts.remove_gc_contact(self.account, gc_contact)
+ parent_iter = model.iter_parent(iter_)
+ model.remove(iter_)
+ if model.iter_n_children(parent_iter) == 0:
+ model.remove(parent_iter)
+
+ def send_message(self, message, xhtml=None, process_commands=True):
+ """
+ Call this function to send our message
+ """
+ if not message:
+ return
+
+ if process_commands and self.process_as_command(message):
+ return
+
+ message = helpers.remove_invalid_xml_chars(message)
+
+ if not message:
+ return
+
+ if message != '' or message != '\n':
+ self.save_sent_message(message)
+
+ # Send the message
+ gajim.connections[self.account].send_gc_message(self.room_jid,
+ message, xhtml=xhtml)
+ self.msg_textview.get_buffer().set_text('')
+ self.msg_textview.grab_focus()
+
+ def get_role(self, nick):
+ gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ nick)
+ if gc_contact:
+ return gc_contact.role
+ else:
+ return 'visitor'
+
+ def minimizable(self):
+ if self.contact.jid in gajim.config.get_per('accounts', self.account,
+ 'minimized_gc').split(' '):
+ return True
+ return False
+
+ def minimize(self, status='offline'):
+ # Minimize it
+ win = gajim.interface.msg_win_mgr.get_window(self.contact.jid,
+ self.account)
+ ctrl = win.get_control(self.contact.jid, self.account)
+
+ ctrl_page = win.notebook.page_num(ctrl.widget)
+ control = win.notebook.get_nth_page(ctrl_page)
+
+ win.notebook.remove_page(ctrl_page)
+ control.unparent()
+ ctrl.parent_win = None
+
+ gajim.interface.roster.add_groupchat(self.contact.jid, self.account,
+ status = self.subject)
+
+ del win._controls[self.account][self.contact.jid]
+
+ def shutdown(self, status='offline'):
+ # Preventing autorejoin from being activated
+ self.autorejoin = False
+
+ if self.room_jid in gajim.gc_connected[self.account] and \
+ gajim.gc_connected[self.account][self.room_jid]:
+ # Tell connection to note the date we disconnect to avoid duplicate
+ # logs. We do it only when connected because if connection was lost
+ # there may be new messages since disconnection.
+ gajim.connections[self.account].gc_got_disconnected(self.room_jid)
+ gajim.connections[self.account].send_gc_status(self.nick, self.room_jid,
+ show='offline', status=status)
+ nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
+ for nick in nick_list:
+ # Update pm chat window
+ fjid = self.room_jid + '/' + nick
+ ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid, self.account)
+ if ctrl:
+ contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+ nick)
+ contact.show = 'offline'
+ contact.status = ''
+ ctrl.update_ui()
+ ctrl.parent_win.redraw_tab(ctrl)
+ for sess in gajim.connections[self.account].get_sessions(fjid):
+ if sess.control:
+ sess.control.no_autonegotiation = False
+ if sess.enable_encryption:
+ sess.terminate_e2e()
+ gajim.connections[self.account].delete_session(fjid,
+ sess.thread_id)
+ # They can already be removed by the destroy function
+ if self.room_jid in gajim.contacts.get_gc_list(self.account):
+ gajim.contacts.remove_room(self.account, self.room_jid)
+ del gajim.gc_connected[self.account][self.room_jid]
+ # Save hpaned position
+ gajim.config.set('gc-hpaned-position', self.hpaned.get_position())
+ # remove all register handlers on wigets, created by self.xml
+ # to prevent circular references among objects
+ for i in self.handlers.keys():
+ if self.handlers[i].handler_is_connected(i):
+ self.handlers[i].disconnect(i)
+ del self.handlers[i]
+ # Remove unread events from systray
+ gajim.events.remove_events(self.account, self.room_jid)
+
+ def safe_shutdown(self):
+ if self.minimizable():
+ return True
+ includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
+ excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
+ # whether to ask for comfirmation before closing muc
+ if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \
+ and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\
+ in excludes:
+ return False
+ return True
+
+ def allow_shutdown(self, method, on_yes, on_no, on_minimize):
+ if self.minimizable():
+ on_minimize(self)
+ return
+ if method == self.parent_win.CLOSE_ESC:
+ iter_ = self.list_treeview.get_selection().get_selected()[1]
+ if iter_:
+ self.list_treeview.get_selection().unselect_all()
+ on_no(self)
+ return
+ includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
+ excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
+ # whether to ask for comfirmation before closing muc
+ if (gajim.config.get('confirm_close_muc') or self.room_jid in includes) \
+ and gajim.gc_connected[self.account][self.room_jid] and self.room_jid not\
+ in excludes:
+
+ def on_ok(clicked):
+ if clicked:
+ # user does not want to be asked again
+ gajim.config.set('confirm_close_muc', False)
+ on_yes(self)
+
+ def on_cancel(clicked):
+ if clicked:
+ # user does not want to be asked again
+ gajim.config.set('confirm_close_muc', False)
+ on_no(self)
+
+ pritext = _('Are you sure you want to leave group chat "%s"?')\
+ % self.name
+ sectext = _('If you close this window, you will be disconnected '
+ 'from this group chat.')
+
+ dialogs.ConfirmationDialogCheck(pritext, sectext,
+ _('Do _not ask me again'), on_response_ok=on_ok,
+ on_response_cancel=on_cancel)
+ return
+
+ on_yes(self)
+
+ def set_control_active(self, state):
+ self.conv_textview.allow_focus_out_line = True
+ self.attention_flag = False
+ ChatControlBase.set_control_active(self, state)
+ if not state:
+ # add the focus-out line to the tab we are leaving
+ self.check_and_possibly_add_focus_out_line()
+ # Sending active to undo unread state
+ self.parent_win.redraw_tab(self, 'active')
+
+ def get_specific_unread(self):
+ # returns the number of the number of unread msgs
+ # for room_jid & number of unread private msgs with each contact
+ # that we have
+ nb = 0
+ for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
+ fjid = self.room_jid + '/' + nick
+ nb += len(gajim.events.get_events(self.account, fjid))
+ # gc can only have messages as event
+ return nb
+
+ def _on_change_subject_menuitem_activate(self, widget):
+ def on_ok(subject):
+ # Note, we don't update self.subject since we don't know whether it
+ # will work yet
+ gajim.connections[self.account].send_gc_subject(self.room_jid, subject)
+
+ dialogs.InputTextDialog(_('Changing Subject'),
+ _('Please specify the new subject:'), input_str=self.subject,
+ ok_handler=on_ok)
+
+ def _on_change_nick_menuitem_activate(self, widget):
+ if 'change_nick_dialog' in gajim.interface.instances:
+ gajim.interface.instances['change_nick_dialog'].present()
+ else:
+ title = _('Changing Nickname')
+ prompt = _('Please specify the new nickname you want to use:')
+ gajim.interface.instances['change_nick_dialog'] = \
+ dialogs.ChangeNickDialog(self.account, self.room_jid, title,
+ prompt)
+
+ def _on_configure_room_menuitem_activate(self, widget):
+ c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick)
+ if c.affiliation == 'owner':
+ gajim.connections[self.account].request_gc_config(self.room_jid)
+ elif c.affiliation == 'admin':
+ if self.room_jid not in gajim.interface.instances[self.account][
+ 'gc_config']:
+ gajim.interface.instances[self.account]['gc_config'][self.room_jid]\
+ = config.GroupchatConfigWindow(self.account, self.room_jid)
+
+ def _on_destroy_room_menuitem_activate(self, widget):
+ def on_ok(reason, jid):
+ if jid:
+ # Test jid
+ try:
+ jid = helpers.parse_jid(jid)
+ except Exception:
+ dialogs.ErrorDialog(_('Invalid group chat Jabber ID'),
+ _('The group chat Jabber ID has not allowed characters.'))
+ return
+ gajim.connections[self.account].destroy_gc_room(self.room_jid, reason,
+ jid)
+
+ # Ask for a reason
+ dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid,
+ _('You are going to definitively destroy this room.\n'
+ 'You may specify a reason below:'),
+ _('You may also enter an alternate venue:'), ok_handler=on_ok)
+
+ def _on_bookmark_room_menuitem_activate(self, widget):
+ """
+ Bookmark the room, without autojoin and not minimized
+ """
+ password = gajim.gc_passwords.get(self.room_jid, '')
+ gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid, \
+ '0', '0', password, self.nick)
+
+ def _on_drag_data_received(self, widget, context, x, y, selection,
+ target_type, timestamp):
+ # Invite contact to groupchat
+ treeview = gajim.interface.roster.tree
+ model = treeview.get_model()
+ if not selection.data or target_type == 80:
+ # target_type = 80 means a file is dropped
+ return
+ data = selection.data
+ path = treeview.get_selection().get_selected_rows()[1][0]
+ iter_ = model.get_iter(path)
+ type_ = model[iter_][2]
+ if type_ != 'contact': # source is not a contact
+ return
+ contact_jid = data.decode('utf-8')
+ gajim.connections[self.account].send_invite(self.room_jid, contact_jid)
+
+ def handle_message_textview_mykey_press(self, widget, event_keyval,
+ event_keymod):
+ # NOTE: handles mykeypress which is custom signal connected to this
+ # CB in new_room(). for this singal see message_textview.py
+
+ # construct event instance from binding
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
+ event.keyval = event_keyval
+ event.state = event_keymod
+ event.time = 0 # assign current time
+
+ message_buffer = widget.get_buffer()
+ start_iter, end_iter = message_buffer.get_bounds()
+
+ if event.keyval == gtk.keysyms.Tab: # TAB
+ cursor_position = message_buffer.get_insert()
+ end_iter = message_buffer.get_iter_at_mark(cursor_position)
+ text = message_buffer.get_text(start_iter, end_iter, False).decode(
+ 'utf-8')
+
+ splitted_text = text.split()
+
+ # HACK: Not the best soltution.
+ if (text.startswith(self.COMMAND_PREFIX) and not
+ text.startswith(self.COMMAND_PREFIX * 2) and len(splitted_text) == 1):
+ return super(GroupchatControl,
+ self).handle_message_textview_mykey_press(widget, event_keyval,
+ event_keymod)
+
+ # nick completion
+ # check if tab is pressed with empty message
+ if len(splitted_text): # if there are any words
+ begin = splitted_text[-1] # last word we typed
+ else:
+ begin = ''
+
+ gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
+ with_refer_to_nick_char = False
+ after_nick_len = 1 # the space that is printed after we type [Tab]
+
+ # first part of this if : works fine even if refer_to_nick_char
+ if gc_refer_to_nick_char and text.endswith(
+ gc_refer_to_nick_char + ' '):
+ with_refer_to_nick_char = True
+ after_nick_len = len(gc_refer_to_nick_char + ' ')
+ if len(self.nick_hits) and self.last_key_tabs and \
+ text[:-after_nick_len].endswith(self.nick_hits[0]):
+ # we should cycle
+ # Previous nick in list may had a space inside, so we check text and
+ # not splitted_text and store it into 'begin' var
+ self.nick_hits.append(self.nick_hits[0])
+ begin = self.nick_hits.pop(0)
+ else:
+ self.nick_hits = [] # clear the hit list
+ list_nick = gajim.contacts.get_nick_list(self.account,
+ self.room_jid)
+ list_nick.sort(key=unicode.lower) # case-insensitive sort
+ if begin == '':
+ # empty message, show lasts nicks that highlighted us first
+ for nick in self.attention_list:
+ if nick in list_nick:
+ list_nick.remove(nick)
+ list_nick.insert(0, nick)
+
+ list_nick.remove(self.nick) # Skip self
+ for nick in list_nick:
+ if nick.lower().startswith(begin.lower()):
+ # the word is the begining of a nick
+ self.nick_hits.append(nick)
+ if len(self.nick_hits):
+ if len(splitted_text) < 2 or with_refer_to_nick_char:
+ # This is the 1st word of the line or no word or we are cycling
+ # at the beginning, possibly with a space in one nick
+ add = gc_refer_to_nick_char + ' '
+ else:
+ add = ' '
+ start_iter = end_iter.copy()
+ if self.last_key_tabs and with_refer_to_nick_char or (text and \
+ text[-1] == ' '):
+ # have to accomodate for the added space from last
+ # completion
+ # gc_refer_to_nick_char may be more than one char!
+ start_iter.backward_chars(len(begin) + len(add))
+ elif self.last_key_tabs and not gajim.config.get(
+ 'shell_like_completion'):
+ # have to accomodate for the added space from last
+ # completion
+ start_iter.backward_chars(len(begin) + \
+ len(gc_refer_to_nick_char))
+ else:
+ start_iter.backward_chars(len(begin))
+
+ message_buffer.delete(start_iter, end_iter)
+ # get a shell-like completion
+ # if there's more than one nick for this completion, complete only
+ # the part that all these nicks have in common
+ if gajim.config.get('shell_like_completion') and \
+ len(self.nick_hits) > 1:
+ end = False
+ completion = ''
+ add = "" # if nick is not complete, don't add anything
+ while not end and len(completion) < len(self.nick_hits[0]):
+ completion = self.nick_hits[0][:len(completion)+1]
+ for nick in self.nick_hits:
+ if completion.lower() not in nick.lower():
+ end = True
+ completion = completion[:-1]
+ break
+ # if the current nick matches a COMPLETE existing nick,
+ # and if the user tab TWICE, complete that nick (with the "add")
+ if self.last_key_tabs:
+ for nick in self.nick_hits:
+ if nick == completion:
+ # The user seems to want this nick, so
+ # complete it as if it were the only nick
+ # available
+ add = gc_refer_to_nick_char + ' '
+ else:
+ completion = self.nick_hits[0]
+ message_buffer.insert_at_cursor(completion + add)
+ self.last_key_tabs = True
+ return True
+ self.last_key_tabs = False
+
+ def on_list_treeview_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ selection = widget.get_selection()
+ iter_ = selection.get_selected()[1]
+ if iter_:
+ widget.get_selection().unselect_all()
+ return True
+
+ def on_list_treeview_row_expanded(self, widget, iter_, path):
+ """
+ When a row is expanded: change the icon of the arrow
+ """
+ model = widget.get_model()
+ image = gajim.interface.jabber_state_images['16']['opened']
+ model[iter_][C_IMG] = image
+
+ def on_list_treeview_row_collapsed(self, widget, iter_, path):
+ """
+ When a row is collapsed: change the icon of the arrow
+ """
+ model = widget.get_model()
+ image = gajim.interface.jabber_state_images['16']['closed']
+ model[iter_][C_IMG] = image
+
+ def kick(self, widget, nick):
+ """
+ Kick a user
+ """
+ def on_ok(reason):
+ gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+ 'none', reason)
+
+ # ask for reason
+ dialogs.InputDialog(_('Kicking %s') % nick,
+ _('You may specify a reason below:'), ok_handler=on_ok)
+
+ def mk_menu(self, event, iter_):
+ """
+ Make contact's popup menu
+ """
+ model = self.list_treeview.get_model()
+ nick = model[iter_][C_NICK].decode('utf-8')
+ c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+ fjid = self.room_jid + '/' + nick
+ jid = c.jid
+ target_affiliation = c.affiliation
+ target_role = c.role
+
+ # looking for user's affiliation and role
+ user_nick = self.nick
+ user_affiliation = gajim.contacts.get_gc_contact(self.account,
+ self.room_jid, user_nick).affiliation
+ user_role = self.get_role(user_nick)
+
+ # making menu from gtk builder
+ xml = gtkgui_helpers.get_gtk_builder('gc_occupants_menu.ui')
+
+ # these conditions were taken from JEP 0045
+ item = xml.get_object('kick_menuitem')
+ if user_role != 'moderator' or \
+ (user_affiliation == 'admin' and target_affiliation == 'owner') or \
+ (user_affiliation == 'member' and target_affiliation in ('admin',
+ 'owner')) or (user_affiliation == 'none' and target_affiliation != \
+ 'none'):
+ item.set_sensitive(False)
+ id_ = item.connect('activate', self.kick, nick)
+ self.handlers[id_] = item
+
+ item = xml.get_object('voice_checkmenuitem')
+ item.set_active(target_role != 'visitor')
+ if user_role != 'moderator' or \
+ user_affiliation == 'none' or \
+ (user_affiliation=='member' and target_affiliation!='none') or \
+ target_affiliation in ('admin', 'owner'):
+ item.set_sensitive(False)
+ id_ = item.connect('activate', self.on_voice_checkmenuitem_activate,
+ nick)
+ self.handlers[id_] = item
+
+ item = xml.get_object('moderator_checkmenuitem')
+ item.set_active(target_role == 'moderator')
+ if not user_affiliation in ('admin', 'owner') or \
+ target_affiliation in ('admin', 'owner'):
+ item.set_sensitive(False)
+ id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate,
+ nick)
+ self.handlers[id_] = item
+
+ item = xml.get_object('ban_menuitem')
+ if not user_affiliation in ('admin', 'owner') or \
+ (target_affiliation in ('admin', 'owner') and\
+ user_affiliation != 'owner'):
+ item.set_sensitive(False)
+ id_ = item.connect('activate', self.ban, jid)
+ self.handlers[id_] = item
+
+ item = xml.get_object('member_checkmenuitem')
+ item.set_active(target_affiliation != 'none')
+ if not user_affiliation in ('admin', 'owner') or \
+ (user_affiliation != 'owner' and target_affiliation in ('admin','owner')):
+ item.set_sensitive(False)
+ id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid)
+ self.handlers[id_] = item
+
+ item = xml.get_object('admin_checkmenuitem')
+ item.set_active(target_affiliation in ('admin', 'owner'))
+ if not user_affiliation == 'owner':
+ item.set_sensitive(False)
+ id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid)
+ self.handlers[id_] = item
+
+ item = xml.get_object('owner_checkmenuitem')
+ item.set_active(target_affiliation == 'owner')
+ if not user_affiliation == 'owner':
+ item.set_sensitive(False)
+ id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid)
+ self.handlers[id_] = item
+
+ item = xml.get_object('information_menuitem')
+ id_ = item.connect('activate', self.on_info, nick)
+ self.handlers[id_] = item
+
+ item = xml.get_object('history_menuitem')
+ id_ = item.connect('activate', self.on_history, nick)
+ self.handlers[id_] = item
+
+ item = xml.get_object('add_to_roster_menuitem')
+ our_jid = gajim.get_jid_from_account(self.account)
+ if not jid or jid == our_jid:
+ item.set_sensitive(False)
+ else:
+ id_ = item.connect('activate', self.on_add_to_roster, jid)
+ self.handlers[id_] = item
+
+ item = xml.get_object('block_menuitem')
+ item2 = xml.get_object('unblock_menuitem')
+ if helpers.jid_is_blocked(self.account, fjid):
+ item.set_no_show_all(True)
+ item.hide()
+ id_ = item2.connect('activate', self.on_unblock, nick)
+ self.handlers[id_] = item2
+ else:
+ id_ = item.connect('activate', self.on_block, nick)
+ self.handlers[id_] = item
+ item2.set_no_show_all(True)
+ item2.hide()
+
+ item = xml.get_object('send_private_message_menuitem')
+ id_ = item.connect('activate', self.on_send_pm, model, iter_)
+ self.handlers[id_] = item
+
+ item = xml.get_object('send_file_menuitem')
+ # add a special img for send file menuitem
+ path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
+ img = gtk.Image()
+ img.set_from_file(path_to_upload_img)
+ item.set_image(img)
+
+ if not c.resource:
+ item.set_sensitive(False)
+ else:
+ id_ = item.connect('activate', self.on_send_file, c)
+ self.handlers[id_] = item
+
+ # show the popup now!
+ menu = xml.get_object('gc_occupants_menu')
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+
+ def _start_private_message(self, nick):
+ gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+ nick_jid = gc_c.get_full_jid()
+
+ ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account)
+ if not ctrl:
+ ctrl = gajim.interface.new_private_chat(gc_c, self.account)
+
+ if ctrl:
+ ctrl.parent_win.set_active_tab(ctrl)
+
+ return ctrl
+
+ def on_row_activated(self, widget, path):
+ """
+ When an iter is activated (dubblick or single click if gnome is set this
+ way
+ """
+ model = widget.get_model()
+ if len(path) == 1: # It's a group
+ if (widget.row_expanded(path)):
+ widget.collapse_row(path)
+ else:
+ widget.expand_row(path, False)
+ else: # We want to send a private message
+ nick = model[path][C_NICK].decode('utf-8')
+ self._start_private_message(nick)
+
+ def on_list_treeview_row_activated(self, widget, path, col=0):
+ """
+ When an iter is double clicked: open the chat window
+ """
+ if not gajim.single_click:
+ self.on_row_activated(widget, path)
+
+ def on_list_treeview_button_press_event(self, widget, event):
+ """
+ Popup user's group's or agent menu
+ """
+ # hide tooltip, no matter the button is pressed
+ self.tooltip.hide_tooltip()
+ try:
+ pos = widget.get_path_at_pos(int(event.x), int(event.y))
+ path, x = pos[0], pos[2]
+ except TypeError:
+ widget.get_selection().unselect_all()
+ return
+ if event.button == 3: # right click
+ widget.get_selection().select_path(path)
+ model = widget.get_model()
+ iter_ = model.get_iter(path)
+ if len(path) == 2:
+ self.mk_menu(event, iter_)
+ return True
+
+ elif event.button == 2: # middle click
+ widget.get_selection().select_path(path)
+ model = widget.get_model()
+ iter_ = model.get_iter(path)
+ if len(path) == 2:
+ nick = model[iter_][C_NICK].decode('utf-8')
+ self._start_private_message(nick)
+ return True
+
+ elif event.button == 1: # left click
+ if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK:
+ self.on_row_activated(widget, path)
+ return True
+ else:
+ model = widget.get_model()
+ iter_ = model.get_iter(path)
+ nick = model[iter_][C_NICK].decode('utf-8')
+ if not nick in gajim.contacts.get_nick_list(self.account,
+ self.room_jid):
+ # it's a group
+ if x < 27:
+ if (widget.row_expanded(path)):
+ widget.collapse_row(path)
+ else:
+ widget.expand_row(path, False)
+ elif event.state & gtk.gdk.SHIFT_MASK:
+ self.append_nick_in_msg_textview(self.msg_textview, nick)
+ self.msg_textview.grab_focus()
+ return True
+
+ def append_nick_in_msg_textview(self, widget, nick):
+ message_buffer = self.msg_textview.get_buffer()
+ start_iter, end_iter = message_buffer.get_bounds()
+ cursor_position = message_buffer.get_insert()
+ end_iter = message_buffer.get_iter_at_mark(cursor_position)
+ text = message_buffer.get_text(start_iter, end_iter, False)
+ start = ''
+ if text: # Cursor is not at first position
+ if not text[-1] in (' ', '\n', '\t'):
+ start = ' '
+ add = ' '
+ else:
+ gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
+ add = gc_refer_to_nick_char + ' '
+ message_buffer.insert_at_cursor(start + nick + add)
+
+ def on_list_treeview_motion_notify_event(self, widget, event):
+ model = widget.get_model()
+ props = widget.get_path_at_pos(int(event.x), int(event.y))
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id != props[0]:
+ self.tooltip.hide_tooltip()
+ if props:
+ [row, col, x, y] = props
+ iter_ = None
+ try:
+ iter_ = model.get_iter(row)
+ except Exception:
+ self.tooltip.hide_tooltip()
+ return
+ typ = model[iter_][C_TYPE].decode('utf-8')
+ if typ == 'contact':
+ account = self.account
+
+ if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
+ self.tooltip.id = row
+ nick = model[iter_][C_NICK].decode('utf-8')
+ self.tooltip.timeout = gobject.timeout_add(500,
+ self.show_tooltip, gajim.contacts.get_gc_contact(account,
+ self.room_jid, nick))
+
+ def on_list_treeview_leave_notify_event(self, widget, event):
+ props = widget.get_path_at_pos(int(event.x), int(event.y))
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id == props[0]:
+ self.tooltip.hide_tooltip()
+
+ def show_tooltip(self, contact):
+ if not self.list_treeview.window:
+ # control has been destroyed since tooltip was requested
+ return
+ pointer = self.list_treeview.get_pointer()
+ props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1])
+ # check if the current pointer is at the same path
+ # as it was before setting the timeout
+ if props and self.tooltip.id == props[0]:
+ rect = self.list_treeview.get_cell_area(props[0],props[1])
+ position = self.list_treeview.window.get_origin()
+ self.tooltip.show_tooltip(contact, rect.height,
+ position[1] + rect.y)
+ else:
+ self.tooltip.hide_tooltip()
+
+ def grant_voice(self, widget, nick):
+ """
+ Grant voice privilege to a user
+ """
+ gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+ 'participant')
+
+ def revoke_voice(self, widget, nick):
+ """
+ Revoke voice privilege to a user
+ """
+ gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+ 'visitor')
+
+ def grant_moderator(self, widget, nick):
+ """
+ Grant moderator privilege to a user
+ """
+ gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+ 'moderator')
+
+ def revoke_moderator(self, widget, nick):
+ """
+ Revoke moderator privilege to a user
+ """
+ gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+ 'participant')
+
+ def ban(self, widget, jid):
+ """
+ Ban a user
+ """
+ def on_ok(reason):
+ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
+ 'outcast', reason)
+
+ # to ban we know the real jid. so jid is not fakejid
+ nick = gajim.get_nick_from_jid(jid)
+ # ask for reason
+ dialogs.InputDialog(_('Banning %s') % nick,
+ _('You may specify a reason below:'), ok_handler=on_ok)
+
+ def grant_membership(self, widget, jid):
+ """
+ Grant membership privilege to a user
+ """
+ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
+ 'member')
+
+ def revoke_membership(self, widget, jid):
+ """
+ Revoke membership privilege to a user
+ """
+ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
+ 'none')
+
+ def grant_admin(self, widget, jid):
+ """
+ Grant administrative privilege to a user
+ """
+ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
+ 'admin')
+
+ def revoke_admin(self, widget, jid):
+ """
+ Revoke administrative privilege to a user
+ """
+ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
+ 'member')
+
+ def grant_owner(self, widget, jid):
+ """
+ Grant owner privilege to a user
+ """
+ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
+ 'owner')
+
+ def revoke_owner(self, widget, jid):
+ """
+ Revoke owner privilege to a user
+ """
+ gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
+ 'admin')
+
+ def on_info(self, widget, nick):
+ """
+ Call vcard_information_window class to display user's information
+ """
+ gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+ contact = gc_contact.as_contact()
+ if contact.jid in gajim.interface.instances[self.account]['infos']:
+ gajim.interface.instances[self.account]['infos'][contact.jid].window.\
+ present()
+ else:
+ gajim.interface.instances[self.account]['infos'][contact.jid] = \
+ vcard.VcardWindow(contact, self.account, gc_contact)
+
+ def on_history(self, widget, nick):
+ jid = gajim.construct_fjid(self.room_jid, nick)
+ self._on_history_menuitem_activate(widget=widget, jid=jid)
+
+ def on_add_to_roster(self, widget, jid):
+ dialogs.AddNewContactWindow(self.account, jid)
+
+ def on_block(self, widget, nick):
+ fjid = self.room_jid + '/' + nick
+ connection = gajim.connections[self.account]
+ if fjid in connection.blocked_contacts:
+ return
+ new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny',
+ 'value' : fjid, 'child': [u'message', u'iq', u'presence-out']}
+ connection.blocked_list.append(new_rule)
+ connection.blocked_contacts.append(fjid)
+ self.draw_contact(nick)
+ connection.set_privacy_list('block', connection.blocked_list)
+ if len(connection.blocked_list) == 1:
+ connection.set_active_list('block')
+ connection.set_default_list('block')
+ connection.get_privacy_list('block')
+
+ def on_unblock(self, widget, nick):
+ fjid = self.room_jid + '/' + nick
+ connection = gajim.connections[self.account]
+ connection.new_blocked_list = []
+ # needed for draw_contact:
+ if fjid in connection.blocked_contacts:
+ connection.blocked_contacts.remove(fjid)
+ self.draw_contact(nick)
+ for rule in connection.blocked_list:
+ if rule['action'] != 'deny' or rule['type'] != 'jid' \
+ or rule['value'] != fjid:
+ connection.new_blocked_list.append(rule)
+
+ connection.set_privacy_list('block', connection.new_blocked_list)
+ connection.get_privacy_list('block')
+ if len(connection.new_blocked_list) == 0:
+ connection.blocked_list = []
+ connection.blocked_contacts = []
+ connection.blocked_groups = []
+ connection.set_default_list('')
+ connection.set_active_list('')
+ connection.del_privacy_list('block')
+ if 'blocked_contacts' in gajim.interface.instances[self.account]:
+ gajim.interface.instances[self.account]['blocked_contacts'].\
+ privacy_list_received([])
+
+ def on_voice_checkmenuitem_activate(self, widget, nick):
+ if widget.get_active():
+ self.grant_voice(widget, nick)
+ else:
+ self.revoke_voice(widget, nick)
+
+ def on_moderator_checkmenuitem_activate(self, widget, nick):
+ if widget.get_active():
+ self.grant_moderator(widget, nick)
+ else:
+ self.revoke_moderator(widget, nick)
+
+ def on_member_checkmenuitem_activate(self, widget, jid):
+ if widget.get_active():
+ self.grant_membership(widget, jid)
+ else:
+ self.revoke_membership(widget, jid)
+
+ def on_admin_checkmenuitem_activate(self, widget, jid):
+ if widget.get_active():
+ self.grant_admin(widget, jid)
+ else:
+ self.revoke_admin(widget, jid)
+
+ def on_owner_checkmenuitem_activate(self, widget, jid):
+ if widget.get_active():
+ self.grant_owner(widget, jid)
+ else:
+ self.revoke_owner(widget, jid)
diff --git a/src/groups.py b/src/groups.py
index 26e4e36b1..298e1f4b6 100644
--- a/src/groups.py
+++ b/src/groups.py
@@ -25,51 +25,49 @@ from common import gajim, xmpp
import gtkgui_helpers
class GroupsPostWindow:
- def __init__(self, account, servicejid, groupid):
- """
- Open new 'create post' window to create message for groupid on servicejid
- service
- """
- assert isinstance(servicejid, basestring)
- assert isinstance(groupid, basestring)
+ def __init__(self, account, servicejid, groupid):
+ """
+ Open new 'create post' window to create message for groupid on servicejid
+ service
+ """
+ assert isinstance(servicejid, basestring)
+ assert isinstance(groupid, basestring)
- self.account = account
- self.servicejid = servicejid
- self.groupid = groupid
+ self.account = account
+ self.servicejid = servicejid
+ self.groupid = groupid
- self.xml = gtkgui_helpers.get_gtk_builder('groups_post_window.ui')
- self.window = self.xml.get_object('groups_post_window')
- for name in ('from_entry', 'subject_entry', 'contents_textview'):
- self.__dict__[name] = self.xml.get_object(name)
+ self.xml = gtkgui_helpers.get_gtk_builder('groups_post_window.ui')
+ self.window = self.xml.get_object('groups_post_window')
+ for name in ('from_entry', 'subject_entry', 'contents_textview'):
+ self.__dict__[name] = self.xml.get_object(name)
- self.xml.connect_signals(self)
- self.window.show_all()
+ self.xml.connect_signals(self)
+ self.window.show_all()
- def on_cancel_button_clicked(self, w):
- """
- Close window
- """
- self.window.destroy()
+ def on_cancel_button_clicked(self, w):
+ """
+ Close window
+ """
+ self.window.destroy()
- def on_send_button_clicked(self, w):
- """
- Gather info from widgets and send it as a message
- """
- # constructing item to publish... that's atom:entry element
- item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'})
- author = item.addChild('author')
- author.addChild('name', {}, [self.from_entry.get_text()])
- item.addChild('generator', {}, ['Gajim'])
- item.addChild('title', {}, [self.subject_entry.get_text()])
- item.addChild('id', {}, ['0'])
+ def on_send_button_clicked(self, w):
+ """
+ Gather info from widgets and send it as a message
+ """
+ # constructing item to publish... that's atom:entry element
+ item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'})
+ author = item.addChild('author')
+ author.addChild('name', {}, [self.from_entry.get_text()])
+ item.addChild('generator', {}, ['Gajim'])
+ item.addChild('title', {}, [self.subject_entry.get_text()])
+ item.addChild('id', {}, ['0'])
- buf = self.contents_textview.get_buffer()
- item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter())])
+ buf = self.contents_textview.get_buffer()
+ item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter())])
- # publish it to node
- gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0')
+ # publish it to node
+ gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0')
- # close the window
- self.window.destroy()
-
-# vim: se ts=3:
+ # close the window
+ self.window.destroy()
diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py
index 1c08fcd24..f4d6d73c1 100644
--- a/src/gtkexcepthook.py
+++ b/src/gtkexcepthook.py
@@ -36,72 +36,70 @@ from common import helpers
_exception_in_progress = threading.Lock()
def _info(type_, value, tb):
- if not _exception_in_progress.acquire(False):
- # Exceptions have piled up, so we use the default exception
- # handler for such exceptions
- _excepthook_save(type_, value, tb)
- return
+ if not _exception_in_progress.acquire(False):
+ # Exceptions have piled up, so we use the default exception
+ # handler for such exceptions
+ _excepthook_save(type_, value, tb)
+ return
- dialog = dialogs.HigDialog(None, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE,
- _('A programming error has been detected'),
- _('It probably is not fatal, but should be reported '
- 'to the developers nonetheless.'))
+ dialog = dialogs.HigDialog(None, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE,
+ _('A programming error has been detected'),
+ _('It probably is not fatal, but should be reported '
+ 'to the developers nonetheless.'))
- dialog.set_modal(False)
- #FIXME: add icon to this button
- RESPONSE_REPORT_BUG = 42
- dialog.add_buttons(gtk.STOCK_CLOSE, gtk.BUTTONS_CLOSE,
- _('_Report Bug'), RESPONSE_REPORT_BUG)
- report_button = dialog.action_area.get_children()[0] # right to left
- report_button.grab_focus()
+ dialog.set_modal(False)
+ #FIXME: add icon to this button
+ RESPONSE_REPORT_BUG = 42
+ dialog.add_buttons(gtk.STOCK_CLOSE, gtk.BUTTONS_CLOSE,
+ _('_Report Bug'), RESPONSE_REPORT_BUG)
+ report_button = dialog.action_area.get_children()[0] # right to left
+ report_button.grab_focus()
- # Details
- textview = gtk.TextView()
- textview.set_editable(False)
- textview.modify_font(pango.FontDescription('Monospace'))
- sw = gtk.ScrolledWindow()
- sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- sw.add(textview)
- frame = gtk.Frame()
- frame.set_shadow_type(gtk.SHADOW_IN)
- frame.add(sw)
- frame.set_border_width(6)
- textbuffer = textview.get_buffer()
- trace = StringIO()
- traceback.print_exception(type_, value, tb, None, trace)
- textbuffer.set_text(trace.getvalue())
- textview.set_size_request(
- gtk.gdk.screen_width() / 3,
- gtk.gdk.screen_height() / 4)
- expander = gtk.Expander(_('Details'))
- expander.add(frame)
- dialog.vbox.add(expander)
+ # Details
+ textview = gtk.TextView()
+ textview.set_editable(False)
+ textview.modify_font(pango.FontDescription('Monospace'))
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ sw.add(textview)
+ frame = gtk.Frame()
+ frame.set_shadow_type(gtk.SHADOW_IN)
+ frame.add(sw)
+ frame.set_border_width(6)
+ textbuffer = textview.get_buffer()
+ trace = StringIO()
+ traceback.print_exception(type_, value, tb, None, trace)
+ textbuffer.set_text(trace.getvalue())
+ textview.set_size_request(
+ gtk.gdk.screen_width() / 3,
+ gtk.gdk.screen_height() / 4)
+ expander = gtk.Expander(_('Details'))
+ expander.add(frame)
+ dialog.vbox.add(expander)
- dialog.set_resizable(True)
- # on expand the details the dialog remains centered on screen
- dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+ dialog.set_resizable(True)
+ # on expand the details the dialog remains centered on screen
+ dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)
- def on_dialog_response(dialog, response):
- if response == RESPONSE_REPORT_BUG:
- url = 'http://trac.gajim.org/wiki/HowToCreateATicket'
- helpers.launch_browser_mailer('url', url)
- else:
- dialog.destroy()
- dialog.connect('response', on_dialog_response)
- dialog.show_all()
+ def on_dialog_response(dialog, response):
+ if response == RESPONSE_REPORT_BUG:
+ url = 'http://trac.gajim.org/wiki/HowToCreateATicket'
+ helpers.launch_browser_mailer('url', url)
+ else:
+ dialog.destroy()
+ dialog.connect('response', on_dialog_response)
+ dialog.show_all()
- _exception_in_progress.release()
+ _exception_in_progress.release()
# gdb/kdm etc if we use startx this is not True
if os.name == 'nt' or not sys.stderr.isatty():
- #FIXME: maybe always show dialog?
- _excepthook_save = sys.excepthook
- sys.excepthook = _info
+ #FIXME: maybe always show dialog?
+ _excepthook_save = sys.excepthook
+ sys.excepthook = _info
# this is just to assist testing (python gtkexcepthook.py)
if __name__ == '__main__':
- _excepthook_save = sys.excepthook
- sys.excepthook = _info
- raise Exception()
-
-# vim: se ts=3:
+ _excepthook_save = sys.excepthook
+ sys.excepthook = _info
+ raise Exception()
diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py
index 7f3ab1f57..9d5188207 100644
--- a/src/gtkgui_helpers.py
+++ b/src/gtkgui_helpers.py
@@ -45,21 +45,21 @@ gtk_icon_theme = gtk.icon_theme_get_default()
gtk_icon_theme.append_search_path(gajim.ICONS_DIR)
def get_icon_pixmap(icon_name, size=16):
- try:
- return gtk_icon_theme.load_icon(icon_name, size, 0)
- except gobject.GError, e:
- log.error('Unable to load icon %s: %s' % (icon_name, str(e)))
+ try:
+ return gtk_icon_theme.load_icon(icon_name, size, 0)
+ except gobject.GError, e:
+ log.error('Unable to load icon %s: %s' % (icon_name, str(e)))
def get_icon_path(icon_name, size=16):
- try:
- icon_info = gtk_icon_theme.lookup_icon(icon_name, size, 0)
- if icon_info == None:
- log.error('Icon not found: %s' % icon_name)
- return ""
- else:
- return icon_info.get_filename()
- except gobject.GError, e:
- log.error("Unable to find icon %s: %s" % (icon_name, str(e)))
+ try:
+ icon_info = gtk_icon_theme.lookup_icon(icon_name, size, 0)
+ if icon_info == None:
+ log.error('Icon not found: %s' % icon_name)
+ return ""
+ else:
+ return icon_info.get_filename()
+ except gobject.GError, e:
+ log.error("Unable to find icon %s: %s" % (icon_name, str(e)))
import vcard
import dialogs
@@ -67,12 +67,12 @@ import dialogs
HAS_PYWIN32 = True
if os.name == 'nt':
- try:
- import win32file
- import win32con
- import pywintypes
- except ImportError:
- HAS_PYWIN32 = False
+ try:
+ import win32file
+ import win32con
+ import pywintypes
+ except ImportError:
+ HAS_PYWIN32 = False
from common import helpers
@@ -80,690 +80,690 @@ screen_w = gtk.gdk.screen_width()
screen_h = gtk.gdk.screen_height()
def add_image_to_menuitem(menuitem, icon_name):
- img = gtk.Image()
- path_img = get_icon_path(icon_name)
- img.set_from_file(path_img)
- menuitem.set_image(img)
+ img = gtk.Image()
+ path_img = get_icon_path(icon_name)
+ img.set_from_file(path_img)
+ menuitem.set_image(img)
def add_image_to_button(button, icon_name):
- add_image_to_menuitem(button, icon_name)
+ add_image_to_menuitem(button, icon_name)
GUI_DIR = os.path.join(gajim.DATA_DIR, 'gui')
def get_gtk_builder(file_name, widget=None):
- file_path = os.path.join(GUI_DIR, file_name)
- builder = gtk.Builder()
- builder.set_translation_domain(i18n.APP)
- if widget:
- builder.add_objects_from_file(file_path, [widget])
- else:
- builder.add_from_file(file_path)
- return builder
+ file_path = os.path.join(GUI_DIR, file_name)
+ builder = gtk.Builder()
+ builder.set_translation_domain(i18n.APP)
+ if widget:
+ builder.add_objects_from_file(file_path, [widget])
+ else:
+ builder.add_from_file(file_path)
+ return builder
def get_completion_liststore(entry):
- """
- Create a completion model for entry widget completion list consists of
- (Pixbuf, Text) rows
- """
- completion = gtk.EntryCompletion()
- liststore = gtk.ListStore(gtk.gdk.Pixbuf, str)
-
- render_pixbuf = gtk.CellRendererPixbuf()
- completion.pack_start(render_pixbuf, expand = False)
- completion.add_attribute(render_pixbuf, 'pixbuf', 0)
-
- render_text = gtk.CellRendererText()
- completion.pack_start(render_text, expand = True)
- completion.add_attribute(render_text, 'text', 1)
- completion.set_property('text_column', 1)
- completion.set_model(liststore)
- entry.set_completion(completion)
- return liststore
+ """
+ Create a completion model for entry widget completion list consists of
+ (Pixbuf, Text) rows
+ """
+ completion = gtk.EntryCompletion()
+ liststore = gtk.ListStore(gtk.gdk.Pixbuf, str)
+
+ render_pixbuf = gtk.CellRendererPixbuf()
+ completion.pack_start(render_pixbuf, expand = False)
+ completion.add_attribute(render_pixbuf, 'pixbuf', 0)
+
+ render_text = gtk.CellRendererText()
+ completion.pack_start(render_text, expand = True)
+ completion.add_attribute(render_text, 'text', 1)
+ completion.set_property('text_column', 1)
+ completion.set_model(liststore)
+ entry.set_completion(completion)
+ return liststore
def popup_emoticons_under_button(menu, button, parent_win):
- """
- Popup the emoticons menu under button, which is in parent_win
- """
- window_x1, window_y1 = parent_win.get_origin()
- def position_menu_under_button(menu):
- # inline function, which will not keep refs, when used as CB
- button_x, button_y = button.allocation.x, button.allocation.y
-
- # now convert them to X11-relative
- window_x, window_y = window_x1, window_y1
- x = window_x + button_x
- y = window_y + button_y
-
- menu_height = menu.size_request()[1]
-
- ## should we pop down or up?
- if (y + button.allocation.height + menu_height
- < gtk.gdk.screen_height()):
- # now move the menu below the button
- y += button.allocation.height
- else:
- # now move the menu above the button
- y -= menu_height
-
- # push_in is True so all the menuitems are always inside screen
- push_in = True
- return (x, y, push_in)
-
- menu.popup(None, None, position_menu_under_button, 1, 0)
+ """
+ Popup the emoticons menu under button, which is in parent_win
+ """
+ window_x1, window_y1 = parent_win.get_origin()
+ def position_menu_under_button(menu):
+ # inline function, which will not keep refs, when used as CB
+ button_x, button_y = button.allocation.x, button.allocation.y
+
+ # now convert them to X11-relative
+ window_x, window_y = window_x1, window_y1
+ x = window_x + button_x
+ y = window_y + button_y
+
+ menu_height = menu.size_request()[1]
+
+ ## should we pop down or up?
+ if (y + button.allocation.height + menu_height
+ < gtk.gdk.screen_height()):
+ # now move the menu below the button
+ y += button.allocation.height
+ else:
+ # now move the menu above the button
+ y -= menu_height
+
+ # push_in is True so all the menuitems are always inside screen
+ push_in = True
+ return (x, y, push_in)
+
+ menu.popup(None, None, position_menu_under_button, 1, 0)
def get_theme_font_for_option(theme, option):
- """
- Return string description of the font, stored in theme preferences
- """
- font_name = gajim.config.get_per('themes', theme, option)
- font_desc = pango.FontDescription()
- font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs')
- if font_prop_str:
- if font_prop_str.find('B') != -1:
- font_desc.set_weight(pango.WEIGHT_BOLD)
- if font_prop_str.find('I') != -1:
- font_desc.set_style(pango.STYLE_ITALIC)
- fd = pango.FontDescription(font_name)
- fd.merge(font_desc, True)
- return fd.to_string()
+ """
+ Return string description of the font, stored in theme preferences
+ """
+ font_name = gajim.config.get_per('themes', theme, option)
+ font_desc = pango.FontDescription()
+ font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs')
+ if font_prop_str:
+ if font_prop_str.find('B') != -1:
+ font_desc.set_weight(pango.WEIGHT_BOLD)
+ if font_prop_str.find('I') != -1:
+ font_desc.set_style(pango.STYLE_ITALIC)
+ fd = pango.FontDescription(font_name)
+ fd.merge(font_desc, True)
+ return fd.to_string()
def get_default_font():
- """
- Get the desktop setting for application font first check for GNOME, then
- Xfce and last KDE it returns None on failure or else a string 'Font Size'
- """
- try:
- import gconf
- # in try because daemon may not be there
- client = gconf.client_get_default()
-
- return client.get_string('/desktop/gnome/interface/font_name'
- ).decode('utf-8')
- except Exception:
- pass
-
- # try to get Xfce default font
- # Xfce 4.2 and higher follow freedesktop.org's Base Directory Specification
- # see http://www.xfce.org/~benny/xfce/file-locations.html
- # and http://freedesktop.org/Standards/basedir-spec
- xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '')
- if xdg_config_home == '':
- xdg_config_home = os.path.expanduser('~/.config') # default
- xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml')
-
- kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals')
-
- if os.path.exists(xfce_config_file):
- try:
- for line in open(xfce_config_file):
- if line.find('name="Gtk/FontName"') != -1:
- start = line.find('value="') + 7
- return line[start:line.find('"', start)].decode('utf-8')
- except Exception:
- #we talk about file
- print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file
-
- elif os.path.exists(kde_config_file):
- try:
- for line in open(kde_config_file):
- if line.find('font=') == 0: # font=Verdana,9,other_numbers
- start = 5 # 5 is len('font=')
- line = line[start:]
- values = line.split(',')
- font_name = values[0]
- font_size = values[1]
- font_string = '%s %s' % (font_name, font_size) # Verdana 9
- return font_string.decode('utf-8')
- except Exception:
- #we talk about file
- print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file
-
- return None
+ """
+ Get the desktop setting for application font first check for GNOME, then
+ Xfce and last KDE it returns None on failure or else a string 'Font Size'
+ """
+ try:
+ import gconf
+ # in try because daemon may not be there
+ client = gconf.client_get_default()
+
+ return client.get_string('/desktop/gnome/interface/font_name'
+ ).decode('utf-8')
+ except Exception:
+ pass
+
+ # try to get Xfce default font
+ # Xfce 4.2 and higher follow freedesktop.org's Base Directory Specification
+ # see http://www.xfce.org/~benny/xfce/file-locations.html
+ # and http://freedesktop.org/Standards/basedir-spec
+ xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '')
+ if xdg_config_home == '':
+ xdg_config_home = os.path.expanduser('~/.config') # default
+ xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml')
+
+ kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals')
+
+ if os.path.exists(xfce_config_file):
+ try:
+ for line in open(xfce_config_file):
+ if line.find('name="Gtk/FontName"') != -1:
+ start = line.find('value="') + 7
+ return line[start:line.find('"', start)].decode('utf-8')
+ except Exception:
+ #we talk about file
+ print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file
+
+ elif os.path.exists(kde_config_file):
+ try:
+ for line in open(kde_config_file):
+ if line.find('font=') == 0: # font=Verdana,9,other_numbers
+ start = 5 # 5 is len('font=')
+ line = line[start:]
+ values = line.split(',')
+ font_name = values[0]
+ font_size = values[1]
+ font_string = '%s %s' % (font_name, font_size) # Verdana 9
+ return font_string.decode('utf-8')
+ except Exception:
+ #we talk about file
+ print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file
+
+ return None
def autodetect_browser_mailer():
- # recognize the environment and set appropriate browser/mailer
- if user_runs_gnome():
- gajim.config.set('openwith', 'gnome-open')
- elif user_runs_kde():
- gajim.config.set('openwith', 'kfmclient exec')
- elif user_runs_xfce():
- gajim.config.set('openwith', 'exo-open')
- else:
- gajim.config.set('openwith', 'custom')
+ # recognize the environment and set appropriate browser/mailer
+ if user_runs_gnome():
+ gajim.config.set('openwith', 'gnome-open')
+ elif user_runs_kde():
+ gajim.config.set('openwith', 'kfmclient exec')
+ elif user_runs_xfce():
+ gajim.config.set('openwith', 'exo-open')
+ else:
+ gajim.config.set('openwith', 'custom')
def user_runs_gnome():
- return 'gnome-session' in get_running_processes()
+ return 'gnome-session' in get_running_processes()
def user_runs_kde():
- return 'startkde' in get_running_processes()
+ return 'startkde' in get_running_processes()
def user_runs_xfce():
- procs = get_running_processes()
- if 'startxfce4' in procs or 'xfce4-session' in procs:
- return True
- return False
+ procs = get_running_processes()
+ if 'startxfce4' in procs or 'xfce4-session' in procs:
+ return True
+ return False
def get_running_processes():
- """
- Return running processes or None (if /proc does not exist)
- """
- if os.path.isdir('/proc'):
- # under Linux: checking if 'gnome-session' or
- # 'startkde' programs were run before gajim, by
- # checking /proc (if it exists)
- #
- # if something is unclear, read `man proc`;
- # if /proc exists, directories that have only numbers
- # in their names contain data about processes.
- # /proc/[xxx]/exe is a symlink to executable started
- # as process number [xxx].
- # filter out everything that we are not interested in:
- files = os.listdir('/proc')
-
- # files that doesn't have only digits in names...
- files = filter(str.isdigit, files)
-
- # files that aren't directories...
- files = [f for f in files if os.path.isdir('/proc/' + f)]
-
- # processes owned by somebody not running gajim...
- # (we check if we have access to that file)
- files = [f for f in files if os.access('/proc/' + f +'/exe', os.F_OK)]
-
- # be sure that /proc/[number]/exe is really a symlink
- # to avoid TBs in incorrectly configured systems
- files = [f for f in files if os.path.islink('/proc/' + f + '/exe')]
-
- # list of processes
- processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files]
-
- return processes
- return []
+ """
+ Return running processes or None (if /proc does not exist)
+ """
+ if os.path.isdir('/proc'):
+ # under Linux: checking if 'gnome-session' or
+ # 'startkde' programs were run before gajim, by
+ # checking /proc (if it exists)
+ #
+ # if something is unclear, read `man proc`;
+ # if /proc exists, directories that have only numbers
+ # in their names contain data about processes.
+ # /proc/[xxx]/exe is a symlink to executable started
+ # as process number [xxx].
+ # filter out everything that we are not interested in:
+ files = os.listdir('/proc')
+
+ # files that doesn't have only digits in names...
+ files = filter(str.isdigit, files)
+
+ # files that aren't directories...
+ files = [f for f in files if os.path.isdir('/proc/' + f)]
+
+ # processes owned by somebody not running gajim...
+ # (we check if we have access to that file)
+ files = [f for f in files if os.access('/proc/' + f +'/exe', os.F_OK)]
+
+ # be sure that /proc/[number]/exe is really a symlink
+ # to avoid TBs in incorrectly configured systems
+ files = [f for f in files if os.path.islink('/proc/' + f + '/exe')]
+
+ # list of processes
+ processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files]
+
+ return processes
+ return []
def move_window(window, x, y):
- """
- Move the window, but also check if out of screen
- """
- if x < 0:
- x = 0
- if y < 0:
- y = 0
- w, h = window.get_size()
- if x + w > screen_w:
- x = screen_w - w
- if y + h > screen_h:
- y = screen_h - h
- window.move(x, y)
+ """
+ Move the window, but also check if out of screen
+ """
+ if x < 0:
+ x = 0
+ if y < 0:
+ y = 0
+ w, h = window.get_size()
+ if x + w > screen_w:
+ x = screen_w - w
+ if y + h > screen_h:
+ y = screen_h - h
+ window.move(x, y)
def resize_window(window, w, h):
- """
- Resize window, but also checks if huge window or negative values
- """
- if not w or not h:
- return
- if w > screen_w:
- w = screen_w
- if h > screen_h:
- h = screen_h
- window.resize(abs(w), abs(h))
+ """
+ Resize window, but also checks if huge window or negative values
+ """
+ if not w or not h:
+ return
+ if w > screen_w:
+ w = screen_w
+ if h > screen_h:
+ h = screen_h
+ window.resize(abs(w), abs(h))
class HashDigest:
- def __init__(self, algo, digest):
- self.algo = self.cleanID(algo)
- self.digest = self.cleanID(digest)
-
- def cleanID(self, id_):
- id_ = id_.strip().lower()
- for strip in (' :.-_'): id_ = id_.replace(strip, '')
- return id_
-
- def __eq__(self, other):
- sa, sd = self.algo, self.digest
- if isinstance(other, self.__class__):
- oa, od = other.algo, other.digest
- elif isinstance(other, basestring):
- sa, oa, od = None, None, self.cleanID(other)
- elif isinstance(other, tuple) and len(other) == 2:
- oa, od = self.cleanID(other[0]), self.cleanID(other[1])
- else:
- return False
-
- return sa == oa and sd == od
-
- def __ne__(self, other):
- return not self == other
-
- def __hash__(self):
- return self.algo ^ self.digest
-
- def __str__(self):
- prettydigest = ''
- for i in xrange(0, len(self.digest), 2):
- prettydigest += self.digest[i:i + 2] + ':'
- return prettydigest[:-1]
-
- def __repr__(self):
- return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self)))
+ def __init__(self, algo, digest):
+ self.algo = self.cleanID(algo)
+ self.digest = self.cleanID(digest)
+
+ def cleanID(self, id_):
+ id_ = id_.strip().lower()
+ for strip in (' :.-_'): id_ = id_.replace(strip, '')
+ return id_
+
+ def __eq__(self, other):
+ sa, sd = self.algo, self.digest
+ if isinstance(other, self.__class__):
+ oa, od = other.algo, other.digest
+ elif isinstance(other, basestring):
+ sa, oa, od = None, None, self.cleanID(other)
+ elif isinstance(other, tuple) and len(other) == 2:
+ oa, od = self.cleanID(other[0]), self.cleanID(other[1])
+ else:
+ return False
+
+ return sa == oa and sd == od
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return self.algo ^ self.digest
+
+ def __str__(self):
+ prettydigest = ''
+ for i in xrange(0, len(self.digest), 2):
+ prettydigest += self.digest[i:i + 2] + ':'
+ return prettydigest[:-1]
+
+ def __repr__(self):
+ return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self)))
class ServersXMLHandler(xml.sax.ContentHandler):
- def __init__(self):
- xml.sax.ContentHandler.__init__(self)
- self.servers = []
-
- def startElement(self, name, attributes):
- if name == 'item':
- # we will get the port next time so we just set it 0 here
- sitem = [None, 0, {}]
- sitem[2]['digest'] = {}
- sitem[2]['hidden'] = False
- for attribute in attributes.getNames():
- if attribute == 'jid':
- jid = attributes.getValue(attribute)
- sitem[0] = jid
- elif attribute == 'hidden':
- hidden = attributes.getValue(attribute)
- if hidden.lower() in ('1', 'y', 'yes', 't', 'true', 'on'):
- sitem[2]['hidden'] = True
- self.servers.append(sitem)
- elif name == 'active':
- for attribute in attributes.getNames():
- if attribute == 'port':
- port = attributes.getValue(attribute)
- # we received the jid last time, so we now assign the port
- # number to the last jid in the list
- self.servers[-1][1] = port
- elif name == 'digest':
- algo, digest = None, None
- for attribute in attributes.getNames():
- if attribute == 'algo':
- algo = attributes.getValue(attribute)
- elif attribute == 'value':
- digest = attributes.getValue(attribute)
- hd = HashDigest(algo, digest)
- self.servers[-1][2]['digest'][hd.algo] = hd
-
- def endElement(self, name):
- pass
+ def __init__(self):
+ xml.sax.ContentHandler.__init__(self)
+ self.servers = []
+
+ def startElement(self, name, attributes):
+ if name == 'item':
+ # we will get the port next time so we just set it 0 here
+ sitem = [None, 0, {}]
+ sitem[2]['digest'] = {}
+ sitem[2]['hidden'] = False
+ for attribute in attributes.getNames():
+ if attribute == 'jid':
+ jid = attributes.getValue(attribute)
+ sitem[0] = jid
+ elif attribute == 'hidden':
+ hidden = attributes.getValue(attribute)
+ if hidden.lower() in ('1', 'y', 'yes', 't', 'true', 'on'):
+ sitem[2]['hidden'] = True
+ self.servers.append(sitem)
+ elif name == 'active':
+ for attribute in attributes.getNames():
+ if attribute == 'port':
+ port = attributes.getValue(attribute)
+ # we received the jid last time, so we now assign the port
+ # number to the last jid in the list
+ self.servers[-1][1] = port
+ elif name == 'digest':
+ algo, digest = None, None
+ for attribute in attributes.getNames():
+ if attribute == 'algo':
+ algo = attributes.getValue(attribute)
+ elif attribute == 'value':
+ digest = attributes.getValue(attribute)
+ hd = HashDigest(algo, digest)
+ self.servers[-1][2]['digest'][hd.algo] = hd
+
+ def endElement(self, name):
+ pass
def parse_server_xml(path_to_file):
- try:
- handler = ServersXMLHandler()
- xml.sax.parse(path_to_file, handler)
- return handler.servers
- # handle exception if unable to open file
- except IOError, message:
- print >> sys.stderr, _('Error reading file:'), message
- # handle exception parsing file
- except xml.sax.SAXParseException, message:
- print >> sys.stderr, _('Error parsing file:'), message
+ try:
+ handler = ServersXMLHandler()
+ xml.sax.parse(path_to_file, handler)
+ return handler.servers
+ # handle exception if unable to open file
+ except IOError, message:
+ print >> sys.stderr, _('Error reading file:'), message
+ # handle exception parsing file
+ except xml.sax.SAXParseException, message:
+ print >> sys.stderr, _('Error parsing file:'), message
def set_unset_urgency_hint(window, unread_messages_no):
- """
- Sets/unset urgency hint in window argument depending if we have unread
- messages or not
- """
- if gajim.config.get('use_urgency_hint'):
- if unread_messages_no > 0:
- window.props.urgency_hint = True
- else:
- window.props.urgency_hint = False
+ """
+ Sets/unset urgency hint in window argument depending if we have unread
+ messages or not
+ """
+ if gajim.config.get('use_urgency_hint'):
+ if unread_messages_no > 0:
+ window.props.urgency_hint = True
+ else:
+ window.props.urgency_hint = False
def get_abspath_for_script(scriptname, want_type = False):
- """
- Check if we are svn or normal user and return abspath to asked script if
- want_type is True we return 'svn' or 'install'
- """
- if os.path.isdir('.svn'): # we are svn user
- type_ = 'svn'
- cwd = os.getcwd() # it's always ending with src
-
- if scriptname == 'gajim-remote':
- path_to_script = cwd + '/gajim-remote.py'
-
- elif scriptname == 'gajim':
- script = '#!/bin/sh\n' # the script we may create
- script += 'cd %s' % cwd
- path_to_script = cwd + '/../scripts/gajim_sm_script'
-
- try:
- if os.path.exists(path_to_script):
- os.remove(path_to_script)
-
- f = open(path_to_script, 'w')
- script += '\nexec python -OOt gajim.py $0 $@\n'
- f.write(script)
- f.close()
- os.chmod(path_to_script, 0700)
- except OSError: # do not traceback (could be a permission problem)
- #we talk about a file here
- s = _('Could not write to %s. Session Management support will not work') % path_to_script
- print >> sys.stderr, s
-
- else: # normal user (not svn user)
- type_ = 'install'
- # always make it like '/usr/local/bin/gajim'
- path_to_script = helpers.is_in_path(scriptname, True)
-
-
- if want_type:
- return path_to_script, type_
- else:
- return path_to_script
+ """
+ Check if we are svn or normal user and return abspath to asked script if
+ want_type is True we return 'svn' or 'install'
+ """
+ if os.path.isdir('.svn'): # we are svn user
+ type_ = 'svn'
+ cwd = os.getcwd() # it's always ending with src
+
+ if scriptname == 'gajim-remote':
+ path_to_script = cwd + '/gajim-remote.py'
+
+ elif scriptname == 'gajim':
+ script = '#!/bin/sh\n' # the script we may create
+ script += 'cd %s' % cwd
+ path_to_script = cwd + '/../scripts/gajim_sm_script'
+
+ try:
+ if os.path.exists(path_to_script):
+ os.remove(path_to_script)
+
+ f = open(path_to_script, 'w')
+ script += '\nexec python -OOt gajim.py $0 $@\n'
+ f.write(script)
+ f.close()
+ os.chmod(path_to_script, 0700)
+ except OSError: # do not traceback (could be a permission problem)
+ #we talk about a file here
+ s = _('Could not write to %s. Session Management support will not work') % path_to_script
+ print >> sys.stderr, s
+
+ else: # normal user (not svn user)
+ type_ = 'install'
+ # always make it like '/usr/local/bin/gajim'
+ path_to_script = helpers.is_in_path(scriptname, True)
+
+
+ if want_type:
+ return path_to_script, type_
+ else:
+ return path_to_script
def get_pixbuf_from_data(file_data, want_type = False):
- """
- Get image data and returns gtk.gdk.Pixbuf if want_type is True it also
- returns 'jpeg', 'png' etc
- """
- pixbufloader = gtk.gdk.PixbufLoader()
- try:
- pixbufloader.write(file_data)
- pixbufloader.close()
- pixbuf = pixbufloader.get_pixbuf()
- except gobject.GError: # 'unknown image format'
- pixbufloader.close()
- pixbuf = None
- if want_type:
- return None, None
- else:
- return None
-
- if want_type:
- typ = pixbufloader.get_format()['name']
- return pixbuf, typ
- else:
- return pixbuf
+ """
+ Get image data and returns gtk.gdk.Pixbuf if want_type is True it also
+ returns 'jpeg', 'png' etc
+ """
+ pixbufloader = gtk.gdk.PixbufLoader()
+ try:
+ pixbufloader.write(file_data)
+ pixbufloader.close()
+ pixbuf = pixbufloader.get_pixbuf()
+ except gobject.GError: # 'unknown image format'
+ pixbufloader.close()
+ pixbuf = None
+ if want_type:
+ return None, None
+ else:
+ return None
+
+ if want_type:
+ typ = pixbufloader.get_format()['name']
+ return pixbuf, typ
+ else:
+ return pixbuf
def get_invisible_cursor():
- pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
- color = gtk.gdk.Color()
- cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)
- return cursor
+ pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
+ color = gtk.gdk.Color()
+ cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)
+ return cursor
def get_current_desktop(window):
- """
- Return the current virtual desktop for given window
+ """
+ Return the current virtual desktop for given window
- NOTE: Window is a GDK window.
- """
- prop = window.property_get('_NET_CURRENT_DESKTOP')
- if prop is None: # it means it's normal window (not root window)
- # so we look for it's current virtual desktop in another property
- prop = window.property_get('_NET_WM_DESKTOP')
+ NOTE: Window is a GDK window.
+ """
+ prop = window.property_get('_NET_CURRENT_DESKTOP')
+ if prop is None: # it means it's normal window (not root window)
+ # so we look for it's current virtual desktop in another property
+ prop = window.property_get('_NET_WM_DESKTOP')
- if prop is not None:
- # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0]
- current_virtual_desktop_no = prop[2][0]
- return current_virtual_desktop_no
+ if prop is not None:
+ # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0]
+ current_virtual_desktop_no = prop[2][0]
+ return current_virtual_desktop_no
def possibly_move_window_in_current_desktop(window):
- """
- Moves GTK window to current virtual desktop if it is not in the current
- virtual desktop
-
- NOTE: Window is a GDK window.
- """
- if os.name == 'nt':
- return False
-
- root_window = gtk.gdk.screen_get_default().get_root_window()
- # current user's vd
- current_virtual_desktop_no = get_current_desktop(root_window)
-
- # vd roster window is in
- window_virtual_desktop = get_current_desktop(window.window)
-
- # if one of those is None, something went wrong and we cannot know
- # VD info, just hide it (default action) and not show it afterwards
- if None not in (window_virtual_desktop, current_virtual_desktop_no):
- if current_virtual_desktop_no != window_virtual_desktop:
- # we are in another VD that the window was
- # so show it in current VD
- window.present()
- return True
- return False
+ """
+ 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
+
+ root_window = gtk.gdk.screen_get_default().get_root_window()
+ # current user's vd
+ current_virtual_desktop_no = get_current_desktop(root_window)
+
+ # vd roster window is in
+ window_virtual_desktop = get_current_desktop(window.window)
+
+ # if one of those is None, something went wrong and we cannot know
+ # VD info, just hide it (default action) and not show it afterwards
+ if None not in (window_virtual_desktop, current_virtual_desktop_no):
+ if current_virtual_desktop_no != window_virtual_desktop:
+ # we are in another VD that the window was
+ # so show it in current VD
+ window.present()
+ return True
+ return False
def file_is_locked(path_to_file):
- """
- Return True if file is locked
-
- NOTE: Windows only.
- """
- if os.name != 'nt': # just in case
- return
-
- if not HAS_PYWIN32:
- return
-
- secur_att = pywintypes.SECURITY_ATTRIBUTES()
- secur_att.Initialize()
-
- try:
- # try make a handle for READING the file
- hfile = win32file.CreateFile(
- path_to_file, # path to file
- win32con.GENERIC_READ, # open for reading
- 0, # do not share with other proc
- secur_att,
- win32con.OPEN_EXISTING, # existing file only
- win32con.FILE_ATTRIBUTE_NORMAL, # normal file
- 0 # no attr. template
- )
- except pywintypes.error:
- return True
- else: # in case all went ok, close file handle (go to hell WinAPI)
- hfile.Close()
- return False
+ """
+ Return True if file is locked
+
+ NOTE: Windows only.
+ """
+ if os.name != 'nt': # just in case
+ return
+
+ if not HAS_PYWIN32:
+ return
+
+ secur_att = pywintypes.SECURITY_ATTRIBUTES()
+ secur_att.Initialize()
+
+ try:
+ # try make a handle for READING the file
+ hfile = win32file.CreateFile(
+ path_to_file, # path to file
+ win32con.GENERIC_READ, # open for reading
+ 0, # do not share with other proc
+ secur_att,
+ win32con.OPEN_EXISTING, # existing file only
+ win32con.FILE_ATTRIBUTE_NORMAL, # normal file
+ 0 # no attr. template
+ )
+ except pywintypes.error:
+ return True
+ else: # in case all went ok, close file handle (go to hell WinAPI)
+ hfile.Close()
+ return False
def _get_fade_color(treeview, selected, focused):
- """
- Get a gdk color that is between foreground and background in 0.3
- 0.7 respectively colors of the cell for the given treeview
- """
- style = treeview.style
- if selected:
- if focused: # is the window focused?
- state = gtk.STATE_SELECTED
- else: # is it not? NOTE: many gtk themes change bg on this
- state = gtk.STATE_ACTIVE
- else:
- state = gtk.STATE_NORMAL
- bg = style.base[state]
- fg = style.text[state]
-
- p = 0.3 # background
- q = 0.7 # foreground # p + q should do 1.0
- return gtk.gdk.Color(int(bg.red*p + fg.red*q),
- int(bg.green*p + fg.green*q),
- int(bg.blue*p + fg.blue*q))
+ """
+ Get a gdk color that is between foreground and background in 0.3
+ 0.7 respectively colors of the cell for the given treeview
+ """
+ style = treeview.style
+ if selected:
+ if focused: # is the window focused?
+ state = gtk.STATE_SELECTED
+ else: # is it not? NOTE: many gtk themes change bg on this
+ state = gtk.STATE_ACTIVE
+ else:
+ state = gtk.STATE_NORMAL
+ bg = style.base[state]
+ fg = style.text[state]
+
+ p = 0.3 # background
+ q = 0.7 # foreground # p + q should do 1.0
+ return gtk.gdk.Color(int(bg.red*p + fg.red*q),
+ int(bg.green*p + fg.green*q),
+ int(bg.blue*p + fg.blue*q))
def get_scaled_pixbuf(pixbuf, kind):
- """
- Return scaled pixbuf, keeping ratio etc or None kind is either "chat",
- "roster", "notification", "tooltip", "vcard"
- """
- # resize to a width / height for the avatar not to have distortion
- # (keep aspect ratio)
- width = gajim.config.get(kind + '_avatar_width')
- height = gajim.config.get(kind + '_avatar_height')
- if width < 1 or height < 1:
- return None
-
- # Pixbuf size
- pix_width = pixbuf.get_width()
- pix_height = pixbuf.get_height()
- # don't make avatars bigger than they are
- if pix_width < width and pix_height < height:
- return pixbuf # we don't want to make avatar bigger
-
- ratio = float(pix_width) / float(pix_height)
- if ratio > 1:
- w = width
- h = int(w / ratio)
- else:
- h = height
- w = int(h * ratio)
- scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER)
- return scaled_buf
+ """
+ Return scaled pixbuf, keeping ratio etc or None kind is either "chat",
+ "roster", "notification", "tooltip", "vcard"
+ """
+ # resize to a width / height for the avatar not to have distortion
+ # (keep aspect ratio)
+ width = gajim.config.get(kind + '_avatar_width')
+ height = gajim.config.get(kind + '_avatar_height')
+ if width < 1 or height < 1:
+ return None
+
+ # Pixbuf size
+ pix_width = pixbuf.get_width()
+ pix_height = pixbuf.get_height()
+ # don't make avatars bigger than they are
+ if pix_width < width and pix_height < height:
+ return pixbuf # we don't want to make avatar bigger
+
+ ratio = float(pix_width) / float(pix_height)
+ if ratio > 1:
+ w = width
+ h = int(w / ratio)
+ else:
+ h = height
+ w = int(h * ratio)
+ scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER)
+ return scaled_buf
def get_avatar_pixbuf_from_cache(fjid, use_local=True):
- """
- Check if jid has cached avatar and if that avatar is valid image (can be
- shown)
-
- Returns None if there is no image in vcard/
- Returns 'ask' if cached vcard should not be used (user changed his vcard, so
- we have new sha) or if we don't have the vcard
- """
- jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
- if gajim.config.get('hide_avatar_of_transport') and\
- gajim.jid_is_transport(jid):
- # don't show avatar for the transport itself
- return None
-
- if any(jid in gajim.contacts.get_gc_list(acc) for acc in gajim.connections):
- is_groupchat_contact = True
- else:
- is_groupchat_contact = False
-
- puny_jid = helpers.sanitize_filename(jid)
- if is_groupchat_contact:
- puny_nick = helpers.sanitize_filename(nick)
- path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid,
- puny_nick) + '_local'
- else:
- path = os.path.join(gajim.VCARD_PATH, puny_jid)
- local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
- '_local'
- if use_local:
- for extension in ('.png', '.jpeg'):
- local_avatar_path = local_avatar_basepath + extension
- if os.path.isfile(local_avatar_path):
- avatar_file = open(local_avatar_path, 'rb')
- avatar_data = avatar_file.read()
- avatar_file.close()
- return get_pixbuf_from_data(avatar_data)
-
- if not os.path.isfile(path):
- return 'ask'
-
- vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid,
- is_groupchat_contact)
- if not vcard_dict: # This can happen if cached vcard is too old
- return 'ask'
- if 'PHOTO' not in vcard_dict:
- return None
- pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0]
- return pixbuf
+ """
+ Check if jid has cached avatar and if that avatar is valid image (can be
+ shown)
+
+ Returns None if there is no image in vcard/
+ Returns 'ask' if cached vcard should not be used (user changed his vcard, so
+ we have new sha) or if we don't have the vcard
+ """
+ jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
+ if gajim.config.get('hide_avatar_of_transport') and\
+ gajim.jid_is_transport(jid):
+ # don't show avatar for the transport itself
+ return None
+
+ if any(jid in gajim.contacts.get_gc_list(acc) for acc in gajim.connections):
+ is_groupchat_contact = True
+ else:
+ is_groupchat_contact = False
+
+ puny_jid = helpers.sanitize_filename(jid)
+ if is_groupchat_contact:
+ puny_nick = helpers.sanitize_filename(nick)
+ path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+ local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid,
+ puny_nick) + '_local'
+ else:
+ path = os.path.join(gajim.VCARD_PATH, puny_jid)
+ local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
+ '_local'
+ if use_local:
+ for extension in ('.png', '.jpeg'):
+ local_avatar_path = local_avatar_basepath + extension
+ if os.path.isfile(local_avatar_path):
+ avatar_file = open(local_avatar_path, 'rb')
+ avatar_data = avatar_file.read()
+ avatar_file.close()
+ return get_pixbuf_from_data(avatar_data)
+
+ if not os.path.isfile(path):
+ return 'ask'
+
+ vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid,
+ is_groupchat_contact)
+ if not vcard_dict: # This can happen if cached vcard is too old
+ return 'ask'
+ if 'PHOTO' not in vcard_dict:
+ return None
+ pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0]
+ return pixbuf
def make_gtk_month_python_month(month):
- """
- GTK starts counting months from 0, so January is 0 but Python's time start
- from 1, so align to Python
+ """
+ GTK starts counting months from 0, so January is 0 but Python's time start
+ from 1, so align to Python
- NOTE: Month MUST be an integer.
- """
- return month + 1
+ NOTE: Month MUST be an integer.
+ """
+ return month + 1
def make_python_month_gtk_month(month):
- return month - 1
+ return month - 1
def make_color_string(color):
- """
- Create #aabbcc color string from gtk color
- """
- col = '#'
- for i in ('red', 'green', 'blue'):
- h = hex(getattr(color, i) / (16*16)).split('x')[1]
- if len(h) == 1:
- h = '0' + h
- col += h
- return col
+ """
+ Create #aabbcc color string from gtk color
+ """
+ col = '#'
+ for i in ('red', 'green', 'blue'):
+ h = hex(getattr(color, i) / (16*16)).split('x')[1]
+ if len(h) == 1:
+ h = '0' + h
+ col += h
+ return col
def make_pixbuf_grayscale(pixbuf):
- pixbuf2 = pixbuf.copy()
- pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False)
- return pixbuf2
+ pixbuf2 = pixbuf.copy()
+ pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False)
+ return pixbuf2
def get_path_to_generic_or_avatar(generic, jid = None, suffix = None):
- """
- Choose between avatar image and default image
-
- Returns full path to the avatar image if it exists, otherwise returns full
- path to the image. generic must be with extension and suffix without
- """
- if jid:
- # we want an avatar
- puny_jid = helpers.sanitize_filename(jid)
- path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix
- path_to_local_file = path_to_file + '_local'
- for extension in ('.png', '.jpeg'):
- path_to_local_file_full = path_to_local_file + extension
- if os.path.exists(path_to_local_file_full):
- return path_to_local_file_full
- for extension in ('.png', '.jpeg'):
- path_to_file_full = path_to_file + extension
- if os.path.exists(path_to_file_full):
- return path_to_file_full
- return os.path.abspath(generic)
+ """
+ Choose between avatar image and default image
+
+ Returns full path to the avatar image if it exists, otherwise returns full
+ path to the image. generic must be with extension and suffix without
+ """
+ if jid:
+ # we want an avatar
+ puny_jid = helpers.sanitize_filename(jid)
+ path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix
+ path_to_local_file = path_to_file + '_local'
+ for extension in ('.png', '.jpeg'):
+ path_to_local_file_full = path_to_local_file + extension
+ if os.path.exists(path_to_local_file_full):
+ return path_to_local_file_full
+ for extension in ('.png', '.jpeg'):
+ path_to_file_full = path_to_file + extension
+ if os.path.exists(path_to_file_full):
+ return path_to_file_full
+ return os.path.abspath(generic)
def decode_filechooser_file_paths(file_paths):
- """
- Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX
- file_paths MUST be LIST
- """
- file_paths_list = list()
-
- if os.name == 'nt': # decode as UTF-8 under Windows
- for file_path in file_paths:
- file_path = file_path.decode('utf8')
- file_paths_list.append(file_path)
- else:
- for file_path in file_paths:
- try:
- file_path = file_path.decode(sys.getfilesystemencoding())
- except Exception:
- try:
- file_path = file_path.decode('utf-8')
- except Exception:
- pass
- file_paths_list.append(file_path)
-
- return file_paths_list
+ """
+ Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX
+ file_paths MUST be LIST
+ """
+ file_paths_list = list()
+
+ if os.name == 'nt': # decode as UTF-8 under Windows
+ for file_path in file_paths:
+ file_path = file_path.decode('utf8')
+ file_paths_list.append(file_path)
+ else:
+ for file_path in file_paths:
+ try:
+ file_path = file_path.decode(sys.getfilesystemencoding())
+ except Exception:
+ try:
+ file_path = file_path.decode('utf-8')
+ except Exception:
+ pass
+ file_paths_list.append(file_path)
+
+ return file_paths_list
def possibly_set_gajim_as_xmpp_handler():
- """
- Register (by default only the first time) 'xmmp:' to Gajim
- """
- path_to_dot_kde = os.path.expanduser('~/.kde')
- if os.path.exists(path_to_dot_kde):
- path_to_kde_file = os.path.join(path_to_dot_kde,
- 'share/services/xmpp.protocol')
- else:
- path_to_kde_file = None
-
- def set_gajim_as_xmpp_handler(is_checked=None):
- if is_checked is not None:
- # come from confirmation dialog
- gajim.config.set('check_if_gajim_is_default', is_checked)
- path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True)
- if path_to_gajim_script:
- if typ == 'svn':
- command = path_to_gajim_script + ' handle_uri %s'
- else: # 'installed'
- command = 'gajim-remote handle_uri %s'
-
- # setting for GNOME/Gconf
- client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True)
- client.set_string('/desktop/gnome/url-handlers/xmpp/command', command)
- client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False)
-
- # setting for KDE
- if path_to_kde_file is not None: # user has run kde at least once
- try:
- f = open(path_to_kde_file, 'a')
- f.write('''\
+ """
+ Register (by default only the first time) 'xmmp:' to Gajim
+ """
+ path_to_dot_kde = os.path.expanduser('~/.kde')
+ if os.path.exists(path_to_dot_kde):
+ path_to_kde_file = os.path.join(path_to_dot_kde,
+ 'share/services/xmpp.protocol')
+ else:
+ path_to_kde_file = None
+
+ def set_gajim_as_xmpp_handler(is_checked=None):
+ if is_checked is not None:
+ # come from confirmation dialog
+ gajim.config.set('check_if_gajim_is_default', is_checked)
+ path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True)
+ if path_to_gajim_script:
+ if typ == 'svn':
+ command = path_to_gajim_script + ' handle_uri %s'
+ else: # 'installed'
+ command = 'gajim-remote handle_uri %s'
+
+ # setting for GNOME/Gconf
+ client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True)
+ client.set_string('/desktop/gnome/url-handlers/xmpp/command', command)
+ client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False)
+
+ # setting for KDE
+ if path_to_kde_file is not None: # user has run kde at least once
+ try:
+ f = open(path_to_kde_file, 'a')
+ f.write('''\
[Protocol]
exec=%s "%%u"
protocol=xmpp
@@ -778,355 +778,353 @@ deleting=false
icon=gajim
Description=xmpp
''' % command)
- f.close()
- except IOError:
- log.debug("I/O Error writing settings to %s", repr(path_to_kde_file), exc_info=True)
- else: # no gajim remote, stop ask user everytime
- gajim.config.set('check_if_gajim_is_default', False)
-
- try:
- import gconf
- # in try because daemon may not be there
- client = gconf.client_get_default()
- except Exception:
- return
-
- old_command = client.get_string('/desktop/gnome/url-handlers/xmpp/command')
- if not old_command or old_command.endswith(' open_chat %s'):
- # first time (GNOME/GCONF) or old Gajim version
- we_set = True
- elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file):
- # only the first time (KDE)
- we_set = True
- else:
- we_set = False
-
- if we_set:
- set_gajim_as_xmpp_handler()
- elif old_command and not old_command.endswith(' handle_uri %s'):
- # xmpp: is currently handled by another program, so ask the user
- pritext = _('Gajim is not the default Jabber client')
- sectext = _('Would you like to make Gajim the default Jabber client?')
- checktext = _('Always check to see if Gajim is the default Jabber client '
- 'on startup')
- def on_cancel(checked):
- gajim.config.set('check_if_gajim_is_default', checked)
- dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, checktext,
- set_gajim_as_xmpp_handler, on_cancel)
- if gajim.config.get('check_if_gajim_is_default'):
- dlg.checkbutton.set_active(True)
+ f.close()
+ except IOError:
+ log.debug("I/O Error writing settings to %s", repr(path_to_kde_file), exc_info=True)
+ else: # no gajim remote, stop ask user everytime
+ gajim.config.set('check_if_gajim_is_default', False)
+
+ try:
+ import gconf
+ # in try because daemon may not be there
+ client = gconf.client_get_default()
+ except Exception:
+ return
+
+ old_command = client.get_string('/desktop/gnome/url-handlers/xmpp/command')
+ if not old_command or old_command.endswith(' open_chat %s'):
+ # first time (GNOME/GCONF) or old Gajim version
+ we_set = True
+ elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file):
+ # only the first time (KDE)
+ we_set = True
+ else:
+ we_set = False
+
+ if we_set:
+ set_gajim_as_xmpp_handler()
+ elif old_command and not old_command.endswith(' handle_uri %s'):
+ # xmpp: is currently handled by another program, so ask the user
+ pritext = _('Gajim is not the default Jabber client')
+ sectext = _('Would you like to make Gajim the default Jabber client?')
+ checktext = _('Always check to see if Gajim is the default Jabber client '
+ 'on startup')
+ def on_cancel(checked):
+ gajim.config.set('check_if_gajim_is_default', checked)
+ dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, checktext,
+ set_gajim_as_xmpp_handler, on_cancel)
+ if gajim.config.get('check_if_gajim_is_default'):
+ dlg.checkbutton.set_active(True)
def escape_underscore(s):
- """
- Escape underlines to prevent them from being interpreted as keyboard
- accelerators
- """
- return s.replace('_', '__')
+ """
+ Escape underlines to prevent them from being interpreted as keyboard
+ accelerators
+ """
+ return s.replace('_', '__')
def get_state_image_from_file_path_show(file_path, show):
- state_file = show.replace(' ', '_')
- files = []
- files.append(os.path.join(file_path, state_file + '.png'))
- files.append(os.path.join(file_path, state_file + '.gif'))
- image = gtk.Image()
- image.set_from_pixbuf(None)
- for file_ in files:
- if os.path.exists(file_):
- image.set_from_file(file_)
- break
-
- return image
+ state_file = show.replace(' ', '_')
+ files = []
+ files.append(os.path.join(file_path, state_file + '.png'))
+ files.append(os.path.join(file_path, state_file + '.gif'))
+ image = gtk.Image()
+ image.set_from_pixbuf(None)
+ for file_ in files:
+ if os.path.exists(file_):
+ image.set_from_file(file_)
+ break
+
+ return image
def get_possible_button_event(event):
- """
- Mouse or keyboard caused the event?
- """
- if event.type == gtk.gdk.KEY_PRESS:
- return 0 # no event.button so pass 0
- # BUTTON_PRESS event, so pass event.button
- return event.button
+ """
+ Mouse or keyboard caused the event?
+ """
+ if event.type == gtk.gdk.KEY_PRESS:
+ return 0 # no event.button so pass 0
+ # BUTTON_PRESS event, so pass event.button
+ return event.button
def destroy_widget(widget):
- widget.destroy()
+ widget.destroy()
def on_avatar_save_as_menuitem_activate(widget, jid, account, default_name=''):
- def on_continue(response, file_path):
- if response < 0:
- return
- pixbuf = get_avatar_pixbuf_from_cache(jid)
- path, extension = os.path.splitext(file_path)
- if not extension:
- # Silently save as Jpeg image
- image_format = 'jpeg'
- file_path += '.jpeg'
- elif extension == 'jpg':
- image_format = 'jpeg'
- else:
- image_format = extension[1:] # remove leading dot
-
- # Save image
- try:
- pixbuf.save(file_path, image_format)
- except glib.GError, e:
- log.debug('Error saving avatar: %s' % str(e))
- if os.path.exists(file_path):
- os.remove(file_path)
- new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg'
- def on_ok(file_path, pixbuf):
- pixbuf.save(file_path, 'jpeg')
- dialogs.ConfirmationDialog(_('Extension not supported'),
- _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?'
- ) % {'type': image_format, 'new_filename': new_file_path},
- on_response_ok = (on_ok, new_file_path, pixbuf))
- else:
- dialog.destroy()
-
- def on_ok(widget):
- file_path = dialog.get_filename()
- file_path = decode_filechooser_file_paths((file_path,))[0]
- if os.path.exists(file_path):
- # check if we have write permissions
- if not os.access(file_path, os.W_OK):
- file_name = os.path.basename(file_path)
- dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' %
- file_name),
- _('A file with this name already exists and you do not have '
- 'permission to overwrite it.'))
- return
- dialog2 = dialogs.FTOverwriteConfirmationDialog(
- _('This file already exists'), _('What do you want to do?'),
- propose_resume=False, on_response=(on_continue, file_path))
- dialog2.set_transient_for(dialog)
- dialog2.set_destroy_with_parent(True)
- else:
- dirname = os.path.dirname(file_path)
- if not os.access(dirname, os.W_OK):
- dialogs.ErrorDialog(_('Directory "%s" is not writable') % \
- dirname, _('You do not have permission to create files in this'
- ' directory.'))
- return
-
- on_continue(0, file_path)
-
- def on_cancel(widget):
- dialog.destroy()
-
- dialog = dialogs.FileChooserDialog(title_text=_('Save Image as...'),
- action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK),
- default_response=gtk.RESPONSE_OK,
- current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok,
- on_response_cancel=on_cancel)
-
- dialog.set_current_name(default_name + '.jpeg')
- dialog.connect('delete-event', lambda widget, event:
- on_cancel(widget))
+ def on_continue(response, file_path):
+ if response < 0:
+ return
+ pixbuf = get_avatar_pixbuf_from_cache(jid)
+ path, extension = os.path.splitext(file_path)
+ if not extension:
+ # Silently save as Jpeg image
+ image_format = 'jpeg'
+ file_path += '.jpeg'
+ elif extension == 'jpg':
+ image_format = 'jpeg'
+ else:
+ image_format = extension[1:] # remove leading dot
+
+ # Save image
+ try:
+ pixbuf.save(file_path, image_format)
+ except glib.GError, e:
+ log.debug('Error saving avatar: %s' % str(e))
+ if os.path.exists(file_path):
+ os.remove(file_path)
+ new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg'
+ def on_ok(file_path, pixbuf):
+ pixbuf.save(file_path, 'jpeg')
+ dialogs.ConfirmationDialog(_('Extension not supported'),
+ _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?'
+ ) % {'type': image_format, 'new_filename': new_file_path},
+ on_response_ok = (on_ok, new_file_path, pixbuf))
+ else:
+ dialog.destroy()
+
+ def on_ok(widget):
+ file_path = dialog.get_filename()
+ file_path = decode_filechooser_file_paths((file_path,))[0]
+ if os.path.exists(file_path):
+ # check if we have write permissions
+ if not os.access(file_path, os.W_OK):
+ file_name = os.path.basename(file_path)
+ dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' %
+ file_name),
+ _('A file with this name already exists and you do not have '
+ 'permission to overwrite it.'))
+ return
+ dialog2 = dialogs.FTOverwriteConfirmationDialog(
+ _('This file already exists'), _('What do you want to do?'),
+ propose_resume=False, on_response=(on_continue, file_path))
+ dialog2.set_transient_for(dialog)
+ dialog2.set_destroy_with_parent(True)
+ else:
+ dirname = os.path.dirname(file_path)
+ if not os.access(dirname, os.W_OK):
+ dialogs.ErrorDialog(_('Directory "%s" is not writable') % \
+ dirname, _('You do not have permission to create files in this'
+ ' directory.'))
+ return
+
+ on_continue(0, file_path)
+
+ def on_cancel(widget):
+ dialog.destroy()
+
+ dialog = dialogs.FileChooserDialog(title_text=_('Save Image as...'),
+ action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK),
+ default_response=gtk.RESPONSE_OK,
+ current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok,
+ on_response_cancel=on_cancel)
+
+ dialog.set_current_name(default_name + '.jpeg')
+ dialog.connect('delete-event', lambda widget, event:
+ on_cancel(widget))
def on_bm_header_changed_state(widget, event):
- widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
+ widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
def create_combobox(value_list, selected_value = None):
- """
- Value_list is [(label1, value1)]
- """
- liststore = gtk.ListStore(str, str)
- combobox = gtk.ComboBox(liststore)
- cell = gtk.CellRendererText()
- combobox.pack_start(cell, True)
- combobox.add_attribute(cell, 'text', 0)
- i = -1
- for value in value_list:
- liststore.append(value)
- if selected_value == value[1]:
- i = value_list.index(value)
- if i > -1:
- combobox.set_active(i)
- combobox.show_all()
- return combobox
+ """
+ Value_list is [(label1, value1)]
+ """
+ liststore = gtk.ListStore(str, str)
+ combobox = gtk.ComboBox(liststore)
+ cell = gtk.CellRendererText()
+ combobox.pack_start(cell, True)
+ combobox.add_attribute(cell, 'text', 0)
+ i = -1
+ for value in value_list:
+ liststore.append(value)
+ if selected_value == value[1]:
+ i = value_list.index(value)
+ if i > -1:
+ combobox.set_active(i)
+ combobox.show_all()
+ return combobox
def create_list_multi(value_list, selected_values=None):
- """
- Value_list is [(label1, value1)]
- """
- liststore = gtk.ListStore(str, str)
- treeview = gtk.TreeView(liststore)
- treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
- treeview.set_headers_visible(False)
- col = gtk.TreeViewColumn()
- treeview.append_column(col)
- cell = gtk.CellRendererText()
- col.pack_start(cell, True)
- col.set_attributes(cell, text=0)
- for value in value_list:
- iter = liststore.append(value)
- if value[1] in selected_values:
- treeview.get_selection().select_iter(iter)
- treeview.show_all()
- return treeview
+ """
+ Value_list is [(label1, value1)]
+ """
+ liststore = gtk.ListStore(str, str)
+ treeview = gtk.TreeView(liststore)
+ treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ treeview.set_headers_visible(False)
+ col = gtk.TreeViewColumn()
+ treeview.append_column(col)
+ cell = gtk.CellRendererText()
+ col.pack_start(cell, True)
+ col.set_attributes(cell, text=0)
+ for value in value_list:
+ iter = liststore.append(value)
+ if value[1] in selected_values:
+ treeview.get_selection().select_iter(iter)
+ treeview.show_all()
+ return treeview
def load_iconset(path, pixbuf2=None, transport=False):
- """
- Load full iconset from the given path, and add pixbuf2 on top left of each
- static images
- """
- path += '/'
- if transport:
- list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
- 'not in roster')
- else:
- list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible', 'offline', 'error', 'requested', 'event', 'opened',
- 'closed', 'not in roster', 'muc_active', 'muc_inactive')
- if pixbuf2:
- list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'offline', 'error', 'requested', 'event', 'not in roster')
- return _load_icon_list(list_, path, pixbuf2)
+ """
+ Load full iconset from the given path, and add pixbuf2 on top left of each
+ static images
+ """
+ path += '/'
+ if transport:
+ list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
+ 'not in roster')
+ else:
+ list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
+ 'invisible', 'offline', 'error', 'requested', 'event', 'opened',
+ 'closed', 'not in roster', 'muc_active', 'muc_inactive')
+ if pixbuf2:
+ list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
+ 'offline', 'error', 'requested', 'event', 'not in roster')
+ return _load_icon_list(list_, path, pixbuf2)
def load_icon(icon_name):
- """
- Load an icon from the iconset in 16x16
- """
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '')
- icon_list = _load_icon_list([icon_name], path)
- return icon_list[icon_name]
+ """
+ Load an icon from the iconset in 16x16
+ """
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '')
+ icon_list = _load_icon_list([icon_name], path)
+ return icon_list[icon_name]
def load_mood_icon(icon_name):
- """
- Load an icon from the mood iconset in 16x16
- """
- iconset = gajim.config.get('mood_iconset')
- path = os.path.join(helpers.get_mood_iconset_path(iconset), '')
- icon_list = _load_icon_list([icon_name], path)
- return icon_list[icon_name]
+ """
+ Load an icon from the mood iconset in 16x16
+ """
+ iconset = gajim.config.get('mood_iconset')
+ path = os.path.join(helpers.get_mood_iconset_path(iconset), '')
+ icon_list = _load_icon_list([icon_name], path)
+ return icon_list[icon_name]
def load_activity_icon(category, activity = None):
- """
- Load an icon from the activity iconset in 16x16
- """
- iconset = gajim.config.get('activity_iconset')
- path = os.path.join(helpers.get_activity_iconset_path(iconset),
- category, '')
- if activity is None:
- activity = 'category'
- icon_list = _load_icon_list([activity], path)
- return icon_list[activity]
+ """
+ Load an icon from the activity iconset in 16x16
+ """
+ iconset = gajim.config.get('activity_iconset')
+ path = os.path.join(helpers.get_activity_iconset_path(iconset),
+ category, '')
+ if activity is None:
+ activity = 'category'
+ icon_list = _load_icon_list([activity], path)
+ return icon_list[activity]
def load_icons_meta():
- """
- Load and return - AND + small icons to put on top left of an icon for meta
- contacts
- """
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- # try to find opened_meta.png file, else opened.png else nopixbuf merge
- path_opened = os.path.join(path, 'opened_meta.png')
- if not os.path.isfile(path_opened):
- path_opened = os.path.join(path, 'opened.png')
- if os.path.isfile(path_opened):
- pixo = gtk.gdk.pixbuf_new_from_file(path_opened)
- else:
- pixo = None
- # Same thing for closed
- path_closed = os.path.join(path, 'opened_meta.png')
- if not os.path.isfile(path_closed):
- path_closed = os.path.join(path, 'closed.png')
- if os.path.isfile(path_closed):
- pixc = gtk.gdk.pixbuf_new_from_file(path_closed)
- else:
- pixc = None
- return pixo, pixc
+ """
+ Load and return - AND + small icons to put on top left of an icon for meta
+ contacts
+ """
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ # try to find opened_meta.png file, else opened.png else nopixbuf merge
+ path_opened = os.path.join(path, 'opened_meta.png')
+ if not os.path.isfile(path_opened):
+ path_opened = os.path.join(path, 'opened.png')
+ if os.path.isfile(path_opened):
+ pixo = gtk.gdk.pixbuf_new_from_file(path_opened)
+ else:
+ pixo = None
+ # Same thing for closed
+ path_closed = os.path.join(path, 'opened_meta.png')
+ if not os.path.isfile(path_closed):
+ path_closed = os.path.join(path, 'closed.png')
+ if os.path.isfile(path_closed):
+ pixc = gtk.gdk.pixbuf_new_from_file(path_closed)
+ else:
+ pixc = None
+ return pixo, pixc
def _load_icon_list(icons_list, path, pixbuf2 = None):
- """
- Load icons in icons_list from the given path, and add pixbuf2 on top left of
- each static images
- """
- imgs = {}
- for icon in icons_list:
- # try to open a pixfile with the correct method
- icon_file = icon.replace(' ', '_')
- files = []
- files.append(path + icon_file + '.gif')
- files.append(path + icon_file + '.png')
- image = gtk.Image()
- image.show()
- imgs[icon] = image
- for file_ in files: # loop seeking for either gif or png
- if os.path.exists(file_):
- image.set_from_file(file_)
- if pixbuf2 and image.get_storage_type() == gtk.IMAGE_PIXBUF:
- # add pixbuf2 on top-left corner of image
- pixbuf1 = image.get_pixbuf()
- pixbuf2.composite(pixbuf1, 0, 0,
- pixbuf2.get_property('width'),
- pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
- gtk.gdk.INTERP_NEAREST, 255)
- image.set_from_pixbuf(pixbuf1)
- break
- return imgs
+ """
+ Load icons in icons_list from the given path, and add pixbuf2 on top left of
+ each static images
+ """
+ imgs = {}
+ for icon in icons_list:
+ # try to open a pixfile with the correct method
+ icon_file = icon.replace(' ', '_')
+ files = []
+ files.append(path + icon_file + '.gif')
+ files.append(path + icon_file + '.png')
+ image = gtk.Image()
+ image.show()
+ imgs[icon] = image
+ for file_ in files: # loop seeking for either gif or png
+ if os.path.exists(file_):
+ image.set_from_file(file_)
+ if pixbuf2 and image.get_storage_type() == gtk.IMAGE_PIXBUF:
+ # add pixbuf2 on top-left corner of image
+ pixbuf1 = image.get_pixbuf()
+ pixbuf2.composite(pixbuf1, 0, 0,
+ pixbuf2.get_property('width'),
+ pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
+ gtk.gdk.INTERP_NEAREST, 255)
+ image.set_from_pixbuf(pixbuf1)
+ break
+ return imgs
def make_jabber_state_images():
- """
- Initialize jabber_state_images dictionary
- """
- iconset = gajim.config.get('iconset')
- if iconset:
- if helpers.get_iconset_path(iconset):
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- if not os.path.exists(path):
- iconset = gajim.config.DEFAULT_ICONSET
- gajim.config.set('iconset', iconset)
- else:
- iconset = gajim.config.DEFAULT_ICONSET
- gajim.config.set('iconset', iconset)
- else:
- iconset = gajim.config.DEFAULT_ICONSET
- gajim.config.set('iconset', iconset)
-
- path = os.path.join(helpers.get_iconset_path(iconset), '32x32')
- gajim.interface.jabber_state_images['32'] = load_iconset(path)
-
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- gajim.interface.jabber_state_images['16'] = load_iconset(path)
-
- pixo, pixc = load_icons_meta()
- gajim.interface.jabber_state_images['opened'] = load_iconset(path, pixo)
- gajim.interface.jabber_state_images['closed'] = load_iconset(path, pixc)
+ """
+ Initialize jabber_state_images dictionary
+ """
+ iconset = gajim.config.get('iconset')
+ if iconset:
+ if helpers.get_iconset_path(iconset):
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ if not os.path.exists(path):
+ iconset = gajim.config.DEFAULT_ICONSET
+ gajim.config.set('iconset', iconset)
+ else:
+ iconset = gajim.config.DEFAULT_ICONSET
+ gajim.config.set('iconset', iconset)
+ else:
+ iconset = gajim.config.DEFAULT_ICONSET
+ gajim.config.set('iconset', iconset)
+
+ path = os.path.join(helpers.get_iconset_path(iconset), '32x32')
+ gajim.interface.jabber_state_images['32'] = load_iconset(path)
+
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ gajim.interface.jabber_state_images['16'] = load_iconset(path)
+
+ pixo, pixc = load_icons_meta()
+ gajim.interface.jabber_state_images['opened'] = load_iconset(path, pixo)
+ gajim.interface.jabber_state_images['closed'] = load_iconset(path, pixc)
def reload_jabber_state_images():
- make_jabber_state_images()
- gajim.interface.roster.update_jabber_state_images()
+ make_jabber_state_images()
+ gajim.interface.roster.update_jabber_state_images()
def label_set_autowrap(widget):
- """
- Make labels automatically re-wrap if their containers are resized.
- Accepts label or container widgets
- """
- if isinstance (widget, gtk.Container):
- children = widget.get_children()
- for i in xrange (len (children)):
- label_set_autowrap(children[i])
- elif isinstance(widget, gtk.Label):
- widget.set_line_wrap(True)
- widget.connect_after('size-allocate', __label_size_allocate)
+ """
+ Make labels automatically re-wrap if their containers are resized.
+ Accepts label or container widgets
+ """
+ if isinstance (widget, gtk.Container):
+ children = widget.get_children()
+ for i in xrange (len (children)):
+ label_set_autowrap(children[i])
+ elif isinstance(widget, gtk.Label):
+ widget.set_line_wrap(True)
+ widget.connect_after('size-allocate', __label_size_allocate)
def __label_size_allocate(widget, allocation):
- """
- Callback which re-allocates the size of a label
- """
- layout = widget.get_layout()
-
- lw_old, lh_old = layout.get_size()
- # fixed width labels
- if lw_old/pango.SCALE == allocation.width:
- return
-
- # set wrap width to the pango.Layout of the labels ###
- layout.set_width (allocation.width * pango.SCALE)
- lw, lh = layout.get_size ()
-
- if lh_old != lh:
- widget.set_size_request (-1, lh / pango.SCALE)
-
-# vim: se ts=3:
+ """
+ Callback which re-allocates the size of a label
+ """
+ layout = widget.get_layout()
+
+ lw_old, lh_old = layout.get_size()
+ # fixed width labels
+ if lw_old/pango.SCALE == allocation.width:
+ return
+
+ # set wrap width to the pango.Layout of the labels ###
+ layout.set_width (allocation.width * pango.SCALE)
+ lw, lh = layout.get_size ()
+
+ if lh_old != lh:
+ widget.set_size_request (-1, lh / pango.SCALE)
diff --git a/src/gtkspell.py b/src/gtkspell.py
index c5dc62af8..aa1adaec7 100644
--- a/src/gtkspell.py
+++ b/src/gtkspell.py
@@ -97,4 +97,3 @@ class Spell(object):
def get_from_text_view(textview):
return Spell(textview, create=False)
-
diff --git a/src/gui_interface.py b/src/gui_interface.py
index e2eeaf1b2..4ee0b1236 100644
--- a/src/gui_interface.py
+++ b/src/gui_interface.py
@@ -49,9 +49,9 @@ from common import gajim
from common import dbus_support
if dbus_support.supported:
- from music_track_listener import MusicTrackListener
- from common import location_listener
- import dbus
+ from music_track_listener import MusicTrackListener
+ from common import location_listener
+ import dbus
import gtkgui_helpers
@@ -102,3350 +102,3348 @@ class Interface:
### Methods handling events from connection
################################################################################
- def handle_event_roster(self, account, data):
- #('ROSTER', account, array)
- # FIXME: Those methods depend to highly on each other
- # and the order in which they are called
- self.roster.fill_contacts_and_groups_dicts(data, account)
- self.roster.add_account_contacts(account)
- self.roster.fire_up_unread_messages_events(account)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('Roster', (account, data))
-
- def handle_event_warning(self, unused, data):
- #('WARNING', account, (title_text, section_text))
- dialogs.WarningDialog(data[0], data[1])
-
- def handle_event_error(self, unused, data):
- #('ERROR', account, (title_text, section_text))
- dialogs.ErrorDialog(data[0], data[1])
-
- def handle_event_information(self, unused, data):
- #('INFORMATION', account, (title_text, section_text))
- dialogs.InformationDialog(data[0], data[1])
-
- def handle_event_ask_new_nick(self, account, data):
- #('ASK_NEW_NICK', account, (room_jid,))
- room_jid = data[0]
- title = _('Unable to join group chat')
- prompt = _('Your desired nickname in group chat %s is in use or '
- 'registered by another occupant.\nPlease specify another nickname '
- 'below:') % room_jid
- check_text = _('Always use this nickname when there is a conflict')
- if 'change_nick_dialog' in self.instances:
- self.instances['change_nick_dialog'].add_room(account, room_jid,
- prompt)
- else:
- self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
- account, room_jid, title, prompt)
-
- def handle_event_http_auth(self, account, data):
- #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
- def response(account, iq_obj, answer):
- self.dialog.destroy()
- gajim.connections[account].build_http_auth_answer(iq_obj, answer)
-
- def on_yes(is_checked, account, iq_obj):
- response(account, iq_obj, 'yes')
-
- sec_msg = _('Do you accept this request?')
- if gajim.get_number_of_connected_accounts() > 1:
- sec_msg = _('Do you accept this request on account %s?') % account
- if data[4]:
- sec_msg = data[4] + '\n' + sec_msg
- self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
- '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1],
- 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]),
- on_response_no=(response, account, data[3], 'no'))
-
- def handle_event_error_answer(self, account, array):
- #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
- id_, jid_from, errmsg, errcode = array
- if unicode(errcode) in ('400', '403', '406') and id_:
- # show the error dialog
- ft = self.instances['file_transfers']
- sid = id_
- if len(id_) > 3 and id_[2] == '_':
- sid = id_[3:]
- if sid in ft.files_props['s']:
- file_props = ft.files_props['s'][sid]
- if unicode(errcode) == '400':
- file_props['error'] = -3
- else:
- file_props['error'] = -4
- self.handle_event_file_request_error(account,
- (jid_from, file_props, errmsg))
- conn = gajim.connections[account]
- conn.disconnect_transfer(file_props)
- return
- elif unicode(errcode) == '404':
- conn = gajim.connections[account]
- sid = id_
- if len(id_) > 3 and id_[2] == '_':
- sid = id_[3:]
- if sid in conn.files_props:
- file_props = conn.files_props[sid]
- self.handle_event_file_send_error(account,
- (jid_from, file_props))
- conn.disconnect_transfer(file_props)
- return
-
- ctrl = self.msg_win_mgr.get_control(jid_from, account)
- if ctrl and ctrl.type_id == message_control.TYPE_GC:
- ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
-
- def handle_event_con_type(self, account, con_type):
- # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
- gajim.con_types[account] = con_type
- self.roster.draw_account(account)
-
- def handle_event_connection_lost(self, account, array):
- # ('CONNECTION_LOST', account, [title, text])
- path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48)
- notify.popup(_('Connection Failed'), account, account,
- 'connection_failed', path, array[0], array[1])
-
- def unblock_signed_in_notifications(self, account):
- gajim.block_signed_in_notifications[account] = False
-
- def handle_event_status(self, account, show): # OUR status
- #('STATUS', account, show)
- model = self.roster.status_combobox.get_model()
- if show in ('offline', 'error'):
- for name in self.instances[account]['online_dialog'].keys():
- # .keys() is needed to not have a dictionary length changed during
- # iteration error
- self.instances[account]['online_dialog'][name].destroy()
- del self.instances[account]['online_dialog'][name]
- for request in self.gpg_passphrase.values():
- if request:
- request.interrupt()
- if account in self.pass_dialog:
- self.pass_dialog[account].window.destroy()
- if show == 'offline':
- # sensitivity for this menuitem
- if gajim.get_number_of_connected_accounts() == 0:
- model[self.roster.status_message_menuitem_iter][3] = False
- gajim.block_signed_in_notifications[account] = True
- else:
- # 30 seconds after we change our status to sth else than offline
- # we stop blocking notifications of any kind
- # this prevents from getting the roster items as 'just signed in'
- # contacts. 30 seconds should be enough time
- gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account)
- # sensitivity for this menuitem
- model[self.roster.status_message_menuitem_iter][3] = True
-
- # Inform all controls for this account of the connection state change
- ctrls = self.msg_win_mgr.get_controls()
- if account in self.minimized_controls:
- # Can not be the case when we remove account
- ctrls += self.minimized_controls[account].values()
- for ctrl in ctrls:
- if ctrl.account == account:
- if show == 'offline' or (show == 'invisible' and \
- gajim.connections[account].is_zeroconf):
- ctrl.got_disconnected()
- else:
- # Other code rejoins all GCs, so we don't do it here
- if not ctrl.type_id == message_control.TYPE_GC:
- ctrl.got_connected()
- if ctrl.parent_win:
- ctrl.parent_win.redraw_tab(ctrl)
-
- self.roster.on_status_changed(account, show)
- if account in self.show_vcard_when_connect and show not in ('offline',
- 'error'):
- self.edit_own_details(account)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('AccountPresence', (show, account))
-
- def handle_event_new_jid(self, account, data):
- #('NEW_JID', account, (old_jid, new_jid))
- """
- This event is raised when our JID changed (most probably because we use
- anonymous account. We update contact and roster entry in this case
- """
- self.roster.rename_self_contact(data[0], data[1], account)
-
- def edit_own_details(self, account):
- jid = gajim.get_jid_from_account(account)
- if 'profile' not in self.instances[account]:
- self.instances[account]['profile'] = \
- profile_window.ProfileWindow(account)
- gajim.connections[account].request_vcard(jid)
-
- def handle_event_notify(self, account, array):
- # 'NOTIFY' (account, (jid, status, status message, resource,
- # priority, # keyID, timestamp, contact_nickname))
- #
- # Contact changed show
-
- # FIXME: Drop and rewrite...
-
- statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible']
- # Ignore invalid show
- if array[1] not in statuss:
- return
- old_show = 0
- new_show = statuss.index(array[1])
- status_message = array[2]
- jid = array[0].split('/')[0]
- keyID = array[5]
- contact_nickname = array[7]
-
- # Get the proper keyID
- keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID)
-
- resource = array[3]
- if not resource:
- resource = ''
- priority = array[4]
- if gajim.jid_is_transport(jid):
- # It must be an agent
- ji = jid.replace('@', '')
- else:
- ji = jid
-
- highest = gajim.contacts. \
- get_contact_with_highest_priority(account, jid)
- was_highest = (highest and highest.resource == resource)
-
- conn = gajim.connections[account]
-
- # Update contact
- jid_list = gajim.contacts.get_jid_list(account)
- if ji in jid_list or jid == gajim.get_jid_from_account(account):
- lcontact = gajim.contacts.get_contacts(account, ji)
- contact1 = None
- resources = []
- for c in lcontact:
- resources.append(c.resource)
- if c.resource == resource:
- contact1 = c
- break
-
- if contact1:
- if contact1.show in statuss:
- old_show = statuss.index(contact1.show)
- # nick changed
- if contact_nickname is not None and \
- contact1.contact_name != contact_nickname:
- contact1.contact_name = contact_nickname
- self.roster.draw_contact(jid, account)
-
- if old_show == new_show and contact1.status == status_message and \
- contact1.priority == priority: # no change
- return
- else:
- contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
- if not contact1:
- # Presence of another resource of our
- # jid
- # Create self contact and add to roster
- if resource == conn.server_resource:
- return
- # Ignore offline presence of unknown self resource
- if new_show < 2:
- return
- contact1 = gajim.contacts.create_self_contact(jid=ji,
- account=account, show=array[1], status=status_message,
- priority=priority, keyID=keyID, resource=resource)
- old_show = 0
- gajim.contacts.add_contact(account, contact1)
- lcontact.append(contact1)
- elif contact1.show in statuss:
- old_show = statuss.index(contact1.show)
- if (resources != [''] and (len(lcontact) != 1 or \
- lcontact[0].show != 'offline')) and jid.find('@') > 0:
- # Another resource of an existing contact connected
- old_show = 0
- contact1 = gajim.contacts.copy_contact(contact1)
- lcontact.append(contact1)
- contact1.resource = resource
-
- self.roster.add_contact(contact1.jid, account)
-
- if contact1.jid.find('@') > 0 and len(lcontact) == 1:
- # It's not an agent
- if old_show == 0 and new_show > 1:
- if not contact1.jid in gajim.newly_added[account]:
- gajim.newly_added[account].append(contact1.jid)
- if contact1.jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].remove(contact1.jid)
- gobject.timeout_add_seconds(5, self.roster.remove_newly_added,
- contact1.jid, account)
- elif old_show > 1 and new_show == 0 and conn.connected > 1:
- if not contact1.jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].append(contact1.jid)
- if contact1.jid in gajim.newly_added[account]:
- gajim.newly_added[account].remove(contact1.jid)
- self.roster.draw_contact(contact1.jid, account)
- gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed,
- contact1.jid, account)
-
- # unset custom status
- if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\
- and conn.connected > 1):
- if account in self.status_sent_to_users and \
- jid in self.status_sent_to_users[account]:
- del self.status_sent_to_users[account][jid]
-
- contact1.show = array[1]
- contact1.status = status_message
- contact1.priority = priority
- contact1.keyID = keyID
- timestamp = array[6]
- if timestamp:
- contact1.last_status_time = timestamp
- elif not gajim.block_signed_in_notifications[account]:
- # We're connected since more that 30 seconds
- contact1.last_status_time = time.localtime()
- contact1.contact_nickname = contact_nickname
-
- if gajim.jid_is_transport(jid):
- # It must be an agent
- if ji in jid_list:
- # Update existing iter and group counting
- self.roster.draw_contact(ji, account)
- self.roster.draw_group(_('Transports'), account)
- if new_show > 1 and ji in gajim.transport_avatar[account]:
- # transport just signed in.
- # request avatars
- for jid_ in gajim.transport_avatar[account][ji]:
- conn.request_vcard(jid_)
- # transport just signed in/out, don't show
- # popup notifications for 30s
- account_ji = account + '/' + ji
- gajim.block_signed_in_notifications[account_ji] = True
- gobject.timeout_add_seconds(30,
- self.unblock_signed_in_notifications, account_ji)
- locations = (self.instances, self.instances[account])
- for location in locations:
- if 'add_contact' in location:
- if old_show == 0 and new_show > 1:
- location['add_contact'].transport_signed_in(jid)
- break
- elif old_show > 1 and new_show == 0:
- location['add_contact'].transport_signed_out(jid)
- break
- elif ji in jid_list:
- # It isn't an agent
- # reset chatstate if needed:
- # (when contact signs out or has errors)
- if array[1] in ('offline', 'error'):
- contact1.our_chatstate = contact1.chatstate = \
- contact1.composing_xep = None
-
- # TODO: This causes problems when another
- # resource signs off!
- conn.stop_all_active_file_transfers(contact1)
-
- # disable encryption, since if any messages are
- # lost they'll be not decryptable (note that
- # this contradicts XEP-0201 - trying to get that
- # in the XEP, though)
-
- # there won't be any sessions here if the contact terminated
- # their sessions before going offline (which we do)
- for sess in conn.get_sessions(ji):
- if (ji+'/'+resource) != str(sess.jid):
- continue
- if sess.control:
- sess.control.no_autonegotiation = False
- if sess.enable_encryption:
- sess.terminate_e2e()
- conn.delete_session(jid, sess.thread_id)
-
- self.roster.chg_contact_status(contact1, array[1], status_message,
- account)
- # Notifications
- if old_show < 2 and new_show > 1:
- notify.notify('contact_connected', jid, account, status_message)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('ContactPresence', (account,
- array))
-
- elif old_show > 1 and new_show < 2:
- notify.notify('contact_disconnected', jid, account, status_message)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
- # FIXME: stop non active file transfers
- # Status change (not connected/disconnected or
- # error (<1))
- elif new_show > 1:
- notify.notify('status_change', jid, account, [new_show,
- status_message])
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('ContactStatus', (account, array))
- else:
- # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't
- # follow the XEP, still the case in 2008.
- # It's maybe a GC_NOTIFY (specialy for MSN gc)
- self.handle_event_gc_notify(account, (jid, array[1], status_message,
- array[3], None, None, None, None, None, [], None, None))
-
- highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
- is_highest = (highest and highest.resource == resource)
-
- # disconnect the session from the ctrl if the highest resource has changed
- if (was_highest and not is_highest) or (not was_highest and is_highest):
- ctrl = self.msg_win_mgr.get_control(jid, account)
-
- if ctrl:
- ctrl.no_autonegotiation = False
- ctrl.set_session(None)
- ctrl.contact = highest
-
- def handle_event_msgerror(self, account, array):
- #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session]))
- full_jid_with_resource = array[0]
- jids = full_jid_with_resource.split('/', 1)
- jid = jids[0]
-
- if array[1] == '503':
- # If we get server-not-found error, stop sending chatstates
- for contact in gajim.contacts.get_contacts(account, jid):
- contact.composing_xep = False
-
- session = None
- if len(array) > 5:
- session = array[5]
-
- gc_control = self.msg_win_mgr.get_gc_control(jid, account)
- if not gc_control and \
- jid in self.minimized_controls[account]:
- gc_control = self.minimized_controls[account][jid]
- if gc_control and gc_control.type_id != message_control.TYPE_GC:
- gc_control = None
- if gc_control:
- if len(jids) > 1: # it's a pm
- nick = jids[1]
-
- if session:
- ctrl = session.control
- else:
- ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
-
- if not ctrl:
- tv = gc_control.list_treeview
- model = tv.get_model()
- iter_ = gc_control.get_contact_iter(nick)
- if iter_:
- show = model[iter_][3]
- else:
- show = 'offline'
- gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account,
- name=nick, show=show)
- ctrl = self.new_private_chat(gc_c, account, session)
-
- ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
- 'code': array[1], 'msg': array[2]}, 'status')
- return
-
- gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
- 'code': array[1], 'msg': array[2]}, 'status')
- if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid:
- gc_control.set_subject(gc_control.subject)
- return
-
- if gajim.jid_is_transport(jid):
- jid = jid.replace('@', '')
- msg = array[2]
- if array[3]:
- msg = _('error while sending %(message)s ( %(error)s )') % {
- 'message': array[3], 'error': msg}
- if session:
- session.roster_message(jid, msg, array[4], msg_type='error')
-
- def handle_event_msgsent(self, account, array):
- #('MSGSENT', account, (jid, msg, keyID))
- msg = array[1]
- # do not play sound when standalone chatstate message (eg no msg)
- if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'):
- helpers.play_sound('message_sent')
-
- def handle_event_msgnotsent(self, account, array):
- #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
- msg = _('error while sending %(message)s ( %(error)s )') % {
- 'message': array[2], 'error': array[1]}
- if not array[4]:
- # No session. This can happen when sending a message from gajim-remote
- log.warn(msg)
- return
- array[4].roster_message(array[0], msg, array[3], account,
- msg_type='error')
-
- def handle_event_subscribe(self, account, array):
- #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('Subscribe', (account, array))
-
- jid = array[0]
- text = array[1]
- nick = array[2]
- if helpers.allow_popup_window(account) or not self.systray_enabled:
- dialogs.SubscriptionRequestWindow(jid, text, account, nick)
- return
-
- self.add_event(account, jid, 'subscription_request', (text, nick))
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48)
- event_type = _('Subscription request')
- notify.popup(event_type, jid, account, 'subscription_request', path,
- event_type, jid)
-
- def handle_event_subscribed(self, account, array):
- #('SUBSCRIBED', account, (jid, resource))
- jid = array[0]
- if jid in gajim.contacts.get_jid_list(account):
- c = gajim.contacts.get_first_contact_from_jid(account, jid)
- c.resource = array[1]
- self.roster.remove_contact_from_groups(c.jid, account,
- [_('Not in Roster'), _('Observers')], update=False)
- else:
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
- name = jid.split('@', 1)[0]
- name = name.split('%', 1)[0]
- contact1 = gajim.contacts.create_contact(jid=jid, account=account,
- name=name, groups=[], show='online', status='online',
- ask='to', resource=array[1], keyID=keyID)
- gajim.contacts.add_contact(account, contact1)
- self.roster.add_contact(jid, account)
- dialogs.InformationDialog(_('Authorization accepted'),
- _('The contact "%s" has authorized you to see his or her status.')
- % jid)
- if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'):
- gajim.connections[account].ack_subscribed(jid)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('Subscribed', (account, array))
-
- def show_unsubscribed_dialog(self, account, contact):
- def on_yes(is_checked, list_):
- self.roster.on_req_usub(None, list_)
- list_ = [(contact, account)]
- dialogs.YesNoDialog(
- _('Contact "%s" removed subscription from you') % contact.jid,
- _('You will always see him or her as offline.\nDo you want to '
- 'remove him or her from your contact list?'),
- on_response_yes=(on_yes, list_))
- # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
- # not show deny
-
- def handle_event_unsubscribed(self, account, jid):
- #('UNSUBSCRIBED', account, jid)
- gajim.connections[account].ack_unsubscribed(jid)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('Unsubscribed', (account, jid))
-
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if not contact:
- return
-
- if helpers.allow_popup_window(account) or not self.systray_enabled:
- self.show_unsubscribed_dialog(account, contact)
- return
-
- self.add_event(account, jid, 'unsubscribed', contact)
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48)
- event_type = _('Unsubscribed')
- notify.popup(event_type, jid, account, 'unsubscribed', path,
- event_type, jid)
-
- def handle_event_agent_removed(self, account, agent):
- # remove transport's contacts from treeview
- jid_list = gajim.contacts.get_jid_list(account)
- for jid in jid_list:
- if jid.endswith('@' + agent):
- c = gajim.contacts.get_first_contact_from_jid(account, jid)
- gajim.log.debug(
- 'Removing contact %s due to unregistered transport %s'\
- % (jid, agent))
- gajim.connections[account].unsubscribe(c.jid)
- # Transport contacts can't have 2 resources
- if c.jid in gajim.to_be_removed[account]:
- # This way we'll really remove it
- gajim.to_be_removed[account].remove(c.jid)
- self.roster.remove_contact(c.jid, account, backend=True)
-
- def handle_event_register_agent_info(self, account, array):
- # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
- # info in a dataform if is_form is True
- if array[2] or 'instructions' in array[1]:
- config.ServiceRegistrationWindow(array[0], array[1], account,
- array[2])
- else:
- dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \
- % array[0], _('Check your connection or try again later.'))
-
- def handle_event_agent_info_items(self, account, array):
- #('AGENT_INFO_ITEMS', account, (agent, node, items))
- our_jid = gajim.get_jid_from_account(account)
- if 'pep_services' in gajim.interface.instances[account] and \
- array[0] == our_jid:
- gajim.interface.instances[account]['pep_services'].items_received(
- array[2])
-
- def handle_event_acc_ok(self, account, array):
- #('ACC_OK', account, (config))
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('NewAccount', (account, array))
-
- def handle_event_quit(self, p1, p2):
- self.roster.quit_gtkgui_interface()
-
- def handle_event_myvcard(self, account, array):
- nick = ''
- if 'NICKNAME' in array and array['NICKNAME']:
- gajim.nicks[account] = array['NICKNAME']
- elif 'FN' in array and array['FN']:
- gajim.nicks[account] = array['FN']
- if 'profile' in self.instances[account]:
- win = self.instances[account]['profile']
- win.set_values(array)
- if account in self.show_vcard_when_connect:
- self.show_vcard_when_connect.remove(account)
- jid = array['jid']
- if jid in self.instances[account]['infos']:
- self.instances[account]['infos'][jid].set_values(array)
-
- def handle_event_vcard(self, account, vcard):
- # ('VCARD', account, data)
- '''vcard holds the vcard data'''
- jid = vcard['jid']
- resource = vcard.get('resource', '')
- fjid = jid + '/' + str(resource)
-
- # vcard window
- win = None
- if jid in self.instances[account]['infos']:
- win = self.instances[account]['infos'][jid]
- elif resource and fjid in self.instances[account]['infos']:
- win = self.instances[account]['infos'][fjid]
- if win:
- win.set_values(vcard)
-
- # show avatar in chat
- ctrl = None
- if resource and self.msg_win_mgr.has_window(fjid, account):
- win = self.msg_win_mgr.get_window(fjid, account)
- ctrl = win.get_control(fjid, account)
- elif self.msg_win_mgr.has_window(jid, account):
- win = self.msg_win_mgr.get_window(jid, account)
- ctrl = win.get_control(jid, account)
-
- if ctrl and ctrl.type_id != message_control.TYPE_GC:
- ctrl.show_avatar()
-
- # Show avatar in roster or gc_roster
- gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account)
- if not gc_ctrl and \
- jid in self.minimized_controls[account]:
- gc_ctrl = self.minimized_controls[account][jid]
- if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC:
- gc_ctrl.draw_avatar(resource)
- else:
- self.roster.draw_avatar(jid, account)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('VcardInfo', (account, vcard))
-
- def handle_event_last_status_time(self, account, array):
- # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
- tim = array[2]
- if tim < 0:
- # Ann error occured
- return
- win = None
- if array[0] in self.instances[account]['infos']:
- win = self.instances[account]['infos'][array[0]]
- elif array[0] + '/' + array[1] in self.instances[account]['infos']:
- win = self.instances[account]['infos'][array[0] + '/' + array[1]]
- c = gajim.contacts.get_contact(account, array[0], array[1])
- if c: # c can be none if it's a gc contact
- if array[3]:
- c.status = array[3]
- self.roster.draw_contact(c.jid, account) # draw offline status
- last_time = time.localtime(time.time() - tim)
- if c.show == 'offline':
- c.last_status_time = last_time
- else:
- c.last_activity_time = last_time
- if win:
- win.set_last_status_time()
- if self.roster.tooltip.id and self.roster.tooltip.win:
- self.roster.tooltip.update_last_time(last_time)
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('LastStatusTime', (account, array))
-
- def handle_event_os_info(self, account, array):
- #'OS_INFO' (account, (jid, resource, client_info, os_info))
- win = None
- if array[0] in self.instances[account]['infos']:
- win = self.instances[account]['infos'][array[0]]
- elif array[0] + '/' + array[1] in self.instances[account]['infos']:
- win = self.instances[account]['infos'][array[0] + '/' + array[1]]
- if win:
- win.set_os_info(array[1], array[2], array[3])
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('OsInfo', (account, array))
-
- def handle_event_entity_time(self, account, array):
- #'ENTITY_TIME' (account, (jid, resource, time_info))
- win = None
- if array[0] in self.instances[account]['infos']:
- win = self.instances[account]['infos'][array[0]]
- elif array[0] + '/' + array[1] in self.instances[account]['infos']:
- win = self.instances[account]['infos'][array[0] + '/' + array[1]]
- if win:
- win.set_entity_time(array[1], array[2])
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('EntityTime', (account, array))
-
- def handle_event_gc_notify(self, account, array):
- #'GC_NOTIFY' (account, (room_jid, show, status, nick,
- # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha))
- nick = array[3]
- if not nick:
- return
- room_jid = array[0]
- fjid = room_jid + '/' + nick
- show = array[1]
- status = array[2]
- conn = gajim.connections[account]
-
- # Get the window and control for the updated status, this may be a
- # PrivateChatControl
- control = self.msg_win_mgr.get_gc_control(room_jid, account)
-
- if not control and \
- room_jid in self.minimized_controls[account]:
- control = self.minimized_controls[account][room_jid]
-
- if not control or (control and control.type_id != message_control.TYPE_GC):
- return
-
- control.chg_contact_status(nick, show, status, array[4], array[5],
- array[6], array[7], array[8], array[9], array[10], array[11])
-
- contact = gajim.contacts.\
- get_contact_with_highest_priority(account, room_jid)
- if contact:
- self.roster.draw_contact(room_jid, account)
-
- # print status in chat window and update status/GPG image
- ctrl = self.msg_win_mgr.get_control(fjid, account)
- if ctrl:
- statusCode = array[9]
- if '303' in statusCode:
- new_nick = array[10]
- ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \
- % {'nick': nick, 'new_nick': new_nick}, 'status')
- gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick)
- c = gc_c.as_contact()
- ctrl.gc_contact = gc_c
- ctrl.contact = c
- if ctrl.session:
- # stop e2e
- if ctrl.session.enable_encryption:
- thread_id = ctrl.session.thread_id
- ctrl.session.terminate_e2e()
- conn.delete_session(fjid, thread_id)
- ctrl.no_autonegotiation = False
- ctrl.draw_banner()
- old_jid = room_jid + '/' + nick
- new_jid = room_jid + '/' + new_nick
- self.msg_win_mgr.change_key(old_jid, new_jid, account)
- else:
- contact = ctrl.contact
- contact.show = show
- contact.status = status
- gc_contact = ctrl.gc_contact
- gc_contact.show = show
- gc_contact.status = status
- uf_show = helpers.get_uf_show(show)
- ctrl.print_conversation(_('%(nick)s is now %(status)s') % {
- 'nick': nick, 'status': uf_show}, 'status')
- if status:
- ctrl.print_conversation(' (', 'status', simple=True)
- ctrl.print_conversation('%s' % (status), 'status', simple=True)
- ctrl.print_conversation(')', 'status', simple=True)
- ctrl.parent_win.redraw_tab(ctrl)
- ctrl.update_ui()
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('GCPresence', (account, array))
-
- def handle_event_gc_msg(self, account, array):
- # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg,
- # [status_codes]))
- jids = array[0].split('/', 1)
- room_jid = jids[0]
-
- msg = array[1]
-
- gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
- if not gc_control and \
- room_jid in self.minimized_controls[account]:
- gc_control = self.minimized_controls[account][room_jid]
-
- if not gc_control:
- return
- xhtml = array[4]
-
- if gajim.config.get('ignore_incoming_xhtml'):
- xhtml = None
- if len(jids) == 1:
- # message from server
- nick = ''
- else:
- # message from someone
- nick = jids[1]
-
- gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5])
-
- if self.remote_ctrl:
- highlight = gc_control.needs_visual_notification(msg)
- array += (highlight,)
- self.remote_ctrl.raise_signal('GCMessage', (account, array))
-
- def handle_event_gc_subject(self, account, array):
- #('GC_SUBJECT', account, (jid, subject, body, has_timestamp))
- jids = array[0].split('/', 1)
- jid = jids[0]
-
- gc_control = self.msg_win_mgr.get_gc_control(jid, account)
-
- if not gc_control and \
- jid in self.minimized_controls[account]:
- gc_control = self.minimized_controls[account][jid]
-
- contact = gajim.contacts.\
- get_contact_with_highest_priority(account, jid)
- if contact:
- contact.status = array[1]
- self.roster.draw_contact(jid, account)
-
- if not gc_control:
- return
- gc_control.set_subject(array[1])
- # Standard way, the message comes from the occupant who set the subject
- text = None
- if len(jids) > 1:
- text = _('%(jid)s has set the subject to %(subject)s') % {
- 'jid': jids[1], 'subject': array[1]}
- # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be
- # deleted one day. We can receive a subject with a body that contains
- # "X has set the subject to Y" ...
- elif array[2]:
- text = array[2]
- if text is not None:
- if array[3]:
- gc_control.print_old_conversation(text)
- else:
- gc_control.print_conversation(text)
-
- def handle_event_gc_config(self, account, array):
- #('GC_CONFIG', account, (jid, form)) config is a dict
- room_jid = array[0].split('/')[0]
- if room_jid in gajim.automatic_rooms[account]:
- if 'continue_tag' in gajim.automatic_rooms[account][room_jid]:
- # We're converting chat to muc. allow participants to invite
- form = dataforms.ExtendForm(node = array[1])
- for f in form.iter_fields():
- if f.var == 'muc#roomconfig_allowinvites':
- f.value = True
- elif f.var == 'muc#roomconfig_publicroom':
- f.value = False
- elif f.var == 'muc#roomconfig_membersonly':
- f.value = True
- elif f.var == 'public_list':
- f.value = False
- gajim.connections[account].send_gc_config(room_jid, form)
- else:
- # use default configuration
- gajim.connections[account].send_gc_config(room_jid, array[1])
- # invite contacts
- # check if it is necessary to add <continue />
- continue_tag = False
- if 'continue_tag' in gajim.automatic_rooms[account][room_jid]:
- continue_tag = True
- if 'invities' in gajim.automatic_rooms[account][room_jid]:
- for jid in gajim.automatic_rooms[account][room_jid]['invities']:
- gajim.connections[account].send_invite(room_jid, jid,
- continue_tag=continue_tag)
- del gajim.automatic_rooms[account][room_jid]
- elif room_jid not in self.instances[account]['gc_config']:
- self.instances[account]['gc_config'][room_jid] = \
- config.GroupchatConfigWindow(account, room_jid, array[1])
-
- def handle_event_gc_config_change(self, account, array):
- #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list
- # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
- # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init
- jid = array[0]
- statusCode = array[1]
-
- gc_control = self.msg_win_mgr.get_gc_control(jid, account)
- if not gc_control and \
- jid in self.minimized_controls[account]:
- gc_control = self.minimized_controls[account][jid]
- if not gc_control:
- return
-
- changes = []
- if '100' in statusCode:
- # Can be a presence (see chg_contact_status in groupchat_control.py)
- changes.append(_('Any occupant is allowed to see your full JID'))
- gc_control.is_anonymous = False
- if '102' in statusCode:
- changes.append(_('Room now shows unavailable member'))
- if '103' in statusCode:
- changes.append(_('room now does not show unavailable members'))
- if '104' in statusCode:
- changes.append(
- _('A non-privacy-related room configuration change has occurred'))
- if '170' in statusCode:
- # Can be a presence (see chg_contact_status in groupchat_control.py)
- changes.append(_('Room logging is now enabled'))
- if '171' in statusCode:
- changes.append(_('Room logging is now disabled'))
- if '172' in statusCode:
- changes.append(_('Room is now non-anonymous'))
- gc_control.is_anonymous = False
- if '173' in statusCode:
- changes.append(_('Room is now semi-anonymous'))
- gc_control.is_anonymous = True
- if '174' in statusCode:
- changes.append(_('Room is now fully-anonymous'))
- gc_control.is_anonymous = True
-
- for change in changes:
- gc_control.print_conversation(change)
-
- def handle_event_gc_affiliation(self, account, array):
- #('GC_AFFILIATION', account, (room_jid, users_dict))
- room_jid = array[0]
- if room_jid in self.instances[account]['gc_config']:
- self.instances[account]['gc_config'][room_jid].\
- affiliation_list_received(array[1])
-
- def handle_event_gc_password_required(self, account, array):
- #('GC_PASSWORD_REQUIRED', account, (room_jid, nick))
- room_jid = array[0]
- nick = array[1]
-
- def on_ok(text):
- gajim.connections[account].join_gc(nick, room_jid, text)
- gajim.gc_passwords[room_jid] = text
-
- def on_cancel():
- # get and destroy window
- if room_jid in gajim.interface.minimized_controls[account]:
- self.roster.on_disconnect(None, room_jid, account)
- else:
- win = self.msg_win_mgr.get_window(room_jid, account)
- ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
- win.remove_tab(ctrl, 3)
-
- dlg = dialogs.InputDialog(_('Password Required'),
- _('A Password is required to join the room %s. Please type it.') % \
- room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel)
- dlg.input_entry.set_visibility(False)
-
- def handle_event_gc_invitation(self, account, array):
- #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
- jid = gajim.get_jid_without_resource(array[1])
- room_jid = array[0]
- if helpers.allow_popup_window(account) or not self.systray_enabled:
- dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3],
- array[2], is_continued=array[4])
- return
-
- self.add_event(account, jid, 'gc-invitation', (room_jid, array[2],
- array[3], array[4]))
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48)
- event_type = _('Groupchat Invitation')
- notify.popup(event_type, jid, account, 'gc-invitation', path,
- event_type, room_jid)
-
- def forget_gpg_passphrase(self, keyid):
- if keyid in self.gpg_passphrase:
- del self.gpg_passphrase[keyid]
- return False
-
- def handle_event_bad_passphrase(self, account, array):
- #('BAD_PASSPHRASE', account, ())
- use_gpg_agent = gajim.config.get('use_gpg_agent')
- sectext = ''
- if use_gpg_agent:
- sectext = _('You configured Gajim to use GPG agent, but there is no '
- 'GPG agent running or it returned a wrong passphrase.\n')
- sectext += _('You are currently connected without your OpenPGP key.')
- dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext)
- else:
- path = gtkgui_helpers.get_icon_path('gajim-warning', 48)
- notify.popup('warning', account, account, 'warning', path,
- _('OpenGPG Passphrase Incorrect'),
- _('You are currently connected without your OpenPGP key.'))
- keyID = gajim.config.get_per('accounts', account, 'keyid')
- self.forget_gpg_passphrase(keyID)
-
- def handle_event_gpg_password_required(self, account, array):
- #('GPG_PASSWORD_REQUIRED', account, (callback,))
- callback = array[0]
- keyid = gajim.config.get_per('accounts', account, 'keyid')
- if keyid in self.gpg_passphrase:
- request = self.gpg_passphrase[keyid]
- else:
- request = PassphraseRequest(keyid)
- self.gpg_passphrase[keyid] = request
- request.add_callback(account, callback)
-
- def handle_event_gpg_always_trust(self, account, callback):
- #('GPG_ALWAYS_TRUST', account, callback)
- def on_yes(checked):
- if checked:
- gajim.connections[account].gpg.always_trust = True
- callback(True)
-
- def on_no():
- callback(False)
-
- dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
- 'encrypt this chat is not trusted. Do you really want to encrypt this '
- 'message?'), checktext=_('Do _not ask me again'),
- on_response_yes=on_yes, on_response_no=on_no)
-
- def handle_event_password_required(self, account, array):
- #('PASSWORD_REQUIRED', account, None)
- if account in self.pass_dialog:
- return
- text = _('Enter your password for account %s') % account
- if passwords.USER_HAS_GNOMEKEYRING and \
- not passwords.USER_USES_GNOMEKEYRING:
- text += '\n' + _('Gnome Keyring is installed but not \
- correctly started (environment variable probably not \
- correctly set)')
-
- def on_ok(passphrase, save):
- if save:
- gajim.config.set_per('accounts', account, 'savepass', True)
- passwords.save_password(account, passphrase)
- gajim.connections[account].set_password(passphrase)
- del self.pass_dialog[account]
-
- def on_cancel():
- self.roster.set_state(account, 'offline')
- self.roster.update_status_combobox()
- del self.pass_dialog[account]
-
- self.pass_dialog[account] = dialogs.PassphraseDialog(
- _('Password Required'), text, _('Save password'), ok_handler=on_ok,
- cancel_handler=on_cancel)
-
- def handle_event_roster_info(self, account, array):
- #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
- jid = array[0]
- name = array[1]
- sub = array[2]
- ask = array[3]
- groups = array[4]
- contacts = gajim.contacts.get_contacts(account, jid)
- if (not sub or sub == 'none') and (not ask or ask == 'none') and \
- not name and not groups:
- # contact removed us.
- if contacts:
- self.roster.remove_contact(jid, account, backend=True)
- return
- elif not contacts:
- if sub == 'remove':
- return
- # Add new contact to roster
- contact = gajim.contacts.create_contact(jid=jid, account=account,
- name=name, groups=groups, show='offline', sub=sub, ask=ask)
- gajim.contacts.add_contact(account, contact)
- self.roster.add_contact(jid, account)
- else:
- # it is an existing contact that might has changed
- re_place = False
- # If contact has changed (sub, ask or group) update roster
- # Mind about observer status changes:
- # According to xep 0162, a contact is not an observer anymore when
- # we asked for auth, so also remove him if ask changed
- old_groups = contacts[0].groups
- if contacts[0].sub != sub or contacts[0].ask != ask\
- or old_groups != groups:
- re_place = True
- # c.get_shown_groups() has changed. Reflect that in roster_winodow
- self.roster.remove_contact(jid, account, force=True)
- for contact in contacts:
- contact.name = name or ''
- contact.sub = sub
- contact.ask = ask
- contact.groups = groups or []
- if re_place:
- self.roster.add_contact(jid, account)
- # Refilter and update old groups
- for group in old_groups:
- self.roster.draw_group(group, account)
- else:
- self.roster.draw_contact(jid, account)
-
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('RosterInfo', (account, array))
-
- def handle_event_bookmarks(self, account, bms):
- # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
- # We received a bookmark item from the server (JEP48)
- # Auto join GC windows if neccessary
-
- self.roster.set_actions_menu_needs_rebuild()
- invisible_show = gajim.SHOW_LIST.index('invisible')
- # do not autojoin if we are invisible
- if gajim.connections[account].connected == invisible_show:
- return
-
- self.auto_join_bookmarks(account)
-
- def handle_event_file_send_error(self, account, array):
- jid = array[0]
- file_props = array[1]
- ft = self.instances['file_transfers']
- ft.set_status(file_props['type'], file_props['sid'], 'stop')
-
- if helpers.allow_popup_window(account):
- ft.show_send_error(file_props)
- return
-
- self.add_event(account, jid, 'file-send-error', file_props)
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
- event_type = _('File Transfer Error')
- notify.popup(event_type, jid, account, 'file-send-error', path,
- event_type, file_props['name'])
-
- def handle_event_gmail_notify(self, account, array):
- jid = array[0]
- gmail_new_messages = int(array[1])
- gmail_messages_list = array[2]
- if gajim.config.get('notify_on_new_gmail_email'):
- path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
- title = _('New mail on %(gmail_mail_address)s') % \
- {'gmail_mail_address': jid}
- text = i18n.ngettext('You have %d new mail conversation',
- 'You have %d new mail conversations', gmail_new_messages,
- gmail_new_messages, gmail_new_messages)
-
- if gajim.config.get('notify_on_new_gmail_email_extra'):
- cnt = 0
- for gmessage in gmail_messages_list:
- #FIXME: emulate Gtalk client popups. find out what they parse and
- # how they decide what to show each message has a 'From',
- # 'Subject' and 'Snippet' field
- if cnt >=5:
- break
- senders = ',\n '.join(reversed(gmessage['From']))
- text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \
- {'from_address': senders, 'subject': gmessage['Subject'],
- 'snippet': gmessage['Snippet']}
- cnt += 1
-
- if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
- helpers.play_sound('gmail_received')
- notify.popup(_('New E-mail'), jid, account, 'gmail',
- path_to_image=path, title=title,
- text=text)
-
- if self.remote_ctrl:
- self.remote_ctrl.raise_signal('NewGmail', (account, array))
-
- def handle_event_file_request_error(self, account, array):
- # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
- jid, file_props, errmsg = array
- jid = gajim.get_jid_without_resource(jid)
- ft = self.instances['file_transfers']
- ft.set_status(file_props['type'], file_props['sid'], 'stop')
- errno = file_props['error']
-
- if helpers.allow_popup_window(account):
- if errno in (-4, -5):
- ft.show_stopped(jid, file_props, errmsg)
- else:
- ft.show_request_error(file_props)
- return
-
- if errno in (-4, -5):
- msg_type = 'file-error'
- else:
- msg_type = 'file-request-error'
-
- self.add_event(account, jid, msg_type, file_props)
-
- if helpers.allow_showing_notification(account):
- # check if we should be notified
- path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
- event_type = _('File Transfer Error')
- notify.popup(event_type, jid, account, msg_type, path,
- title = event_type, text = file_props['name'])
-
- def handle_event_file_request(self, account, array):
- jid = array[0]
- jid = gajim.get_jid_without_resource(jid)
- if jid not in gajim.contacts.get_jid_list(account):
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
- contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
- account=account, keyID=keyID)
- gajim.contacts.add_contact(account, contact)
- self.roster.add_contact(contact.jid, account)
- file_props = array[1]
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
-
- if helpers.allow_popup_window(account):
- self.instances['file_transfers'].show_file_request(account, contact,
- file_props)
- return
-
- self.add_event(account, jid, 'file-request', file_props)
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48)
- txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
- account, jid)
- event_type = _('File Transfer Request')
- notify.popup(event_type, jid, account, 'file-request',
- path_to_image = path, title = event_type, text = txt)
-
- def handle_event_file_error(self, title, message):
- dialogs.ErrorDialog(title, message)
-
- def handle_event_file_progress(self, account, file_props):
- if time.time() - self.last_ftwindow_update > 0.5:
- # update ft window every 500ms
- self.last_ftwindow_update = time.time()
- self.instances['file_transfers'].set_progress(file_props['type'],
- file_props['sid'], file_props['received-len'])
-
- def handle_event_file_rcv_completed(self, account, file_props):
- ft = self.instances['file_transfers']
- if file_props['error'] == 0:
- ft.set_progress(file_props['type'], file_props['sid'],
- file_props['received-len'])
- else:
- ft.set_status(file_props['type'], file_props['sid'], 'stop')
- if 'stalled' in file_props and file_props['stalled'] or \
- 'paused' in file_props and file_props['paused']:
- return
- if file_props['type'] == 'r': # we receive a file
- jid = unicode(file_props['sender'])
- else: # we send a file
- jid = unicode(file_props['receiver'])
-
- if helpers.allow_popup_window(account):
- if file_props['error'] == 0:
- if gajim.config.get('notify_on_file_complete'):
- ft.show_completed(jid, file_props)
- elif file_props['error'] == -1:
- ft.show_stopped(jid, file_props,
- error_msg=_('Remote contact stopped transfer'))
- elif file_props['error'] == -6:
- ft.show_stopped(jid, file_props, error_msg=_('Error opening file'))
- return
-
- msg_type = ''
- event_type = ''
- if file_props['error'] == 0 and gajim.config.get(
- 'notify_on_file_complete'):
- msg_type = 'file-completed'
- event_type = _('File Transfer Completed')
- elif file_props['error'] in (-1, -6):
- msg_type = 'file-stopped'
- event_type = _('File Transfer Stopped')
-
- if event_type == '':
- # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
- # this should never happen but it does. see process_result() in socks5.py
- # who calls this func (sth is really wrong unless this func is also registered
- # as progress_cb
- return
-
- if msg_type:
- self.add_event(account, jid, msg_type, file_props)
-
- if file_props is not None:
- if file_props['type'] == 'r':
- # get the name of the sender, as it is in the roster
- sender = unicode(file_props['sender']).split('/')[0]
- name = gajim.contacts.get_first_contact_from_jid(account,
- sender).get_shown_name()
- filename = os.path.basename(file_props['file-name'])
- if event_type == _('File Transfer Completed'):
- txt = _('You successfully received %(filename)s from %(name)s.')\
- % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_done'
- else: # ft stopped
- txt = _('File transfer of %(filename)s from %(name)s stopped.')\
- % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_stopped'
- else:
- receiver = file_props['receiver']
- if hasattr(receiver, 'jid'):
- receiver = receiver.jid
- receiver = receiver.split('/')[0]
- # get the name of the contact, as it is in the roster
- name = gajim.contacts.get_first_contact_from_jid(account,
- receiver).get_shown_name()
- filename = os.path.basename(file_props['file-name'])
- if event_type == _('File Transfer Completed'):
- txt = _('You successfully sent %(filename)s to %(name)s.')\
- % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_done'
- else: # ft stopped
- txt = _('File transfer of %(filename)s to %(name)s stopped.')\
- % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_stopped'
- path = gtkgui_helpers.get_icon_path(img_name, 48)
- else:
- txt = ''
- path = ''
-
- if gajim.config.get('notify_on_file_complete') and \
- (gajim.config.get('autopopupaway') or \
- gajim.connections[account].connected in (2, 3)):
- # we want to be notified and we are online/chat or we don't mind
- # bugged when away/na/busy
- notify.popup(event_type, jid, account, msg_type, path_to_image=path,
- title=event_type, text=txt)
-
- def handle_event_stanza_arrived(self, account, stanza):
- if account not in self.instances:
- return
- if 'xml_console' in self.instances[account]:
- self.instances[account]['xml_console'].print_stanza(stanza, 'incoming')
-
- def handle_event_stanza_sent(self, account, stanza):
- if account not in self.instances:
- return
- if 'xml_console' in self.instances[account]:
- self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing')
-
- def handle_event_vcard_published(self, account, array):
- if 'profile' in self.instances[account]:
- win = self.instances[account]['profile']
- win.vcard_published()
- for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \
- self.minimized_controls[account].values():
- if gc_control.account == account:
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- status = gajim.connections[account].status
- gajim.connections[account].send_gc_status(gc_control.nick,
- gc_control.room_jid, show, status)
-
- def handle_event_vcard_not_published(self, account, array):
- if 'profile' in self.instances[account]:
- win = self.instances[account]['profile']
- win.vcard_not_published()
-
- def ask_offline_status(self, account):
- for contact in gajim.contacts.iter_contacts(account):
- gajim.connections[account].request_last_status_time(contact.jid,
- contact.resource)
-
- def handle_event_signed_in(self, account, empty):
- """
- SIGNED_IN event is emitted when we sign in, so handle it
- """
- # ('SIGNED_IN', account, ())
- # block signed in notifications for 30 seconds
- gajim.block_signed_in_notifications[account] = True
- self.roster.set_actions_menu_needs_rebuild()
- self.roster.draw_account(account)
- state = self.sleeper.getState()
- connected = gajim.connections[account].connected
- if gajim.config.get('ask_offline_status_on_connection'):
- # Ask offline status in 1 minute so w'are sure we got all online
- # presences
- gobject.timeout_add_seconds(60, self.ask_offline_status, account)
- if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3):
- # we go online or free for chat, so we activate auto status
- gajim.sleeper_state[account] = 'online'
- elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \
- (state == common.sleepy.STATE_XA and connected == 5)):
- # If we are autoaway/xa and come back after a disconnection, do nothing
- # Else disable autoaway
- gajim.sleeper_state[account] = 'off'
- invisible_show = gajim.SHOW_LIST.index('invisible')
- # We cannot join rooms if we are invisible
- if gajim.connections[account].connected == invisible_show:
- return
- # join already open groupchats
- for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \
- + self.minimized_controls[account].values():
- if account != gc_control.account:
- continue
- room_jid = gc_control.room_jid
- if room_jid in gajim.gc_connected[account] and \
- gajim.gc_connected[account][room_jid]:
- continue
- nick = gc_control.nick
- password = gajim.gc_passwords.get(room_jid, '')
- gajim.connections[account].join_gc(nick, room_jid, password)
- # send currently played music
- if gajim.connections[account].pep_supported and dbus_support.supported \
- and gajim.config.get_per('accounts', account, 'publish_tune'):
- self.enable_music_listener()
- # enable location listener
- if gajim.connections[account].pep_supported and dbus_support.supported \
- and gajim.config.get_per('accounts', account, 'publish_location'):
- location_listener.enable()
-
- def handle_event_metacontacts(self, account, tags_list):
- gajim.contacts.define_metacontacts(account, tags_list)
- self.roster.redraw_metacontacts(account)
-
- def handle_atom_entry(self, account, data):
- atom_entry, = data
- AtomWindow.newAtomEntry(atom_entry)
-
- def handle_event_failed_decrypt(self, account, data):
- jid, tim, session = data
-
- details = _('Unable to decrypt message from '
- '%s\nIt may have been tampered with.') % jid
-
- ctrl = session.control
- if ctrl:
- ctrl.print_conversation_line(details, 'status', '', tim)
- else:
- dialogs.WarningDialog(_('Unable to decrypt message'),
- details)
-
- # terminate the session
- session.terminate_e2e()
- session.conn.delete_session(jid, session.thread_id)
-
- # restart the session
- if ctrl:
- ctrl.begin_e2e_negotiation()
-
- def handle_event_privacy_lists_received(self, account, data):
- # ('PRIVACY_LISTS_RECEIVED', account, list)
- if account not in self.instances:
- return
- if 'privacy_lists' in self.instances[account]:
- self.instances[account]['privacy_lists'].privacy_lists_received(data)
-
- def handle_event_privacy_list_received(self, account, data):
- # ('PRIVACY_LIST_RECEIVED', account, (name, rules))
- if account not in self.instances:
- return
- name = data[0]
- rules = data[1]
- if 'privacy_list_%s' % name in self.instances[account]:
- self.instances[account]['privacy_list_%s' % name].\
- privacy_list_received(rules)
- if name == 'block':
- gajim.connections[account].blocked_contacts = []
- gajim.connections[account].blocked_groups = []
- gajim.connections[account].blocked_list = []
- gajim.connections[account].blocked_all = False
- for rule in rules:
- if not 'type' in rule:
- gajim.connections[account].blocked_all = True
- elif rule['type'] == 'jid' and rule['action'] == 'deny':
- gajim.connections[account].blocked_contacts.append(rule['value'])
- elif rule['type'] == 'group' and rule['action'] == 'deny':
- gajim.connections[account].blocked_groups.append(rule['value'])
- gajim.connections[account].blocked_list.append(rule)
- #elif rule['type'] == "group" and action == "deny":
- # text_item = _('%s group "%s"') % _(rule['action']), rule['value']
- # self.store.append([text_item])
- # self.global_rules.append(rule)
- #else:
- # self.global_rules_to_append.append(rule)
- if 'blocked_contacts' in self.instances[account]:
- self.instances[account]['blocked_contacts'].\
- privacy_list_received(rules)
-
- def handle_event_privacy_lists_active_default(self, account, data):
- if not data:
- return
- # Send to all privacy_list_* windows as we can't know which one asked
- for win in self.instances[account]:
- if win.startswith('privacy_list_'):
- self.instances[account][win].check_active_default(data)
-
- def handle_event_privacy_list_removed(self, account, name):
- # ('PRIVACY_LISTS_REMOVED', account, name)
- if account not in self.instances:
- return
- if 'privacy_lists' in self.instances[account]:
- self.instances[account]['privacy_lists'].privacy_list_removed(name)
-
- def handle_event_zc_name_conflict(self, account, data):
- def on_ok(new_name):
- gajim.config.set_per('accounts', account, 'name', new_name)
- status = gajim.connections[account].status
- gajim.connections[account].username = new_name
- gajim.connections[account].change_status(status, '')
- def on_cancel():
- gajim.connections[account].change_status('offline','')
-
- dlg = dialogs.InputDialog(_('Username Conflict'),
- _('Please type a new username for your local account'), input_str=data,
- is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel)
-
- def handle_event_ping_sent(self, account, contact):
- if contact.jid == contact.get_full_jid():
- # If contact is a groupchat user
- jids = [contact.jid]
- else:
- jids = [contact.jid, contact.get_full_jid()]
- for jid in jids:
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl:
- ctrl.print_conversation(_('Ping?'), 'status')
-
- def handle_event_ping_reply(self, account, data):
- contact = data[0]
- seconds = data[1]
- if contact.jid == contact.get_full_jid():
- # If contact is a groupchat user
- jids = [contact.jid]
- else:
- jids = [contact.jid, contact.get_full_jid()]
- for jid in jids:
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl:
- ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status')
-
- def handle_event_ping_error(self, account, contact):
- if contact.jid == contact.get_full_jid():
- # If contact is a groupchat user
- jids = [contact.jid]
- else:
- jids = [contact.jid, contact.get_full_jid()]
- for jid in jids:
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl:
- ctrl.print_conversation(_('Error.'), 'status')
-
- def handle_event_search_form(self, account, data):
- # ('SEARCH_FORM', account, (jid, dataform, is_dataform))
- if data[0] not in self.instances[account]['search']:
- return
- self.instances[account]['search'][data[0]].on_form_arrived(data[1],
- data[2])
-
- def handle_event_search_result(self, account, data):
- # ('SEARCH_RESULT', account, (jid, dataform, is_dataform))
- if data[0] not in self.instances[account]['search']:
- return
- self.instances[account]['search'][data[0]].on_result_arrived(data[1],
- data[2])
-
- def handle_event_resource_conflict(self, account, data):
- # ('RESOURCE_CONFLICT', account, ())
- # First we go offline, but we don't overwrite status message
- self.roster.send_status(account, 'offline',
- gajim.connections[account].status)
- def on_ok(new_resource):
- gajim.config.set_per('accounts', account, 'resource', new_resource)
- self.roster.send_status(account, gajim.connections[account].old_show,
- gajim.connections[account].status)
- proposed_resource = gajim.connections[account].server_resource
- proposed_resource += gajim.config.get('gc_proposed_nick_char')
- dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
- _('You are already connected to this account with the same resource. '
- 'Please type a new one'), resource=proposed_resource, ok_handler=on_ok)
-
- def handle_event_jingle_incoming(self, account, data):
- # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
- # data...))
- # TODO: conditional blocking if peer is not in roster
-
- # unpack data
- peerjid, sid, contents = data
- content_types = set(c[0] for c in contents)
-
- # check type of jingle session
- if 'audio' in content_types or 'video' in content_types:
- # a voip session...
- # we now handle only voip, so the only thing we will do here is
- # not to return from function
- pass
- else:
- # unknown session type... it should be declined in common/jingle.py
- return
-
- jid = gajim.get_jid_without_resource(peerjid)
- resource = gajim.get_resource_from_jid(peerjid)
- ctrl = self.msg_win_mgr.get_control(peerjid, account)
- if not ctrl:
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl:
- if 'audio' in content_types:
- ctrl.set_audio_state('connection_received', sid)
- if 'video' in content_types:
- ctrl.set_video_state('connection_received', sid)
-
- dlg = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid)
- if dlg:
- dlg.add_contents(content_types)
- return
-
- if helpers.allow_popup_window(account):
- dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
- return
-
- self.add_event(account, peerjid, 'jingle-incoming', (peerjid, sid,
- content_types))
-
- if helpers.allow_showing_notification(account):
- # TODO: we should use another pixmap ;-)
- txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid(
- account, peerjid)
- path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
- event_type = _('Voice Chat Request')
- notify.popup(event_type, peerjid, account, 'jingle-incoming',
- path_to_image = path, title = event_type, text = txt)
-
- def handle_event_jingle_connected(self, account, data):
- # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
- peerjid, sid, media = data
- if media in ('audio', 'video'):
- jid = gajim.get_jid_without_resource(peerjid)
- resource = gajim.get_resource_from_jid(peerjid)
- ctrl = self.msg_win_mgr.get_control(peerjid, account)
- if not ctrl:
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl:
- if media == 'audio':
- ctrl.set_audio_state('connected', sid)
- else:
- ctrl.set_video_state('connected', sid)
-
- def handle_event_jingle_disconnected(self, account, data):
- # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
- peerjid, sid, media, reason = data
- jid = gajim.get_jid_without_resource(peerjid)
- resource = gajim.get_resource_from_jid(peerjid)
- ctrl = self.msg_win_mgr.get_control(peerjid, account)
- if not ctrl:
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl:
- if media in ('audio', None):
- ctrl.set_audio_state('stop', sid=sid, reason=reason)
- if media in ('video', None):
- ctrl.set_video_state('stop', sid=sid, reason=reason)
- dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid)
- if dialog:
- dialog.dialog.destroy()
-
- def handle_event_jingle_error(self, account, data):
- # ('JINGLE_ERROR', account, (peerjid, sid, reason))
- peerjid, sid, reason = data
- jid = gajim.get_jid_without_resource(peerjid)
- resource = gajim.get_resource_from_jid(peerjid)
- ctrl = self.msg_win_mgr.get_control(peerjid, account)
- if not ctrl:
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl:
- ctrl.set_audio_state('error', reason=reason)
-
- def handle_event_pep_config(self, account, data):
- # ('PEP_CONFIG', account, (node, form))
- if 'pep_services' in self.instances[account]:
- self.instances[account]['pep_services'].config(data[0], data[1])
-
- def handle_event_roster_item_exchange(self, account, data):
- # data = (action in [add, delete, modify], exchange_list, jid_from)
- dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2])
-
- def handle_event_unique_room_id_supported(self, account, data):
- """
- Receive confirmation that unique_room_id are supported
- """
- # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
- instance = data[1]
- instance.unique_room_id_supported(data[0], data[2])
-
- def handle_event_unique_room_id_unsupported(self, account, data):
- # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance)
- instance = data[1]
- instance.unique_room_id_error(data[0])
-
- def handle_event_ssl_error(self, account, data):
- # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
- server = gajim.config.get_per('accounts', account, 'hostname')
-
- def on_ok(is_checked):
- del self.instances[account]['online_dialog']['ssl_error']
- if is_checked[0]:
- # Check if cert is already in file
- certs = ''
- if os.path.isfile(gajim.MY_CACERTS):
- f = open(gajim.MY_CACERTS)
- certs = f.read()
- f.close()
- if data[2] in certs:
- dialogs.ErrorDialog(_('Certificate Already in File'),
- _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS)
- else:
- f = open(gajim.MY_CACERTS, 'a')
- f.write(server + '\n')
- f.write(data[2] + '\n\n')
- f.close()
- gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
- data[3])
- if is_checked[1]:
- ignore_ssl_errors = gajim.config.get_per('accounts', account,
- 'ignore_ssl_errors').split()
- ignore_ssl_errors.append(str(data[1]))
- gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
- ' '.join(ignore_ssl_errors))
- gajim.connections[account].ssl_certificate_accepted()
-
- def on_cancel():
- del self.instances[account]['online_dialog']['ssl_error']
- gajim.connections[account].disconnect(on_purpose=True)
- self.handle_event_status(account, 'offline')
-
- pritext = _('Error verifying SSL certificate')
- sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]}
- if data[1] in (18, 27):
- checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3]
- else:
- checktext1 = ''
- checktext2 = _('Ignore this error for this certificate.')
- if 'ssl_error' in self.instances[account]['online_dialog']:
- self.instances[account]['online_dialog']['ssl_error'].destroy()
- self.instances[account]['online_dialog']['ssl_error'] = \
- dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1,
- checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel)
-
- def handle_event_fingerprint_error(self, account, data):
- # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
- def on_yes(is_checked):
- del self.instances[account]['online_dialog']['fingerprint_error']
- gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
- data[0])
- # Reset the ignored ssl errors
- gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
- gajim.connections[account].ssl_certificate_accepted()
- def on_no():
- del self.instances[account]['online_dialog']['fingerprint_error']
- gajim.connections[account].disconnect(on_purpose=True)
- self.handle_event_status(account, 'offline')
- pritext = _('SSL certificate error')
- sectext = _('It seems the SSL certificate of account %(account)s has '
- 'changed or your connection is being hacked.\nOld fingerprint: %(old)s'
- '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update'
- ' the fingerprint of the certificate?') % {'account': account,
- 'old': gajim.config.get_per('accounts', account,
- 'ssl_fingerprint_sha1'), 'new': data[0]}
- if 'fingerprint_error' in self.instances[account]['online_dialog']:
- self.instances[account]['online_dialog']['fingerprint_error'].destroy()
- self.instances[account]['online_dialog']['fingerprint_error'] = \
- dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
- on_response_no=on_no)
-
- def handle_event_plain_connection(self, account, data):
- # ('PLAIN_CONNECTION', account, (connection))
- server = gajim.config.get_per('accounts', account, 'hostname')
- def on_ok(is_checked):
- if not is_checked[0]:
- on_cancel()
- return
- # On cancel call del self.instances, so don't call it another time
- # before
- del self.instances[account]['online_dialog']['plain_connection']
- if is_checked[1]:
- gajim.config.set_per('accounts', account,
- 'warn_when_plaintext_connection', False)
- gajim.connections[account].connection_accepted(data[0], 'plain')
- def on_cancel():
- del self.instances[account]['online_dialog']['plain_connection']
- gajim.connections[account].disconnect(on_purpose=True)
- self.handle_event_status(account, 'offline')
- pritext = _('Insecure connection')
- sectext = _('You are about to send your password on an unencrypted '
- 'connection. Are you sure you want to do that?')
- checktext1 = _('Yes, I really want to connect insecurely')
- checktext2 = _('Do _not ask me again')
- if 'plain_connection' in self.instances[account]['online_dialog']:
- self.instances[account]['online_dialog']['plain_connection'].destroy()
- self.instances[account]['online_dialog']['plain_connection'] = \
- dialogs.ConfirmationDialogDubbleCheck(pritext, sectext,
- checktext1, checktext2, on_response_ok=on_ok,
- on_response_cancel=on_cancel, is_modal=False)
-
- def handle_event_insecure_ssl_connection(self, account, data):
- # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
- server = gajim.config.get_per('accounts', account, 'hostname')
- def on_ok(is_checked):
- if not is_checked[0]:
- on_cancel()
- return
- del self.instances[account]['online_dialog']['insecure_ssl']
- if is_checked[1]:
- gajim.config.set_per('accounts', account,
- 'warn_when_insecure_ssl_connection', False)
- if gajim.connections[account].connected == 0:
- # We have been disconnecting (too long time since window is opened)
- # re-connect with auto-accept
- gajim.connections[account].connection_auto_accepted = True
- show, msg = gajim.connections[account].continue_connect_info[:2]
- self.roster.send_status(account, show, msg)
- return
- gajim.connections[account].connection_accepted(data[0], data[1])
- def on_cancel():
- del self.instances[account]['online_dialog']['insecure_ssl']
- gajim.connections[account].disconnect(on_purpose=True)
- self.handle_event_status(account, 'offline')
- pritext = _('Insecure connection')
- sectext = _('You are about to send your password on an insecure '
- 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?')
- checktext1 = _('Yes, I really want to connect insecurely')
- checktext2 = _('Do _not ask me again')
- if 'insecure_ssl' in self.instances[account]['online_dialog']:
- self.instances[account]['online_dialog']['insecure_ssl'].destroy()
- self.instances[account]['online_dialog']['insecure_ssl'] = \
- dialogs.ConfirmationDialogDubbleCheck(pritext, sectext,
- checktext1, checktext2, on_response_ok=on_ok,
- on_response_cancel=on_cancel, is_modal=False)
-
- def handle_event_pubsub_node_removed(self, account, data):
- # ('PUBSUB_NODE_REMOVED', account, (jid, node))
- if 'pep_services' in self.instances[account]:
- if data[0] == gajim.get_jid_from_account(account):
- self.instances[account]['pep_services'].node_removed(data[1])
-
- def handle_event_pubsub_node_not_removed(self, account, data):
- # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg))
- if data[0] == gajim.get_jid_from_account(account):
- dialogs.WarningDialog(_('PEP node was not removed'),
- _('PEP node %(node)s was not removed: %(message)s') % {
- 'node': data[1], 'message': data[2]})
-
- def handle_event_pep_received(self, account, data):
- # ('PEP_RECEIVED', account, (jid, pep_type))
- jid = data[0]
- pep_type = data[1]
- ctrl = common.gajim.interface.msg_win_mgr.get_control(jid, account)
-
- if jid == common.gajim.get_jid_from_account(account):
- self.roster.draw_account(account)
-
- if pep_type == 'nickname':
- self.roster.draw_contact(jid, account)
- if ctrl:
- ctrl.update_ui()
- win = ctrl.parent_win
- win.redraw_tab(ctrl)
- win.show_title()
- else:
- self.roster.draw_pep(jid, account, pep_type)
- if ctrl:
- ctrl.update_pep(pep_type)
-
- def handle_event_caps_received(self, account, data):
- # ('CAPS_RECEIVED', account, (full_jid))
- full_jid = data[0]
- pm_ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, account)
- if pm_ctrl and hasattr(pm_ctrl, "update_contact"):
- pm_ctrl.update_contact()
-
- def create_core_handlers_list(self):
- self.handlers = {
- 'ROSTER': [self.handle_event_roster],
- 'WARNING': [self.handle_event_warning],
- 'ERROR': [self.handle_event_error],
- 'INFORMATION': [self.handle_event_information],
- 'ERROR_ANSWER': [self.handle_event_error_answer],
- 'STATUS': [self.handle_event_status],
- 'NEW_JID': [self.handle_event_new_jid],
- 'NOTIFY': [self.handle_event_notify],
- 'MSGERROR': [self.handle_event_msgerror],
- 'MSGSENT': [self.handle_event_msgsent],
- 'MSGNOTSENT': [self.handle_event_msgnotsent],
- 'SUBSCRIBED': [self.handle_event_subscribed],
- 'UNSUBSCRIBED': [self.handle_event_unsubscribed],
- 'SUBSCRIBE': [self.handle_event_subscribe],
- 'AGENT_REMOVED': [self.handle_event_agent_removed],
- 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info],
- 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items],
- 'QUIT': [self.handle_event_quit],
- 'ACC_OK': [self.handle_event_acc_ok],
- 'MYVCARD': [self.handle_event_myvcard],
- 'VCARD': [self.handle_event_vcard],
- 'LAST_STATUS_TIME': [self.handle_event_last_status_time],
- 'OS_INFO': [self.handle_event_os_info],
- 'ENTITY_TIME': [self.handle_event_entity_time],
- 'GC_NOTIFY': [self.handle_event_gc_notify],
- 'GC_MSG': [self.handle_event_gc_msg],
- 'GC_SUBJECT': [self.handle_event_gc_subject],
- 'GC_CONFIG': [self.handle_event_gc_config],
- 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change],
- 'GC_INVITATION': [self.handle_event_gc_invitation],
- 'GC_AFFILIATION': [self.handle_event_gc_affiliation],
- 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required],
- 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase],
- 'ROSTER_INFO': [self.handle_event_roster_info],
- 'BOOKMARKS': [self.handle_event_bookmarks],
- 'CON_TYPE': [self.handle_event_con_type],
- 'CONNECTION_LOST': [self.handle_event_connection_lost],
- 'FILE_REQUEST': [self.handle_event_file_request],
- 'GMAIL_NOTIFY': [self.handle_event_gmail_notify],
- 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
- 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
- 'STANZA_ARRIVED': [self.handle_event_stanza_arrived],
- 'STANZA_SENT': [self.handle_event_stanza_sent],
- 'HTTP_AUTH': [self.handle_event_http_auth],
- 'VCARD_PUBLISHED': [self.handle_event_vcard_published],
- 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published],
- 'ASK_NEW_NICK': [self.handle_event_ask_new_nick],
- 'SIGNED_IN': [self.handle_event_signed_in],
- 'METACONTACTS': [self.handle_event_metacontacts],
- 'ATOM_ENTRY': [self.handle_atom_entry],
- 'FAILED_DECRYPT': [self.handle_event_failed_decrypt],
- 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received],
- 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received],
- 'PRIVACY_LISTS_ACTIVE_DEFAULT': \
- [self.handle_event_privacy_lists_active_default],
- 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed],
- 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict],
- 'PING_SENT': [self.handle_event_ping_sent],
- 'PING_REPLY': [self.handle_event_ping_reply],
- 'PING_ERROR': [self.handle_event_ping_error],
- 'SEARCH_FORM': [self.handle_event_search_form],
- 'SEARCH_RESULT': [self.handle_event_search_result],
- 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict],
- 'ROSTERX': [self.handle_event_roster_item_exchange],
- 'PEP_CONFIG': [self.handle_event_pep_config],
- 'UNIQUE_ROOM_ID_UNSUPPORTED': \
- [self.handle_event_unique_room_id_unsupported],
- 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported],
- 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required],
- 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust],
- 'PASSWORD_REQUIRED': [self.handle_event_password_required],
- 'SSL_ERROR': [self.handle_event_ssl_error],
- 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error],
- 'PLAIN_CONNECTION': [self.handle_event_plain_connection],
- 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection],
- 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed],
- 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed],
- 'JINGLE_INCOMING': [self.handle_event_jingle_incoming],
- 'JINGLE_CONNECTED': [self.handle_event_jingle_connected],
- 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected],
- 'JINGLE_ERROR': [self.handle_event_jingle_error],
- 'PEP_RECEIVED': [self.handle_event_pep_received],
- 'CAPS_RECEIVED': [self.handle_event_caps_received]
- }
-
- def register_core_handlers(self):
- """
- Register core handlers in Global Events Dispatcher (GED).
-
- This is part of rewriting whole events handling system to use GED.
- """
- for event_name, event_handlers in self.handlers.iteritems():
- for event_handler in event_handlers:
- gajim.ged.register_event_handler(event_name, ged.CORE,
- event_handler)
+ def handle_event_roster(self, account, data):
+ #('ROSTER', account, array)
+ # FIXME: Those methods depend to highly on each other
+ # and the order in which they are called
+ self.roster.fill_contacts_and_groups_dicts(data, account)
+ self.roster.add_account_contacts(account)
+ self.roster.fire_up_unread_messages_events(account)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('Roster', (account, data))
+
+ def handle_event_warning(self, unused, data):
+ #('WARNING', account, (title_text, section_text))
+ dialogs.WarningDialog(data[0], data[1])
+
+ def handle_event_error(self, unused, data):
+ #('ERROR', account, (title_text, section_text))
+ dialogs.ErrorDialog(data[0], data[1])
+
+ def handle_event_information(self, unused, data):
+ #('INFORMATION', account, (title_text, section_text))
+ dialogs.InformationDialog(data[0], data[1])
+
+ def handle_event_ask_new_nick(self, account, data):
+ #('ASK_NEW_NICK', account, (room_jid,))
+ room_jid = data[0]
+ title = _('Unable to join group chat')
+ prompt = _('Your desired nickname in group chat %s is in use or '
+ 'registered by another occupant.\nPlease specify another nickname '
+ 'below:') % room_jid
+ check_text = _('Always use this nickname when there is a conflict')
+ if 'change_nick_dialog' in self.instances:
+ self.instances['change_nick_dialog'].add_room(account, room_jid,
+ prompt)
+ else:
+ self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
+ account, room_jid, title, prompt)
+
+ def handle_event_http_auth(self, account, data):
+ #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
+ def response(account, iq_obj, answer):
+ self.dialog.destroy()
+ gajim.connections[account].build_http_auth_answer(iq_obj, answer)
+
+ def on_yes(is_checked, account, iq_obj):
+ response(account, iq_obj, 'yes')
+
+ sec_msg = _('Do you accept this request?')
+ if gajim.get_number_of_connected_accounts() > 1:
+ sec_msg = _('Do you accept this request on account %s?') % account
+ if data[4]:
+ sec_msg = data[4] + '\n' + sec_msg
+ self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
+ '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1],
+ 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]),
+ on_response_no=(response, account, data[3], 'no'))
+
+ def handle_event_error_answer(self, account, array):
+ #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
+ id_, jid_from, errmsg, errcode = array
+ if unicode(errcode) in ('400', '403', '406') and id_:
+ # show the error dialog
+ ft = self.instances['file_transfers']
+ sid = id_
+ if len(id_) > 3 and id_[2] == '_':
+ sid = id_[3:]
+ if sid in ft.files_props['s']:
+ file_props = ft.files_props['s'][sid]
+ if unicode(errcode) == '400':
+ file_props['error'] = -3
+ else:
+ file_props['error'] = -4
+ self.handle_event_file_request_error(account,
+ (jid_from, file_props, errmsg))
+ conn = gajim.connections[account]
+ conn.disconnect_transfer(file_props)
+ return
+ elif unicode(errcode) == '404':
+ conn = gajim.connections[account]
+ sid = id_
+ if len(id_) > 3 and id_[2] == '_':
+ sid = id_[3:]
+ if sid in conn.files_props:
+ file_props = conn.files_props[sid]
+ self.handle_event_file_send_error(account,
+ (jid_from, file_props))
+ conn.disconnect_transfer(file_props)
+ return
+
+ ctrl = self.msg_win_mgr.get_control(jid_from, account)
+ if ctrl and ctrl.type_id == message_control.TYPE_GC:
+ ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
+
+ def handle_event_con_type(self, account, con_type):
+ # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
+ gajim.con_types[account] = con_type
+ self.roster.draw_account(account)
+
+ def handle_event_connection_lost(self, account, array):
+ # ('CONNECTION_LOST', account, [title, text])
+ path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48)
+ notify.popup(_('Connection Failed'), account, account,
+ 'connection_failed', path, array[0], array[1])
+
+ def unblock_signed_in_notifications(self, account):
+ gajim.block_signed_in_notifications[account] = False
+
+ def handle_event_status(self, account, show): # OUR status
+ #('STATUS', account, show)
+ model = self.roster.status_combobox.get_model()
+ if show in ('offline', 'error'):
+ for name in self.instances[account]['online_dialog'].keys():
+ # .keys() is needed to not have a dictionary length changed during
+ # iteration error
+ self.instances[account]['online_dialog'][name].destroy()
+ del self.instances[account]['online_dialog'][name]
+ for request in self.gpg_passphrase.values():
+ if request:
+ request.interrupt()
+ if account in self.pass_dialog:
+ self.pass_dialog[account].window.destroy()
+ if show == 'offline':
+ # sensitivity for this menuitem
+ if gajim.get_number_of_connected_accounts() == 0:
+ model[self.roster.status_message_menuitem_iter][3] = False
+ gajim.block_signed_in_notifications[account] = True
+ else:
+ # 30 seconds after we change our status to sth else than offline
+ # we stop blocking notifications of any kind
+ # this prevents from getting the roster items as 'just signed in'
+ # contacts. 30 seconds should be enough time
+ gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account)
+ # sensitivity for this menuitem
+ model[self.roster.status_message_menuitem_iter][3] = True
+
+ # Inform all controls for this account of the connection state change
+ ctrls = self.msg_win_mgr.get_controls()
+ if account in self.minimized_controls:
+ # Can not be the case when we remove account
+ ctrls += self.minimized_controls[account].values()
+ for ctrl in ctrls:
+ if ctrl.account == account:
+ if show == 'offline' or (show == 'invisible' and \
+ gajim.connections[account].is_zeroconf):
+ ctrl.got_disconnected()
+ else:
+ # Other code rejoins all GCs, so we don't do it here
+ if not ctrl.type_id == message_control.TYPE_GC:
+ ctrl.got_connected()
+ if ctrl.parent_win:
+ ctrl.parent_win.redraw_tab(ctrl)
+
+ self.roster.on_status_changed(account, show)
+ if account in self.show_vcard_when_connect and show not in ('offline',
+ 'error'):
+ self.edit_own_details(account)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('AccountPresence', (show, account))
+
+ def handle_event_new_jid(self, account, data):
+ #('NEW_JID', account, (old_jid, new_jid))
+ """
+ This event is raised when our JID changed (most probably because we use
+ anonymous account. We update contact and roster entry in this case
+ """
+ self.roster.rename_self_contact(data[0], data[1], account)
+
+ def edit_own_details(self, account):
+ jid = gajim.get_jid_from_account(account)
+ if 'profile' not in self.instances[account]:
+ self.instances[account]['profile'] = \
+ profile_window.ProfileWindow(account)
+ gajim.connections[account].request_vcard(jid)
+
+ def handle_event_notify(self, account, array):
+ # 'NOTIFY' (account, (jid, status, status message, resource,
+ # priority, # keyID, timestamp, contact_nickname))
+ #
+ # Contact changed show
+
+ # FIXME: Drop and rewrite...
+
+ statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
+ 'invisible']
+ # Ignore invalid show
+ if array[1] not in statuss:
+ return
+ old_show = 0
+ new_show = statuss.index(array[1])
+ status_message = array[2]
+ jid = array[0].split('/')[0]
+ keyID = array[5]
+ contact_nickname = array[7]
+
+ # Get the proper keyID
+ keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID)
+
+ resource = array[3]
+ if not resource:
+ resource = ''
+ priority = array[4]
+ if gajim.jid_is_transport(jid):
+ # It must be an agent
+ ji = jid.replace('@', '')
+ else:
+ ji = jid
+
+ highest = gajim.contacts. \
+ get_contact_with_highest_priority(account, jid)
+ was_highest = (highest and highest.resource == resource)
+
+ conn = gajim.connections[account]
+
+ # Update contact
+ jid_list = gajim.contacts.get_jid_list(account)
+ if ji in jid_list or jid == gajim.get_jid_from_account(account):
+ lcontact = gajim.contacts.get_contacts(account, ji)
+ contact1 = None
+ resources = []
+ for c in lcontact:
+ resources.append(c.resource)
+ if c.resource == resource:
+ contact1 = c
+ break
+
+ if contact1:
+ if contact1.show in statuss:
+ old_show = statuss.index(contact1.show)
+ # nick changed
+ if contact_nickname is not None and \
+ contact1.contact_name != contact_nickname:
+ contact1.contact_name = contact_nickname
+ self.roster.draw_contact(jid, account)
+
+ if old_show == new_show and contact1.status == status_message and \
+ contact1.priority == priority: # no change
+ return
+ else:
+ contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
+ if not contact1:
+ # Presence of another resource of our
+ # jid
+ # Create self contact and add to roster
+ if resource == conn.server_resource:
+ return
+ # Ignore offline presence of unknown self resource
+ if new_show < 2:
+ return
+ contact1 = gajim.contacts.create_self_contact(jid=ji,
+ account=account, show=array[1], status=status_message,
+ priority=priority, keyID=keyID, resource=resource)
+ old_show = 0
+ gajim.contacts.add_contact(account, contact1)
+ lcontact.append(contact1)
+ elif contact1.show in statuss:
+ old_show = statuss.index(contact1.show)
+ if (resources != [''] and (len(lcontact) != 1 or \
+ lcontact[0].show != 'offline')) and jid.find('@') > 0:
+ # Another resource of an existing contact connected
+ old_show = 0
+ contact1 = gajim.contacts.copy_contact(contact1)
+ lcontact.append(contact1)
+ contact1.resource = resource
+
+ self.roster.add_contact(contact1.jid, account)
+
+ if contact1.jid.find('@') > 0 and len(lcontact) == 1:
+ # It's not an agent
+ if old_show == 0 and new_show > 1:
+ if not contact1.jid in gajim.newly_added[account]:
+ gajim.newly_added[account].append(contact1.jid)
+ if contact1.jid in gajim.to_be_removed[account]:
+ gajim.to_be_removed[account].remove(contact1.jid)
+ gobject.timeout_add_seconds(5, self.roster.remove_newly_added,
+ contact1.jid, account)
+ elif old_show > 1 and new_show == 0 and conn.connected > 1:
+ if not contact1.jid in gajim.to_be_removed[account]:
+ gajim.to_be_removed[account].append(contact1.jid)
+ if contact1.jid in gajim.newly_added[account]:
+ gajim.newly_added[account].remove(contact1.jid)
+ self.roster.draw_contact(contact1.jid, account)
+ gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed,
+ contact1.jid, account)
+
+ # unset custom status
+ if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\
+ and conn.connected > 1):
+ if account in self.status_sent_to_users and \
+ jid in self.status_sent_to_users[account]:
+ del self.status_sent_to_users[account][jid]
+
+ contact1.show = array[1]
+ contact1.status = status_message
+ contact1.priority = priority
+ contact1.keyID = keyID
+ timestamp = array[6]
+ if timestamp:
+ contact1.last_status_time = timestamp
+ elif not gajim.block_signed_in_notifications[account]:
+ # We're connected since more that 30 seconds
+ contact1.last_status_time = time.localtime()
+ contact1.contact_nickname = contact_nickname
+
+ if gajim.jid_is_transport(jid):
+ # It must be an agent
+ if ji in jid_list:
+ # Update existing iter and group counting
+ self.roster.draw_contact(ji, account)
+ self.roster.draw_group(_('Transports'), account)
+ if new_show > 1 and ji in gajim.transport_avatar[account]:
+ # transport just signed in.
+ # request avatars
+ for jid_ in gajim.transport_avatar[account][ji]:
+ conn.request_vcard(jid_)
+ # transport just signed in/out, don't show
+ # popup notifications for 30s
+ account_ji = account + '/' + ji
+ gajim.block_signed_in_notifications[account_ji] = True
+ gobject.timeout_add_seconds(30,
+ self.unblock_signed_in_notifications, account_ji)
+ locations = (self.instances, self.instances[account])
+ for location in locations:
+ if 'add_contact' in location:
+ if old_show == 0 and new_show > 1:
+ location['add_contact'].transport_signed_in(jid)
+ break
+ elif old_show > 1 and new_show == 0:
+ location['add_contact'].transport_signed_out(jid)
+ break
+ elif ji in jid_list:
+ # It isn't an agent
+ # reset chatstate if needed:
+ # (when contact signs out or has errors)
+ if array[1] in ('offline', 'error'):
+ contact1.our_chatstate = contact1.chatstate = \
+ contact1.composing_xep = None
+
+ # TODO: This causes problems when another
+ # resource signs off!
+ conn.stop_all_active_file_transfers(contact1)
+
+ # disable encryption, since if any messages are
+ # lost they'll be not decryptable (note that
+ # this contradicts XEP-0201 - trying to get that
+ # in the XEP, though)
+
+ # there won't be any sessions here if the contact terminated
+ # their sessions before going offline (which we do)
+ for sess in conn.get_sessions(ji):
+ if (ji+'/'+resource) != str(sess.jid):
+ continue
+ if sess.control:
+ sess.control.no_autonegotiation = False
+ if sess.enable_encryption:
+ sess.terminate_e2e()
+ conn.delete_session(jid, sess.thread_id)
+
+ self.roster.chg_contact_status(contact1, array[1], status_message,
+ account)
+ # Notifications
+ if old_show < 2 and new_show > 1:
+ notify.notify('contact_connected', jid, account, status_message)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('ContactPresence', (account,
+ array))
+
+ elif old_show > 1 and new_show < 2:
+ notify.notify('contact_disconnected', jid, account, status_message)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
+ # FIXME: stop non active file transfers
+ # Status change (not connected/disconnected or
+ # error (<1))
+ elif new_show > 1:
+ notify.notify('status_change', jid, account, [new_show,
+ status_message])
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('ContactStatus', (account, array))
+ else:
+ # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't
+ # follow the XEP, still the case in 2008.
+ # It's maybe a GC_NOTIFY (specialy for MSN gc)
+ self.handle_event_gc_notify(account, (jid, array[1], status_message,
+ array[3], None, None, None, None, None, [], None, None))
+
+ highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ is_highest = (highest and highest.resource == resource)
+
+ # disconnect the session from the ctrl if the highest resource has changed
+ if (was_highest and not is_highest) or (not was_highest and is_highest):
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+
+ if ctrl:
+ ctrl.no_autonegotiation = False
+ ctrl.set_session(None)
+ ctrl.contact = highest
+
+ def handle_event_msgerror(self, account, array):
+ #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session]))
+ full_jid_with_resource = array[0]
+ jids = full_jid_with_resource.split('/', 1)
+ jid = jids[0]
+
+ if array[1] == '503':
+ # If we get server-not-found error, stop sending chatstates
+ for contact in gajim.contacts.get_contacts(account, jid):
+ contact.composing_xep = False
+
+ session = None
+ if len(array) > 5:
+ session = array[5]
+
+ gc_control = self.msg_win_mgr.get_gc_control(jid, account)
+ if not gc_control and \
+ jid in self.minimized_controls[account]:
+ gc_control = self.minimized_controls[account][jid]
+ if gc_control and gc_control.type_id != message_control.TYPE_GC:
+ gc_control = None
+ if gc_control:
+ if len(jids) > 1: # it's a pm
+ nick = jids[1]
+
+ if session:
+ ctrl = session.control
+ else:
+ ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
+
+ if not ctrl:
+ tv = gc_control.list_treeview
+ model = tv.get_model()
+ iter_ = gc_control.get_contact_iter(nick)
+ if iter_:
+ show = model[iter_][3]
+ else:
+ show = 'offline'
+ gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account,
+ name=nick, show=show)
+ ctrl = self.new_private_chat(gc_c, account, session)
+
+ ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
+ 'code': array[1], 'msg': array[2]}, 'status')
+ return
+
+ gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
+ 'code': array[1], 'msg': array[2]}, 'status')
+ if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid:
+ gc_control.set_subject(gc_control.subject)
+ return
+
+ if gajim.jid_is_transport(jid):
+ jid = jid.replace('@', '')
+ msg = array[2]
+ if array[3]:
+ msg = _('error while sending %(message)s ( %(error)s )') % {
+ 'message': array[3], 'error': msg}
+ if session:
+ session.roster_message(jid, msg, array[4], msg_type='error')
+
+ def handle_event_msgsent(self, account, array):
+ #('MSGSENT', account, (jid, msg, keyID))
+ msg = array[1]
+ # do not play sound when standalone chatstate message (eg no msg)
+ if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'):
+ helpers.play_sound('message_sent')
+
+ def handle_event_msgnotsent(self, account, array):
+ #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
+ msg = _('error while sending %(message)s ( %(error)s )') % {
+ 'message': array[2], 'error': array[1]}
+ if not array[4]:
+ # No session. This can happen when sending a message from gajim-remote
+ log.warn(msg)
+ return
+ array[4].roster_message(array[0], msg, array[3], account,
+ msg_type='error')
+
+ def handle_event_subscribe(self, account, array):
+ #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('Subscribe', (account, array))
+
+ jid = array[0]
+ text = array[1]
+ nick = array[2]
+ if helpers.allow_popup_window(account) or not self.systray_enabled:
+ dialogs.SubscriptionRequestWindow(jid, text, account, nick)
+ return
+
+ self.add_event(account, jid, 'subscription_request', (text, nick))
+
+ if helpers.allow_showing_notification(account):
+ path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48)
+ event_type = _('Subscription request')
+ notify.popup(event_type, jid, account, 'subscription_request', path,
+ event_type, jid)
+
+ def handle_event_subscribed(self, account, array):
+ #('SUBSCRIBED', account, (jid, resource))
+ jid = array[0]
+ if jid in gajim.contacts.get_jid_list(account):
+ c = gajim.contacts.get_first_contact_from_jid(account, jid)
+ c.resource = array[1]
+ self.roster.remove_contact_from_groups(c.jid, account,
+ [_('Not in Roster'), _('Observers')], update=False)
+ else:
+ keyID = ''
+ attached_keys = gajim.config.get_per('accounts', account,
+ 'attached_gpg_keys').split()
+ if jid in attached_keys:
+ keyID = attached_keys[attached_keys.index(jid) + 1]
+ name = jid.split('@', 1)[0]
+ name = name.split('%', 1)[0]
+ contact1 = gajim.contacts.create_contact(jid=jid, account=account,
+ name=name, groups=[], show='online', status='online',
+ ask='to', resource=array[1], keyID=keyID)
+ gajim.contacts.add_contact(account, contact1)
+ self.roster.add_contact(jid, account)
+ dialogs.InformationDialog(_('Authorization accepted'),
+ _('The contact "%s" has authorized you to see his or her status.')
+ % jid)
+ if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'):
+ gajim.connections[account].ack_subscribed(jid)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('Subscribed', (account, array))
+
+ def show_unsubscribed_dialog(self, account, contact):
+ def on_yes(is_checked, list_):
+ self.roster.on_req_usub(None, list_)
+ list_ = [(contact, account)]
+ dialogs.YesNoDialog(
+ _('Contact "%s" removed subscription from you') % contact.jid,
+ _('You will always see him or her as offline.\nDo you want to '
+ 'remove him or her from your contact list?'),
+ on_response_yes=(on_yes, list_))
+ # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
+ # not show deny
+
+ def handle_event_unsubscribed(self, account, jid):
+ #('UNSUBSCRIBED', account, jid)
+ gajim.connections[account].ack_unsubscribed(jid)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('Unsubscribed', (account, jid))
+
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ if not contact:
+ return
+
+ if helpers.allow_popup_window(account) or not self.systray_enabled:
+ self.show_unsubscribed_dialog(account, contact)
+ return
+
+ self.add_event(account, jid, 'unsubscribed', contact)
+
+ if helpers.allow_showing_notification(account):
+ path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48)
+ event_type = _('Unsubscribed')
+ notify.popup(event_type, jid, account, 'unsubscribed', path,
+ event_type, jid)
+
+ def handle_event_agent_removed(self, account, agent):
+ # remove transport's contacts from treeview
+ jid_list = gajim.contacts.get_jid_list(account)
+ for jid in jid_list:
+ if jid.endswith('@' + agent):
+ c = gajim.contacts.get_first_contact_from_jid(account, jid)
+ gajim.log.debug(
+ 'Removing contact %s due to unregistered transport %s'\
+ % (jid, agent))
+ gajim.connections[account].unsubscribe(c.jid)
+ # Transport contacts can't have 2 resources
+ if c.jid in gajim.to_be_removed[account]:
+ # This way we'll really remove it
+ gajim.to_be_removed[account].remove(c.jid)
+ self.roster.remove_contact(c.jid, account, backend=True)
+
+ def handle_event_register_agent_info(self, account, array):
+ # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
+ # info in a dataform if is_form is True
+ if array[2] or 'instructions' in array[1]:
+ config.ServiceRegistrationWindow(array[0], array[1], account,
+ array[2])
+ else:
+ dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \
+ % array[0], _('Check your connection or try again later.'))
+
+ def handle_event_agent_info_items(self, account, array):
+ #('AGENT_INFO_ITEMS', account, (agent, node, items))
+ our_jid = gajim.get_jid_from_account(account)
+ if 'pep_services' in gajim.interface.instances[account] and \
+ array[0] == our_jid:
+ gajim.interface.instances[account]['pep_services'].items_received(
+ array[2])
+
+ def handle_event_acc_ok(self, account, array):
+ #('ACC_OK', account, (config))
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('NewAccount', (account, array))
+
+ def handle_event_quit(self, p1, p2):
+ self.roster.quit_gtkgui_interface()
+
+ def handle_event_myvcard(self, account, array):
+ nick = ''
+ if 'NICKNAME' in array and array['NICKNAME']:
+ gajim.nicks[account] = array['NICKNAME']
+ elif 'FN' in array and array['FN']:
+ gajim.nicks[account] = array['FN']
+ if 'profile' in self.instances[account]:
+ win = self.instances[account]['profile']
+ win.set_values(array)
+ if account in self.show_vcard_when_connect:
+ self.show_vcard_when_connect.remove(account)
+ jid = array['jid']
+ if jid in self.instances[account]['infos']:
+ self.instances[account]['infos'][jid].set_values(array)
+
+ def handle_event_vcard(self, account, vcard):
+ # ('VCARD', account, data)
+ '''vcard holds the vcard data'''
+ jid = vcard['jid']
+ resource = vcard.get('resource', '')
+ fjid = jid + '/' + str(resource)
+
+ # vcard window
+ win = None
+ if jid in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][jid]
+ elif resource and fjid in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][fjid]
+ if win:
+ win.set_values(vcard)
+
+ # show avatar in chat
+ ctrl = None
+ if resource and self.msg_win_mgr.has_window(fjid, account):
+ win = self.msg_win_mgr.get_window(fjid, account)
+ ctrl = win.get_control(fjid, account)
+ elif self.msg_win_mgr.has_window(jid, account):
+ win = self.msg_win_mgr.get_window(jid, account)
+ ctrl = win.get_control(jid, account)
+
+ if ctrl and ctrl.type_id != message_control.TYPE_GC:
+ ctrl.show_avatar()
+
+ # Show avatar in roster or gc_roster
+ gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account)
+ if not gc_ctrl and \
+ jid in self.minimized_controls[account]:
+ gc_ctrl = self.minimized_controls[account][jid]
+ if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC:
+ gc_ctrl.draw_avatar(resource)
+ else:
+ self.roster.draw_avatar(jid, account)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('VcardInfo', (account, vcard))
+
+ def handle_event_last_status_time(self, account, array):
+ # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
+ tim = array[2]
+ if tim < 0:
+ # Ann error occured
+ return
+ win = None
+ if array[0] in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][array[0]]
+ elif array[0] + '/' + array[1] in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][array[0] + '/' + array[1]]
+ c = gajim.contacts.get_contact(account, array[0], array[1])
+ if c: # c can be none if it's a gc contact
+ if array[3]:
+ c.status = array[3]
+ self.roster.draw_contact(c.jid, account) # draw offline status
+ last_time = time.localtime(time.time() - tim)
+ if c.show == 'offline':
+ c.last_status_time = last_time
+ else:
+ c.last_activity_time = last_time
+ if win:
+ win.set_last_status_time()
+ if self.roster.tooltip.id and self.roster.tooltip.win:
+ self.roster.tooltip.update_last_time(last_time)
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('LastStatusTime', (account, array))
+
+ def handle_event_os_info(self, account, array):
+ #'OS_INFO' (account, (jid, resource, client_info, os_info))
+ win = None
+ if array[0] in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][array[0]]
+ elif array[0] + '/' + array[1] in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][array[0] + '/' + array[1]]
+ if win:
+ win.set_os_info(array[1], array[2], array[3])
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('OsInfo', (account, array))
+
+ def handle_event_entity_time(self, account, array):
+ #'ENTITY_TIME' (account, (jid, resource, time_info))
+ win = None
+ if array[0] in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][array[0]]
+ elif array[0] + '/' + array[1] in self.instances[account]['infos']:
+ win = self.instances[account]['infos'][array[0] + '/' + array[1]]
+ if win:
+ win.set_entity_time(array[1], array[2])
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('EntityTime', (account, array))
+
+ def handle_event_gc_notify(self, account, array):
+ #'GC_NOTIFY' (account, (room_jid, show, status, nick,
+ # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha))
+ nick = array[3]
+ if not nick:
+ return
+ room_jid = array[0]
+ fjid = room_jid + '/' + nick
+ show = array[1]
+ status = array[2]
+ conn = gajim.connections[account]
+
+ # Get the window and control for the updated status, this may be a
+ # PrivateChatControl
+ control = self.msg_win_mgr.get_gc_control(room_jid, account)
+
+ if not control and \
+ room_jid in self.minimized_controls[account]:
+ control = self.minimized_controls[account][room_jid]
+
+ if not control or (control and control.type_id != message_control.TYPE_GC):
+ return
+
+ control.chg_contact_status(nick, show, status, array[4], array[5],
+ array[6], array[7], array[8], array[9], array[10], array[11])
+
+ contact = gajim.contacts.\
+ get_contact_with_highest_priority(account, room_jid)
+ if contact:
+ self.roster.draw_contact(room_jid, account)
+
+ # print status in chat window and update status/GPG image
+ ctrl = self.msg_win_mgr.get_control(fjid, account)
+ if ctrl:
+ statusCode = array[9]
+ if '303' in statusCode:
+ new_nick = array[10]
+ ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \
+ % {'nick': nick, 'new_nick': new_nick}, 'status')
+ gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick)
+ c = gc_c.as_contact()
+ ctrl.gc_contact = gc_c
+ ctrl.contact = c
+ if ctrl.session:
+ # stop e2e
+ if ctrl.session.enable_encryption:
+ thread_id = ctrl.session.thread_id
+ ctrl.session.terminate_e2e()
+ conn.delete_session(fjid, thread_id)
+ ctrl.no_autonegotiation = False
+ ctrl.draw_banner()
+ old_jid = room_jid + '/' + nick
+ new_jid = room_jid + '/' + new_nick
+ self.msg_win_mgr.change_key(old_jid, new_jid, account)
+ else:
+ contact = ctrl.contact
+ contact.show = show
+ contact.status = status
+ gc_contact = ctrl.gc_contact
+ gc_contact.show = show
+ gc_contact.status = status
+ uf_show = helpers.get_uf_show(show)
+ ctrl.print_conversation(_('%(nick)s is now %(status)s') % {
+ 'nick': nick, 'status': uf_show}, 'status')
+ if status:
+ ctrl.print_conversation(' (', 'status', simple=True)
+ ctrl.print_conversation('%s' % (status), 'status', simple=True)
+ ctrl.print_conversation(')', 'status', simple=True)
+ ctrl.parent_win.redraw_tab(ctrl)
+ ctrl.update_ui()
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('GCPresence', (account, array))
+
+ def handle_event_gc_msg(self, account, array):
+ # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg,
+ # [status_codes]))
+ jids = array[0].split('/', 1)
+ room_jid = jids[0]
+
+ msg = array[1]
+
+ gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
+ if not gc_control and \
+ room_jid in self.minimized_controls[account]:
+ gc_control = self.minimized_controls[account][room_jid]
+
+ if not gc_control:
+ return
+ xhtml = array[4]
+
+ if gajim.config.get('ignore_incoming_xhtml'):
+ xhtml = None
+ if len(jids) == 1:
+ # message from server
+ nick = ''
+ else:
+ # message from someone
+ nick = jids[1]
+
+ gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5])
+
+ if self.remote_ctrl:
+ highlight = gc_control.needs_visual_notification(msg)
+ array += (highlight,)
+ self.remote_ctrl.raise_signal('GCMessage', (account, array))
+
+ def handle_event_gc_subject(self, account, array):
+ #('GC_SUBJECT', account, (jid, subject, body, has_timestamp))
+ jids = array[0].split('/', 1)
+ jid = jids[0]
+
+ gc_control = self.msg_win_mgr.get_gc_control(jid, account)
+
+ if not gc_control and \
+ jid in self.minimized_controls[account]:
+ gc_control = self.minimized_controls[account][jid]
+
+ contact = gajim.contacts.\
+ get_contact_with_highest_priority(account, jid)
+ if contact:
+ contact.status = array[1]
+ self.roster.draw_contact(jid, account)
+
+ if not gc_control:
+ return
+ gc_control.set_subject(array[1])
+ # Standard way, the message comes from the occupant who set the subject
+ text = None
+ if len(jids) > 1:
+ text = _('%(jid)s has set the subject to %(subject)s') % {
+ 'jid': jids[1], 'subject': array[1]}
+ # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be
+ # deleted one day. We can receive a subject with a body that contains
+ # "X has set the subject to Y" ...
+ elif array[2]:
+ text = array[2]
+ if text is not None:
+ if array[3]:
+ gc_control.print_old_conversation(text)
+ else:
+ gc_control.print_conversation(text)
+
+ def handle_event_gc_config(self, account, array):
+ #('GC_CONFIG', account, (jid, form)) config is a dict
+ room_jid = array[0].split('/')[0]
+ if room_jid in gajim.automatic_rooms[account]:
+ if 'continue_tag' in gajim.automatic_rooms[account][room_jid]:
+ # We're converting chat to muc. allow participants to invite
+ form = dataforms.ExtendForm(node = array[1])
+ for f in form.iter_fields():
+ if f.var == 'muc#roomconfig_allowinvites':
+ f.value = True
+ elif f.var == 'muc#roomconfig_publicroom':
+ f.value = False
+ elif f.var == 'muc#roomconfig_membersonly':
+ f.value = True
+ elif f.var == 'public_list':
+ f.value = False
+ gajim.connections[account].send_gc_config(room_jid, form)
+ else:
+ # use default configuration
+ gajim.connections[account].send_gc_config(room_jid, array[1])
+ # invite contacts
+ # check if it is necessary to add <continue />
+ continue_tag = False
+ if 'continue_tag' in gajim.automatic_rooms[account][room_jid]:
+ continue_tag = True
+ if 'invities' in gajim.automatic_rooms[account][room_jid]:
+ for jid in gajim.automatic_rooms[account][room_jid]['invities']:
+ gajim.connections[account].send_invite(room_jid, jid,
+ continue_tag=continue_tag)
+ del gajim.automatic_rooms[account][room_jid]
+ elif room_jid not in self.instances[account]['gc_config']:
+ self.instances[account]['gc_config'][room_jid] = \
+ config.GroupchatConfigWindow(account, room_jid, array[1])
+
+ def handle_event_gc_config_change(self, account, array):
+ #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list
+ # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
+ # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init
+ jid = array[0]
+ statusCode = array[1]
+
+ gc_control = self.msg_win_mgr.get_gc_control(jid, account)
+ if not gc_control and \
+ jid in self.minimized_controls[account]:
+ gc_control = self.minimized_controls[account][jid]
+ if not gc_control:
+ return
+
+ changes = []
+ if '100' in statusCode:
+ # Can be a presence (see chg_contact_status in groupchat_control.py)
+ changes.append(_('Any occupant is allowed to see your full JID'))
+ gc_control.is_anonymous = False
+ if '102' in statusCode:
+ changes.append(_('Room now shows unavailable member'))
+ if '103' in statusCode:
+ changes.append(_('room now does not show unavailable members'))
+ if '104' in statusCode:
+ changes.append(
+ _('A non-privacy-related room configuration change has occurred'))
+ if '170' in statusCode:
+ # Can be a presence (see chg_contact_status in groupchat_control.py)
+ changes.append(_('Room logging is now enabled'))
+ if '171' in statusCode:
+ changes.append(_('Room logging is now disabled'))
+ if '172' in statusCode:
+ changes.append(_('Room is now non-anonymous'))
+ gc_control.is_anonymous = False
+ if '173' in statusCode:
+ changes.append(_('Room is now semi-anonymous'))
+ gc_control.is_anonymous = True
+ if '174' in statusCode:
+ changes.append(_('Room is now fully-anonymous'))
+ gc_control.is_anonymous = True
+
+ for change in changes:
+ gc_control.print_conversation(change)
+
+ def handle_event_gc_affiliation(self, account, array):
+ #('GC_AFFILIATION', account, (room_jid, users_dict))
+ room_jid = array[0]
+ if room_jid in self.instances[account]['gc_config']:
+ self.instances[account]['gc_config'][room_jid].\
+ affiliation_list_received(array[1])
+
+ def handle_event_gc_password_required(self, account, array):
+ #('GC_PASSWORD_REQUIRED', account, (room_jid, nick))
+ room_jid = array[0]
+ nick = array[1]
+
+ def on_ok(text):
+ gajim.connections[account].join_gc(nick, room_jid, text)
+ gajim.gc_passwords[room_jid] = text
+
+ def on_cancel():
+ # get and destroy window
+ if room_jid in gajim.interface.minimized_controls[account]:
+ self.roster.on_disconnect(None, room_jid, account)
+ else:
+ win = self.msg_win_mgr.get_window(room_jid, account)
+ ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
+ win.remove_tab(ctrl, 3)
+
+ dlg = dialogs.InputDialog(_('Password Required'),
+ _('A Password is required to join the room %s. Please type it.') % \
+ room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel)
+ dlg.input_entry.set_visibility(False)
+
+ def handle_event_gc_invitation(self, account, array):
+ #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
+ jid = gajim.get_jid_without_resource(array[1])
+ room_jid = array[0]
+ if helpers.allow_popup_window(account) or not self.systray_enabled:
+ dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3],
+ array[2], is_continued=array[4])
+ return
+
+ self.add_event(account, jid, 'gc-invitation', (room_jid, array[2],
+ array[3], array[4]))
+
+ if helpers.allow_showing_notification(account):
+ path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48)
+ event_type = _('Groupchat Invitation')
+ notify.popup(event_type, jid, account, 'gc-invitation', path,
+ event_type, room_jid)
+
+ def forget_gpg_passphrase(self, keyid):
+ if keyid in self.gpg_passphrase:
+ del self.gpg_passphrase[keyid]
+ return False
+
+ def handle_event_bad_passphrase(self, account, array):
+ #('BAD_PASSPHRASE', account, ())
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+ sectext = ''
+ if use_gpg_agent:
+ sectext = _('You configured Gajim to use GPG agent, but there is no '
+ 'GPG agent running or it returned a wrong passphrase.\n')
+ sectext += _('You are currently connected without your OpenPGP key.')
+ dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext)
+ else:
+ path = gtkgui_helpers.get_icon_path('gajim-warning', 48)
+ notify.popup('warning', account, account, 'warning', path,
+ _('OpenGPG Passphrase Incorrect'),
+ _('You are currently connected without your OpenPGP key.'))
+ keyID = gajim.config.get_per('accounts', account, 'keyid')
+ self.forget_gpg_passphrase(keyID)
+
+ def handle_event_gpg_password_required(self, account, array):
+ #('GPG_PASSWORD_REQUIRED', account, (callback,))
+ callback = array[0]
+ keyid = gajim.config.get_per('accounts', account, 'keyid')
+ if keyid in self.gpg_passphrase:
+ request = self.gpg_passphrase[keyid]
+ else:
+ request = PassphraseRequest(keyid)
+ self.gpg_passphrase[keyid] = request
+ request.add_callback(account, callback)
+
+ def handle_event_gpg_always_trust(self, account, callback):
+ #('GPG_ALWAYS_TRUST', account, callback)
+ def on_yes(checked):
+ if checked:
+ gajim.connections[account].gpg.always_trust = True
+ callback(True)
+
+ def on_no():
+ callback(False)
+
+ dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
+ 'encrypt this chat is not trusted. Do you really want to encrypt this '
+ 'message?'), checktext=_('Do _not ask me again'),
+ on_response_yes=on_yes, on_response_no=on_no)
+
+ def handle_event_password_required(self, account, array):
+ #('PASSWORD_REQUIRED', account, None)
+ if account in self.pass_dialog:
+ return
+ text = _('Enter your password for account %s') % account
+ if passwords.USER_HAS_GNOMEKEYRING and \
+ not passwords.USER_USES_GNOMEKEYRING:
+ text += '\n' + _('Gnome Keyring is installed but not \
+ correctly started (environment variable probably not \
+ correctly set)')
+
+ def on_ok(passphrase, save):
+ if save:
+ gajim.config.set_per('accounts', account, 'savepass', True)
+ passwords.save_password(account, passphrase)
+ gajim.connections[account].set_password(passphrase)
+ del self.pass_dialog[account]
+
+ def on_cancel():
+ self.roster.set_state(account, 'offline')
+ self.roster.update_status_combobox()
+ del self.pass_dialog[account]
+
+ self.pass_dialog[account] = dialogs.PassphraseDialog(
+ _('Password Required'), text, _('Save password'), ok_handler=on_ok,
+ cancel_handler=on_cancel)
+
+ def handle_event_roster_info(self, account, array):
+ #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
+ jid = array[0]
+ name = array[1]
+ sub = array[2]
+ ask = array[3]
+ groups = array[4]
+ contacts = gajim.contacts.get_contacts(account, jid)
+ if (not sub or sub == 'none') and (not ask or ask == 'none') and \
+ not name and not groups:
+ # contact removed us.
+ if contacts:
+ self.roster.remove_contact(jid, account, backend=True)
+ return
+ elif not contacts:
+ if sub == 'remove':
+ return
+ # Add new contact to roster
+ contact = gajim.contacts.create_contact(jid=jid, account=account,
+ name=name, groups=groups, show='offline', sub=sub, ask=ask)
+ gajim.contacts.add_contact(account, contact)
+ self.roster.add_contact(jid, account)
+ else:
+ # it is an existing contact that might has changed
+ re_place = False
+ # If contact has changed (sub, ask or group) update roster
+ # Mind about observer status changes:
+ # According to xep 0162, a contact is not an observer anymore when
+ # we asked for auth, so also remove him if ask changed
+ old_groups = contacts[0].groups
+ if contacts[0].sub != sub or contacts[0].ask != ask\
+ or old_groups != groups:
+ re_place = True
+ # c.get_shown_groups() has changed. Reflect that in roster_winodow
+ self.roster.remove_contact(jid, account, force=True)
+ for contact in contacts:
+ contact.name = name or ''
+ contact.sub = sub
+ contact.ask = ask
+ contact.groups = groups or []
+ if re_place:
+ self.roster.add_contact(jid, account)
+ # Refilter and update old groups
+ for group in old_groups:
+ self.roster.draw_group(group, account)
+ else:
+ self.roster.draw_contact(jid, account)
+
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('RosterInfo', (account, array))
+
+ def handle_event_bookmarks(self, account, bms):
+ # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
+ # We received a bookmark item from the server (JEP48)
+ # Auto join GC windows if neccessary
+
+ self.roster.set_actions_menu_needs_rebuild()
+ invisible_show = gajim.SHOW_LIST.index('invisible')
+ # do not autojoin if we are invisible
+ if gajim.connections[account].connected == invisible_show:
+ return
+
+ self.auto_join_bookmarks(account)
+
+ def handle_event_file_send_error(self, account, array):
+ jid = array[0]
+ file_props = array[1]
+ ft = self.instances['file_transfers']
+ ft.set_status(file_props['type'], file_props['sid'], 'stop')
+
+ if helpers.allow_popup_window(account):
+ ft.show_send_error(file_props)
+ return
+
+ self.add_event(account, jid, 'file-send-error', file_props)
+
+ if helpers.allow_showing_notification(account):
+ path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
+ event_type = _('File Transfer Error')
+ notify.popup(event_type, jid, account, 'file-send-error', path,
+ event_type, file_props['name'])
+
+ def handle_event_gmail_notify(self, account, array):
+ jid = array[0]
+ gmail_new_messages = int(array[1])
+ gmail_messages_list = array[2]
+ if gajim.config.get('notify_on_new_gmail_email'):
+ path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
+ title = _('New mail on %(gmail_mail_address)s') % \
+ {'gmail_mail_address': jid}
+ text = i18n.ngettext('You have %d new mail conversation',
+ 'You have %d new mail conversations', gmail_new_messages,
+ gmail_new_messages, gmail_new_messages)
+
+ if gajim.config.get('notify_on_new_gmail_email_extra'):
+ cnt = 0
+ for gmessage in gmail_messages_list:
+ #FIXME: emulate Gtalk client popups. find out what they parse and
+ # how they decide what to show each message has a 'From',
+ # 'Subject' and 'Snippet' field
+ if cnt >=5:
+ break
+ senders = ',\n '.join(reversed(gmessage['From']))
+ text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \
+ {'from_address': senders, 'subject': gmessage['Subject'],
+ 'snippet': gmessage['Snippet']}
+ cnt += 1
+
+ if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
+ helpers.play_sound('gmail_received')
+ notify.popup(_('New E-mail'), jid, account, 'gmail',
+ path_to_image=path, title=title,
+ text=text)
+
+ if self.remote_ctrl:
+ self.remote_ctrl.raise_signal('NewGmail', (account, array))
+
+ def handle_event_file_request_error(self, account, array):
+ # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
+ jid, file_props, errmsg = array
+ jid = gajim.get_jid_without_resource(jid)
+ ft = self.instances['file_transfers']
+ ft.set_status(file_props['type'], file_props['sid'], 'stop')
+ errno = file_props['error']
+
+ if helpers.allow_popup_window(account):
+ if errno in (-4, -5):
+ ft.show_stopped(jid, file_props, errmsg)
+ else:
+ ft.show_request_error(file_props)
+ return
+
+ if errno in (-4, -5):
+ msg_type = 'file-error'
+ else:
+ msg_type = 'file-request-error'
+
+ self.add_event(account, jid, msg_type, file_props)
+
+ if helpers.allow_showing_notification(account):
+ # check if we should be notified
+ path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
+ event_type = _('File Transfer Error')
+ notify.popup(event_type, jid, account, msg_type, path,
+ title = event_type, text = file_props['name'])
+
+ def handle_event_file_request(self, account, array):
+ jid = array[0]
+ jid = gajim.get_jid_without_resource(jid)
+ if jid not in gajim.contacts.get_jid_list(account):
+ keyID = ''
+ attached_keys = gajim.config.get_per('accounts', account,
+ 'attached_gpg_keys').split()
+ if jid in attached_keys:
+ keyID = attached_keys[attached_keys.index(jid) + 1]
+ contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
+ account=account, keyID=keyID)
+ gajim.contacts.add_contact(account, contact)
+ self.roster.add_contact(contact.jid, account)
+ file_props = array[1]
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+
+ if helpers.allow_popup_window(account):
+ self.instances['file_transfers'].show_file_request(account, contact,
+ file_props)
+ return
+
+ self.add_event(account, jid, 'file-request', file_props)
+
+ if helpers.allow_showing_notification(account):
+ path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48)
+ txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
+ account, jid)
+ event_type = _('File Transfer Request')
+ notify.popup(event_type, jid, account, 'file-request',
+ path_to_image = path, title = event_type, text = txt)
+
+ def handle_event_file_error(self, title, message):
+ dialogs.ErrorDialog(title, message)
+
+ def handle_event_file_progress(self, account, file_props):
+ if time.time() - self.last_ftwindow_update > 0.5:
+ # update ft window every 500ms
+ self.last_ftwindow_update = time.time()
+ self.instances['file_transfers'].set_progress(file_props['type'],
+ file_props['sid'], file_props['received-len'])
+
+ def handle_event_file_rcv_completed(self, account, file_props):
+ ft = self.instances['file_transfers']
+ if file_props['error'] == 0:
+ ft.set_progress(file_props['type'], file_props['sid'],
+ file_props['received-len'])
+ else:
+ ft.set_status(file_props['type'], file_props['sid'], 'stop')
+ if 'stalled' in file_props and file_props['stalled'] or \
+ 'paused' in file_props and file_props['paused']:
+ return
+ if file_props['type'] == 'r': # we receive a file
+ jid = unicode(file_props['sender'])
+ else: # we send a file
+ jid = unicode(file_props['receiver'])
+
+ if helpers.allow_popup_window(account):
+ if file_props['error'] == 0:
+ if gajim.config.get('notify_on_file_complete'):
+ ft.show_completed(jid, file_props)
+ elif file_props['error'] == -1:
+ ft.show_stopped(jid, file_props,
+ error_msg=_('Remote contact stopped transfer'))
+ elif file_props['error'] == -6:
+ ft.show_stopped(jid, file_props, error_msg=_('Error opening file'))
+ return
+
+ msg_type = ''
+ event_type = ''
+ if file_props['error'] == 0 and gajim.config.get(
+ 'notify_on_file_complete'):
+ msg_type = 'file-completed'
+ event_type = _('File Transfer Completed')
+ elif file_props['error'] in (-1, -6):
+ msg_type = 'file-stopped'
+ event_type = _('File Transfer Stopped')
+
+ if event_type == '':
+ # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
+ # this should never happen but it does. see process_result() in socks5.py
+ # who calls this func (sth is really wrong unless this func is also registered
+ # as progress_cb
+ return
+
+ if msg_type:
+ self.add_event(account, jid, msg_type, file_props)
+
+ if file_props is not None:
+ if file_props['type'] == 'r':
+ # get the name of the sender, as it is in the roster
+ sender = unicode(file_props['sender']).split('/')[0]
+ name = gajim.contacts.get_first_contact_from_jid(account,
+ sender).get_shown_name()
+ filename = os.path.basename(file_props['file-name'])
+ if event_type == _('File Transfer Completed'):
+ txt = _('You successfully received %(filename)s from %(name)s.')\
+ % {'filename': filename, 'name': name}
+ img_name = 'gajim-ft_done'
+ else: # ft stopped
+ txt = _('File transfer of %(filename)s from %(name)s stopped.')\
+ % {'filename': filename, 'name': name}
+ img_name = 'gajim-ft_stopped'
+ else:
+ receiver = file_props['receiver']
+ if hasattr(receiver, 'jid'):
+ receiver = receiver.jid
+ receiver = receiver.split('/')[0]
+ # get the name of the contact, as it is in the roster
+ name = gajim.contacts.get_first_contact_from_jid(account,
+ receiver).get_shown_name()
+ filename = os.path.basename(file_props['file-name'])
+ if event_type == _('File Transfer Completed'):
+ txt = _('You successfully sent %(filename)s to %(name)s.')\
+ % {'filename': filename, 'name': name}
+ img_name = 'gajim-ft_done'
+ else: # ft stopped
+ txt = _('File transfer of %(filename)s to %(name)s stopped.')\
+ % {'filename': filename, 'name': name}
+ img_name = 'gajim-ft_stopped'
+ path = gtkgui_helpers.get_icon_path(img_name, 48)
+ else:
+ txt = ''
+ path = ''
+
+ if gajim.config.get('notify_on_file_complete') and \
+ (gajim.config.get('autopopupaway') or \
+ gajim.connections[account].connected in (2, 3)):
+ # we want to be notified and we are online/chat or we don't mind
+ # bugged when away/na/busy
+ notify.popup(event_type, jid, account, msg_type, path_to_image=path,
+ title=event_type, text=txt)
+
+ def handle_event_stanza_arrived(self, account, stanza):
+ if account not in self.instances:
+ return
+ if 'xml_console' in self.instances[account]:
+ self.instances[account]['xml_console'].print_stanza(stanza, 'incoming')
+
+ def handle_event_stanza_sent(self, account, stanza):
+ if account not in self.instances:
+ return
+ if 'xml_console' in self.instances[account]:
+ self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing')
+
+ def handle_event_vcard_published(self, account, array):
+ if 'profile' in self.instances[account]:
+ win = self.instances[account]['profile']
+ win.vcard_published()
+ for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \
+ self.minimized_controls[account].values():
+ if gc_control.account == account:
+ show = gajim.SHOW_LIST[gajim.connections[account].connected]
+ status = gajim.connections[account].status
+ gajim.connections[account].send_gc_status(gc_control.nick,
+ gc_control.room_jid, show, status)
+
+ def handle_event_vcard_not_published(self, account, array):
+ if 'profile' in self.instances[account]:
+ win = self.instances[account]['profile']
+ win.vcard_not_published()
+
+ def ask_offline_status(self, account):
+ for contact in gajim.contacts.iter_contacts(account):
+ gajim.connections[account].request_last_status_time(contact.jid,
+ contact.resource)
+
+ def handle_event_signed_in(self, account, empty):
+ """
+ SIGNED_IN event is emitted when we sign in, so handle it
+ """
+ # ('SIGNED_IN', account, ())
+ # block signed in notifications for 30 seconds
+ gajim.block_signed_in_notifications[account] = True
+ self.roster.set_actions_menu_needs_rebuild()
+ self.roster.draw_account(account)
+ state = self.sleeper.getState()
+ connected = gajim.connections[account].connected
+ if gajim.config.get('ask_offline_status_on_connection'):
+ # Ask offline status in 1 minute so w'are sure we got all online
+ # presences
+ gobject.timeout_add_seconds(60, self.ask_offline_status, account)
+ if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3):
+ # we go online or free for chat, so we activate auto status
+ gajim.sleeper_state[account] = 'online'
+ elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \
+ (state == common.sleepy.STATE_XA and connected == 5)):
+ # If we are autoaway/xa and come back after a disconnection, do nothing
+ # Else disable autoaway
+ gajim.sleeper_state[account] = 'off'
+ invisible_show = gajim.SHOW_LIST.index('invisible')
+ # We cannot join rooms if we are invisible
+ if gajim.connections[account].connected == invisible_show:
+ return
+ # join already open groupchats
+ for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \
+ + self.minimized_controls[account].values():
+ if account != gc_control.account:
+ continue
+ room_jid = gc_control.room_jid
+ if room_jid in gajim.gc_connected[account] and \
+ gajim.gc_connected[account][room_jid]:
+ continue
+ nick = gc_control.nick
+ password = gajim.gc_passwords.get(room_jid, '')
+ gajim.connections[account].join_gc(nick, room_jid, password)
+ # send currently played music
+ if gajim.connections[account].pep_supported and dbus_support.supported \
+ and gajim.config.get_per('accounts', account, 'publish_tune'):
+ self.enable_music_listener()
+ # enable location listener
+ if gajim.connections[account].pep_supported and dbus_support.supported \
+ and gajim.config.get_per('accounts', account, 'publish_location'):
+ location_listener.enable()
+
+ def handle_event_metacontacts(self, account, tags_list):
+ gajim.contacts.define_metacontacts(account, tags_list)
+ self.roster.redraw_metacontacts(account)
+
+ def handle_atom_entry(self, account, data):
+ atom_entry, = data
+ AtomWindow.newAtomEntry(atom_entry)
+
+ def handle_event_failed_decrypt(self, account, data):
+ jid, tim, session = data
+
+ details = _('Unable to decrypt message from '
+ '%s\nIt may have been tampered with.') % jid
+
+ ctrl = session.control
+ if ctrl:
+ ctrl.print_conversation_line(details, 'status', '', tim)
+ else:
+ dialogs.WarningDialog(_('Unable to decrypt message'),
+ details)
+
+ # terminate the session
+ session.terminate_e2e()
+ session.conn.delete_session(jid, session.thread_id)
+
+ # restart the session
+ if ctrl:
+ ctrl.begin_e2e_negotiation()
+
+ def handle_event_privacy_lists_received(self, account, data):
+ # ('PRIVACY_LISTS_RECEIVED', account, list)
+ if account not in self.instances:
+ return
+ if 'privacy_lists' in self.instances[account]:
+ self.instances[account]['privacy_lists'].privacy_lists_received(data)
+
+ def handle_event_privacy_list_received(self, account, data):
+ # ('PRIVACY_LIST_RECEIVED', account, (name, rules))
+ if account not in self.instances:
+ return
+ name = data[0]
+ rules = data[1]
+ if 'privacy_list_%s' % name in self.instances[account]:
+ self.instances[account]['privacy_list_%s' % name].\
+ privacy_list_received(rules)
+ if name == 'block':
+ gajim.connections[account].blocked_contacts = []
+ gajim.connections[account].blocked_groups = []
+ gajim.connections[account].blocked_list = []
+ gajim.connections[account].blocked_all = False
+ for rule in rules:
+ if not 'type' in rule:
+ gajim.connections[account].blocked_all = True
+ elif rule['type'] == 'jid' and rule['action'] == 'deny':
+ gajim.connections[account].blocked_contacts.append(rule['value'])
+ elif rule['type'] == 'group' and rule['action'] == 'deny':
+ gajim.connections[account].blocked_groups.append(rule['value'])
+ gajim.connections[account].blocked_list.append(rule)
+ #elif rule['type'] == "group" and action == "deny":
+ # text_item = _('%s group "%s"') % _(rule['action']), rule['value']
+ # self.store.append([text_item])
+ # self.global_rules.append(rule)
+ #else:
+ # self.global_rules_to_append.append(rule)
+ if 'blocked_contacts' in self.instances[account]:
+ self.instances[account]['blocked_contacts'].\
+ privacy_list_received(rules)
+
+ def handle_event_privacy_lists_active_default(self, account, data):
+ if not data:
+ return
+ # Send to all privacy_list_* windows as we can't know which one asked
+ for win in self.instances[account]:
+ if win.startswith('privacy_list_'):
+ self.instances[account][win].check_active_default(data)
+
+ def handle_event_privacy_list_removed(self, account, name):
+ # ('PRIVACY_LISTS_REMOVED', account, name)
+ if account not in self.instances:
+ return
+ if 'privacy_lists' in self.instances[account]:
+ self.instances[account]['privacy_lists'].privacy_list_removed(name)
+
+ def handle_event_zc_name_conflict(self, account, data):
+ def on_ok(new_name):
+ gajim.config.set_per('accounts', account, 'name', new_name)
+ status = gajim.connections[account].status
+ gajim.connections[account].username = new_name
+ gajim.connections[account].change_status(status, '')
+ def on_cancel():
+ gajim.connections[account].change_status('offline','')
+
+ dlg = dialogs.InputDialog(_('Username Conflict'),
+ _('Please type a new username for your local account'), input_str=data,
+ is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel)
+
+ def handle_event_ping_sent(self, account, contact):
+ if contact.jid == contact.get_full_jid():
+ # If contact is a groupchat user
+ jids = [contact.jid]
+ else:
+ jids = [contact.jid, contact.get_full_jid()]
+ for jid in jids:
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ ctrl.print_conversation(_('Ping?'), 'status')
+
+ def handle_event_ping_reply(self, account, data):
+ contact = data[0]
+ seconds = data[1]
+ if contact.jid == contact.get_full_jid():
+ # If contact is a groupchat user
+ jids = [contact.jid]
+ else:
+ jids = [contact.jid, contact.get_full_jid()]
+ for jid in jids:
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status')
+
+ def handle_event_ping_error(self, account, contact):
+ if contact.jid == contact.get_full_jid():
+ # If contact is a groupchat user
+ jids = [contact.jid]
+ else:
+ jids = [contact.jid, contact.get_full_jid()]
+ for jid in jids:
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ ctrl.print_conversation(_('Error.'), 'status')
+
+ def handle_event_search_form(self, account, data):
+ # ('SEARCH_FORM', account, (jid, dataform, is_dataform))
+ if data[0] not in self.instances[account]['search']:
+ return
+ self.instances[account]['search'][data[0]].on_form_arrived(data[1],
+ data[2])
+
+ def handle_event_search_result(self, account, data):
+ # ('SEARCH_RESULT', account, (jid, dataform, is_dataform))
+ if data[0] not in self.instances[account]['search']:
+ return
+ self.instances[account]['search'][data[0]].on_result_arrived(data[1],
+ data[2])
+
+ def handle_event_resource_conflict(self, account, data):
+ # ('RESOURCE_CONFLICT', account, ())
+ # First we go offline, but we don't overwrite status message
+ self.roster.send_status(account, 'offline',
+ gajim.connections[account].status)
+ def on_ok(new_resource):
+ gajim.config.set_per('accounts', account, 'resource', new_resource)
+ self.roster.send_status(account, gajim.connections[account].old_show,
+ gajim.connections[account].status)
+ proposed_resource = gajim.connections[account].server_resource
+ proposed_resource += gajim.config.get('gc_proposed_nick_char')
+ dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
+ _('You are already connected to this account with the same resource. '
+ 'Please type a new one'), resource=proposed_resource, ok_handler=on_ok)
+
+ def handle_event_jingle_incoming(self, account, data):
+ # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
+ # data...))
+ # TODO: conditional blocking if peer is not in roster
+
+ # unpack data
+ peerjid, sid, contents = data
+ content_types = set(c[0] for c in contents)
+
+ # check type of jingle session
+ if 'audio' in content_types or 'video' in content_types:
+ # a voip session...
+ # we now handle only voip, so the only thing we will do here is
+ # not to return from function
+ pass
+ else:
+ # unknown session type... it should be declined in common/jingle.py
+ return
+
+ jid = gajim.get_jid_without_resource(peerjid)
+ resource = gajim.get_resource_from_jid(peerjid)
+ ctrl = self.msg_win_mgr.get_control(peerjid, account)
+ if not ctrl:
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ if 'audio' in content_types:
+ ctrl.set_audio_state('connection_received', sid)
+ if 'video' in content_types:
+ ctrl.set_video_state('connection_received', sid)
+
+ dlg = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid)
+ if dlg:
+ dlg.add_contents(content_types)
+ return
+
+ if helpers.allow_popup_window(account):
+ dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
+ return
+
+ self.add_event(account, peerjid, 'jingle-incoming', (peerjid, sid,
+ content_types))
+
+ if helpers.allow_showing_notification(account):
+ # TODO: we should use another pixmap ;-)
+ txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid(
+ account, peerjid)
+ path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
+ event_type = _('Voice Chat Request')
+ notify.popup(event_type, peerjid, account, 'jingle-incoming',
+ path_to_image = path, title = event_type, text = txt)
+
+ def handle_event_jingle_connected(self, account, data):
+ # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
+ peerjid, sid, media = data
+ if media in ('audio', 'video'):
+ jid = gajim.get_jid_without_resource(peerjid)
+ resource = gajim.get_resource_from_jid(peerjid)
+ ctrl = self.msg_win_mgr.get_control(peerjid, account)
+ if not ctrl:
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ if media == 'audio':
+ ctrl.set_audio_state('connected', sid)
+ else:
+ ctrl.set_video_state('connected', sid)
+
+ def handle_event_jingle_disconnected(self, account, data):
+ # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
+ peerjid, sid, media, reason = data
+ jid = gajim.get_jid_without_resource(peerjid)
+ resource = gajim.get_resource_from_jid(peerjid)
+ ctrl = self.msg_win_mgr.get_control(peerjid, account)
+ if not ctrl:
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ if media in ('audio', None):
+ ctrl.set_audio_state('stop', sid=sid, reason=reason)
+ if media in ('video', None):
+ ctrl.set_video_state('stop', sid=sid, reason=reason)
+ dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid)
+ if dialog:
+ dialog.dialog.destroy()
+
+ def handle_event_jingle_error(self, account, data):
+ # ('JINGLE_ERROR', account, (peerjid, sid, reason))
+ peerjid, sid, reason = data
+ jid = gajim.get_jid_without_resource(peerjid)
+ resource = gajim.get_resource_from_jid(peerjid)
+ ctrl = self.msg_win_mgr.get_control(peerjid, account)
+ if not ctrl:
+ ctrl = self.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ ctrl.set_audio_state('error', reason=reason)
+
+ def handle_event_pep_config(self, account, data):
+ # ('PEP_CONFIG', account, (node, form))
+ if 'pep_services' in self.instances[account]:
+ self.instances[account]['pep_services'].config(data[0], data[1])
+
+ def handle_event_roster_item_exchange(self, account, data):
+ # data = (action in [add, delete, modify], exchange_list, jid_from)
+ dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2])
+
+ def handle_event_unique_room_id_supported(self, account, data):
+ """
+ Receive confirmation that unique_room_id are supported
+ """
+ # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
+ instance = data[1]
+ instance.unique_room_id_supported(data[0], data[2])
+
+ def handle_event_unique_room_id_unsupported(self, account, data):
+ # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance)
+ instance = data[1]
+ instance.unique_room_id_error(data[0])
+
+ def handle_event_ssl_error(self, account, data):
+ # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
+ server = gajim.config.get_per('accounts', account, 'hostname')
+
+ def on_ok(is_checked):
+ del self.instances[account]['online_dialog']['ssl_error']
+ if is_checked[0]:
+ # Check if cert is already in file
+ certs = ''
+ if os.path.isfile(gajim.MY_CACERTS):
+ f = open(gajim.MY_CACERTS)
+ certs = f.read()
+ f.close()
+ if data[2] in certs:
+ dialogs.ErrorDialog(_('Certificate Already in File'),
+ _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS)
+ else:
+ f = open(gajim.MY_CACERTS, 'a')
+ f.write(server + '\n')
+ f.write(data[2] + '\n\n')
+ f.close()
+ gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
+ data[3])
+ if is_checked[1]:
+ ignore_ssl_errors = gajim.config.get_per('accounts', account,
+ 'ignore_ssl_errors').split()
+ ignore_ssl_errors.append(str(data[1]))
+ gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
+ ' '.join(ignore_ssl_errors))
+ gajim.connections[account].ssl_certificate_accepted()
+
+ def on_cancel():
+ del self.instances[account]['online_dialog']['ssl_error']
+ gajim.connections[account].disconnect(on_purpose=True)
+ self.handle_event_status(account, 'offline')
+
+ pritext = _('Error verifying SSL certificate')
+ sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]}
+ if data[1] in (18, 27):
+ checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3]
+ else:
+ checktext1 = ''
+ checktext2 = _('Ignore this error for this certificate.')
+ if 'ssl_error' in self.instances[account]['online_dialog']:
+ self.instances[account]['online_dialog']['ssl_error'].destroy()
+ self.instances[account]['online_dialog']['ssl_error'] = \
+ dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1,
+ checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel)
+
+ def handle_event_fingerprint_error(self, account, data):
+ # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
+ def on_yes(is_checked):
+ del self.instances[account]['online_dialog']['fingerprint_error']
+ gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
+ data[0])
+ # Reset the ignored ssl errors
+ gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
+ gajim.connections[account].ssl_certificate_accepted()
+ def on_no():
+ del self.instances[account]['online_dialog']['fingerprint_error']
+ gajim.connections[account].disconnect(on_purpose=True)
+ self.handle_event_status(account, 'offline')
+ pritext = _('SSL certificate error')
+ sectext = _('It seems the SSL certificate of account %(account)s has '
+ 'changed or your connection is being hacked.\nOld fingerprint: %(old)s'
+ '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update'
+ ' the fingerprint of the certificate?') % {'account': account,
+ 'old': gajim.config.get_per('accounts', account,
+ 'ssl_fingerprint_sha1'), 'new': data[0]}
+ if 'fingerprint_error' in self.instances[account]['online_dialog']:
+ self.instances[account]['online_dialog']['fingerprint_error'].destroy()
+ self.instances[account]['online_dialog']['fingerprint_error'] = \
+ dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
+ on_response_no=on_no)
+
+ def handle_event_plain_connection(self, account, data):
+ # ('PLAIN_CONNECTION', account, (connection))
+ server = gajim.config.get_per('accounts', account, 'hostname')
+ def on_ok(is_checked):
+ if not is_checked[0]:
+ on_cancel()
+ return
+ # On cancel call del self.instances, so don't call it another time
+ # before
+ del self.instances[account]['online_dialog']['plain_connection']
+ if is_checked[1]:
+ gajim.config.set_per('accounts', account,
+ 'warn_when_plaintext_connection', False)
+ gajim.connections[account].connection_accepted(data[0], 'plain')
+ def on_cancel():
+ del self.instances[account]['online_dialog']['plain_connection']
+ gajim.connections[account].disconnect(on_purpose=True)
+ self.handle_event_status(account, 'offline')
+ pritext = _('Insecure connection')
+ sectext = _('You are about to send your password on an unencrypted '
+ 'connection. Are you sure you want to do that?')
+ checktext1 = _('Yes, I really want to connect insecurely')
+ checktext2 = _('Do _not ask me again')
+ if 'plain_connection' in self.instances[account]['online_dialog']:
+ self.instances[account]['online_dialog']['plain_connection'].destroy()
+ self.instances[account]['online_dialog']['plain_connection'] = \
+ dialogs.ConfirmationDialogDubbleCheck(pritext, sectext,
+ checktext1, checktext2, on_response_ok=on_ok,
+ on_response_cancel=on_cancel, is_modal=False)
+
+ def handle_event_insecure_ssl_connection(self, account, data):
+ # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
+ server = gajim.config.get_per('accounts', account, 'hostname')
+ def on_ok(is_checked):
+ if not is_checked[0]:
+ on_cancel()
+ return
+ del self.instances[account]['online_dialog']['insecure_ssl']
+ if is_checked[1]:
+ gajim.config.set_per('accounts', account,
+ 'warn_when_insecure_ssl_connection', False)
+ if gajim.connections[account].connected == 0:
+ # We have been disconnecting (too long time since window is opened)
+ # re-connect with auto-accept
+ gajim.connections[account].connection_auto_accepted = True
+ show, msg = gajim.connections[account].continue_connect_info[:2]
+ self.roster.send_status(account, show, msg)
+ return
+ gajim.connections[account].connection_accepted(data[0], data[1])
+ def on_cancel():
+ del self.instances[account]['online_dialog']['insecure_ssl']
+ gajim.connections[account].disconnect(on_purpose=True)
+ self.handle_event_status(account, 'offline')
+ pritext = _('Insecure connection')
+ sectext = _('You are about to send your password on an insecure '
+ 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?')
+ checktext1 = _('Yes, I really want to connect insecurely')
+ checktext2 = _('Do _not ask me again')
+ if 'insecure_ssl' in self.instances[account]['online_dialog']:
+ self.instances[account]['online_dialog']['insecure_ssl'].destroy()
+ self.instances[account]['online_dialog']['insecure_ssl'] = \
+ dialogs.ConfirmationDialogDubbleCheck(pritext, sectext,
+ checktext1, checktext2, on_response_ok=on_ok,
+ on_response_cancel=on_cancel, is_modal=False)
+
+ def handle_event_pubsub_node_removed(self, account, data):
+ # ('PUBSUB_NODE_REMOVED', account, (jid, node))
+ if 'pep_services' in self.instances[account]:
+ if data[0] == gajim.get_jid_from_account(account):
+ self.instances[account]['pep_services'].node_removed(data[1])
+
+ def handle_event_pubsub_node_not_removed(self, account, data):
+ # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg))
+ if data[0] == gajim.get_jid_from_account(account):
+ dialogs.WarningDialog(_('PEP node was not removed'),
+ _('PEP node %(node)s was not removed: %(message)s') % {
+ 'node': data[1], 'message': data[2]})
+
+ def handle_event_pep_received(self, account, data):
+ # ('PEP_RECEIVED', account, (jid, pep_type))
+ jid = data[0]
+ pep_type = data[1]
+ ctrl = common.gajim.interface.msg_win_mgr.get_control(jid, account)
+
+ if jid == common.gajim.get_jid_from_account(account):
+ self.roster.draw_account(account)
+
+ if pep_type == 'nickname':
+ self.roster.draw_contact(jid, account)
+ if ctrl:
+ ctrl.update_ui()
+ win = ctrl.parent_win
+ win.redraw_tab(ctrl)
+ win.show_title()
+ else:
+ self.roster.draw_pep(jid, account, pep_type)
+ if ctrl:
+ ctrl.update_pep(pep_type)
+
+ def handle_event_caps_received(self, account, data):
+ # ('CAPS_RECEIVED', account, (full_jid))
+ full_jid = data[0]
+ pm_ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, account)
+ if pm_ctrl and hasattr(pm_ctrl, "update_contact"):
+ pm_ctrl.update_contact()
+
+ def create_core_handlers_list(self):
+ self.handlers = {
+ 'ROSTER': [self.handle_event_roster],
+ 'WARNING': [self.handle_event_warning],
+ 'ERROR': [self.handle_event_error],
+ 'INFORMATION': [self.handle_event_information],
+ 'ERROR_ANSWER': [self.handle_event_error_answer],
+ 'STATUS': [self.handle_event_status],
+ 'NEW_JID': [self.handle_event_new_jid],
+ 'NOTIFY': [self.handle_event_notify],
+ 'MSGERROR': [self.handle_event_msgerror],
+ 'MSGSENT': [self.handle_event_msgsent],
+ 'MSGNOTSENT': [self.handle_event_msgnotsent],
+ 'SUBSCRIBED': [self.handle_event_subscribed],
+ 'UNSUBSCRIBED': [self.handle_event_unsubscribed],
+ 'SUBSCRIBE': [self.handle_event_subscribe],
+ 'AGENT_REMOVED': [self.handle_event_agent_removed],
+ 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info],
+ 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items],
+ 'QUIT': [self.handle_event_quit],
+ 'ACC_OK': [self.handle_event_acc_ok],
+ 'MYVCARD': [self.handle_event_myvcard],
+ 'VCARD': [self.handle_event_vcard],
+ 'LAST_STATUS_TIME': [self.handle_event_last_status_time],
+ 'OS_INFO': [self.handle_event_os_info],
+ 'ENTITY_TIME': [self.handle_event_entity_time],
+ 'GC_NOTIFY': [self.handle_event_gc_notify],
+ 'GC_MSG': [self.handle_event_gc_msg],
+ 'GC_SUBJECT': [self.handle_event_gc_subject],
+ 'GC_CONFIG': [self.handle_event_gc_config],
+ 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change],
+ 'GC_INVITATION': [self.handle_event_gc_invitation],
+ 'GC_AFFILIATION': [self.handle_event_gc_affiliation],
+ 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required],
+ 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase],
+ 'ROSTER_INFO': [self.handle_event_roster_info],
+ 'BOOKMARKS': [self.handle_event_bookmarks],
+ 'CON_TYPE': [self.handle_event_con_type],
+ 'CONNECTION_LOST': [self.handle_event_connection_lost],
+ 'FILE_REQUEST': [self.handle_event_file_request],
+ 'GMAIL_NOTIFY': [self.handle_event_gmail_notify],
+ 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
+ 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
+ 'STANZA_ARRIVED': [self.handle_event_stanza_arrived],
+ 'STANZA_SENT': [self.handle_event_stanza_sent],
+ 'HTTP_AUTH': [self.handle_event_http_auth],
+ 'VCARD_PUBLISHED': [self.handle_event_vcard_published],
+ 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published],
+ 'ASK_NEW_NICK': [self.handle_event_ask_new_nick],
+ 'SIGNED_IN': [self.handle_event_signed_in],
+ 'METACONTACTS': [self.handle_event_metacontacts],
+ 'ATOM_ENTRY': [self.handle_atom_entry],
+ 'FAILED_DECRYPT': [self.handle_event_failed_decrypt],
+ 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received],
+ 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received],
+ 'PRIVACY_LISTS_ACTIVE_DEFAULT': \
+ [self.handle_event_privacy_lists_active_default],
+ 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed],
+ 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict],
+ 'PING_SENT': [self.handle_event_ping_sent],
+ 'PING_REPLY': [self.handle_event_ping_reply],
+ 'PING_ERROR': [self.handle_event_ping_error],
+ 'SEARCH_FORM': [self.handle_event_search_form],
+ 'SEARCH_RESULT': [self.handle_event_search_result],
+ 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict],
+ 'ROSTERX': [self.handle_event_roster_item_exchange],
+ 'PEP_CONFIG': [self.handle_event_pep_config],
+ 'UNIQUE_ROOM_ID_UNSUPPORTED': \
+ [self.handle_event_unique_room_id_unsupported],
+ 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported],
+ 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required],
+ 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust],
+ 'PASSWORD_REQUIRED': [self.handle_event_password_required],
+ 'SSL_ERROR': [self.handle_event_ssl_error],
+ 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error],
+ 'PLAIN_CONNECTION': [self.handle_event_plain_connection],
+ 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection],
+ 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed],
+ 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed],
+ 'JINGLE_INCOMING': [self.handle_event_jingle_incoming],
+ 'JINGLE_CONNECTED': [self.handle_event_jingle_connected],
+ 'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected],
+ 'JINGLE_ERROR': [self.handle_event_jingle_error],
+ 'PEP_RECEIVED': [self.handle_event_pep_received],
+ 'CAPS_RECEIVED': [self.handle_event_caps_received]
+ }
+
+ def register_core_handlers(self):
+ """
+ Register core handlers in Global Events Dispatcher (GED).
+
+ This is part of rewriting whole events handling system to use GED.
+ """
+ for event_name, event_handlers in self.handlers.iteritems():
+ for event_handler in event_handlers:
+ gajim.ged.register_event_handler(event_name, ged.CORE,
+ event_handler)
################################################################################
### Methods dealing with gajim.events
################################################################################
- def add_event(self, account, jid, type_, event_args):
- """
- Add an event to the gajim.events var
- """
- # We add it to the gajim.events queue
- # Do we have a queue?
- jid = gajim.get_jid_without_resource(jid)
- no_queue = len(gajim.events.get_events(account, jid)) == 0
- # type_ can be gc-invitation file-send-error file-error file-request-error
- # file-request file-completed file-stopped jingle-incoming
- # event_type can be in advancedNotificationWindow.events_list
- event_types = {'file-request': 'ft_request',
- 'file-completed': 'ft_finished'}
- event_type = event_types.get(type_)
- show_in_roster = notify.get_show_in_roster(event_type, account, jid)
- show_in_systray = notify.get_show_in_systray(event_type, account, jid)
- event = gajim.events.create_event(type_, event_args,
- show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- gajim.events.add_event(account, jid, event)
-
- self.roster.show_title()
- if no_queue: # We didn't have a queue: we change icons
- if not gajim.contacts.get_contact_with_highest_priority(account, jid):
- if type_ == 'gc-invitation':
- self.roster.add_groupchat(jid, account, status='offline')
- else:
- # add contact to roster ("Not In The Roster") if he is not
- self.roster.add_to_not_in_the_roster(account, jid)
- else:
- self.roster.draw_contact(jid, account)
-
- # Select the big brother contact in roster, it's visible because it has
- # events.
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- gajim.contacts.get_nearby_family_and_big_brother(family, account)
- else:
- bb_jid, bb_account = jid, account
- self.roster.select_contact(bb_jid, bb_account)
-
- def handle_event(self, account, fjid, type_):
- w = None
- ctrl = None
- session = None
-
- resource = gajim.get_resource_from_jid(fjid)
- jid = gajim.get_jid_without_resource(fjid)
-
- if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
- w = self.msg_win_mgr.get_window(jid, account)
- if jid in self.minimized_controls[account]:
- self.roster.on_groupchat_maximized(None, jid, account)
- return
- else:
- ctrl = self.msg_win_mgr.get_gc_control(jid, account)
-
- elif type_ in ('printed_chat', 'chat', ''):
- # '' is for log in/out notifications
-
- if type_ != '':
- event = gajim.events.get_first_event(account, fjid, type_)
- if not event:
- event = gajim.events.get_first_event(account, jid, type_)
- if not event:
- return
-
- if type_ == 'printed_chat':
- ctrl = event.parameters[0]
- elif type_ == 'chat':
- session = event.parameters[8]
- ctrl = session.control
- elif type_ == '':
- ctrl = self.msg_win_mgr.get_control(fjid, account)
-
- if not ctrl:
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- # jid can have a window if this resource was lower when he sent
- # message and is now higher because the other one is offline
- if resource and highest_contact.resource == resource and \
- not self.msg_win_mgr.has_window(jid, account):
- # remove resource of events too
- gajim.events.change_jid(account, fjid, jid)
- resource = None
- fjid = jid
- contact = None
- if resource:
- contact = gajim.contacts.get_contact(account, jid, resource)
- if not contact:
- contact = highest_contact
-
- ctrl = self.new_chat(contact, account, resource = resource, session = session)
-
- gajim.last_message_time[account][jid] = 0 # long time ago
-
- w = ctrl.parent_win
- elif type_ in ('printed_pm', 'pm'):
- # assume that the most recently updated control we have for this party
- # is the one that this event was in
- event = gajim.events.get_first_event(account, fjid, type_)
- if not event:
- event = gajim.events.get_first_event(account, jid, type_)
-
- if type_ == 'printed_pm':
- ctrl = event.parameters[0]
- elif type_ == 'pm':
- session = event.parameters[8]
-
- if session and session.control:
- ctrl = session.control
- elif not ctrl:
- room_jid = jid
- nick = resource
- gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
- nick)
- if gc_contact:
- show = gc_contact.show
- else:
- show = 'offline'
- gc_contact = gajim.contacts.create_gc_contact(
- room_jid=room_jid, account=account, name=nick, show=show)
-
- if not session:
- session = gajim.connections[account].make_new_session(
- fjid, None, type_='pm')
-
- self.new_private_chat(gc_contact, account, session=session)
- ctrl = session.control
-
- w = ctrl.parent_win
- elif type_ in ('normal', 'file-request', 'file-request-error',
- 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
- 'jingle-incoming'):
- # Get the first single message event
- event = gajim.events.get_first_event(account, fjid, type_)
- if not event:
- # default to jid without resource
- event = gajim.events.get_first_event(account, jid, type_)
- if not event:
- return
- # Open the window
- self.roster.open_event(account, jid, event)
- else:
- # Open the window
- self.roster.open_event(account, fjid, event)
- elif type_ == 'gmail':
- url=gajim.connections[account].gmail_url
- if url:
- helpers.launch_browser_mailer('url', url)
- elif type_ == 'gc-invitation':
- event = gajim.events.get_first_event(account, jid, type_)
- data = event.parameters
- dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
- data[1], data[3])
- gajim.events.remove_events(account, jid, event)
- self.roster.draw_contact(jid, account)
- elif type_ == 'subscription_request':
- event = gajim.events.get_first_event(account, jid, type_)
- data = event.parameters
- dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
- gajim.events.remove_events(account, jid, event)
- self.roster.draw_contact(jid, account)
- elif type_ == 'unsubscribed':
- event = gajim.events.get_first_event(account, jid, type_)
- contact = event.parameters
- self.show_unsubscribed_dialog(account, contact)
- gajim.events.remove_events(account, jid, event)
- self.roster.draw_contact(jid, account)
- if w:
- w.set_active_tab(ctrl)
- w.window.window.focus(gtk.get_current_event_time())
- # Using isinstance here because we want to catch all derived types
- if isinstance(ctrl, ChatControlBase):
- tv = ctrl.conv_textview
- tv.scroll_to_end()
+ def add_event(self, account, jid, type_, event_args):
+ """
+ Add an event to the gajim.events var
+ """
+ # We add it to the gajim.events queue
+ # Do we have a queue?
+ jid = gajim.get_jid_without_resource(jid)
+ no_queue = len(gajim.events.get_events(account, jid)) == 0
+ # type_ can be gc-invitation file-send-error file-error file-request-error
+ # file-request file-completed file-stopped jingle-incoming
+ # event_type can be in advancedNotificationWindow.events_list
+ event_types = {'file-request': 'ft_request',
+ 'file-completed': 'ft_finished'}
+ event_type = event_types.get(type_)
+ show_in_roster = notify.get_show_in_roster(event_type, account, jid)
+ show_in_systray = notify.get_show_in_systray(event_type, account, jid)
+ event = gajim.events.create_event(type_, event_args,
+ show_in_roster=show_in_roster,
+ show_in_systray=show_in_systray)
+ gajim.events.add_event(account, jid, event)
+
+ self.roster.show_title()
+ if no_queue: # We didn't have a queue: we change icons
+ if not gajim.contacts.get_contact_with_highest_priority(account, jid):
+ if type_ == 'gc-invitation':
+ self.roster.add_groupchat(jid, account, status='offline')
+ else:
+ # add contact to roster ("Not In The Roster") if he is not
+ self.roster.add_to_not_in_the_roster(account, jid)
+ else:
+ self.roster.draw_contact(jid, account)
+
+ # Select the big brother contact in roster, it's visible because it has
+ # events.
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ nearby_family, bb_jid, bb_account = \
+ gajim.contacts.get_nearby_family_and_big_brother(family, account)
+ else:
+ bb_jid, bb_account = jid, account
+ self.roster.select_contact(bb_jid, bb_account)
+
+ def handle_event(self, account, fjid, type_):
+ w = None
+ ctrl = None
+ session = None
+
+ resource = gajim.get_resource_from_jid(fjid)
+ jid = gajim.get_jid_without_resource(fjid)
+
+ if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
+ w = self.msg_win_mgr.get_window(jid, account)
+ if jid in self.minimized_controls[account]:
+ self.roster.on_groupchat_maximized(None, jid, account)
+ return
+ else:
+ ctrl = self.msg_win_mgr.get_gc_control(jid, account)
+
+ elif type_ in ('printed_chat', 'chat', ''):
+ # '' is for log in/out notifications
+
+ if type_ != '':
+ event = gajim.events.get_first_event(account, fjid, type_)
+ if not event:
+ event = gajim.events.get_first_event(account, jid, type_)
+ if not event:
+ return
+
+ if type_ == 'printed_chat':
+ ctrl = event.parameters[0]
+ elif type_ == 'chat':
+ session = event.parameters[8]
+ ctrl = session.control
+ elif type_ == '':
+ ctrl = self.msg_win_mgr.get_control(fjid, account)
+
+ if not ctrl:
+ highest_contact = gajim.contacts.get_contact_with_highest_priority(
+ account, jid)
+ # jid can have a window if this resource was lower when he sent
+ # message and is now higher because the other one is offline
+ if resource and highest_contact.resource == resource and \
+ not self.msg_win_mgr.has_window(jid, account):
+ # remove resource of events too
+ gajim.events.change_jid(account, fjid, jid)
+ resource = None
+ fjid = jid
+ contact = None
+ if resource:
+ contact = gajim.contacts.get_contact(account, jid, resource)
+ if not contact:
+ contact = highest_contact
+
+ ctrl = self.new_chat(contact, account, resource = resource, session = session)
+
+ gajim.last_message_time[account][jid] = 0 # long time ago
+
+ w = ctrl.parent_win
+ elif type_ in ('printed_pm', 'pm'):
+ # assume that the most recently updated control we have for this party
+ # is the one that this event was in
+ event = gajim.events.get_first_event(account, fjid, type_)
+ if not event:
+ event = gajim.events.get_first_event(account, jid, type_)
+
+ if type_ == 'printed_pm':
+ ctrl = event.parameters[0]
+ elif type_ == 'pm':
+ session = event.parameters[8]
+
+ if session and session.control:
+ ctrl = session.control
+ elif not ctrl:
+ room_jid = jid
+ nick = resource
+ gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
+ nick)
+ if gc_contact:
+ show = gc_contact.show
+ else:
+ show = 'offline'
+ gc_contact = gajim.contacts.create_gc_contact(
+ room_jid=room_jid, account=account, name=nick, show=show)
+
+ if not session:
+ session = gajim.connections[account].make_new_session(
+ fjid, None, type_='pm')
+
+ self.new_private_chat(gc_contact, account, session=session)
+ ctrl = session.control
+
+ w = ctrl.parent_win
+ elif type_ in ('normal', 'file-request', 'file-request-error',
+ 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
+ 'jingle-incoming'):
+ # Get the first single message event
+ event = gajim.events.get_first_event(account, fjid, type_)
+ if not event:
+ # default to jid without resource
+ event = gajim.events.get_first_event(account, jid, type_)
+ if not event:
+ return
+ # Open the window
+ self.roster.open_event(account, jid, event)
+ else:
+ # Open the window
+ self.roster.open_event(account, fjid, event)
+ elif type_ == 'gmail':
+ url=gajim.connections[account].gmail_url
+ if url:
+ helpers.launch_browser_mailer('url', url)
+ elif type_ == 'gc-invitation':
+ event = gajim.events.get_first_event(account, jid, type_)
+ data = event.parameters
+ dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
+ data[1], data[3])
+ gajim.events.remove_events(account, jid, event)
+ self.roster.draw_contact(jid, account)
+ elif type_ == 'subscription_request':
+ event = gajim.events.get_first_event(account, jid, type_)
+ data = event.parameters
+ dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
+ gajim.events.remove_events(account, jid, event)
+ self.roster.draw_contact(jid, account)
+ elif type_ == 'unsubscribed':
+ event = gajim.events.get_first_event(account, jid, type_)
+ contact = event.parameters
+ self.show_unsubscribed_dialog(account, contact)
+ gajim.events.remove_events(account, jid, event)
+ self.roster.draw_contact(jid, account)
+ if w:
+ w.set_active_tab(ctrl)
+ w.window.window.focus(gtk.get_current_event_time())
+ # Using isinstance here because we want to catch all derived types
+ if isinstance(ctrl, ChatControlBase):
+ tv = ctrl.conv_textview
+ tv.scroll_to_end()
################################################################################
### Methods dealing with emoticons
################################################################################
- def image_is_ok(self, image):
- if not os.path.exists(image):
- return False
- img = gtk.Image()
- try:
- img.set_from_file(image)
- except Exception:
- return False
- t = img.get_storage_type()
- if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
- return False
- return True
-
- @property
- def basic_pattern_re(self):
- try:
- return self._basic_pattern_re
- except AttributeError:
- self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE)
- return self._basic_pattern_re
-
- @property
- def emot_and_basic_re(self):
- try:
- return self._emot_and_basic_re
- except AttributeError:
- self._emot_and_basic_re = re.compile(self.emot_and_basic,
- re.IGNORECASE + re.UNICODE)
- return self._emot_and_basic_re
-
- @property
- def sth_at_sth_dot_sth_re(self):
- try:
- return self._sth_at_sth_dot_sth_re
- except AttributeError:
- self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
- return self._sth_at_sth_dot_sth_re
-
- @property
- def invalid_XML_chars_re(self):
- try:
- return self._invalid_XML_chars_re
- except AttributeError:
- self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
- return self._invalid_XML_chars_re
-
- def make_regexps(self):
- # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
- # one escapes the metachars with \
- # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
- # \s matches any whitespace character
- # \w any alphanumeric character
- # \W any non-alphanumeric character
- # \b means word boundary. This is a zero-width assertion that
- # matches only at the beginning or end of a word.
- # ^ matches at the beginning of lines
- #
- # * means 0 or more times
- # + means 1 or more times
- # ? means 0 or 1 time
- # | means or
- # [^*] anything but '*' (inside [] you don't have to escape metachars)
- # [^\s*] anything but whitespaces and '*'
- # (?<!\S) is a one char lookbehind assertion and asks for any leading whitespace
- # and mathces beginning of lines so we have correct formatting detection
- # even if the the text is just '*foo*'
- # (?!\S) is the same thing but it's a lookahead assertion
- # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at the end
- # so http://be) will match http://be and http://be)be) will match http://be)be
-
- legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
- r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
- r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
- # NOTE: it's ok to catch www.gr such stuff exist!
-
- #FIXME: recognize xmpp: and treat it specially
- links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
- r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
- r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
-
- #2nd one: at_least_one_char@at_least_one_char.at_least_one_char
- mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
-
- #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
- #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
- formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
- r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
- r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
-
- latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
-
- basic_pattern = links + '|' + mail + '|' + legacy_prefixes
-
- link_pattern = basic_pattern
- self.link_pattern_re = re.compile(link_pattern, re.IGNORECASE)
-
- if gajim.config.get('use_latex'):
- basic_pattern += latex
-
- if gajim.config.get('ascii_formatting'):
- basic_pattern += formatting
- self.basic_pattern = basic_pattern
-
- emoticons_pattern = ''
- if gajim.config.get('emoticons_theme'):
- # When an emoticon is bordered by an alpha-numeric character it is NOT
- # expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
- # We still allow multiple emoticons side-by-side like :P:P:P
- # sort keys by length so :qwe emot is checked before :q
- keys = sorted(self.emoticons, key=len, reverse=True)
- emoticons_pattern_prematch = ''
- emoticons_pattern_postmatch = ''
- emoticon_length = 0
- for emoticon in keys: # travel thru emoticons list
- emoticon = emoticon.decode('utf-8')
- emoticon_escaped = re.escape(emoticon) # espace regexp metachars
- emoticons_pattern += emoticon_escaped + '|'# | means or in regexp
- if (emoticon_length != len(emoticon)):
- # Build up expressions to match emoticons next to other emoticons
- emoticons_pattern_prematch = emoticons_pattern_prematch[:-1] + ')|(?<='
- emoticons_pattern_postmatch = emoticons_pattern_postmatch[:-1] + ')|(?='
- emoticon_length = len(emoticon)
- emoticons_pattern_prematch += emoticon_escaped + '|'
- emoticons_pattern_postmatch += emoticon_escaped + '|'
- # We match from our list of emoticons, but they must either have
- # whitespace, or another emoticon next to it to match successfully
- # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
- emoticons_pattern = '|' + \
- '(?:(?<![\w.]' + emoticons_pattern_prematch[:-1] + '))' + \
- '(?:' + emoticons_pattern[:-1] + ')' + \
- '(?:(?![\w]' + emoticons_pattern_postmatch[:-1] + '))'
-
- # because emoticons match later (in the string) they need to be after
- # basic matches that may occur earlier
- self.emot_and_basic = basic_pattern + emoticons_pattern
-
- # needed for xhtml display
- self.emot_only = emoticons_pattern
-
- # at least one character in 3 parts (before @, after @, after .)
- self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
-
- # Invalid XML chars
- self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|[\ud800-\udfff]|[\ufffe-\uffff]'
-
- def popup_emoticons_under_button(self, button, parent_win):
- """
- Popup the emoticons menu under button, located in parent_win
- """
- gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
- button, parent_win)
-
- def prepare_emoticons_menu(self):
- menu = gtk.Menu()
- def emoticon_clicked(w, str_):
- if self.emoticon_menuitem_clicked:
- self.emoticon_menuitem_clicked(str_)
- # don't keep reference to CB of object
- # this will prevent making it uncollectable
- self.emoticon_menuitem_clicked = None
- def selection_done(widget):
- # remove reference to CB of object, which will
- # make it uncollectable
- self.emoticon_menuitem_clicked = None
- counter = 0
- # Calculate the side lenght of the popup to make it a square
- size = int(round(math.sqrt(len(self.emoticons_images))))
- for image in self.emoticons_images:
- item = gtk.MenuItem()
- img = gtk.Image()
- if isinstance(image[1], gtk.gdk.PixbufAnimation):
- img.set_from_animation(image[1])
- else:
- img.set_from_pixbuf(image[1])
- item.add(img)
- item.connect('activate', emoticon_clicked, image[0])
- #FIXME: add tooltip with ascii
- menu.attach(item, counter % size, counter % size + 1,
- counter / size, counter / size + 1)
- counter += 1
- menu.connect('selection-done', selection_done)
- menu.show_all()
- return menu
-
- def _init_emoticons(self, path, need_reload = False):
- #initialize emoticons dictionary and unique images list
- self.emoticons_images = list()
- self.emoticons = dict()
- self.emoticons_animations = dict()
-
- sys.path.append(path)
- import emoticons
- if need_reload:
- # we need to reload else that doesn't work when changing emoticon set
- reload(emoticons)
- emots = emoticons.emoticons
- for emot_filename in emots:
- emot_file = os.path.join(path, emot_filename)
- if not self.image_is_ok(emot_file):
- continue
- for emot in emots[emot_filename]:
- emot = emot.decode('utf-8')
- # This avoids duplicated emoticons with the same image eg. :) and :-)
- if not emot_file in self.emoticons.values():
- if emot_file.endswith('.gif'):
- pix = gtk.gdk.PixbufAnimation(emot_file)
- else:
- pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file, 16, 16)
- self.emoticons_images.append((emot, pix))
- self.emoticons[emot.upper()] = emot_file
- del emoticons
- sys.path.remove(path)
-
- def init_emoticons(self, need_reload = False):
- emot_theme = gajim.config.get('emoticons_theme')
- if not emot_theme:
- return
-
- path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
- if not os.path.exists(path):
- # It's maybe a user theme
- path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
- if not os.path.exists(path): # theme doesn't exist, disable emoticons
- dialogs.WarningDialog(_('Emoticons disabled'),
- _('Your configured emoticons theme has not been found, so emoticons have been disabled.'))
- gajim.config.set('emoticons_theme', '')
- return
- self._init_emoticons(path, need_reload)
- if len(self.emoticons) == 0:
- # maybe old format of emoticons file, try to convert it
- try:
- import pprint
- import emoticons
- emots = emoticons.emoticons
- fd = open(os.path.join(path, 'emoticons.py'), 'w')
- fd.write('emoticons = ')
- pprint.pprint( dict([
- (file_, [i for i in emots.keys() if emots[i] == file_])
- for file_ in set(emots.values())]), fd)
- fd.close()
- del emoticons
- self._init_emoticons(path, need_reload=True)
- except Exception:
- pass
- if len(self.emoticons) == 0:
- dialogs.WarningDialog(_('Emoticons disabled'),
- _('Your configured emoticons theme cannot been loaded. You maybe need to update the format of emoticons.py file. See http://trac.gajim.org/wiki/Emoticons for more details.'))
- if self.emoticons_menu:
- self.emoticons_menu.destroy()
- self.emoticons_menu = self.prepare_emoticons_menu()
+ def image_is_ok(self, image):
+ if not os.path.exists(image):
+ return False
+ img = gtk.Image()
+ try:
+ img.set_from_file(image)
+ except Exception:
+ return False
+ t = img.get_storage_type()
+ if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
+ return False
+ return True
+
+ @property
+ def basic_pattern_re(self):
+ try:
+ return self._basic_pattern_re
+ except AttributeError:
+ self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE)
+ return self._basic_pattern_re
+
+ @property
+ def emot_and_basic_re(self):
+ try:
+ return self._emot_and_basic_re
+ except AttributeError:
+ self._emot_and_basic_re = re.compile(self.emot_and_basic,
+ re.IGNORECASE + re.UNICODE)
+ return self._emot_and_basic_re
+
+ @property
+ def sth_at_sth_dot_sth_re(self):
+ try:
+ return self._sth_at_sth_dot_sth_re
+ except AttributeError:
+ self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
+ return self._sth_at_sth_dot_sth_re
+
+ @property
+ def invalid_XML_chars_re(self):
+ try:
+ return self._invalid_XML_chars_re
+ except AttributeError:
+ self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
+ return self._invalid_XML_chars_re
+
+ def make_regexps(self):
+ # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
+ # one escapes the metachars with \
+ # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
+ # \s matches any whitespace character
+ # \w any alphanumeric character
+ # \W any non-alphanumeric character
+ # \b means word boundary. This is a zero-width assertion that
+ # matches only at the beginning or end of a word.
+ # ^ matches at the beginning of lines
+ #
+ # * means 0 or more times
+ # + means 1 or more times
+ # ? means 0 or 1 time
+ # | means or
+ # [^*] anything but '*' (inside [] you don't have to escape metachars)
+ # [^\s*] anything but whitespaces and '*'
+ # (?<!\S) is a one char lookbehind assertion and asks for any leading whitespace
+ # and mathces beginning of lines so we have correct formatting detection
+ # even if the the text is just '*foo*'
+ # (?!\S) is the same thing but it's a lookahead assertion
+ # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at the end
+ # so http://be) will match http://be and http://be)be) will match http://be)be
+
+ legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
+ r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
+ r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
+ # NOTE: it's ok to catch www.gr such stuff exist!
+
+ #FIXME: recognize xmpp: and treat it specially
+ links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
+ r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
+ r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
+
+ #2nd one: at_least_one_char@at_least_one_char.at_least_one_char
+ mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
+
+ #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
+ #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
+ formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
+ r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
+ r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
+
+ latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
+
+ basic_pattern = links + '|' + mail + '|' + legacy_prefixes
+
+ link_pattern = basic_pattern
+ self.link_pattern_re = re.compile(link_pattern, re.IGNORECASE)
+
+ if gajim.config.get('use_latex'):
+ basic_pattern += latex
+
+ if gajim.config.get('ascii_formatting'):
+ basic_pattern += formatting
+ self.basic_pattern = basic_pattern
+
+ emoticons_pattern = ''
+ if gajim.config.get('emoticons_theme'):
+ # When an emoticon is bordered by an alpha-numeric character it is NOT
+ # expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
+ # We still allow multiple emoticons side-by-side like :P:P:P
+ # sort keys by length so :qwe emot is checked before :q
+ keys = sorted(self.emoticons, key=len, reverse=True)
+ emoticons_pattern_prematch = ''
+ emoticons_pattern_postmatch = ''
+ emoticon_length = 0
+ for emoticon in keys: # travel thru emoticons list
+ emoticon = emoticon.decode('utf-8')
+ emoticon_escaped = re.escape(emoticon) # espace regexp metachars
+ emoticons_pattern += emoticon_escaped + '|'# | means or in regexp
+ if (emoticon_length != len(emoticon)):
+ # Build up expressions to match emoticons next to other emoticons
+ emoticons_pattern_prematch = emoticons_pattern_prematch[:-1] + ')|(?<='
+ emoticons_pattern_postmatch = emoticons_pattern_postmatch[:-1] + ')|(?='
+ emoticon_length = len(emoticon)
+ emoticons_pattern_prematch += emoticon_escaped + '|'
+ emoticons_pattern_postmatch += emoticon_escaped + '|'
+ # We match from our list of emoticons, but they must either have
+ # whitespace, or another emoticon next to it to match successfully
+ # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
+ emoticons_pattern = '|' + \
+ '(?:(?<![\w.]' + emoticons_pattern_prematch[:-1] + '))' + \
+ '(?:' + emoticons_pattern[:-1] + ')' + \
+ '(?:(?![\w]' + emoticons_pattern_postmatch[:-1] + '))'
+
+ # because emoticons match later (in the string) they need to be after
+ # basic matches that may occur earlier
+ self.emot_and_basic = basic_pattern + emoticons_pattern
+
+ # needed for xhtml display
+ self.emot_only = emoticons_pattern
+
+ # at least one character in 3 parts (before @, after @, after .)
+ self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
+
+ # Invalid XML chars
+ self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|[\ud800-\udfff]|[\ufffe-\uffff]'
+
+ def popup_emoticons_under_button(self, button, parent_win):
+ """
+ Popup the emoticons menu under button, located in parent_win
+ """
+ gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
+ button, parent_win)
+
+ def prepare_emoticons_menu(self):
+ menu = gtk.Menu()
+ def emoticon_clicked(w, str_):
+ if self.emoticon_menuitem_clicked:
+ self.emoticon_menuitem_clicked(str_)
+ # don't keep reference to CB of object
+ # this will prevent making it uncollectable
+ self.emoticon_menuitem_clicked = None
+ def selection_done(widget):
+ # remove reference to CB of object, which will
+ # make it uncollectable
+ self.emoticon_menuitem_clicked = None
+ counter = 0
+ # Calculate the side lenght of the popup to make it a square
+ size = int(round(math.sqrt(len(self.emoticons_images))))
+ for image in self.emoticons_images:
+ item = gtk.MenuItem()
+ img = gtk.Image()
+ if isinstance(image[1], gtk.gdk.PixbufAnimation):
+ img.set_from_animation(image[1])
+ else:
+ img.set_from_pixbuf(image[1])
+ item.add(img)
+ item.connect('activate', emoticon_clicked, image[0])
+ #FIXME: add tooltip with ascii
+ menu.attach(item, counter % size, counter % size + 1,
+ counter / size, counter / size + 1)
+ counter += 1
+ menu.connect('selection-done', selection_done)
+ menu.show_all()
+ return menu
+
+ def _init_emoticons(self, path, need_reload = False):
+ #initialize emoticons dictionary and unique images list
+ self.emoticons_images = list()
+ self.emoticons = dict()
+ self.emoticons_animations = dict()
+
+ sys.path.append(path)
+ import emoticons
+ if need_reload:
+ # we need to reload else that doesn't work when changing emoticon set
+ reload(emoticons)
+ emots = emoticons.emoticons
+ for emot_filename in emots:
+ emot_file = os.path.join(path, emot_filename)
+ if not self.image_is_ok(emot_file):
+ continue
+ for emot in emots[emot_filename]:
+ emot = emot.decode('utf-8')
+ # This avoids duplicated emoticons with the same image eg. :) and :-)
+ if not emot_file in self.emoticons.values():
+ if emot_file.endswith('.gif'):
+ pix = gtk.gdk.PixbufAnimation(emot_file)
+ else:
+ pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file, 16, 16)
+ self.emoticons_images.append((emot, pix))
+ self.emoticons[emot.upper()] = emot_file
+ del emoticons
+ sys.path.remove(path)
+
+ def init_emoticons(self, need_reload = False):
+ emot_theme = gajim.config.get('emoticons_theme')
+ if not emot_theme:
+ return
+
+ path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
+ if not os.path.exists(path):
+ # It's maybe a user theme
+ path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
+ if not os.path.exists(path): # theme doesn't exist, disable emoticons
+ dialogs.WarningDialog(_('Emoticons disabled'),
+ _('Your configured emoticons theme has not been found, so emoticons have been disabled.'))
+ gajim.config.set('emoticons_theme', '')
+ return
+ self._init_emoticons(path, need_reload)
+ if len(self.emoticons) == 0:
+ # maybe old format of emoticons file, try to convert it
+ try:
+ import pprint
+ import emoticons
+ emots = emoticons.emoticons
+ fd = open(os.path.join(path, 'emoticons.py'), 'w')
+ fd.write('emoticons = ')
+ pprint.pprint( dict([
+ (file_, [i for i in emots.keys() if emots[i] == file_])
+ for file_ in set(emots.values())]), fd)
+ fd.close()
+ del emoticons
+ self._init_emoticons(path, need_reload=True)
+ except Exception:
+ pass
+ if len(self.emoticons) == 0:
+ dialogs.WarningDialog(_('Emoticons disabled'),
+ _('Your configured emoticons theme cannot been loaded. You maybe need to update the format of emoticons.py file. See http://trac.gajim.org/wiki/Emoticons for more details.'))
+ if self.emoticons_menu:
+ self.emoticons_menu.destroy()
+ self.emoticons_menu = self.prepare_emoticons_menu()
################################################################################
### Methods for opening new messages controls
################################################################################
- def join_gc_room(self, account, room_jid, nick, password, minimize=False,
- is_continued=False):
- """
- Join the room immediately
- """
- if not nick:
- nick = gajim.nicks[account]
-
- if self.msg_win_mgr.has_window(room_jid, account) and \
- gajim.gc_connected[account][room_jid]:
- gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
- win = gc_ctrl.parent_win
- win.set_active_tab(gc_ctrl)
- dialogs.ErrorDialog(_('You are already in group chat %s') % room_jid)
- return
-
- invisible_show = gajim.SHOW_LIST.index('invisible')
- if gajim.connections[account].connected == invisible_show:
- dialogs.ErrorDialog(
- _('You cannot join a group chat while you are invisible'))
- return
-
- minimized_control = gajim.interface.minimized_controls[account].get(
- room_jid, None)
-
- if minimized_control is None and not self.msg_win_mgr.has_window(room_jid,
- account):
- # Join new groupchat
- if minimize:
- #GCMIN
- contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick)
- gc_control = GroupchatControl(None, contact, account)
- gajim.interface.minimized_controls[account][room_jid] = gc_control
- self.roster.add_groupchat(room_jid, account)
- else:
- self.new_room(room_jid, nick, account, is_continued=is_continued)
- elif minimized_control is None:
- # We are already in that groupchat
- gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
- gc_control.nick = nick
- gc_control.parent_win.set_active_tab(gc_control)
- else:
- # We are already in this groupchat and it is minimized
- minimized_control.nick = nick
- self.roster.add_groupchat(room_jid, account)
-
- # Connect
- gajim.connections[account].join_gc(nick, room_jid, password)
- if password:
- gajim.gc_passwords[room_jid] = password
-
- def new_room(self, room_jid, nick, account, is_continued=False):
- # Get target window, create a control, and associate it with the window
- # GCMIN
- contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick)
- mw = self.msg_win_mgr.get_window(contact.jid, account)
- if not mw:
- mw = self.msg_win_mgr.create_window(contact, account,
- GroupchatControl.TYPE_ID)
- gc_control = GroupchatControl(mw, contact, account,
- is_continued=is_continued)
- mw.new_tab(gc_control)
-
- def new_private_chat(self, gc_contact, account, session=None):
- conn = gajim.connections[account]
- if not session and gc_contact.get_full_jid() in conn.sessions:
- sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].values()
- if isinstance(s, ChatControlSession)]
-
- # look for an existing session with a chat control
- for s in sessions:
- if s.control:
- session = s
- break
- if not session and not len(sessions) == 0:
- # there are no sessions with chat controls, just take the first one
- session = sessions[0]
- if not session:
- # couldn't find an existing ChatControlSession, just make a new one
- session = conn.make_new_session(gc_contact.get_full_jid(), None, 'pm')
-
- contact = gc_contact.as_contact()
- if not session.control:
- message_window = self.msg_win_mgr.get_window(gc_contact.get_full_jid(),
- account)
- if not message_window:
- message_window = self.msg_win_mgr.create_window(contact, account,
- message_control.TYPE_PM)
-
- session.control = PrivateChatControl(message_window, gc_contact,
- contact, account, session)
- message_window.new_tab(session.control)
-
- if gajim.events.get_events(account, gc_contact.get_full_jid()):
- # We call this here to avoid race conditions with widget validation
- session.control.read_queue()
-
- return session.control
-
- def new_chat(self, contact, account, resource=None, session=None):
- # Get target window, create a control, and associate it with the window
- type_ = message_control.TYPE_CHAT
-
- fjid = contact.jid
- if resource:
- fjid += '/' + resource
-
- mw = self.msg_win_mgr.get_window(fjid, account)
- if not mw:
- mw = self.msg_win_mgr.create_window(contact, account, type_, resource)
-
- chat_control = ChatControl(mw, contact, account, session, resource)
-
- mw.new_tab(chat_control)
-
- if len(gajim.events.get_events(account, fjid)):
- # We call this here to avoid race conditions with widget validation
- chat_control.read_queue()
-
- return chat_control
-
- def new_chat_from_jid(self, account, fjid, message=None):
- jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
- contact = gajim.contacts.get_contact(account, jid, resource)
- added_to_roster = False
- if not contact:
- added_to_roster = True
- contact = self.roster.add_to_not_in_the_roster(account, jid,
- resource=resource)
-
- ctrl = self.msg_win_mgr.get_control(fjid, account)
-
- if not ctrl:
- ctrl = self.new_chat(contact, account,
- resource=resource)
- if len(gajim.events.get_events(account, fjid)):
- ctrl.read_queue()
-
- if message:
- buffer = ctrl.msg_textview.get_buffer()
- buffer.set_text(message)
- mw = ctrl.parent_win
- mw.set_active_tab(ctrl)
- # For JEP-0172
- if added_to_roster:
- ctrl.user_nick = gajim.nicks[account]
- gobject.idle_add(lambda: mw.window.grab_focus())
-
- def on_open_chat_window(self, widget, contact, account, resource=None,
- session=None):
- # Get the window containing the chat
- fjid = contact.jid
-
- if resource:
- fjid += '/' + resource
-
- ctrl = None
-
- if session:
- ctrl = session.control
- if not ctrl:
- win = self.msg_win_mgr.get_window(fjid, account)
-
- if win:
- ctrl = win.get_control(fjid, account)
-
- if not ctrl:
- ctrl = self.new_chat(contact, account, resource=resource,
- session=session)
- # last message is long time ago
- gajim.last_message_time[account][ctrl.get_full_jid()] = 0
-
- win = ctrl.parent_win
-
- win.set_active_tab(ctrl)
-
- if gajim.connections[account].is_zeroconf and \
- gajim.connections[account].status in ('offline', 'invisible'):
- ctrl = win.get_control(fjid, account)
- if ctrl:
- ctrl.got_disconnected()
+ def join_gc_room(self, account, room_jid, nick, password, minimize=False,
+ is_continued=False):
+ """
+ Join the room immediately
+ """
+ if not nick:
+ nick = gajim.nicks[account]
+
+ if self.msg_win_mgr.has_window(room_jid, account) and \
+ gajim.gc_connected[account][room_jid]:
+ gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
+ win = gc_ctrl.parent_win
+ win.set_active_tab(gc_ctrl)
+ dialogs.ErrorDialog(_('You are already in group chat %s') % room_jid)
+ return
+
+ invisible_show = gajim.SHOW_LIST.index('invisible')
+ if gajim.connections[account].connected == invisible_show:
+ dialogs.ErrorDialog(
+ _('You cannot join a group chat while you are invisible'))
+ return
+
+ minimized_control = gajim.interface.minimized_controls[account].get(
+ room_jid, None)
+
+ if minimized_control is None and not self.msg_win_mgr.has_window(room_jid,
+ account):
+ # Join new groupchat
+ if minimize:
+ #GCMIN
+ contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick)
+ gc_control = GroupchatControl(None, contact, account)
+ gajim.interface.minimized_controls[account][room_jid] = gc_control
+ self.roster.add_groupchat(room_jid, account)
+ else:
+ self.new_room(room_jid, nick, account, is_continued=is_continued)
+ elif minimized_control is None:
+ # We are already in that groupchat
+ gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
+ gc_control.nick = nick
+ gc_control.parent_win.set_active_tab(gc_control)
+ else:
+ # We are already in this groupchat and it is minimized
+ minimized_control.nick = nick
+ self.roster.add_groupchat(room_jid, account)
+
+ # Connect
+ gajim.connections[account].join_gc(nick, room_jid, password)
+ if password:
+ gajim.gc_passwords[room_jid] = password
+
+ def new_room(self, room_jid, nick, account, is_continued=False):
+ # Get target window, create a control, and associate it with the window
+ # GCMIN
+ contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick)
+ mw = self.msg_win_mgr.get_window(contact.jid, account)
+ if not mw:
+ mw = self.msg_win_mgr.create_window(contact, account,
+ GroupchatControl.TYPE_ID)
+ gc_control = GroupchatControl(mw, contact, account,
+ is_continued=is_continued)
+ mw.new_tab(gc_control)
+
+ def new_private_chat(self, gc_contact, account, session=None):
+ conn = gajim.connections[account]
+ if not session and gc_contact.get_full_jid() in conn.sessions:
+ sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].values()
+ if isinstance(s, ChatControlSession)]
+
+ # look for an existing session with a chat control
+ for s in sessions:
+ if s.control:
+ session = s
+ break
+ if not session and not len(sessions) == 0:
+ # there are no sessions with chat controls, just take the first one
+ session = sessions[0]
+ if not session:
+ # couldn't find an existing ChatControlSession, just make a new one
+ session = conn.make_new_session(gc_contact.get_full_jid(), None, 'pm')
+
+ contact = gc_contact.as_contact()
+ if not session.control:
+ message_window = self.msg_win_mgr.get_window(gc_contact.get_full_jid(),
+ account)
+ if not message_window:
+ message_window = self.msg_win_mgr.create_window(contact, account,
+ message_control.TYPE_PM)
+
+ session.control = PrivateChatControl(message_window, gc_contact,
+ contact, account, session)
+ message_window.new_tab(session.control)
+
+ if gajim.events.get_events(account, gc_contact.get_full_jid()):
+ # We call this here to avoid race conditions with widget validation
+ session.control.read_queue()
+
+ return session.control
+
+ def new_chat(self, contact, account, resource=None, session=None):
+ # Get target window, create a control, and associate it with the window
+ type_ = message_control.TYPE_CHAT
+
+ fjid = contact.jid
+ if resource:
+ fjid += '/' + resource
+
+ mw = self.msg_win_mgr.get_window(fjid, account)
+ if not mw:
+ mw = self.msg_win_mgr.create_window(contact, account, type_, resource)
+
+ chat_control = ChatControl(mw, contact, account, session, resource)
+
+ mw.new_tab(chat_control)
+
+ if len(gajim.events.get_events(account, fjid)):
+ # We call this here to avoid race conditions with widget validation
+ chat_control.read_queue()
+
+ return chat_control
+
+ def new_chat_from_jid(self, account, fjid, message=None):
+ jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
+ contact = gajim.contacts.get_contact(account, jid, resource)
+ added_to_roster = False
+ if not contact:
+ added_to_roster = True
+ contact = self.roster.add_to_not_in_the_roster(account, jid,
+ resource=resource)
+
+ ctrl = self.msg_win_mgr.get_control(fjid, account)
+
+ if not ctrl:
+ ctrl = self.new_chat(contact, account,
+ resource=resource)
+ if len(gajim.events.get_events(account, fjid)):
+ ctrl.read_queue()
+
+ if message:
+ buffer = ctrl.msg_textview.get_buffer()
+ buffer.set_text(message)
+ mw = ctrl.parent_win
+ mw.set_active_tab(ctrl)
+ # For JEP-0172
+ if added_to_roster:
+ ctrl.user_nick = gajim.nicks[account]
+ gobject.idle_add(lambda: mw.window.grab_focus())
+
+ def on_open_chat_window(self, widget, contact, account, resource=None,
+ session=None):
+ # Get the window containing the chat
+ fjid = contact.jid
+
+ if resource:
+ fjid += '/' + resource
+
+ ctrl = None
+
+ if session:
+ ctrl = session.control
+ if not ctrl:
+ win = self.msg_win_mgr.get_window(fjid, account)
+
+ if win:
+ ctrl = win.get_control(fjid, account)
+
+ if not ctrl:
+ ctrl = self.new_chat(contact, account, resource=resource,
+ session=session)
+ # last message is long time ago
+ gajim.last_message_time[account][ctrl.get_full_jid()] = 0
+
+ win = ctrl.parent_win
+
+ win.set_active_tab(ctrl)
+
+ if gajim.connections[account].is_zeroconf and \
+ gajim.connections[account].status in ('offline', 'invisible'):
+ ctrl = win.get_control(fjid, account)
+ if ctrl:
+ ctrl.got_disconnected()
################################################################################
### Other Methods
################################################################################
- def _change_awn_icon_status(self, status):
- if not dbus_support.supported:
- # do nothing if user doesn't have D-Bus bindings
- return
- try:
- bus = dbus.SessionBus()
- if not 'com.google.code.Awn' in bus.list_names():
- # Awn is not installed
- return
- except Exception:
- return
- iconset = gajim.config.get('iconset')
- prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
- if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
- status = status + '.png'
- elif status == 'online':
- prefix = ''
- status = gtkgui_helpers.get_icon_path('gajim', 32)
- path = os.path.join(prefix, status)
- try:
- obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
- awn = dbus.Interface(obj, 'com.google.code.Awn')
- awn.SetTaskIconByName('Gajim', os.path.abspath(path))
- except Exception:
- pass
-
- def enable_music_listener(self):
- listener = MusicTrackListener.get()
- if not self.music_track_changed_signal:
- self.music_track_changed_signal = listener.connect(
- 'music-track-changed', self.music_track_changed)
- track = listener.get_playing_track()
- self.music_track_changed(listener, track)
-
- def disable_music_listener(self):
- listener = MusicTrackListener.get()
- listener.disconnect(self.music_track_changed_signal)
- self.music_track_changed_signal = None
-
- def music_track_changed(self, unused_listener, music_track_info,
- account=None):
- if not account:
- accounts = gajim.connections.keys()
- else:
- accounts = [account]
-
- is_paused = hasattr(music_track_info, 'paused') and \
- music_track_info.paused == 0
- if not music_track_info or is_paused:
- artist = title = source = ''
- else:
- artist = music_track_info.artist
- title = music_track_info.title
- source = music_track_info.album
- for acct in accounts:
- if not gajim.account_is_connected(acct):
- continue
- if not gajim.config.get_per('accounts', acct, 'publish_tune'):
- continue
- if gajim.connections[acct].music_track_info == music_track_info:
- continue
- gajim.connections[acct].send_tune(artist, title, source)
- gajim.connections[acct].music_track_info = music_track_info
-
- def get_bg_fg_colors(self):
- def gdkcolor_to_rgb (gdkcolor):
- return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
- gdkcolor.blue)]
-
- def format_rgb (r, g, b):
- return ' '.join([str(c) for c in ('rgb', r, g, b)])
-
- def format_gdkcolor (gdkcolor):
- return format_rgb (*gdkcolor_to_rgb (gdkcolor))
-
- # get style colors and create string for dvipng
- dummy = gtk.Invisible()
- dummy.ensure_style()
- style = dummy.get_style()
- bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
- fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
- return (bg_str, fg_str)
-
- def read_sleepy(self):
- """
- Check idle status and change that status if needed
- """
- if not self.sleeper.poll():
- # idle detection is not supported in that OS
- return False # stop looping in vain
- state = self.sleeper.getState()
- for account in gajim.connections:
- if account not in gajim.sleeper_state or \
- not gajim.sleeper_state[account]:
- continue
- if state == common.sleepy.STATE_AWAKE and \
- gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
- # we go online
- self.roster.send_status(account, 'online',
- gajim.status_before_autoaway[account])
- gajim.status_before_autoaway[account] = ''
- gajim.sleeper_state[account] = 'online'
- elif state == common.sleepy.STATE_AWAY and \
- gajim.sleeper_state[account] == 'online' and \
- gajim.config.get('autoaway'):
- # we save out online status
- gajim.status_before_autoaway[account] = \
- gajim.connections[account].status
- # we go away (no auto status) [we pass True to auto param]
- auto_message = gajim.config.get('autoaway_message')
- if not auto_message:
- auto_message = gajim.connections[account].status
- else:
- auto_message = auto_message.replace('$S','%(status)s')
- auto_message = auto_message.replace('$T','%(time)s')
- auto_message = auto_message % {
- 'status': gajim.status_before_autoaway[account],
- 'time': gajim.config.get('autoawaytime')
- }
- self.roster.send_status(account, 'away', auto_message, auto=True)
- gajim.sleeper_state[account] = 'autoaway'
- elif state == common.sleepy.STATE_XA and \
- gajim.sleeper_state[account] in ('online', 'autoaway',
- 'autoaway-forced') and gajim.config.get('autoxa'):
- # we go extended away [we pass True to auto param]
- auto_message = gajim.config.get('autoxa_message')
- if not auto_message:
- auto_message = gajim.connections[account].status
- else:
- auto_message = auto_message.replace('$S','%(status)s')
- auto_message = auto_message.replace('$T','%(time)s')
- auto_message = auto_message % {
- 'status': gajim.status_before_autoaway[account],
- 'time': gajim.config.get('autoxatime')
- }
- self.roster.send_status(account, 'xa', auto_message, auto=True)
- gajim.sleeper_state[account] = 'autoxa'
- return True # renew timeout (loop for ever)
-
- def autoconnect(self):
- """
- Auto connect at startup
- """
- # dict of account that want to connect sorted by status
- shows = {}
- for a in gajim.connections:
- if gajim.config.get_per('accounts', a, 'autoconnect'):
- if gajim.config.get_per('accounts', a, 'restore_last_status'):
- self.roster.send_status(a, gajim.config.get_per('accounts', a,
- 'last_status'), helpers.from_one_line(gajim.config.get_per(
- 'accounts', a, 'last_status_msg')))
- continue
- show = gajim.config.get_per('accounts', a, 'autoconnect_as')
- if not show in gajim.SHOW_LIST:
- continue
- if not show in shows:
- shows[show] = [a]
- else:
- shows[show].append(a)
- def on_message(message, pep_dict):
- if message is None:
- return
- for a in shows[show]:
- self.roster.send_status(a, show, message)
- self.roster.send_pep(a, pep_dict)
- for show in shows:
- message = self.roster.get_status_message(show, on_message)
- return False
-
- def show_systray(self):
- self.systray_enabled = True
- self.systray.show_icon()
-
- def hide_systray(self):
- self.systray_enabled = False
- self.systray.hide_icon()
-
- def on_launch_browser_mailer(self, widget, url, kind):
- helpers.launch_browser_mailer(kind, url)
-
- def process_connections(self):
- """
- Called each foo (200) miliseconds. Check for idlequeue timeouts
- """
- try:
- gajim.idlequeue.process()
- except Exception:
- # Otherwise, an exception will stop our loop
- timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
- if in_seconds:
- gobject.timeout_add_seconds(timeout, self.process_connections)
- else:
- gobject.timeout_add(timeout, self.process_connections)
- raise
- return True # renew timeout (loop for ever)
-
- def save_config(self):
- err_str = parser.write()
- if err_str is not None:
- print >> sys.stderr, err_str
- # it is good to notify the user
- # in case he or she cannot see the output of the console
- dialogs.ErrorDialog(_('Could not save your settings and preferences'),
- err_str)
- sys.exit()
-
- def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
- """
- Save an avatar to a separate file, and generate files for dbus
- notifications. An avatar can be given as a pixmap directly or as an
- decoded image
- """
- puny_jid = helpers.sanitize_filename(jid)
- path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
- if puny_nick:
- path_to_file = os.path.join(path_to_file, puny_nick)
- # remove old avatars
- for typ in ('jpeg', 'png'):
- if local:
- path_to_original_file = path_to_file + '_local'+ '.' + typ
- else:
- path_to_original_file = path_to_file + '.' + typ
- if os.path.isfile(path_to_original_file):
- os.remove(path_to_original_file)
- if local and photo:
- pixbuf = photo
- typ = 'png'
- extension = '_local.png' # save local avatars as png file
- else:
- pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True)
- if pixbuf is None:
- return
- extension = '.' + typ
- if typ not in ('jpeg', 'png'):
- gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ))
- typ = 'png'
- extension = '.png'
- path_to_original_file = path_to_file + extension
- try:
- pixbuf.save(path_to_original_file, typ)
- except Exception, e:
- log.error('Error writing avatar file %s: %s' % (path_to_original_file,
- str(e)))
- # Generate and save the resized, color avatar
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
- if pixbuf:
- path_to_normal_file = path_to_file + '_notif_size_colored' + extension
- try:
- pixbuf.save(path_to_normal_file, 'png')
- except Exception, e:
- log.error('Error writing avatar file %s: %s' % \
- (path_to_original_file, str(e)))
- # Generate and save the resized, black and white avatar
- bwbuf = gtkgui_helpers.get_scaled_pixbuf(
- gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
- if bwbuf:
- path_to_bw_file = path_to_file + '_notif_size_bw' + extension
- try:
- bwbuf.save(path_to_bw_file, 'png')
- except Exception, e:
- log.error('Error writing avatar file %s: %s' % \
- (path_to_original_file, str(e)))
-
- def remove_avatar_files(self, jid, puny_nick = None, local = False):
- """
- Remove avatar files of a jid
- """
- puny_jid = helpers.sanitize_filename(jid)
- path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
- if puny_nick:
- path_to_file = os.path.join(path_to_file, puny_nick)
- for ext in ('.jpeg', '.png'):
- if local:
- ext = '_local' + ext
- path_to_original_file = path_to_file + ext
- if os.path.isfile(path_to_file + ext):
- os.remove(path_to_file + ext)
- if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
- os.remove(path_to_file + '_notif_size_colored' + ext)
- if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
- os.remove(path_to_file + '_notif_size_bw' + ext)
-
- def auto_join_bookmarks(self, account):
- """
- Autojoin bookmarked GCs that have 'auto join' on for this account
- """
- for bm in gajim.connections[account].bookmarks:
- if bm['autojoin'] in ('1', 'true'):
- jid = bm['jid']
- # Only join non-opened groupchats. Opened one are already
- # auto-joined on re-connection
- if not jid in gajim.gc_connected[account]:
- # we are not already connected
- minimize = bm['minimize'] in ('1', 'true')
- gajim.interface.join_gc_room(account, jid, bm['nick'],
- bm['password'], minimize = minimize)
- elif jid in self.minimized_controls[account]:
- # more or less a hack:
- # On disconnect the minimized gc contact instances
- # were set to offline. Reconnect them to show up in the roster.
- self.roster.add_groupchat(jid, account)
-
- def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
- nick):
- """
- Add a bookmark for this account, sorted in bookmark list
- """
- bm = {
- 'name': name,
- 'jid': jid,
- 'autojoin': autojoin,
- 'minimize': minimize,
- 'password': password,
- 'nick': nick
- }
- place_found = False
- index = 0
- # check for duplicate entry and respect alpha order
- for bookmark in gajim.connections[account].bookmarks:
- if bookmark['jid'] == bm['jid']:
- dialogs.ErrorDialog(
- _('Bookmark already set'),
- _('Group Chat "%s" is already in your bookmarks.') % bm['jid'])
- return
- if bookmark['name'] > bm['name']:
- place_found = True
- break
- index += 1
- if place_found:
- gajim.connections[account].bookmarks.insert(index, bm)
- else:
- gajim.connections[account].bookmarks.append(bm)
- gajim.connections[account].store_bookmarks()
- self.roster.set_actions_menu_needs_rebuild()
- dialogs.InformationDialog(
- _('Bookmark has been added successfully'),
- _('You can manage your bookmarks via Actions menu in your roster.'))
-
-
- # does JID exist only within a groupchat?
- def is_pm_contact(self, fjid, account):
- bare_jid = gajim.get_jid_without_resource(fjid)
-
- gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
-
- if not gc_ctrl and \
- bare_jid in self.minimized_controls[account]:
- gc_ctrl = self.minimized_controls[account][bare_jid]
-
- return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
-
- def create_ipython_window(self):
- try:
- from ipython_view import IPythonView
- except ImportError:
- print 'ipython_view not found'
- return
- import pango
-
- if os.name == 'nt':
- font = 'Lucida Console 9'
- else:
- font = 'Luxi Mono 10'
-
- window = gtk.Window()
- window.set_size_request(750,550)
- window.set_resizable(True)
- sw = gtk.ScrolledWindow()
- sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
- view = IPythonView()
- view.modify_font(pango.FontDescription(font))
- view.set_wrap_mode(gtk.WRAP_CHAR)
- sw.add(view)
- window.add(sw)
- window.show_all()
- def on_delete(win, event):
- win.hide()
- return True
- window.connect('delete_event',on_delete)
- view.updateNamespace({'gajim': gajim})
- gajim.ipython_window = window
-
- def run(self):
- if gajim.config.get('trayicon') != 'never':
- self.show_systray()
-
- self.roster = roster_window.RosterWindow()
- for account in gajim.connections:
- gajim.connections[account].load_roster_from_db()
-
- # get instances for windows/dialogs that will show_all()/hide()
- self.instances['file_transfers'] = dialogs.FileTransfersWindow()
-
- gobject.timeout_add(100, self.autoconnect)
- timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
- if in_seconds:
- gobject.timeout_add_seconds(timeout, self.process_connections)
- else:
- gobject.timeout_add(timeout, self.process_connections)
- gobject.timeout_add_seconds(gajim.config.get(
- 'check_idle_every_foo_seconds'), self.read_sleepy)
-
- # when using libasyncns we need to process resolver in regular intervals
- if resolver.USE_LIBASYNCNS:
- gobject.timeout_add(200, gajim.resolver.process)
-
- # setup the indicator
- if gajim.HAVE_INDICATOR:
- notify.setup_indicator_server()
-
- def remote_init():
- if gajim.config.get('remote_control'):
- try:
- import remote_control
- self.remote_ctrl = remote_control.Remote()
- except Exception:
- pass
- gobject.timeout_add_seconds(5, remote_init)
-
-
- def __init__(self):
- gajim.interface = self
- gajim.thread_interface = ThreadInterface
- # This is the manager and factory of message windows set by the module
- self.msg_win_mgr = None
- self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
- 'closed': {}}
- self.emoticons_menu = None
- # handler when an emoticon is clicked in emoticons_menu
- self.emoticon_menuitem_clicked = None
- self.minimized_controls = {}
- self.status_sent_to_users = {}
- self.status_sent_to_groups = {}
- self.gpg_passphrase = {}
- self.pass_dialog = {}
- self.default_colors = {
- 'inmsgcolor': gajim.config.get('inmsgcolor'),
- 'outmsgcolor': gajim.config.get('outmsgcolor'),
- 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
- 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
- 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
- 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
- }
-
- cfg_was_read = parser.read()
-
- from common import latex
- gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
- latex.check_for_latex_support()
-
- gajim.logger.reset_shown_unread_messages()
- # override logging settings from config (don't take care of '-q' option)
- if gajim.config.get('verbose'):
- logging_helpers.set_verbose()
-
- # Is Gajim default app?
- if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
- gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
-
- for account in gajim.config.get_per('accounts'):
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- gajim.ZEROCONF_ACC_NAME = account
- break
- # Is gnome configured to activate row on single click ?
- try:
- import gconf
- client = gconf.client_get_default()
- click_policy = client.get_string(
- '/apps/nautilus/preferences/click_policy')
- if click_policy == 'single':
- gajim.single_click = True
- except Exception:
- pass
- # add default status messages if there is not in the config file
- if len(gajim.config.get_per('statusmsg')) == 0:
- default = gajim.config.statusmsg_default
- for msg in default:
- gajim.config.add_per('statusmsg', msg)
- gajim.config.set_per('statusmsg', msg, 'message', default[msg][0])
- gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1])
- gajim.config.set_per('statusmsg', msg, 'subactivity',
- default[msg][2])
- gajim.config.set_per('statusmsg', msg, 'activity_text',
- default[msg][3])
- gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4])
- gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5])
- #add default themes if there is not in the config file
- theme = gajim.config.get('roster_theme')
- if not theme in gajim.config.get_per('themes'):
- gajim.config.set('roster_theme', _('default'))
- if len(gajim.config.get_per('themes')) == 0:
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
- 'groupfontattrs', 'contacttextcolor', 'contactbgcolor',
- 'contactfont', 'contactfontattrs', 'bannertextcolor',
- 'bannerbgcolor']
-
- default = gajim.config.themes_default
- for theme_name in default:
- gajim.config.add_per('themes', theme_name)
- theme = default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o,
- theme[d.index(o)])
-
- if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
- gtkgui_helpers.autodetect_browser_mailer()
-
- gajim.idlequeue = idlequeue.get_idlequeue()
- # resolve and keep current record of resolved hosts
- gajim.resolver = resolver.get_resolver(gajim.idlequeue)
- gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
- self.handle_event_file_rcv_completed,
- self.handle_event_file_progress,
- self.handle_event_file_error)
- gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
- gajim.default_session_type = ChatControlSession
-
- # Creating Global Events Dispatcher
- from common import ged
- gajim.ged = ged.GlobalEventsDispatcher()
- self.create_core_handlers_list()
- self.register_core_handlers()
-
- if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
- and gajim.HAVE_ZEROCONF:
- gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
- connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
- for account in gajim.config.get_per('accounts'):
- if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \
- gajim.config.get_per('accounts', account, 'active'):
- gajim.connections[account] = common.connection.Connection(account)
-
- # gtk hooks
- gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
- gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
- gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
-
- self.instances = {}
-
- for a in gajim.connections:
- self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
- 'search': {}, 'online_dialog': {}}
- # online_dialog contains all dialogs that have a meaning only when we
- # are not disconnected
- self.minimized_controls[a] = {}
- gajim.contacts.add_account(a)
- gajim.groups[a] = {}
- gajim.gc_connected[a] = {}
- gajim.automatic_rooms[a] = {}
- gajim.newly_added[a] = []
- gajim.to_be_removed[a] = []
- gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
- gajim.block_signed_in_notifications[a] = True
- gajim.sleeper_state[a] = 0
- gajim.encrypted_chats[a] = []
- gajim.last_message_time[a] = {}
- gajim.status_before_autoaway[a] = ''
- gajim.transport_avatar[a] = {}
- gajim.gajim_optional_features[a] = []
- gajim.caps_hash[a] = ''
-
- helpers.update_optional_features()
- # prepopulate data which we are sure of; note: we do not log these info
- for account in gajim.connections:
- gajimcaps = caps_cache.capscache[('sha-1', gajim.caps_hash[account])]
- gajimcaps.identities = [gajim.gajim_identity]
- gajimcaps.features = gajim.gajim_common_features + \
- gajim.gajim_optional_features[account]
-
- self.remote_ctrl = None
-
- if gajim.config.get('networkmanager_support') and dbus_support.supported:
- import network_manager_listener
-
- # Handle gnome screensaver
- if dbus_support.supported:
- def gnome_screensaver_ActiveChanged_cb(active):
- if not active:
- for account in gajim.connections:
- if gajim.sleeper_state[account] == 'autoaway-forced':
- # We came back online ofter gnome-screensaver autoaway
- self.roster.send_status(account, 'online',
- gajim.status_before_autoaway[account])
- gajim.status_before_autoaway[account] = ''
- gajim.sleeper_state[account] = 'online'
- return
- if not gajim.config.get('autoaway'):
- # Don't go auto away if user disabled the option
- return
- for account in gajim.connections:
- if account not in gajim.sleeper_state or \
- not gajim.sleeper_state[account]:
- continue
- if gajim.sleeper_state[account] == 'online':
- # we save out online status
- gajim.status_before_autoaway[account] = \
- gajim.connections[account].status
- # we go away (no auto status) [we pass True to auto param]
- auto_message = gajim.config.get('autoaway_message')
- if not auto_message:
- auto_message = gajim.connections[account].status
- else:
- auto_message = auto_message.replace('$S','%(status)s')
- auto_message = auto_message.replace('$T','%(time)s')
- auto_message = auto_message % {
- 'status': gajim.status_before_autoaway[account],
- 'time': gajim.config.get('autoxatime')
- }
- self.roster.send_status(account, 'away', auto_message,
- auto=True)
- gajim.sleeper_state[account] = 'autoaway-forced'
-
- try:
- bus = dbus.SessionBus()
- bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
- 'ActiveChanged', 'org.gnome.ScreenSaver')
- except Exception:
- pass
-
- self.show_vcard_when_connect = []
-
- self.sleeper = common.sleepy.Sleepy(
- gajim.config.get('autoawaytime') * 60, # make minutes to seconds
- gajim.config.get('autoxatime') * 60)
-
- gtkgui_helpers.make_jabber_state_images()
-
- self.systray_enabled = False
-
- import statusicon
- self.systray = statusicon.StatusIcon()
-
- pix = gtkgui_helpers.get_icon_pixmap('gajim', 32)
- # set the icon to all windows
- gtk.window_set_default_icon(pix)
-
- self.init_emoticons()
- self.make_regexps()
-
- # get transports type from DB
- gajim.transport_type = gajim.logger.get_transports_type()
-
- # test is dictionnary is present for speller
- if gajim.config.get('use_speller'):
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- tv = gtk.TextView()
- try:
- import gtkspell
- spell = gtkspell.Spell(tv, lang)
- except (ImportError, TypeError, RuntimeError, OSError):
- dialogs.AspellDictError(lang)
-
- if gajim.config.get('soundplayer') == '':
- # only on first time Gajim starts
- commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
- for command in commands:
- if helpers.is_in_path(command):
- if command == 'aplay':
- command += ' -q'
- gajim.config.set('soundplayer', command)
- break
-
- self.last_ftwindow_update = 0
-
- self.music_track_changed_signal = None
+ def _change_awn_icon_status(self, status):
+ if not dbus_support.supported:
+ # do nothing if user doesn't have D-Bus bindings
+ return
+ try:
+ bus = dbus.SessionBus()
+ if not 'com.google.code.Awn' in bus.list_names():
+ # Awn is not installed
+ return
+ except Exception:
+ return
+ iconset = gajim.config.get('iconset')
+ prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
+ if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
+ status = status + '.png'
+ elif status == 'online':
+ prefix = ''
+ status = gtkgui_helpers.get_icon_path('gajim', 32)
+ path = os.path.join(prefix, status)
+ try:
+ obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
+ awn = dbus.Interface(obj, 'com.google.code.Awn')
+ awn.SetTaskIconByName('Gajim', os.path.abspath(path))
+ except Exception:
+ pass
+
+ def enable_music_listener(self):
+ listener = MusicTrackListener.get()
+ if not self.music_track_changed_signal:
+ self.music_track_changed_signal = listener.connect(
+ 'music-track-changed', self.music_track_changed)
+ track = listener.get_playing_track()
+ self.music_track_changed(listener, track)
+
+ def disable_music_listener(self):
+ listener = MusicTrackListener.get()
+ listener.disconnect(self.music_track_changed_signal)
+ self.music_track_changed_signal = None
+
+ def music_track_changed(self, unused_listener, music_track_info,
+ account=None):
+ if not account:
+ accounts = gajim.connections.keys()
+ else:
+ accounts = [account]
+
+ is_paused = hasattr(music_track_info, 'paused') and \
+ music_track_info.paused == 0
+ if not music_track_info or is_paused:
+ artist = title = source = ''
+ else:
+ artist = music_track_info.artist
+ title = music_track_info.title
+ source = music_track_info.album
+ for acct in accounts:
+ if not gajim.account_is_connected(acct):
+ continue
+ if not gajim.config.get_per('accounts', acct, 'publish_tune'):
+ continue
+ if gajim.connections[acct].music_track_info == music_track_info:
+ continue
+ gajim.connections[acct].send_tune(artist, title, source)
+ gajim.connections[acct].music_track_info = music_track_info
+
+ def get_bg_fg_colors(self):
+ def gdkcolor_to_rgb (gdkcolor):
+ return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
+ gdkcolor.blue)]
+
+ def format_rgb (r, g, b):
+ return ' '.join([str(c) for c in ('rgb', r, g, b)])
+
+ def format_gdkcolor (gdkcolor):
+ return format_rgb (*gdkcolor_to_rgb (gdkcolor))
+
+ # get style colors and create string for dvipng
+ dummy = gtk.Invisible()
+ dummy.ensure_style()
+ style = dummy.get_style()
+ bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
+ fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
+ return (bg_str, fg_str)
+
+ def read_sleepy(self):
+ """
+ Check idle status and change that status if needed
+ """
+ if not self.sleeper.poll():
+ # idle detection is not supported in that OS
+ return False # stop looping in vain
+ state = self.sleeper.getState()
+ for account in gajim.connections:
+ if account not in gajim.sleeper_state or \
+ not gajim.sleeper_state[account]:
+ continue
+ if state == common.sleepy.STATE_AWAKE and \
+ gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
+ # we go online
+ self.roster.send_status(account, 'online',
+ gajim.status_before_autoaway[account])
+ gajim.status_before_autoaway[account] = ''
+ gajim.sleeper_state[account] = 'online'
+ elif state == common.sleepy.STATE_AWAY and \
+ gajim.sleeper_state[account] == 'online' and \
+ gajim.config.get('autoaway'):
+ # we save out online status
+ gajim.status_before_autoaway[account] = \
+ gajim.connections[account].status
+ # we go away (no auto status) [we pass True to auto param]
+ auto_message = gajim.config.get('autoaway_message')
+ if not auto_message:
+ auto_message = gajim.connections[account].status
+ else:
+ auto_message = auto_message.replace('$S','%(status)s')
+ auto_message = auto_message.replace('$T','%(time)s')
+ auto_message = auto_message % {
+ 'status': gajim.status_before_autoaway[account],
+ 'time': gajim.config.get('autoawaytime')
+ }
+ self.roster.send_status(account, 'away', auto_message, auto=True)
+ gajim.sleeper_state[account] = 'autoaway'
+ elif state == common.sleepy.STATE_XA and \
+ gajim.sleeper_state[account] in ('online', 'autoaway',
+ 'autoaway-forced') and gajim.config.get('autoxa'):
+ # we go extended away [we pass True to auto param]
+ auto_message = gajim.config.get('autoxa_message')
+ if not auto_message:
+ auto_message = gajim.connections[account].status
+ else:
+ auto_message = auto_message.replace('$S','%(status)s')
+ auto_message = auto_message.replace('$T','%(time)s')
+ auto_message = auto_message % {
+ 'status': gajim.status_before_autoaway[account],
+ 'time': gajim.config.get('autoxatime')
+ }
+ self.roster.send_status(account, 'xa', auto_message, auto=True)
+ gajim.sleeper_state[account] = 'autoxa'
+ return True # renew timeout (loop for ever)
+
+ def autoconnect(self):
+ """
+ Auto connect at startup
+ """
+ # dict of account that want to connect sorted by status
+ shows = {}
+ for a in gajim.connections:
+ if gajim.config.get_per('accounts', a, 'autoconnect'):
+ if gajim.config.get_per('accounts', a, 'restore_last_status'):
+ self.roster.send_status(a, gajim.config.get_per('accounts', a,
+ 'last_status'), helpers.from_one_line(gajim.config.get_per(
+ 'accounts', a, 'last_status_msg')))
+ continue
+ show = gajim.config.get_per('accounts', a, 'autoconnect_as')
+ if not show in gajim.SHOW_LIST:
+ continue
+ if not show in shows:
+ shows[show] = [a]
+ else:
+ shows[show].append(a)
+ def on_message(message, pep_dict):
+ if message is None:
+ return
+ for a in shows[show]:
+ self.roster.send_status(a, show, message)
+ self.roster.send_pep(a, pep_dict)
+ for show in shows:
+ message = self.roster.get_status_message(show, on_message)
+ return False
+
+ def show_systray(self):
+ self.systray_enabled = True
+ self.systray.show_icon()
+
+ def hide_systray(self):
+ self.systray_enabled = False
+ self.systray.hide_icon()
+
+ def on_launch_browser_mailer(self, widget, url, kind):
+ helpers.launch_browser_mailer(kind, url)
+
+ def process_connections(self):
+ """
+ Called each foo (200) miliseconds. Check for idlequeue timeouts
+ """
+ try:
+ gajim.idlequeue.process()
+ except Exception:
+ # Otherwise, an exception will stop our loop
+ timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
+ if in_seconds:
+ gobject.timeout_add_seconds(timeout, self.process_connections)
+ else:
+ gobject.timeout_add(timeout, self.process_connections)
+ raise
+ return True # renew timeout (loop for ever)
+
+ def save_config(self):
+ err_str = parser.write()
+ if err_str is not None:
+ print >> sys.stderr, err_str
+ # it is good to notify the user
+ # in case he or she cannot see the output of the console
+ dialogs.ErrorDialog(_('Could not save your settings and preferences'),
+ err_str)
+ sys.exit()
+
+ def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
+ """
+ Save an avatar to a separate file, and generate files for dbus
+ notifications. An avatar can be given as a pixmap directly or as an
+ decoded image
+ """
+ puny_jid = helpers.sanitize_filename(jid)
+ path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
+ if puny_nick:
+ path_to_file = os.path.join(path_to_file, puny_nick)
+ # remove old avatars
+ for typ in ('jpeg', 'png'):
+ if local:
+ path_to_original_file = path_to_file + '_local'+ '.' + typ
+ else:
+ path_to_original_file = path_to_file + '.' + typ
+ if os.path.isfile(path_to_original_file):
+ os.remove(path_to_original_file)
+ if local and photo:
+ pixbuf = photo
+ typ = 'png'
+ extension = '_local.png' # save local avatars as png file
+ else:
+ pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True)
+ if pixbuf is None:
+ return
+ extension = '.' + typ
+ if typ not in ('jpeg', 'png'):
+ gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ))
+ typ = 'png'
+ extension = '.png'
+ path_to_original_file = path_to_file + extension
+ try:
+ pixbuf.save(path_to_original_file, typ)
+ except Exception, e:
+ log.error('Error writing avatar file %s: %s' % (path_to_original_file,
+ str(e)))
+ # Generate and save the resized, color avatar
+ pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
+ if pixbuf:
+ path_to_normal_file = path_to_file + '_notif_size_colored' + extension
+ try:
+ pixbuf.save(path_to_normal_file, 'png')
+ except Exception, e:
+ log.error('Error writing avatar file %s: %s' % \
+ (path_to_original_file, str(e)))
+ # Generate and save the resized, black and white avatar
+ bwbuf = gtkgui_helpers.get_scaled_pixbuf(
+ gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
+ if bwbuf:
+ path_to_bw_file = path_to_file + '_notif_size_bw' + extension
+ try:
+ bwbuf.save(path_to_bw_file, 'png')
+ except Exception, e:
+ log.error('Error writing avatar file %s: %s' % \
+ (path_to_original_file, str(e)))
+
+ def remove_avatar_files(self, jid, puny_nick = None, local = False):
+ """
+ Remove avatar files of a jid
+ """
+ puny_jid = helpers.sanitize_filename(jid)
+ path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
+ if puny_nick:
+ path_to_file = os.path.join(path_to_file, puny_nick)
+ for ext in ('.jpeg', '.png'):
+ if local:
+ ext = '_local' + ext
+ path_to_original_file = path_to_file + ext
+ if os.path.isfile(path_to_file + ext):
+ os.remove(path_to_file + ext)
+ if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
+ os.remove(path_to_file + '_notif_size_colored' + ext)
+ if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
+ os.remove(path_to_file + '_notif_size_bw' + ext)
+
+ def auto_join_bookmarks(self, account):
+ """
+ Autojoin bookmarked GCs that have 'auto join' on for this account
+ """
+ for bm in gajim.connections[account].bookmarks:
+ if bm['autojoin'] in ('1', 'true'):
+ jid = bm['jid']
+ # Only join non-opened groupchats. Opened one are already
+ # auto-joined on re-connection
+ if not jid in gajim.gc_connected[account]:
+ # we are not already connected
+ minimize = bm['minimize'] in ('1', 'true')
+ gajim.interface.join_gc_room(account, jid, bm['nick'],
+ bm['password'], minimize = minimize)
+ elif jid in self.minimized_controls[account]:
+ # more or less a hack:
+ # On disconnect the minimized gc contact instances
+ # were set to offline. Reconnect them to show up in the roster.
+ self.roster.add_groupchat(jid, account)
+
+ def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
+ nick):
+ """
+ Add a bookmark for this account, sorted in bookmark list
+ """
+ bm = {
+ 'name': name,
+ 'jid': jid,
+ 'autojoin': autojoin,
+ 'minimize': minimize,
+ 'password': password,
+ 'nick': nick
+ }
+ place_found = False
+ index = 0
+ # check for duplicate entry and respect alpha order
+ for bookmark in gajim.connections[account].bookmarks:
+ if bookmark['jid'] == bm['jid']:
+ dialogs.ErrorDialog(
+ _('Bookmark already set'),
+ _('Group Chat "%s" is already in your bookmarks.') % bm['jid'])
+ return
+ if bookmark['name'] > bm['name']:
+ place_found = True
+ break
+ index += 1
+ if place_found:
+ gajim.connections[account].bookmarks.insert(index, bm)
+ else:
+ gajim.connections[account].bookmarks.append(bm)
+ gajim.connections[account].store_bookmarks()
+ self.roster.set_actions_menu_needs_rebuild()
+ dialogs.InformationDialog(
+ _('Bookmark has been added successfully'),
+ _('You can manage your bookmarks via Actions menu in your roster.'))
+
+
+ # does JID exist only within a groupchat?
+ def is_pm_contact(self, fjid, account):
+ bare_jid = gajim.get_jid_without_resource(fjid)
+
+ gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
+
+ if not gc_ctrl and \
+ bare_jid in self.minimized_controls[account]:
+ gc_ctrl = self.minimized_controls[account][bare_jid]
+
+ return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
+
+ def create_ipython_window(self):
+ try:
+ from ipython_view import IPythonView
+ except ImportError:
+ print 'ipython_view not found'
+ return
+ import pango
+
+ if os.name == 'nt':
+ font = 'Lucida Console 9'
+ else:
+ font = 'Luxi Mono 10'
+
+ window = gtk.Window()
+ window.set_size_request(750,550)
+ window.set_resizable(True)
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
+ view = IPythonView()
+ view.modify_font(pango.FontDescription(font))
+ view.set_wrap_mode(gtk.WRAP_CHAR)
+ sw.add(view)
+ window.add(sw)
+ window.show_all()
+ def on_delete(win, event):
+ win.hide()
+ return True
+ window.connect('delete_event',on_delete)
+ view.updateNamespace({'gajim': gajim})
+ gajim.ipython_window = window
+
+ def run(self):
+ if gajim.config.get('trayicon') != 'never':
+ self.show_systray()
+
+ self.roster = roster_window.RosterWindow()
+ for account in gajim.connections:
+ gajim.connections[account].load_roster_from_db()
+
+ # get instances for windows/dialogs that will show_all()/hide()
+ self.instances['file_transfers'] = dialogs.FileTransfersWindow()
+
+ gobject.timeout_add(100, self.autoconnect)
+ timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
+ if in_seconds:
+ gobject.timeout_add_seconds(timeout, self.process_connections)
+ else:
+ gobject.timeout_add(timeout, self.process_connections)
+ gobject.timeout_add_seconds(gajim.config.get(
+ 'check_idle_every_foo_seconds'), self.read_sleepy)
+
+ # when using libasyncns we need to process resolver in regular intervals
+ if resolver.USE_LIBASYNCNS:
+ gobject.timeout_add(200, gajim.resolver.process)
+
+ # setup the indicator
+ if gajim.HAVE_INDICATOR:
+ notify.setup_indicator_server()
+
+ def remote_init():
+ if gajim.config.get('remote_control'):
+ try:
+ import remote_control
+ self.remote_ctrl = remote_control.Remote()
+ except Exception:
+ pass
+ gobject.timeout_add_seconds(5, remote_init)
+
+
+ def __init__(self):
+ gajim.interface = self
+ gajim.thread_interface = ThreadInterface
+ # This is the manager and factory of message windows set by the module
+ self.msg_win_mgr = None
+ self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
+ 'closed': {}}
+ self.emoticons_menu = None
+ # handler when an emoticon is clicked in emoticons_menu
+ self.emoticon_menuitem_clicked = None
+ self.minimized_controls = {}
+ self.status_sent_to_users = {}
+ self.status_sent_to_groups = {}
+ self.gpg_passphrase = {}
+ self.pass_dialog = {}
+ self.default_colors = {
+ 'inmsgcolor': gajim.config.get('inmsgcolor'),
+ 'outmsgcolor': gajim.config.get('outmsgcolor'),
+ 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
+ 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
+ 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
+ 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
+ }
+
+ cfg_was_read = parser.read()
+
+ from common import latex
+ gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
+ latex.check_for_latex_support()
+
+ gajim.logger.reset_shown_unread_messages()
+ # override logging settings from config (don't take care of '-q' option)
+ if gajim.config.get('verbose'):
+ logging_helpers.set_verbose()
+
+ # Is Gajim default app?
+ if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
+ gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
+
+ for account in gajim.config.get_per('accounts'):
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ gajim.ZEROCONF_ACC_NAME = account
+ break
+ # Is gnome configured to activate row on single click ?
+ try:
+ import gconf
+ client = gconf.client_get_default()
+ click_policy = client.get_string(
+ '/apps/nautilus/preferences/click_policy')
+ if click_policy == 'single':
+ gajim.single_click = True
+ except Exception:
+ pass
+ # add default status messages if there is not in the config file
+ if len(gajim.config.get_per('statusmsg')) == 0:
+ default = gajim.config.statusmsg_default
+ for msg in default:
+ gajim.config.add_per('statusmsg', msg)
+ gajim.config.set_per('statusmsg', msg, 'message', default[msg][0])
+ gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1])
+ gajim.config.set_per('statusmsg', msg, 'subactivity',
+ default[msg][2])
+ gajim.config.set_per('statusmsg', msg, 'activity_text',
+ default[msg][3])
+ gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4])
+ gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5])
+ #add default themes if there is not in the config file
+ theme = gajim.config.get('roster_theme')
+ if not theme in gajim.config.get_per('themes'):
+ gajim.config.set('roster_theme', _('default'))
+ if len(gajim.config.get_per('themes')) == 0:
+ d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
+ 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
+ 'groupfontattrs', 'contacttextcolor', 'contactbgcolor',
+ 'contactfont', 'contactfontattrs', 'bannertextcolor',
+ 'bannerbgcolor']
+
+ default = gajim.config.themes_default
+ for theme_name in default:
+ gajim.config.add_per('themes', theme_name)
+ theme = default[theme_name]
+ for o in d:
+ gajim.config.set_per('themes', theme_name, o,
+ theme[d.index(o)])
+
+ if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
+ gtkgui_helpers.autodetect_browser_mailer()
+
+ gajim.idlequeue = idlequeue.get_idlequeue()
+ # resolve and keep current record of resolved hosts
+ gajim.resolver = resolver.get_resolver(gajim.idlequeue)
+ gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
+ self.handle_event_file_rcv_completed,
+ self.handle_event_file_progress,
+ self.handle_event_file_error)
+ gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
+ gajim.default_session_type = ChatControlSession
+
+ # Creating Global Events Dispatcher
+ from common import ged
+ gajim.ged = ged.GlobalEventsDispatcher()
+ self.create_core_handlers_list()
+ self.register_core_handlers()
+
+ if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
+ and gajim.HAVE_ZEROCONF:
+ gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
+ connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
+ for account in gajim.config.get_per('accounts'):
+ if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \
+ gajim.config.get_per('accounts', account, 'active'):
+ gajim.connections[account] = common.connection.Connection(account)
+
+ # gtk hooks
+ gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
+ gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
+ gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
+
+ self.instances = {}
+
+ for a in gajim.connections:
+ self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
+ 'search': {}, 'online_dialog': {}}
+ # online_dialog contains all dialogs that have a meaning only when we
+ # are not disconnected
+ self.minimized_controls[a] = {}
+ gajim.contacts.add_account(a)
+ gajim.groups[a] = {}
+ gajim.gc_connected[a] = {}
+ gajim.automatic_rooms[a] = {}
+ gajim.newly_added[a] = []
+ gajim.to_be_removed[a] = []
+ gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
+ gajim.block_signed_in_notifications[a] = True
+ gajim.sleeper_state[a] = 0
+ gajim.encrypted_chats[a] = []
+ gajim.last_message_time[a] = {}
+ gajim.status_before_autoaway[a] = ''
+ gajim.transport_avatar[a] = {}
+ gajim.gajim_optional_features[a] = []
+ gajim.caps_hash[a] = ''
+
+ helpers.update_optional_features()
+ # prepopulate data which we are sure of; note: we do not log these info
+ for account in gajim.connections:
+ gajimcaps = caps_cache.capscache[('sha-1', gajim.caps_hash[account])]
+ gajimcaps.identities = [gajim.gajim_identity]
+ gajimcaps.features = gajim.gajim_common_features + \
+ gajim.gajim_optional_features[account]
+
+ self.remote_ctrl = None
+
+ if gajim.config.get('networkmanager_support') and dbus_support.supported:
+ import network_manager_listener
+
+ # Handle gnome screensaver
+ if dbus_support.supported:
+ def gnome_screensaver_ActiveChanged_cb(active):
+ if not active:
+ for account in gajim.connections:
+ if gajim.sleeper_state[account] == 'autoaway-forced':
+ # We came back online ofter gnome-screensaver autoaway
+ self.roster.send_status(account, 'online',
+ gajim.status_before_autoaway[account])
+ gajim.status_before_autoaway[account] = ''
+ gajim.sleeper_state[account] = 'online'
+ return
+ if not gajim.config.get('autoaway'):
+ # Don't go auto away if user disabled the option
+ return
+ for account in gajim.connections:
+ if account not in gajim.sleeper_state or \
+ not gajim.sleeper_state[account]:
+ continue
+ if gajim.sleeper_state[account] == 'online':
+ # we save out online status
+ gajim.status_before_autoaway[account] = \
+ gajim.connections[account].status
+ # we go away (no auto status) [we pass True to auto param]
+ auto_message = gajim.config.get('autoaway_message')
+ if not auto_message:
+ auto_message = gajim.connections[account].status
+ else:
+ auto_message = auto_message.replace('$S','%(status)s')
+ auto_message = auto_message.replace('$T','%(time)s')
+ auto_message = auto_message % {
+ 'status': gajim.status_before_autoaway[account],
+ 'time': gajim.config.get('autoxatime')
+ }
+ self.roster.send_status(account, 'away', auto_message,
+ auto=True)
+ gajim.sleeper_state[account] = 'autoaway-forced'
+
+ try:
+ bus = dbus.SessionBus()
+ bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
+ 'ActiveChanged', 'org.gnome.ScreenSaver')
+ except Exception:
+ pass
+
+ self.show_vcard_when_connect = []
+
+ self.sleeper = common.sleepy.Sleepy(
+ gajim.config.get('autoawaytime') * 60, # make minutes to seconds
+ gajim.config.get('autoxatime') * 60)
+
+ gtkgui_helpers.make_jabber_state_images()
+
+ self.systray_enabled = False
+
+ import statusicon
+ self.systray = statusicon.StatusIcon()
+
+ pix = gtkgui_helpers.get_icon_pixmap('gajim', 32)
+ # set the icon to all windows
+ gtk.window_set_default_icon(pix)
+
+ self.init_emoticons()
+ self.make_regexps()
+
+ # get transports type from DB
+ gajim.transport_type = gajim.logger.get_transports_type()
+
+ # test is dictionnary is present for speller
+ if gajim.config.get('use_speller'):
+ lang = gajim.config.get('speller_language')
+ if not lang:
+ lang = gajim.LANG
+ tv = gtk.TextView()
+ try:
+ import gtkspell
+ spell = gtkspell.Spell(tv, lang)
+ except (ImportError, TypeError, RuntimeError, OSError):
+ dialogs.AspellDictError(lang)
+
+ if gajim.config.get('soundplayer') == '':
+ # only on first time Gajim starts
+ commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
+ for command in commands:
+ if helpers.is_in_path(command):
+ if command == 'aplay':
+ command += ' -q'
+ gajim.config.set('soundplayer', command)
+ break
+
+ self.last_ftwindow_update = 0
+
+ self.music_track_changed_signal = None
class PassphraseRequest:
- def __init__(self, keyid):
- self.keyid = keyid
- self.callbacks = []
- self.dialog_created = False
- self.dialog = None
- self.completed = False
-
- def interrupt(self):
- self.dialog.window.destroy()
- self.callbacks = []
-
- def run_callback(self, account, callback):
- gajim.connections[account].gpg_passphrase(self.passphrase)
- callback()
-
- def add_callback(self, account, cb):
- if self.completed:
- self.run_callback(account, cb)
- else:
- self.callbacks.append((account, cb))
- if not self.dialog_created:
- self.create_dialog(account)
-
- def complete(self, passphrase):
- self.passphrase = passphrase
- self.completed = True
- if passphrase is not None:
- gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase,
- self.keyid)
- for (account, cb) in self.callbacks:
- self.run_callback(account, cb)
- del self.callbacks
-
- def create_dialog(self, account):
- title = _('Passphrase Required')
- second = _('Enter GPG key passphrase for key %(keyid)s (account '
- '%(account)s).') % {'keyid': self.keyid, 'account': account}
-
- def _cancel():
- # user cancelled, continue without GPG
- self.complete(None)
-
- def _ok(passphrase, checked, count):
- result = gajim.connections[account].test_gpg_passphrase(passphrase)
- if result == 'ok':
- # passphrase is good
- self.complete(passphrase)
- return
- elif result == 'expired':
- dialogs.ErrorDialog(_('GPG key expired'),
- _('Your GPG key has expired, you will be connected to %s without'
- ' OpenPGP.') % account)
- # Don't try to connect with GPG
- gajim.connections[account].continue_connect_info[2] = False
- self.complete(None)
- return
-
- if count < 3:
- # ask again
- dialogs.PassphraseDialog(_('Wrong Passphrase'),
- _('Please retype your GPG passphrase or press Cancel.'),
- ok_handler=(_ok, count + 1), cancel_handler=_cancel)
- else:
- # user failed 3 times, continue without GPG
- self.complete(None)
-
- self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1),
- cancel_handler=_cancel)
- self.dialog_created = True
+ def __init__(self, keyid):
+ self.keyid = keyid
+ self.callbacks = []
+ self.dialog_created = False
+ self.dialog = None
+ self.completed = False
+
+ def interrupt(self):
+ self.dialog.window.destroy()
+ self.callbacks = []
+
+ def run_callback(self, account, callback):
+ gajim.connections[account].gpg_passphrase(self.passphrase)
+ callback()
+
+ def add_callback(self, account, cb):
+ if self.completed:
+ self.run_callback(account, cb)
+ else:
+ self.callbacks.append((account, cb))
+ if not self.dialog_created:
+ self.create_dialog(account)
+
+ def complete(self, passphrase):
+ self.passphrase = passphrase
+ self.completed = True
+ if passphrase is not None:
+ gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase,
+ self.keyid)
+ for (account, cb) in self.callbacks:
+ self.run_callback(account, cb)
+ del self.callbacks
+
+ def create_dialog(self, account):
+ title = _('Passphrase Required')
+ second = _('Enter GPG key passphrase for key %(keyid)s (account '
+ '%(account)s).') % {'keyid': self.keyid, 'account': account}
+
+ def _cancel():
+ # user cancelled, continue without GPG
+ self.complete(None)
+
+ def _ok(passphrase, checked, count):
+ result = gajim.connections[account].test_gpg_passphrase(passphrase)
+ if result == 'ok':
+ # passphrase is good
+ self.complete(passphrase)
+ return
+ elif result == 'expired':
+ dialogs.ErrorDialog(_('GPG key expired'),
+ _('Your GPG key has expired, you will be connected to %s without'
+ ' OpenPGP.') % account)
+ # Don't try to connect with GPG
+ gajim.connections[account].continue_connect_info[2] = False
+ self.complete(None)
+ return
+
+ if count < 3:
+ # ask again
+ dialogs.PassphraseDialog(_('Wrong Passphrase'),
+ _('Please retype your GPG passphrase or press Cancel.'),
+ ok_handler=(_ok, count + 1), cancel_handler=_cancel)
+ else:
+ # user failed 3 times, continue without GPG
+ self.complete(None)
+
+ self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1),
+ cancel_handler=_cancel)
+ self.dialog_created = True
class ThreadInterface:
- def __init__(self, func, func_args, callback, callback_args):
- """
- Call a function in a thread
- """
- def thread_function(func, func_args, callback, callback_args):
- output = func(*func_args)
- gobject.idle_add(callback, output, *callback_args)
-
- Thread(target=thread_function, args=(func, func_args, callback,
- callback_args)).start()
-
-# vim: se ts=3:
+ def __init__(self, func, func_args, callback, callback_args):
+ """
+ Call a function in a thread
+ """
+ def thread_function(func, func_args, callback, callback_args):
+ output = func(*func_args)
+ gobject.idle_add(callback, output, *callback_args)
+
+ Thread(target=thread_function, args=(func, func_args, callback,
+ callback_args)).start()
diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py
index 3e9bbb181..6652badef 100644
--- a/src/gui_menu_builder.py
+++ b/src/gui_menu_builder.py
@@ -28,448 +28,446 @@ from common import helpers
from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION
def build_resources_submenu(contacts, account, action, room_jid=None,
- room_account=None, cap=None):
- """
- Build a submenu with contact's resources. room_jid and room_account are for
- action self.on_invite_to_room
- """
- roster = gajim.interface.roster
- sub_menu = gtk.Menu()
-
- iconset = gajim.config.get('iconset')
- if not iconset:
- iconset = gajim.config.DEFAULT_ICONSET
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for c in contacts:
- # icon MUST be different instance for every item
- state_images = gtkgui_helpers.load_iconset(path)
- item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority)))
- icon_name = helpers.get_icon_name_to_show(c, account)
- icon = state_images[icon_name]
- item.set_image(icon)
- sub_menu.append(item)
-
- if action == roster.on_invite_to_room:
- item.connect('activate', action, [(c, account)], room_jid,
- room_account, c.resource)
- elif action == roster.on_invite_to_new_room:
- item.connect('activate', action, [(c, account)], c.resource)
- else: # start_chat, execute_command, send_file
- item.connect('activate', action, c, account, c.resource)
-
- if cap and not c.supports(cap):
- item.set_sensitive(False)
-
- return sub_menu
+ room_account=None, cap=None):
+ """
+ Build a submenu with contact's resources. room_jid and room_account are for
+ action self.on_invite_to_room
+ """
+ roster = gajim.interface.roster
+ sub_menu = gtk.Menu()
+
+ iconset = gajim.config.get('iconset')
+ if not iconset:
+ iconset = gajim.config.DEFAULT_ICONSET
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ for c in contacts:
+ # icon MUST be different instance for every item
+ state_images = gtkgui_helpers.load_iconset(path)
+ item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority)))
+ icon_name = helpers.get_icon_name_to_show(c, account)
+ icon = state_images[icon_name]
+ item.set_image(icon)
+ sub_menu.append(item)
+
+ if action == roster.on_invite_to_room:
+ item.connect('activate', action, [(c, account)], room_jid,
+ room_account, c.resource)
+ elif action == roster.on_invite_to_new_room:
+ item.connect('activate', action, [(c, account)], c.resource)
+ else: # start_chat, execute_command, send_file
+ item.connect('activate', action, c, account, c.resource)
+
+ if cap and not c.supports(cap):
+ item.set_sensitive(False)
+
+ return sub_menu
def build_invite_submenu(invite_menuitem, list_):
- """
- list_ in a list of (contact, account)
- """
- roster = gajim.interface.roster
- # used if we invite only one contact with several resources
- contact_list = []
- if len(list_) == 1:
- contact, account = list_[0]
- contact_list = gajim.contacts.get_contacts(account, contact.jid)
- contacts_transport = -1
- connected_accounts = []
- # -1 is at start, False when not from the same, None when jabber
- for (contact, account) in list_:
- if not account in connected_accounts:
- connected_accounts.append(account)
- transport = gajim.get_transport_name_from_jid(contact.jid)
- if contacts_transport == -1:
- contacts_transport = transport
- elif contacts_transport != transport:
- contacts_transport = False
-
- if contacts_transport == False:
- # they are not all from the same transport
- invite_menuitem.set_sensitive(False)
- return
- invite_to_submenu = gtk.Menu()
- invite_menuitem.set_submenu(invite_to_submenu)
- invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat'))
- icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
- invite_to_new_room_menuitem.set_image(icon)
- if len(contact_list) > 1: # several resources
- invite_to_new_room_menuitem.set_submenu(build_resources_submenu(
- contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC))
- elif len(list_) == 1 and contact.supports(NS_MUC):
- invite_menuitem.set_sensitive(True)
- # use resource if it's self contact
- if contact.jid == gajim.get_jid_from_account(account):
- resource = contact.resource
- else:
- resource = None
- invite_to_new_room_menuitem.connect('activate',
- roster.on_invite_to_new_room, list_, resource)
- else:
- invite_menuitem.set_sensitive(False)
- # transform None in 'jabber'
- c_t = contacts_transport or 'jabber'
- muc_jid = {}
- for account in connected_accounts:
- for t in gajim.connections[account].muc_jid:
- muc_jid[t] = gajim.connections[account].muc_jid[t]
- if c_t not in muc_jid:
- invite_to_new_room_menuitem.set_sensitive(False)
- rooms = [] # a list of (room_jid, account) tuple
- invite_to_submenu.append(invite_to_new_room_menuitem)
- rooms = [] # a list of (room_jid, account) tuple
- minimized_controls = []
- for account in connected_accounts:
- minimized_controls += gajim.interface.minimized_controls[account].values()
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + minimized_controls:
- acct = gc_control.account
- room_jid = gc_control.room_jid
- if room_jid in gajim.gc_connected[acct] and \
- gajim.gc_connected[acct][room_jid] and \
- contacts_transport == gajim.get_transport_name_from_jid(room_jid):
- rooms.append((room_jid, acct))
- if len(rooms):
- item = gtk.SeparatorMenuItem() # separator
- invite_to_submenu.append(item)
- for (room_jid, account) in rooms:
- menuitem = gtk.MenuItem(room_jid.split('@')[0])
- if len(contact_list) > 1: # several resources
- menuitem.set_submenu(build_resources_submenu(
- contact_list, account, roster.on_invite_to_room, room_jid,
- account))
- else:
- # use resource if it's self contact
- if contact.jid == gajim.get_jid_from_account(account):
- resource = contact.resource
- else:
- resource = None
- menuitem.connect('activate', roster.on_invite_to_room, list_,
- room_jid, account, resource)
- invite_to_submenu.append(menuitem)
+ """
+ list_ in a list of (contact, account)
+ """
+ roster = gajim.interface.roster
+ # used if we invite only one contact with several resources
+ contact_list = []
+ if len(list_) == 1:
+ contact, account = list_[0]
+ contact_list = gajim.contacts.get_contacts(account, contact.jid)
+ contacts_transport = -1
+ connected_accounts = []
+ # -1 is at start, False when not from the same, None when jabber
+ for (contact, account) in list_:
+ if not account in connected_accounts:
+ connected_accounts.append(account)
+ transport = gajim.get_transport_name_from_jid(contact.jid)
+ if contacts_transport == -1:
+ contacts_transport = transport
+ elif contacts_transport != transport:
+ contacts_transport = False
+
+ if contacts_transport == False:
+ # they are not all from the same transport
+ invite_menuitem.set_sensitive(False)
+ return
+ invite_to_submenu = gtk.Menu()
+ invite_menuitem.set_submenu(invite_to_submenu)
+ invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
+ invite_to_new_room_menuitem.set_image(icon)
+ if len(contact_list) > 1: # several resources
+ invite_to_new_room_menuitem.set_submenu(build_resources_submenu(
+ contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC))
+ elif len(list_) == 1 and contact.supports(NS_MUC):
+ invite_menuitem.set_sensitive(True)
+ # use resource if it's self contact
+ if contact.jid == gajim.get_jid_from_account(account):
+ resource = contact.resource
+ else:
+ resource = None
+ invite_to_new_room_menuitem.connect('activate',
+ roster.on_invite_to_new_room, list_, resource)
+ else:
+ invite_menuitem.set_sensitive(False)
+ # transform None in 'jabber'
+ c_t = contacts_transport or 'jabber'
+ muc_jid = {}
+ for account in connected_accounts:
+ for t in gajim.connections[account].muc_jid:
+ muc_jid[t] = gajim.connections[account].muc_jid[t]
+ if c_t not in muc_jid:
+ invite_to_new_room_menuitem.set_sensitive(False)
+ rooms = [] # a list of (room_jid, account) tuple
+ invite_to_submenu.append(invite_to_new_room_menuitem)
+ rooms = [] # a list of (room_jid, account) tuple
+ minimized_controls = []
+ for account in connected_accounts:
+ minimized_controls += gajim.interface.minimized_controls[account].values()
+ for gc_control in gajim.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC) + minimized_controls:
+ acct = gc_control.account
+ room_jid = gc_control.room_jid
+ if room_jid in gajim.gc_connected[acct] and \
+ gajim.gc_connected[acct][room_jid] and \
+ contacts_transport == gajim.get_transport_name_from_jid(room_jid):
+ rooms.append((room_jid, acct))
+ if len(rooms):
+ item = gtk.SeparatorMenuItem() # separator
+ invite_to_submenu.append(item)
+ for (room_jid, account) in rooms:
+ menuitem = gtk.MenuItem(room_jid.split('@')[0])
+ if len(contact_list) > 1: # several resources
+ menuitem.set_submenu(build_resources_submenu(
+ contact_list, account, roster.on_invite_to_room, room_jid,
+ account))
+ else:
+ # use resource if it's self contact
+ if contact.jid == gajim.get_jid_from_account(account):
+ resource = contact.resource
+ else:
+ resource = None
+ menuitem.connect('activate', roster.on_invite_to_room, list_,
+ room_jid, account, resource)
+ invite_to_submenu.append(menuitem)
def get_contact_menu(contact, account, use_multiple_contacts=True,
- show_start_chat=True, show_encryption=False, show_buttonbar_items=True,
- control=None):
- """
- Build contact popup menu for roster and chat window. If control is not set,
- we hide invite_contacts_menuitem
- """
- if not contact:
- return
-
- jid = contact.jid
- our_jid = jid == gajim.get_jid_from_account(account)
- roster = gajim.interface.roster
-
- xml = gtkgui_helpers.get_gtk_builder('contact_context_menu.ui')
- contact_context_menu = xml.get_object('contact_context_menu')
-
- start_chat_menuitem = xml.get_object('start_chat_menuitem')
- execute_command_menuitem = xml.get_object('execute_command_menuitem')
- rename_menuitem = xml.get_object('rename_menuitem')
- edit_groups_menuitem = xml.get_object('edit_groups_menuitem')
- send_file_menuitem = xml.get_object('send_file_menuitem')
- assign_openpgp_key_menuitem = xml.get_object('assign_openpgp_key_menuitem')
- add_special_notification_menuitem = xml.get_object(
- 'add_special_notification_menuitem')
- information_menuitem = xml.get_object('information_menuitem')
- history_menuitem = xml.get_object('history_menuitem')
- send_custom_status_menuitem = xml.get_object('send_custom_status_menuitem')
- send_single_message_menuitem = xml.get_object('send_single_message_menuitem')
- invite_menuitem = xml.get_object('invite_menuitem')
- block_menuitem = xml.get_object('block_menuitem')
- unblock_menuitem = xml.get_object('unblock_menuitem')
- ignore_menuitem = xml.get_object('ignore_menuitem')
- unignore_menuitem = xml.get_object('unignore_menuitem')
- set_custom_avatar_menuitem = xml.get_object('set_custom_avatar_menuitem')
- # Subscription submenu
- subscription_menuitem = xml.get_object('subscription_menuitem')
- send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \
- subscription_menuitem.get_submenu().get_children()
- add_to_roster_menuitem = xml.get_object('add_to_roster_menuitem')
- remove_from_roster_menuitem = xml.get_object(
- 'remove_from_roster_menuitem')
- manage_contact_menuitem = xml.get_object('manage_contact')
- convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem')
- encryption_separator = xml.get_object('encryption_separator')
- toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem')
- toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem')
- last_separator = xml.get_object('last_separator')
-
- items_to_hide = []
-
- # add a special img for send file menuitem
- path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
- img = gtk.Image()
- img.set_from_file(path_to_upload_img)
- send_file_menuitem.set_image(img)
-
- if not our_jid:
- # add a special img for rename menuitem
- gtkgui_helpers.add_image_to_menuitem(rename_menuitem, 'gajim-kbd_input')
-
- muc_icon = gtkgui_helpers.load_icon('muc_active')
- if muc_icon:
- convert_to_gc_menuitem.set_image(muc_icon)
-
- contacts = gajim.contacts.get_contacts(account, jid)
- if len(contacts) > 1 and use_multiple_contacts: # several resources
- start_chat_menuitem.set_submenu(build_resources_submenu(contacts,
- account, gajim.interface.on_open_chat_window))
- send_file_menuitem.set_submenu(build_resources_submenu(contacts,
- account, roster.on_send_file_menuitem_activate, cap=NS_FILE))
- execute_command_menuitem.set_submenu(build_resources_submenu(
- contacts, account, roster.on_execute_command, cap=NS_COMMANDS))
- else:
- start_chat_menuitem.connect('activate',
- gajim.interface.on_open_chat_window, contact, account)
- if contact.supports(NS_FILE):
- send_file_menuitem.set_sensitive(True)
- send_file_menuitem.connect('activate',
- roster.on_send_file_menuitem_activate, contact, account)
- else:
- send_file_menuitem.set_sensitive(False)
-
- if contact.supports(NS_COMMANDS):
- execute_command_menuitem.set_sensitive(True)
- execute_command_menuitem.connect('activate', roster.on_execute_command,
- contact, account, contact.resource)
- else:
- execute_command_menuitem.set_sensitive(False)
-
- rename_menuitem.connect('activate', roster.on_rename, 'contact', jid,
- account)
- history_menuitem.connect('activate', roster.on_history, contact, account)
-
- if control:
- convert_to_gc_menuitem.connect('activate',
- control._on_convert_to_gc_menuitem_activate)
- else:
- items_to_hide.append(convert_to_gc_menuitem)
-
- if _('Not in Roster') not in contact.get_shown_groups():
- # contact is in normal group
- edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact,
- account)])
-
- if gajim.connections[account].gpg:
- assign_openpgp_key_menuitem.connect('activate',
- roster.on_assign_pgp_key, contact, account)
- else:
- assign_openpgp_key_menuitem.set_sensitive(False)
- else:
- # contact is in group 'Not in Roster'
- edit_groups_menuitem.set_sensitive(False)
- assign_openpgp_key_menuitem.set_sensitive(False)
-
- # Hide items when it's self contact row
- if our_jid:
- items_to_hide += [rename_menuitem, edit_groups_menuitem]
-
- # Unsensitive many items when account is offline
- if gajim.account_is_disconnected(account):
- for widget in (start_chat_menuitem, rename_menuitem,
- edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem):
- widget.set_sensitive(False)
-
- if not show_start_chat:
- items_to_hide.append(start_chat_menuitem)
-
- if not show_encryption or not control:
- items_to_hide += [encryption_separator, toggle_gpg_menuitem,
- toggle_e2e_menuitem]
- else:
- e2e_is_active = control.session is not None and \
- control.session.enable_encryption
-
- # check if we support and use gpg
- if not gajim.config.get_per('accounts', account, 'keyid') or \
- not gajim.connections[account].USE_GPG or gajim.jid_is_transport(
- contact.jid):
- toggle_gpg_menuitem.set_sensitive(False)
- else:
- toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \
- not e2e_is_active)
- toggle_gpg_menuitem.set_active(control.gpg_is_active)
- toggle_gpg_menuitem.connect('activate',
- control._on_toggle_gpg_menuitem_activate)
-
- # disable esessions if we or the other client don't support them
- if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \
- not gajim.config.get_per('accounts', account, 'enable_esessions'):
- toggle_e2e_menuitem.set_sensitive(False)
- else:
- toggle_e2e_menuitem.set_active(e2e_is_active)
- toggle_e2e_menuitem.set_sensitive(e2e_is_active or \
- not control.gpg_is_active)
- toggle_e2e_menuitem.connect('activate',
- control._on_toggle_e2e_menuitem_activate)
-
- if not show_buttonbar_items:
- items_to_hide += [history_menuitem, send_file_menuitem,
- information_menuitem, convert_to_gc_menuitem, last_separator]
-
- if not control:
- items_to_hide.append(convert_to_gc_menuitem)
-
- for item in items_to_hide:
- item.set_no_show_all(True)
- item.hide()
-
- # Zeroconf Account
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- for item in (send_custom_status_menuitem, send_single_message_menuitem,
- invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem,
- unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem,
- manage_contact_menuitem, convert_to_gc_menuitem):
- item.set_no_show_all(True)
- item.hide()
-
- if contact.show in ('offline', 'error'):
- information_menuitem.set_sensitive(False)
- send_file_menuitem.set_sensitive(False)
- else:
- information_menuitem.connect('activate', roster.on_info_zeroconf,
- contact, account)
-
- contact_context_menu.connect('selection-done',
- gtkgui_helpers.destroy_widget)
- contact_context_menu.show_all()
- return contact_context_menu
-
- # normal account
-
- # send custom status icon
- blocked = False
- if helpers.jid_is_blocked(account, jid):
- blocked = True
- else:
- for group in contact.get_shown_groups():
- if helpers.group_is_blocked(account, group):
- blocked = True
- break
- if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
- # Transport contact, send custom status unavailable
- send_custom_status_menuitem.set_sensitive(False)
- elif blocked:
- send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline'))
- send_custom_status_menuitem.set_sensitive(False)
- elif account in gajim.interface.status_sent_to_users and \
- jid in gajim.interface.status_sent_to_users[account]:
- send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
- gajim.interface.status_sent_to_users[account][jid]))
- else:
- icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU)
- send_custom_status_menuitem.set_image(icon)
-
- muc_icon = gtkgui_helpers.load_icon('muc_active')
- if muc_icon:
- invite_menuitem.set_image(muc_icon)
-
- build_invite_submenu(invite_menuitem, [(contact, account)])
-
- # One or several resource, we do the same for send_custom_status
- status_menuitems = gtk.Menu()
- send_custom_status_menuitem.set_submenu(status_menuitems)
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
- # icon MUST be different instance for every item
- state_images = gtkgui_helpers.load_iconset(path)
- status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
- status_menuitem.connect('activate', roster.on_send_custom_status,
- [(contact, account)], s)
- icon = state_images[s]
- status_menuitem.set_image(icon)
- status_menuitems.append(status_menuitem)
-
- send_single_message_menuitem.connect('activate',
- roster.on_send_single_message_menuitem_activate, account, contact)
-
- remove_from_roster_menuitem.connect('activate', roster.on_req_usub,
- [(contact, account)])
- information_menuitem.connect('activate', roster.on_info, contact, account)
-
- if _('Not in Roster') not in contact.get_shown_groups():
- # contact is in normal group
- add_to_roster_menuitem.hide()
- add_to_roster_menuitem.set_no_show_all(True)
-
- if contact.sub in ('from', 'both'):
- send_auth_menuitem.set_sensitive(False)
- else:
- send_auth_menuitem.connect('activate', roster.authorize, jid, account)
- if contact.sub in ('to', 'both'):
- ask_auth_menuitem.set_sensitive(False)
- add_special_notification_menuitem.connect('activate',
- roster.on_add_special_notification_menuitem_activate, jid)
- else:
- ask_auth_menuitem.connect('activate', roster.req_sub, jid,
- _('I would like to add you to my roster'), account,
- contact.groups, contact.name)
- if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid(
- jid, use_config_setting=False):
- revoke_auth_menuitem.set_sensitive(False)
- else:
- revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid,
- account)
-
- else:
- # contact is in group 'Not in Roster'
- add_to_roster_menuitem.set_no_show_all(False)
- subscription_menuitem.set_sensitive(False)
-
- add_to_roster_menuitem.connect('activate', roster.on_add_to_roster,
- contact, account)
-
- set_custom_avatar_menuitem.connect('activate',
- roster.on_set_custom_avatar_activate, contact, account)
-
- # Hide items when it's self contact row
- if our_jid:
- manage_contact_menuitem.set_sensitive(False)
-
- # Unsensitive items when account is offline
- if gajim.account_is_disconnected(account):
- for widget in (send_single_message_menuitem, subscription_menuitem,
- add_to_roster_menuitem, remove_from_roster_menuitem,
- execute_command_menuitem, send_custom_status_menuitem):
- widget.set_sensitive(False)
-
- if gajim.connections[account] and gajim.connections[account].\
- privacy_rules_supported:
- if helpers.jid_is_blocked(account, jid):
- block_menuitem.set_no_show_all(True)
- block_menuitem.hide()
- if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
- unblock_menuitem.set_no_show_all(True)
- unblock_menuitem.hide()
- unignore_menuitem.set_no_show_all(False)
- unignore_menuitem.connect('activate', roster.on_unblock, [(contact,
- account)])
- else:
- unblock_menuitem.connect('activate', roster.on_unblock, [(contact,
- account)])
- else:
- unblock_menuitem.set_no_show_all(True)
- unblock_menuitem.hide()
- if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
- block_menuitem.set_no_show_all(True)
- block_menuitem.hide()
- ignore_menuitem.set_no_show_all(False)
- ignore_menuitem.connect('activate', roster.on_block, [(contact,
- account)])
- else:
- block_menuitem.connect('activate', roster.on_block, [(contact,
- account)])
- else:
- unblock_menuitem.set_no_show_all(True)
- block_menuitem.set_sensitive(False)
- unblock_menuitem.hide()
-
- contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- contact_context_menu.show_all()
- return contact_context_menu
-
-# vim: se ts=3:
+ show_start_chat=True, show_encryption=False, show_buttonbar_items=True,
+ control=None):
+ """
+ Build contact popup menu for roster and chat window. If control is not set,
+ we hide invite_contacts_menuitem
+ """
+ if not contact:
+ return
+
+ jid = contact.jid
+ our_jid = jid == gajim.get_jid_from_account(account)
+ roster = gajim.interface.roster
+
+ xml = gtkgui_helpers.get_gtk_builder('contact_context_menu.ui')
+ contact_context_menu = xml.get_object('contact_context_menu')
+
+ start_chat_menuitem = xml.get_object('start_chat_menuitem')
+ execute_command_menuitem = xml.get_object('execute_command_menuitem')
+ rename_menuitem = xml.get_object('rename_menuitem')
+ edit_groups_menuitem = xml.get_object('edit_groups_menuitem')
+ send_file_menuitem = xml.get_object('send_file_menuitem')
+ assign_openpgp_key_menuitem = xml.get_object('assign_openpgp_key_menuitem')
+ add_special_notification_menuitem = xml.get_object(
+ 'add_special_notification_menuitem')
+ information_menuitem = xml.get_object('information_menuitem')
+ history_menuitem = xml.get_object('history_menuitem')
+ send_custom_status_menuitem = xml.get_object('send_custom_status_menuitem')
+ send_single_message_menuitem = xml.get_object('send_single_message_menuitem')
+ invite_menuitem = xml.get_object('invite_menuitem')
+ block_menuitem = xml.get_object('block_menuitem')
+ unblock_menuitem = xml.get_object('unblock_menuitem')
+ ignore_menuitem = xml.get_object('ignore_menuitem')
+ unignore_menuitem = xml.get_object('unignore_menuitem')
+ set_custom_avatar_menuitem = xml.get_object('set_custom_avatar_menuitem')
+ # Subscription submenu
+ subscription_menuitem = xml.get_object('subscription_menuitem')
+ send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \
+ subscription_menuitem.get_submenu().get_children()
+ add_to_roster_menuitem = xml.get_object('add_to_roster_menuitem')
+ remove_from_roster_menuitem = xml.get_object(
+ 'remove_from_roster_menuitem')
+ manage_contact_menuitem = xml.get_object('manage_contact')
+ convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem')
+ encryption_separator = xml.get_object('encryption_separator')
+ toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem')
+ toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem')
+ last_separator = xml.get_object('last_separator')
+
+ items_to_hide = []
+
+ # add a special img for send file menuitem
+ path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
+ img = gtk.Image()
+ img.set_from_file(path_to_upload_img)
+ send_file_menuitem.set_image(img)
+
+ if not our_jid:
+ # add a special img for rename menuitem
+ gtkgui_helpers.add_image_to_menuitem(rename_menuitem, 'gajim-kbd_input')
+
+ muc_icon = gtkgui_helpers.load_icon('muc_active')
+ if muc_icon:
+ convert_to_gc_menuitem.set_image(muc_icon)
+
+ contacts = gajim.contacts.get_contacts(account, jid)
+ if len(contacts) > 1 and use_multiple_contacts: # several resources
+ start_chat_menuitem.set_submenu(build_resources_submenu(contacts,
+ account, gajim.interface.on_open_chat_window))
+ send_file_menuitem.set_submenu(build_resources_submenu(contacts,
+ account, roster.on_send_file_menuitem_activate, cap=NS_FILE))
+ execute_command_menuitem.set_submenu(build_resources_submenu(
+ contacts, account, roster.on_execute_command, cap=NS_COMMANDS))
+ else:
+ start_chat_menuitem.connect('activate',
+ gajim.interface.on_open_chat_window, contact, account)
+ if contact.supports(NS_FILE):
+ send_file_menuitem.set_sensitive(True)
+ send_file_menuitem.connect('activate',
+ roster.on_send_file_menuitem_activate, contact, account)
+ else:
+ send_file_menuitem.set_sensitive(False)
+
+ if contact.supports(NS_COMMANDS):
+ execute_command_menuitem.set_sensitive(True)
+ execute_command_menuitem.connect('activate', roster.on_execute_command,
+ contact, account, contact.resource)
+ else:
+ execute_command_menuitem.set_sensitive(False)
+
+ rename_menuitem.connect('activate', roster.on_rename, 'contact', jid,
+ account)
+ history_menuitem.connect('activate', roster.on_history, contact, account)
+
+ if control:
+ convert_to_gc_menuitem.connect('activate',
+ control._on_convert_to_gc_menuitem_activate)
+ else:
+ items_to_hide.append(convert_to_gc_menuitem)
+
+ if _('Not in Roster') not in contact.get_shown_groups():
+ # contact is in normal group
+ edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact,
+ account)])
+
+ if gajim.connections[account].gpg:
+ assign_openpgp_key_menuitem.connect('activate',
+ roster.on_assign_pgp_key, contact, account)
+ else:
+ assign_openpgp_key_menuitem.set_sensitive(False)
+ else:
+ # contact is in group 'Not in Roster'
+ edit_groups_menuitem.set_sensitive(False)
+ assign_openpgp_key_menuitem.set_sensitive(False)
+
+ # Hide items when it's self contact row
+ if our_jid:
+ items_to_hide += [rename_menuitem, edit_groups_menuitem]
+
+ # Unsensitive many items when account is offline
+ if gajim.account_is_disconnected(account):
+ for widget in (start_chat_menuitem, rename_menuitem,
+ edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem):
+ widget.set_sensitive(False)
+
+ if not show_start_chat:
+ items_to_hide.append(start_chat_menuitem)
+
+ if not show_encryption or not control:
+ items_to_hide += [encryption_separator, toggle_gpg_menuitem,
+ toggle_e2e_menuitem]
+ else:
+ e2e_is_active = control.session is not None and \
+ control.session.enable_encryption
+
+ # check if we support and use gpg
+ if not gajim.config.get_per('accounts', account, 'keyid') or \
+ not gajim.connections[account].USE_GPG or gajim.jid_is_transport(
+ contact.jid):
+ toggle_gpg_menuitem.set_sensitive(False)
+ else:
+ toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \
+ not e2e_is_active)
+ toggle_gpg_menuitem.set_active(control.gpg_is_active)
+ toggle_gpg_menuitem.connect('activate',
+ control._on_toggle_gpg_menuitem_activate)
+
+ # disable esessions if we or the other client don't support them
+ if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \
+ not gajim.config.get_per('accounts', account, 'enable_esessions'):
+ toggle_e2e_menuitem.set_sensitive(False)
+ else:
+ toggle_e2e_menuitem.set_active(e2e_is_active)
+ toggle_e2e_menuitem.set_sensitive(e2e_is_active or \
+ not control.gpg_is_active)
+ toggle_e2e_menuitem.connect('activate',
+ control._on_toggle_e2e_menuitem_activate)
+
+ if not show_buttonbar_items:
+ items_to_hide += [history_menuitem, send_file_menuitem,
+ information_menuitem, convert_to_gc_menuitem, last_separator]
+
+ if not control:
+ items_to_hide.append(convert_to_gc_menuitem)
+
+ for item in items_to_hide:
+ item.set_no_show_all(True)
+ item.hide()
+
+ # Zeroconf Account
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ for item in (send_custom_status_menuitem, send_single_message_menuitem,
+ invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem,
+ unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem,
+ manage_contact_menuitem, convert_to_gc_menuitem):
+ item.set_no_show_all(True)
+ item.hide()
+
+ if contact.show in ('offline', 'error'):
+ information_menuitem.set_sensitive(False)
+ send_file_menuitem.set_sensitive(False)
+ else:
+ information_menuitem.connect('activate', roster.on_info_zeroconf,
+ contact, account)
+
+ contact_context_menu.connect('selection-done',
+ gtkgui_helpers.destroy_widget)
+ contact_context_menu.show_all()
+ return contact_context_menu
+
+ # normal account
+
+ # send custom status icon
+ blocked = False
+ if helpers.jid_is_blocked(account, jid):
+ blocked = True
+ else:
+ for group in contact.get_shown_groups():
+ if helpers.group_is_blocked(account, group):
+ blocked = True
+ break
+ if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
+ # Transport contact, send custom status unavailable
+ send_custom_status_menuitem.set_sensitive(False)
+ elif blocked:
+ send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline'))
+ send_custom_status_menuitem.set_sensitive(False)
+ elif account in gajim.interface.status_sent_to_users and \
+ jid in gajim.interface.status_sent_to_users[account]:
+ send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
+ gajim.interface.status_sent_to_users[account][jid]))
+ else:
+ icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU)
+ send_custom_status_menuitem.set_image(icon)
+
+ muc_icon = gtkgui_helpers.load_icon('muc_active')
+ if muc_icon:
+ invite_menuitem.set_image(muc_icon)
+
+ build_invite_submenu(invite_menuitem, [(contact, account)])
+
+ # One or several resource, we do the same for send_custom_status
+ status_menuitems = gtk.Menu()
+ send_custom_status_menuitem.set_submenu(status_menuitems)
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
+ # icon MUST be different instance for every item
+ state_images = gtkgui_helpers.load_iconset(path)
+ status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
+ status_menuitem.connect('activate', roster.on_send_custom_status,
+ [(contact, account)], s)
+ icon = state_images[s]
+ status_menuitem.set_image(icon)
+ status_menuitems.append(status_menuitem)
+
+ send_single_message_menuitem.connect('activate',
+ roster.on_send_single_message_menuitem_activate, account, contact)
+
+ remove_from_roster_menuitem.connect('activate', roster.on_req_usub,
+ [(contact, account)])
+ information_menuitem.connect('activate', roster.on_info, contact, account)
+
+ if _('Not in Roster') not in contact.get_shown_groups():
+ # contact is in normal group
+ add_to_roster_menuitem.hide()
+ add_to_roster_menuitem.set_no_show_all(True)
+
+ if contact.sub in ('from', 'both'):
+ send_auth_menuitem.set_sensitive(False)
+ else:
+ send_auth_menuitem.connect('activate', roster.authorize, jid, account)
+ if contact.sub in ('to', 'both'):
+ ask_auth_menuitem.set_sensitive(False)
+ add_special_notification_menuitem.connect('activate',
+ roster.on_add_special_notification_menuitem_activate, jid)
+ else:
+ ask_auth_menuitem.connect('activate', roster.req_sub, jid,
+ _('I would like to add you to my roster'), account,
+ contact.groups, contact.name)
+ if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid(
+ jid, use_config_setting=False):
+ revoke_auth_menuitem.set_sensitive(False)
+ else:
+ revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid,
+ account)
+
+ else:
+ # contact is in group 'Not in Roster'
+ add_to_roster_menuitem.set_no_show_all(False)
+ subscription_menuitem.set_sensitive(False)
+
+ add_to_roster_menuitem.connect('activate', roster.on_add_to_roster,
+ contact, account)
+
+ set_custom_avatar_menuitem.connect('activate',
+ roster.on_set_custom_avatar_activate, contact, account)
+
+ # Hide items when it's self contact row
+ if our_jid:
+ manage_contact_menuitem.set_sensitive(False)
+
+ # Unsensitive items when account is offline
+ if gajim.account_is_disconnected(account):
+ for widget in (send_single_message_menuitem, subscription_menuitem,
+ add_to_roster_menuitem, remove_from_roster_menuitem,
+ execute_command_menuitem, send_custom_status_menuitem):
+ widget.set_sensitive(False)
+
+ if gajim.connections[account] and gajim.connections[account].\
+ privacy_rules_supported:
+ if helpers.jid_is_blocked(account, jid):
+ block_menuitem.set_no_show_all(True)
+ block_menuitem.hide()
+ if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
+ unblock_menuitem.set_no_show_all(True)
+ unblock_menuitem.hide()
+ unignore_menuitem.set_no_show_all(False)
+ unignore_menuitem.connect('activate', roster.on_unblock, [(contact,
+ account)])
+ else:
+ unblock_menuitem.connect('activate', roster.on_unblock, [(contact,
+ account)])
+ else:
+ unblock_menuitem.set_no_show_all(True)
+ unblock_menuitem.hide()
+ if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
+ block_menuitem.set_no_show_all(True)
+ block_menuitem.hide()
+ ignore_menuitem.set_no_show_all(False)
+ ignore_menuitem.connect('activate', roster.on_block, [(contact,
+ account)])
+ else:
+ block_menuitem.connect('activate', roster.on_block, [(contact,
+ account)])
+ else:
+ unblock_menuitem.set_no_show_all(True)
+ block_menuitem.set_sensitive(False)
+ unblock_menuitem.hide()
+
+ contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ contact_context_menu.show_all()
+ return contact_context_menu
diff --git a/src/history_manager.py b/src/history_manager.py
index 21c46ca0d..5c1456bde 100644
--- a/src/history_manager.py
+++ b/src/history_manager.py
@@ -30,21 +30,21 @@
import os
if os.name == 'nt':
- import warnings
- warnings.filterwarnings(action='ignore')
-
- if os.path.isdir('gtk'):
- # Used to create windows installer with GTK included
- paths = os.environ['PATH']
- list_ = paths.split(';')
- new_list = []
- for p in list_:
- if p.find('gtk') < 0 and p.find('GTK') < 0:
- new_list.append(p)
- new_list.insert(0, 'gtk/lib')
- new_list.insert(0, 'gtk/bin')
- os.environ['PATH'] = ';'.join(new_list)
- os.environ['GTK_BASEPATH'] = 'gtk'
+ import warnings
+ warnings.filterwarnings(action='ignore')
+
+ if os.path.isdir('gtk'):
+ # Used to create windows installer with GTK included
+ paths = os.environ['PATH']
+ list_ = paths.split(';')
+ new_list = []
+ for p in list_:
+ if p.find('gtk') < 0 and p.find('GTK') < 0:
+ new_list.append(p)
+ new_list.insert(0, 'gtk/lib')
+ new_list.insert(0, 'gtk/bin')
+ os.environ['PATH'] = ';'.join(new_list)
+ os.environ['GTK_BASEPATH'] = 'gtk'
import sys
import signal
@@ -57,23 +57,23 @@ import getopt
from common import i18n
def parseOpts():
- config_path = None
-
- try:
- shortargs = 'hc:'
- longargs = 'help config_path='
- opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
- except getopt.error, msg:
- print str(msg)
- print 'for help use --help'
- sys.exit(2)
- for o, a in opts:
- if o in ('-h', '--help'):
- print 'history_manager [--help] [--config-path]'
- sys.exit()
- elif o in ('-c', '--config-path'):
- config_path = a
- return config_path
+ config_path = None
+
+ try:
+ shortargs = 'hc:'
+ longargs = 'help config_path='
+ opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
+ except getopt.error, msg:
+ print str(msg)
+ print 'for help use --help'
+ sys.exit(2)
+ for o, a in opts:
+ if o in ('-h', '--help'):
+ print 'history_manager [--help] [--config-path]'
+ sys.exit()
+ elif o in ('-c', '--config-path'):
+ config_path = a
+ return config_path
config_path = parseOpts()
del parseOpts
@@ -89,7 +89,7 @@ from common.logger import LOG_DB_PATH, constants
#FIXME: constants should implement 2 way mappings
status = dict((constants.__dict__[i], i[5:].lower()) for i in \
- constants.__dict__.keys() if i.startswith('SHOW_'))
+ constants.__dict__.keys() if i.startswith('SHOW_'))
from common import helpers
import dialogs
@@ -106,557 +106,555 @@ import sqlite3 as sqlite
class HistoryManager:
- def __init__(self):
- pix = gtkgui_helpers.get_icon_pixmap('gajim')
- gtk.window_set_default_icon(pix) # set the icon to all newly opened windows
-
- if not os.path.exists(LOG_DB_PATH):
- dialogs.ErrorDialog(_('Cannot find history logs database'),
- '%s does not exist.' % LOG_DB_PATH)
- sys.exit()
-
- xml = gtkgui_helpers.get_gtk_builder('history_manager.ui')
- self.window = xml.get_object('history_manager_window')
- self.jids_listview = xml.get_object('jids_listview')
- self.logs_listview = xml.get_object('logs_listview')
- self.search_results_listview = xml.get_object('search_results_listview')
- self.search_entry = xml.get_object('search_entry')
- self.logs_scrolledwindow = xml.get_object('logs_scrolledwindow')
- self.search_results_scrolledwindow = xml.get_object(
- 'search_results_scrolledwindow')
- self.welcome_vbox = xml.get_object('welcome_vbox')
-
- self.jids_already_in = [] # holds jids that we already have in DB
- self.AT_LEAST_ONE_DELETION_DONE = False
-
- self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
- isolation_level = 'IMMEDIATE')
- self.cur = self.con.cursor()
-
- self._init_jids_listview()
- self._init_logs_listview()
- self._init_search_results_listview()
-
- self._fill_jids_listview()
-
- self.search_entry.grab_focus()
-
- self.window.show_all()
-
- xml.connect_signals(self)
-
- def _init_jids_listview(self):
- self.jids_liststore = gtk.ListStore(str, str) # jid, jid_id
- self.jids_listview.set_model(self.jids_liststore)
- self.jids_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
-
- renderer_text = gtk.CellRendererText() # holds jid
- col = gtk.TreeViewColumn(_('Contacts'), renderer_text, text = 0)
- self.jids_listview.append_column(col)
-
- self.jids_listview.get_selection().connect('changed',
- self.on_jids_listview_selection_changed)
-
- def _init_logs_listview(self):
- # log_line_id (HIDDEN), jid_id (HIDDEN), time, message, subject, nickname
- self.logs_liststore = gtk.ListStore(str, str, str, str, str, str)
- self.logs_listview.set_model(self.logs_liststore)
- self.logs_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
-
- renderer_text = gtk.CellRendererText() # holds time
- col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME)
- col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
- col.set_resizable(True)
- self.logs_listview.append_column(col)
-
- renderer_text = gtk.CellRendererText() # holds nickname
- col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME)
- col.set_sort_column_id(C_NICKNAME) # user can click this header and sort
- col.set_resizable(True)
- col.set_visible(False)
- self.nickname_col_for_logs = col
- self.logs_listview.append_column(col)
-
- renderer_text = gtk.CellRendererText() # holds message
- col = gtk.TreeViewColumn(_('Message'), renderer_text, markup = C_MESSAGE)
- col.set_sort_column_id(C_MESSAGE) # user can click this header and sort
- col.set_resizable(True)
- self.message_col_for_logs = col
- self.logs_listview.append_column(col)
-
- renderer_text = gtk.CellRendererText() # holds subject
- col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT)
- col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
- col.set_resizable(True)
- col.set_visible(False)
- self.subject_col_for_logs = col
- self.logs_listview.append_column(col)
-
- def _init_search_results_listview(self):
- # log_line_id (HIDDEN), jid, time, message, subject, nickname
- self.search_results_liststore = gtk.ListStore(str, str, str, str, str, str)
- self.search_results_listview.set_model(self.search_results_liststore)
-
- renderer_text = gtk.CellRendererText() # holds JID (who said this)
- col = gtk.TreeViewColumn(_('JID'), renderer_text, text = 1)
- col.set_sort_column_id(1) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = gtk.CellRendererText() # holds time
- col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME)
- col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = gtk.CellRendererText() # holds message
- col = gtk.TreeViewColumn(_('Message'), renderer_text, text = C_MESSAGE)
- col.set_sort_column_id(C_MESSAGE) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = gtk.CellRendererText() # holds subject
- col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT)
- col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = gtk.CellRendererText() # holds nickname
- col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME)
- col.set_sort_column_id(C_NICKNAME) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- def on_history_manager_window_delete_event(self, widget, event):
- if self.AT_LEAST_ONE_DELETION_DONE:
- def on_yes(clicked):
- self.cur.execute('VACUUM')
- self.con.commit()
- gtk.main_quit()
-
- def on_no():
- gtk.main_quit()
-
- dialogs.YesNoDialog(
- _('Do you want to clean up the database? '
- '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'),
- _('Normally allocated database size will not be freed, '
- 'it will just become reusable. If you really want to reduce '
- 'database filesize, click YES, else click NO.'
- '\n\nIn case you click YES, please wait...'),
- on_response_yes=on_yes, on_response_no=on_no)
- return
-
- gtk.main_quit()
-
- def _fill_jids_listview(self):
- # get those jids that have at least one entry in logs
- self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN (SELECT '
- 'distinct logs.jid_id FROM logs) ORDER BY jid')
- rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
- for row in rows:
- self.jids_already_in.append(row[0]) # jid
- self.jids_liststore.append(row) # jid, jid_id
-
- def on_jids_listview_selection_changed(self, widget, data = None):
- liststore, list_of_paths = self.jids_listview.get_selection()\
- .get_selected_rows()
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- self.logs_liststore.clear() # clear the store
-
- self.welcome_vbox.hide()
- self.search_results_scrolledwindow.hide()
- self.logs_scrolledwindow.show()
-
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
-
- for rowref in list_of_rowrefs: # FILL THE STORE, for all rows selected
- path = rowref.get_path()
- if path is None:
- continue
- jid = liststore[path][0] # jid
- self._fill_logs_listview(jid)
-
- def _get_jid_id(self, jid):
- """
- jids table has jid and jid_id
- logs table has log_id, jid_id, contact_name, time, kind, show, message
-
- So to ask logs we need jid_id that matches our jid in jids table this
- method wants jid and returns the jid_id for later sql-ing on logs
- """
- if jid.find('/') != -1: # if it has a /
- jid_is_from_pm = self._jid_is_from_pm(jid)
- if not jid_is_from_pm: # it's normal jid with resource
- jid = jid.split('/', 1)[0] # remove the resource
- self.cur.execute('SELECT jid_id FROM jids WHERE jid = ?', (jid,))
- jid_id = self.cur.fetchone()[0]
- return str(jid_id)
-
- def _get_jid_from_jid_id(self, jid_id):
- """
- jids table has jid and jid_id
-
- This method accepts jid_id and returns the jid for later sql-ing on logs
- """
- self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,))
- jid = self.cur.fetchone()[0]
- return jid
-
- def _jid_is_from_pm(self, jid):
- """
- If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
- is not a normal guy and nkour is not his resource? We ask if gajim@conf
- is already in jids (with type room jid). This fails if user disables
- logging for room and only enables for pm (so higly unlikely) and if we
- fail we do not go chaos (user will see the first pm as if it was message
- in room's public chat) and after that everything is ok
- """
- possible_room_jid = jid.split('/', 1)[0]
-
- self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?',
- (possible_room_jid, constants.JID_ROOM_TYPE))
- row = self.cur.fetchone()
- if row is None:
- return False
- else:
- return True
-
- def _jid_is_room_type(self, jid):
- """
- Return True/False if given id is room type or not eg. if it is room
- """
- self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,))
- row = self.cur.fetchone()
- if row is None:
- raise
- elif row[0] == constants.JID_ROOM_TYPE:
- return True
- else: # normal type
- return False
-
- def _fill_logs_listview(self, jid):
- """
- Fill the listview with all messages that user sent to or received from
- JID
- """
- # no need to lower jid in this context as jid is already lowered
- # as we use those jids from db
- jid_id = self._get_jid_id(jid)
- self.cur.execute('''
- SELECT log_line_id, jid_id, time, kind, message, subject, contact_name, show
- FROM logs
- WHERE jid_id = ?
- ORDER BY time
- ''', (jid_id,))
-
- results = self.cur.fetchall()
-
- if self._jid_is_room_type(jid): # is it room?
- self.nickname_col_for_logs.set_visible(True)
- self.subject_col_for_logs.set_visible(False)
- else:
- self.nickname_col_for_logs.set_visible(False)
- self.subject_col_for_logs.set_visible(True)
-
- for row in results:
- # exposed in UI (TreeViewColumns) are only
- # time, message, subject, nickname
- # but store in liststore
- # log_line_id, jid_id, time, message, subject, nickname
- log_line_id, jid_id, time_, kind, message, subject, nickname, show = row
- try:
- time_ = time.strftime('%x', time.localtime(float(time_))).decode(
- locale.getpreferredencoding())
- except ValueError:
- pass
- else:
- color = None
- if kind in (constants.KIND_SINGLE_MSG_RECV,
- constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG):
- # it is the other side
- color = gajim.config.get('inmsgcolor') # so incoming color
- elif kind in (constants.KIND_SINGLE_MSG_SENT,
- constants.KIND_CHAT_MSG_SENT): # it is us
- color = gajim.config.get('outmsgcolor') # so outgoing color
- elif kind in (constants.KIND_STATUS,
- constants.KIND_GCSTATUS): # is is statuses
- color = gajim.config.get('statusmsgcolor') # so status color
- # include status into (status) message
- if message is None:
- message = ''
- else:
- message = ' : ' + message
- message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message
-
- message_ = '<span'
- if color:
- message_ += ' foreground="%s"' % color
- message_ += '>%s</span>' % \
- gobject.markup_escape_text(message)
- self.logs_liststore.append((log_line_id, jid_id, time_, message_,
- subject, nickname))
-
- def _fill_search_results_listview(self, text):
- """
- Ask db and fill listview with results that match text
- """
- self.search_results_liststore.clear()
- like_sql = '%' + text + '%'
- self.cur.execute('''
- SELECT log_line_id, jid_id, time, message, subject, contact_name
- FROM logs
- WHERE message LIKE ? OR subject LIKE ?
- ORDER BY time
- ''', (like_sql, like_sql))
-
- results = self.cur.fetchall()
- for row in results:
- # exposed in UI (TreeViewColumns) are only
- # JID, time, message, subject, nickname
- # but store in liststore
- # log_line_id, jid (from jid_id), time, message, subject, nickname
- log_line_id, jid_id, time_, message, subject, nickname = row
- try:
- time_ = time.strftime('%x', time.localtime(float(time_))).decode(
- locale.getpreferredencoding())
- except ValueError:
- pass
- else:
- jid = self._get_jid_from_jid_id(jid_id)
-
- self.search_results_liststore.append((log_line_id, jid, time_,
- message, subject, nickname))
-
- def on_logs_listview_key_press_event(self, widget, event):
- liststore, list_of_paths = self.logs_listview.get_selection()\
- .get_selected_rows()
- if event.keyval == gtk.keysyms.Delete:
- self._delete_logs(liststore, list_of_paths)
-
- def on_listview_button_press_event(self, widget, event):
- if event.button == 3: # right click
- xml = gtkgui_helpers.get_gtk_builder('history_manager.ui', 'context_menu')
- if widget.name != 'jids_listview':
- xml.get_object('export_menuitem').hide()
- xml.get_object('delete_menuitem').connect('activate',
- self.on_delete_menuitem_activate, widget)
-
- xml.connect_signals(self)
- xml.get_object('context_menu').popup(None, None, None,
- event.button, event.time)
- return True
-
- def on_export_menuitem_activate(self, widget):
- xml = gtkgui_helpers.get_gtk_builder('history_manager.ui', 'filechooserdialog')
- xml.connect_signals(self)
-
- dlg = xml.get_object('filechooserdialog')
- dlg.set_title(_('Exporting History Logs...'))
- dlg.set_current_folder(gajim.HOME_DIR)
- dlg.props.do_overwrite_confirmation = True
- response = dlg.run()
-
- if response == gtk.RESPONSE_OK: # user want us to export ;)
- liststore, list_of_paths = self.jids_listview.get_selection()\
- .get_selected_rows()
- path_to_file = dlg.get_filename()
- self._export_jids_logs_to_file(liststore, list_of_paths, path_to_file)
-
- dlg.destroy()
-
- def on_delete_menuitem_activate(self, widget, listview):
- liststore, list_of_paths = listview.get_selection().get_selected_rows()
- if listview.name == 'jids_listview':
- self._delete_jid_logs(liststore, list_of_paths)
- elif listview.name in ('logs_listview', 'search_results_listview'):
- self._delete_logs(liststore, list_of_paths)
- else: # Huh ? We don't know this widget
- return
-
- def on_jids_listview_key_press_event(self, widget, event):
- liststore, list_of_paths = self.jids_listview.get_selection()\
- .get_selected_rows()
- if event.keyval == gtk.keysyms.Delete:
- self._delete_jid_logs(liststore, list_of_paths)
-
- def _export_jids_logs_to_file(self, liststore, list_of_paths, path_to_file):
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
-
- for rowref in list_of_rowrefs:
- path = rowref.get_path()
- if path is None:
- continue
- jid_id = liststore[path][1]
- self.cur.execute('''
- SELECT time, kind, message, contact_name FROM logs
- WHERE jid_id = ?
- ORDER BY time
- ''', (jid_id,))
-
- # FIXME: we may have two contacts selected to export. fix that
- # AT THIS TIME FIRST EXECUTE IS LOST! WTH!!!!!
- results = self.cur.fetchall()
- #print results[0]
- file_ = open(path_to_file, 'w')
- for row in results:
- # in store: time, kind, message, contact_name FROM logs
- # in text: JID or You or nickname (if it's gc_msg), time, message
- time_, kind, message, nickname = row
- if kind in (constants.KIND_SINGLE_MSG_RECV,
- constants.KIND_CHAT_MSG_RECV):
- who = self._get_jid_from_jid_id(jid_id)
- elif kind in (constants.KIND_SINGLE_MSG_SENT,
- constants.KIND_CHAT_MSG_SENT):
- who = _('You')
- elif kind == constants.KIND_GC_MSG:
- who = nickname
- else: # status or gc_status. do not save
- #print kind
- continue
-
- try:
- time_ = time.strftime('%c', time.localtime(float(time_))).decode(
- locale.getpreferredencoding())
- except ValueError:
- pass
-
- file_.write(_('%(who)s on %(time)s said: %(message)s\n') % {'who': who,
- 'time': time_, 'message': message})
-
- def _delete_jid_logs(self, liststore, list_of_paths):
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- def on_ok(liststore, list_of_paths):
- # delete all rows from db that match jid_id
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
-
- for rowref in list_of_rowrefs:
- path = rowref.get_path()
- if path is None:
- continue
- jid_id = liststore[path][1]
- del liststore[path] # remove from UI
- # remove from db
- self.cur.execute('''
- DELETE FROM logs
- WHERE jid_id = ?
- ''', (jid_id,))
-
- # now delete "jid, jid_id" row from jids table
- self.cur.execute('''
- DELETE FROM jids
- WHERE jid_id = ?
- ''', (jid_id,))
-
- self.con.commit()
-
- self.AT_LEAST_ONE_DELETION_DONE = True
-
- pri_text = i18n.ngettext(
- 'Do you really want to delete logs of the selected contact?',
- 'Do you really want to delete logs of the selected contacts?',
- paths_len)
- dialogs.ConfirmationDialog(pri_text,
- _('This is an irreversible operation.'), on_response_ok = (on_ok,
- liststore, list_of_paths))
-
- def _delete_logs(self, liststore, list_of_paths):
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- def on_ok(liststore, list_of_paths):
- # delete rows from db that match log_line_id
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
-
- for rowref in list_of_rowrefs:
- path = rowref.get_path()
- if path is None:
- continue
- log_line_id = liststore[path][0]
- del liststore[path] # remove from UI
- # remove from db
- self.cur.execute('''
- DELETE FROM logs
- WHERE log_line_id = ?
- ''', (log_line_id,))
-
- self.con.commit()
-
- self.AT_LEAST_ONE_DELETION_DONE = True
-
-
- pri_text = i18n.ngettext(
- 'Do you really want to delete the selected message?',
- 'Do you really want to delete the selected messages?', paths_len)
- dialogs.ConfirmationDialog(pri_text,
- _('This is an irreversible operation.'), on_response_ok = (on_ok,
- liststore, list_of_paths))
-
- def on_search_db_button_clicked(self, widget):
- text = self.search_entry.get_text().decode('utf-8')
- if not text:
- return
-
- self.welcome_vbox.hide()
- self.logs_scrolledwindow.hide()
- self.search_results_scrolledwindow.show()
-
- self._fill_search_results_listview(text)
-
- def on_search_results_listview_row_activated(self, widget, path, column):
- # get log_line_id, jid_id from row we double clicked
- log_line_id = self.search_results_liststore[path][0]
- jid = self.search_results_liststore[path][1]
- # make it string as in gtk liststores I have them all as strings
- # as this is what db returns so I don't have to fight with types
- jid_id = self._get_jid_id(jid)
-
-
- iter_ = self.jids_liststore.get_iter_root()
- while iter_:
- # self.jids_liststore[iter_][1] holds jid_ids
- if self.jids_liststore[iter_][1] == jid_id:
- break
- iter_ = self.jids_liststore.iter_next(iter_)
-
- if iter_ is None:
- return
-
- path = self.jids_liststore.get_path(iter_)
- self.jids_listview.set_cursor(path)
-
- iter_ = self.logs_liststore.get_iter_root()
- while iter_:
- # self.logs_liststore[iter_][0] holds lon_line_ids
- if self.logs_liststore[iter_][0] == log_line_id:
- break
- iter_ = self.logs_liststore.iter_next(iter_)
-
- path = self.logs_liststore.get_path(iter_)
- self.logs_listview.scroll_to_cell(path)
+ def __init__(self):
+ pix = gtkgui_helpers.get_icon_pixmap('gajim')
+ gtk.window_set_default_icon(pix) # set the icon to all newly opened windows
+
+ if not os.path.exists(LOG_DB_PATH):
+ dialogs.ErrorDialog(_('Cannot find history logs database'),
+ '%s does not exist.' % LOG_DB_PATH)
+ sys.exit()
+
+ xml = gtkgui_helpers.get_gtk_builder('history_manager.ui')
+ self.window = xml.get_object('history_manager_window')
+ self.jids_listview = xml.get_object('jids_listview')
+ self.logs_listview = xml.get_object('logs_listview')
+ self.search_results_listview = xml.get_object('search_results_listview')
+ self.search_entry = xml.get_object('search_entry')
+ self.logs_scrolledwindow = xml.get_object('logs_scrolledwindow')
+ self.search_results_scrolledwindow = xml.get_object(
+ 'search_results_scrolledwindow')
+ self.welcome_vbox = xml.get_object('welcome_vbox')
+
+ self.jids_already_in = [] # holds jids that we already have in DB
+ self.AT_LEAST_ONE_DELETION_DONE = False
+
+ self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
+ isolation_level = 'IMMEDIATE')
+ self.cur = self.con.cursor()
+
+ self._init_jids_listview()
+ self._init_logs_listview()
+ self._init_search_results_listview()
+
+ self._fill_jids_listview()
+
+ self.search_entry.grab_focus()
+
+ self.window.show_all()
+
+ xml.connect_signals(self)
+
+ def _init_jids_listview(self):
+ self.jids_liststore = gtk.ListStore(str, str) # jid, jid_id
+ self.jids_listview.set_model(self.jids_liststore)
+ self.jids_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+
+ renderer_text = gtk.CellRendererText() # holds jid
+ col = gtk.TreeViewColumn(_('Contacts'), renderer_text, text = 0)
+ self.jids_listview.append_column(col)
+
+ self.jids_listview.get_selection().connect('changed',
+ self.on_jids_listview_selection_changed)
+
+ def _init_logs_listview(self):
+ # log_line_id (HIDDEN), jid_id (HIDDEN), time, message, subject, nickname
+ self.logs_liststore = gtk.ListStore(str, str, str, str, str, str)
+ self.logs_listview.set_model(self.logs_liststore)
+ self.logs_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+
+ renderer_text = gtk.CellRendererText() # holds time
+ col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME)
+ col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
+ col.set_resizable(True)
+ self.logs_listview.append_column(col)
+
+ renderer_text = gtk.CellRendererText() # holds nickname
+ col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME)
+ col.set_sort_column_id(C_NICKNAME) # user can click this header and sort
+ col.set_resizable(True)
+ col.set_visible(False)
+ self.nickname_col_for_logs = col
+ self.logs_listview.append_column(col)
+
+ renderer_text = gtk.CellRendererText() # holds message
+ col = gtk.TreeViewColumn(_('Message'), renderer_text, markup = C_MESSAGE)
+ col.set_sort_column_id(C_MESSAGE) # user can click this header and sort
+ col.set_resizable(True)
+ self.message_col_for_logs = col
+ self.logs_listview.append_column(col)
+
+ renderer_text = gtk.CellRendererText() # holds subject
+ col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT)
+ col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
+ col.set_resizable(True)
+ col.set_visible(False)
+ self.subject_col_for_logs = col
+ self.logs_listview.append_column(col)
+
+ def _init_search_results_listview(self):
+ # log_line_id (HIDDEN), jid, time, message, subject, nickname
+ self.search_results_liststore = gtk.ListStore(str, str, str, str, str, str)
+ self.search_results_listview.set_model(self.search_results_liststore)
+
+ renderer_text = gtk.CellRendererText() # holds JID (who said this)
+ col = gtk.TreeViewColumn(_('JID'), renderer_text, text = 1)
+ col.set_sort_column_id(1) # user can click this header and sort
+ col.set_resizable(True)
+ self.search_results_listview.append_column(col)
+
+ renderer_text = gtk.CellRendererText() # holds time
+ col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME)
+ col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
+ col.set_resizable(True)
+ self.search_results_listview.append_column(col)
+
+ renderer_text = gtk.CellRendererText() # holds message
+ col = gtk.TreeViewColumn(_('Message'), renderer_text, text = C_MESSAGE)
+ col.set_sort_column_id(C_MESSAGE) # user can click this header and sort
+ col.set_resizable(True)
+ self.search_results_listview.append_column(col)
+
+ renderer_text = gtk.CellRendererText() # holds subject
+ col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT)
+ col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
+ col.set_resizable(True)
+ self.search_results_listview.append_column(col)
+
+ renderer_text = gtk.CellRendererText() # holds nickname
+ col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME)
+ col.set_sort_column_id(C_NICKNAME) # user can click this header and sort
+ col.set_resizable(True)
+ self.search_results_listview.append_column(col)
+
+ def on_history_manager_window_delete_event(self, widget, event):
+ if self.AT_LEAST_ONE_DELETION_DONE:
+ def on_yes(clicked):
+ self.cur.execute('VACUUM')
+ self.con.commit()
+ gtk.main_quit()
+
+ def on_no():
+ gtk.main_quit()
+
+ dialogs.YesNoDialog(
+ _('Do you want to clean up the database? '
+ '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'),
+ _('Normally allocated database size will not be freed, '
+ 'it will just become reusable. If you really want to reduce '
+ 'database filesize, click YES, else click NO.'
+ '\n\nIn case you click YES, please wait...'),
+ on_response_yes=on_yes, on_response_no=on_no)
+ return
+
+ gtk.main_quit()
+
+ def _fill_jids_listview(self):
+ # get those jids that have at least one entry in logs
+ self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN (SELECT '
+ 'distinct logs.jid_id FROM logs) ORDER BY jid')
+ rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
+ for row in rows:
+ self.jids_already_in.append(row[0]) # jid
+ self.jids_liststore.append(row) # jid, jid_id
+
+ def on_jids_listview_selection_changed(self, widget, data = None):
+ liststore, list_of_paths = self.jids_listview.get_selection()\
+ .get_selected_rows()
+ paths_len = len(list_of_paths)
+ if paths_len == 0: # nothing is selected
+ return
+
+ self.logs_liststore.clear() # clear the store
+
+ self.welcome_vbox.hide()
+ self.search_results_scrolledwindow.hide()
+ self.logs_scrolledwindow.show()
+
+ list_of_rowrefs = []
+ for path in list_of_paths: # make them treerowrefs (it's needed)
+ list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
+
+ for rowref in list_of_rowrefs: # FILL THE STORE, for all rows selected
+ path = rowref.get_path()
+ if path is None:
+ continue
+ jid = liststore[path][0] # jid
+ self._fill_logs_listview(jid)
+
+ def _get_jid_id(self, jid):
+ """
+ jids table has jid and jid_id
+ logs table has log_id, jid_id, contact_name, time, kind, show, message
+
+ So to ask logs we need jid_id that matches our jid in jids table this
+ method wants jid and returns the jid_id for later sql-ing on logs
+ """
+ if jid.find('/') != -1: # if it has a /
+ jid_is_from_pm = self._jid_is_from_pm(jid)
+ if not jid_is_from_pm: # it's normal jid with resource
+ jid = jid.split('/', 1)[0] # remove the resource
+ self.cur.execute('SELECT jid_id FROM jids WHERE jid = ?', (jid,))
+ jid_id = self.cur.fetchone()[0]
+ return str(jid_id)
+
+ def _get_jid_from_jid_id(self, jid_id):
+ """
+ jids table has jid and jid_id
+
+ This method accepts jid_id and returns the jid for later sql-ing on logs
+ """
+ self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,))
+ jid = self.cur.fetchone()[0]
+ return jid
+
+ def _jid_is_from_pm(self, jid):
+ """
+ If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
+ is not a normal guy and nkour is not his resource? We ask if gajim@conf
+ is already in jids (with type room jid). This fails if user disables
+ logging for room and only enables for pm (so higly unlikely) and if we
+ fail we do not go chaos (user will see the first pm as if it was message
+ in room's public chat) and after that everything is ok
+ """
+ possible_room_jid = jid.split('/', 1)[0]
+
+ self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?',
+ (possible_room_jid, constants.JID_ROOM_TYPE))
+ row = self.cur.fetchone()
+ if row is None:
+ return False
+ else:
+ return True
+
+ def _jid_is_room_type(self, jid):
+ """
+ Return True/False if given id is room type or not eg. if it is room
+ """
+ self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,))
+ row = self.cur.fetchone()
+ if row is None:
+ raise
+ elif row[0] == constants.JID_ROOM_TYPE:
+ return True
+ else: # normal type
+ return False
+
+ def _fill_logs_listview(self, jid):
+ """
+ Fill the listview with all messages that user sent to or received from
+ JID
+ """
+ # no need to lower jid in this context as jid is already lowered
+ # as we use those jids from db
+ jid_id = self._get_jid_id(jid)
+ self.cur.execute('''
+ SELECT log_line_id, jid_id, time, kind, message, subject, contact_name, show
+ FROM logs
+ WHERE jid_id = ?
+ ORDER BY time
+ ''', (jid_id,))
+
+ results = self.cur.fetchall()
+
+ if self._jid_is_room_type(jid): # is it room?
+ self.nickname_col_for_logs.set_visible(True)
+ self.subject_col_for_logs.set_visible(False)
+ else:
+ self.nickname_col_for_logs.set_visible(False)
+ self.subject_col_for_logs.set_visible(True)
+
+ for row in results:
+ # exposed in UI (TreeViewColumns) are only
+ # time, message, subject, nickname
+ # but store in liststore
+ # log_line_id, jid_id, time, message, subject, nickname
+ log_line_id, jid_id, time_, kind, message, subject, nickname, show = row
+ try:
+ time_ = time.strftime('%x', time.localtime(float(time_))).decode(
+ locale.getpreferredencoding())
+ except ValueError:
+ pass
+ else:
+ color = None
+ if kind in (constants.KIND_SINGLE_MSG_RECV,
+ constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG):
+ # it is the other side
+ color = gajim.config.get('inmsgcolor') # so incoming color
+ elif kind in (constants.KIND_SINGLE_MSG_SENT,
+ constants.KIND_CHAT_MSG_SENT): # it is us
+ color = gajim.config.get('outmsgcolor') # so outgoing color
+ elif kind in (constants.KIND_STATUS,
+ constants.KIND_GCSTATUS): # is is statuses
+ color = gajim.config.get('statusmsgcolor') # so status color
+ # include status into (status) message
+ if message is None:
+ message = ''
+ else:
+ message = ' : ' + message
+ message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message
+
+ message_ = '<span'
+ if color:
+ message_ += ' foreground="%s"' % color
+ message_ += '>%s</span>' % \
+ gobject.markup_escape_text(message)
+ self.logs_liststore.append((log_line_id, jid_id, time_, message_,
+ subject, nickname))
+
+ def _fill_search_results_listview(self, text):
+ """
+ Ask db and fill listview with results that match text
+ """
+ self.search_results_liststore.clear()
+ like_sql = '%' + text + '%'
+ self.cur.execute('''
+ SELECT log_line_id, jid_id, time, message, subject, contact_name
+ FROM logs
+ WHERE message LIKE ? OR subject LIKE ?
+ ORDER BY time
+ ''', (like_sql, like_sql))
+
+ results = self.cur.fetchall()
+ for row in results:
+ # exposed in UI (TreeViewColumns) are only
+ # JID, time, message, subject, nickname
+ # but store in liststore
+ # log_line_id, jid (from jid_id), time, message, subject, nickname
+ log_line_id, jid_id, time_, message, subject, nickname = row
+ try:
+ time_ = time.strftime('%x', time.localtime(float(time_))).decode(
+ locale.getpreferredencoding())
+ except ValueError:
+ pass
+ else:
+ jid = self._get_jid_from_jid_id(jid_id)
+
+ self.search_results_liststore.append((log_line_id, jid, time_,
+ message, subject, nickname))
+
+ def on_logs_listview_key_press_event(self, widget, event):
+ liststore, list_of_paths = self.logs_listview.get_selection()\
+ .get_selected_rows()
+ if event.keyval == gtk.keysyms.Delete:
+ self._delete_logs(liststore, list_of_paths)
+
+ def on_listview_button_press_event(self, widget, event):
+ if event.button == 3: # right click
+ xml = gtkgui_helpers.get_gtk_builder('history_manager.ui', 'context_menu')
+ if widget.name != 'jids_listview':
+ xml.get_object('export_menuitem').hide()
+ xml.get_object('delete_menuitem').connect('activate',
+ self.on_delete_menuitem_activate, widget)
+
+ xml.connect_signals(self)
+ xml.get_object('context_menu').popup(None, None, None,
+ event.button, event.time)
+ return True
+
+ def on_export_menuitem_activate(self, widget):
+ xml = gtkgui_helpers.get_gtk_builder('history_manager.ui', 'filechooserdialog')
+ xml.connect_signals(self)
+
+ dlg = xml.get_object('filechooserdialog')
+ dlg.set_title(_('Exporting History Logs...'))
+ dlg.set_current_folder(gajim.HOME_DIR)
+ dlg.props.do_overwrite_confirmation = True
+ response = dlg.run()
+
+ if response == gtk.RESPONSE_OK: # user want us to export ;)
+ liststore, list_of_paths = self.jids_listview.get_selection()\
+ .get_selected_rows()
+ path_to_file = dlg.get_filename()
+ self._export_jids_logs_to_file(liststore, list_of_paths, path_to_file)
+
+ dlg.destroy()
+
+ def on_delete_menuitem_activate(self, widget, listview):
+ liststore, list_of_paths = listview.get_selection().get_selected_rows()
+ if listview.name == 'jids_listview':
+ self._delete_jid_logs(liststore, list_of_paths)
+ elif listview.name in ('logs_listview', 'search_results_listview'):
+ self._delete_logs(liststore, list_of_paths)
+ else: # Huh ? We don't know this widget
+ return
+
+ def on_jids_listview_key_press_event(self, widget, event):
+ liststore, list_of_paths = self.jids_listview.get_selection()\
+ .get_selected_rows()
+ if event.keyval == gtk.keysyms.Delete:
+ self._delete_jid_logs(liststore, list_of_paths)
+
+ def _export_jids_logs_to_file(self, liststore, list_of_paths, path_to_file):
+ paths_len = len(list_of_paths)
+ if paths_len == 0: # nothing is selected
+ return
+
+ list_of_rowrefs = []
+ for path in list_of_paths: # make them treerowrefs (it's needed)
+ list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
+
+ for rowref in list_of_rowrefs:
+ path = rowref.get_path()
+ if path is None:
+ continue
+ jid_id = liststore[path][1]
+ self.cur.execute('''
+ SELECT time, kind, message, contact_name FROM logs
+ WHERE jid_id = ?
+ ORDER BY time
+ ''', (jid_id,))
+
+ # FIXME: we may have two contacts selected to export. fix that
+ # AT THIS TIME FIRST EXECUTE IS LOST! WTH!!!!!
+ results = self.cur.fetchall()
+ #print results[0]
+ file_ = open(path_to_file, 'w')
+ for row in results:
+ # in store: time, kind, message, contact_name FROM logs
+ # in text: JID or You or nickname (if it's gc_msg), time, message
+ time_, kind, message, nickname = row
+ if kind in (constants.KIND_SINGLE_MSG_RECV,
+ constants.KIND_CHAT_MSG_RECV):
+ who = self._get_jid_from_jid_id(jid_id)
+ elif kind in (constants.KIND_SINGLE_MSG_SENT,
+ constants.KIND_CHAT_MSG_SENT):
+ who = _('You')
+ elif kind == constants.KIND_GC_MSG:
+ who = nickname
+ else: # status or gc_status. do not save
+ #print kind
+ continue
+
+ try:
+ time_ = time.strftime('%c', time.localtime(float(time_))).decode(
+ locale.getpreferredencoding())
+ except ValueError:
+ pass
+
+ file_.write(_('%(who)s on %(time)s said: %(message)s\n') % {'who': who,
+ 'time': time_, 'message': message})
+
+ def _delete_jid_logs(self, liststore, list_of_paths):
+ paths_len = len(list_of_paths)
+ if paths_len == 0: # nothing is selected
+ return
+
+ def on_ok(liststore, list_of_paths):
+ # delete all rows from db that match jid_id
+ list_of_rowrefs = []
+ for path in list_of_paths: # make them treerowrefs (it's needed)
+ list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
+
+ for rowref in list_of_rowrefs:
+ path = rowref.get_path()
+ if path is None:
+ continue
+ jid_id = liststore[path][1]
+ del liststore[path] # remove from UI
+ # remove from db
+ self.cur.execute('''
+ DELETE FROM logs
+ WHERE jid_id = ?
+ ''', (jid_id,))
+
+ # now delete "jid, jid_id" row from jids table
+ self.cur.execute('''
+ DELETE FROM jids
+ WHERE jid_id = ?
+ ''', (jid_id,))
+
+ self.con.commit()
+
+ self.AT_LEAST_ONE_DELETION_DONE = True
+
+ pri_text = i18n.ngettext(
+ 'Do you really want to delete logs of the selected contact?',
+ 'Do you really want to delete logs of the selected contacts?',
+ paths_len)
+ dialogs.ConfirmationDialog(pri_text,
+ _('This is an irreversible operation.'), on_response_ok = (on_ok,
+ liststore, list_of_paths))
+
+ def _delete_logs(self, liststore, list_of_paths):
+ paths_len = len(list_of_paths)
+ if paths_len == 0: # nothing is selected
+ return
+
+ def on_ok(liststore, list_of_paths):
+ # delete rows from db that match log_line_id
+ list_of_rowrefs = []
+ for path in list_of_paths: # make them treerowrefs (it's needed)
+ list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
+
+ for rowref in list_of_rowrefs:
+ path = rowref.get_path()
+ if path is None:
+ continue
+ log_line_id = liststore[path][0]
+ del liststore[path] # remove from UI
+ # remove from db
+ self.cur.execute('''
+ DELETE FROM logs
+ WHERE log_line_id = ?
+ ''', (log_line_id,))
+
+ self.con.commit()
+
+ self.AT_LEAST_ONE_DELETION_DONE = True
+
+
+ pri_text = i18n.ngettext(
+ 'Do you really want to delete the selected message?',
+ 'Do you really want to delete the selected messages?', paths_len)
+ dialogs.ConfirmationDialog(pri_text,
+ _('This is an irreversible operation.'), on_response_ok = (on_ok,
+ liststore, list_of_paths))
+
+ def on_search_db_button_clicked(self, widget):
+ text = self.search_entry.get_text().decode('utf-8')
+ if not text:
+ return
+
+ self.welcome_vbox.hide()
+ self.logs_scrolledwindow.hide()
+ self.search_results_scrolledwindow.show()
+
+ self._fill_search_results_listview(text)
+
+ def on_search_results_listview_row_activated(self, widget, path, column):
+ # get log_line_id, jid_id from row we double clicked
+ log_line_id = self.search_results_liststore[path][0]
+ jid = self.search_results_liststore[path][1]
+ # make it string as in gtk liststores I have them all as strings
+ # as this is what db returns so I don't have to fight with types
+ jid_id = self._get_jid_id(jid)
+
+
+ iter_ = self.jids_liststore.get_iter_root()
+ while iter_:
+ # self.jids_liststore[iter_][1] holds jid_ids
+ if self.jids_liststore[iter_][1] == jid_id:
+ break
+ iter_ = self.jids_liststore.iter_next(iter_)
+
+ if iter_ is None:
+ return
+
+ path = self.jids_liststore.get_path(iter_)
+ self.jids_listview.set_cursor(path)
+
+ iter_ = self.logs_liststore.get_iter_root()
+ while iter_:
+ # self.logs_liststore[iter_][0] holds lon_line_ids
+ if self.logs_liststore[iter_][0] == log_line_id:
+ break
+ iter_ = self.logs_liststore.iter_next(iter_)
+
+ path = self.logs_liststore.get_path(iter_)
+ self.logs_listview.scroll_to_cell(path)
if __name__ == '__main__':
- signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
- HistoryManager()
- gtk.main()
-
-# vim: se ts=3:
+ signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
+ HistoryManager()
+ gtk.main()
diff --git a/src/history_window.py b/src/history_window.py
index 0713336f4..261fc372e 100644
--- a/src/history_window.py
+++ b/src/history_window.py
@@ -43,602 +43,600 @@ constants = Constants()
# Completion dict
(
- C_INFO_JID,
- C_INFO_ACCOUNT,
- C_INFO_NAME,
- C_INFO_COMPLETION
+ C_INFO_JID,
+ C_INFO_ACCOUNT,
+ C_INFO_NAME,
+ C_INFO_COMPLETION
) = range(4)
# contact_name, date, message, time
(
- C_LOG_JID,
- C_CONTACT_NAME,
- C_UNIXTIME,
- C_MESSAGE,
- C_TIME
+ C_LOG_JID,
+ C_CONTACT_NAME,
+ C_UNIXTIME,
+ C_MESSAGE,
+ C_TIME
) = range(5)
class HistoryWindow:
- """
- Class for browsing logs of conversations with contacts
- """
-
- def __init__(self, jid = None, account = None):
- xml = gtkgui_helpers.get_gtk_builder('history_window.ui')
- self.window = xml.get_object('history_window')
- self.calendar = xml.get_object('calendar')
- scrolledwindow = xml.get_object('scrolledwindow')
- self.history_textview = conversation_textview.ConversationTextview(
- account, used_in_history_window = True)
- scrolledwindow.add(self.history_textview.tv)
- self.history_buffer = self.history_textview.tv.get_buffer()
- self.history_buffer.create_tag('highlight', background = 'yellow')
- self.checkbutton = xml.get_object('log_history_checkbutton')
- self.checkbutton.connect('toggled',
- self.on_log_history_checkbutton_toggled)
- self.query_entry = xml.get_object('query_entry')
- self.query_combobox = xml.get_object('query_combobox')
- self.jid_entry = self.query_combobox.child
- self.jid_entry.connect('activate', self.on_jid_entry_activate)
- self.query_combobox.set_active(0)
- self.results_treeview = xml.get_object('results_treeview')
- self.results_window = xml.get_object('results_scrolledwindow')
-
- # contact_name, date, message, time
- model = gtk.ListStore(str, str, str, str, str)
- self.results_treeview.set_model(model)
- col = gtk.TreeViewColumn(_('Name'))
- self.results_treeview.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = C_CONTACT_NAME)
- col.set_sort_column_id(C_CONTACT_NAME) # user can click this header and sort
- col.set_resizable(True)
-
- col = gtk.TreeViewColumn(_('Date'))
- self.results_treeview.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = C_UNIXTIME)
- col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
- col.set_resizable(True)
-
- col = gtk.TreeViewColumn(_('Message'))
- self.results_treeview.append_column(col)
- renderer = gtk.CellRendererText()
- col.pack_start(renderer)
- col.set_attributes(renderer, text = C_MESSAGE)
- col.set_resizable(True)
-
- self.jid = None # The history we are currently viewing
- self.account = None
- self.completion_dict = {}
- self.accounts_seen_online = [] # Update dict when new accounts connect
- self.jids_to_search = []
-
- # This will load history too
- gobject.idle_add(self._fill_completion_dict().next)
-
- if jid:
- self.jid_entry.set_text(jid)
- else:
- self._load_history(None)
-
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('history_window_width'),
- gajim.config.get('history_window_height'))
- gtkgui_helpers.move_window(self.window,
- gajim.config.get('history_window_x-position'),
- gajim.config.get('history_window_y-position'))
-
- xml.connect_signals(self)
- self.window.show_all()
-
- def _fill_completion_dict(self):
- """
- Fill completion_dict for key auto completion. Then load history for
- current jid (by calling another function)
-
- Key will be either jid or full_completion_name (contact name or long
- description like "pm-contact from groupchat....").
-
- {key : (jid, account, nick_name, full_completion_name}
- This is a generator and does pseudo-threading via idle_add().
- """
- liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry)
-
- # Add all jids in logs.db:
- db_jids = gajim.logger.get_jids_in_db()
- completion_dict = dict.fromkeys(db_jids)
-
- self.accounts_seen_online = gajim.contacts.get_accounts()[:]
-
- # Enhance contacts of online accounts with contact. Needed for mapping below
- for account in self.accounts_seen_online:
- completion_dict.update(helpers.get_contact_dict_for_account(account))
-
- muc_active_img = gtkgui_helpers.load_icon('muc_active')
- contact_img = gajim.interface.jabber_state_images['16']['online']
- muc_active_pix = muc_active_img.get_pixbuf()
- contact_pix = contact_img.get_pixbuf()
-
- keys = completion_dict.keys()
- # Move the actual jid at first so we load history faster
- actual_jid = self.jid_entry.get_text().decode('utf-8')
- if actual_jid in keys:
- keys.remove(actual_jid)
- keys.insert(0, actual_jid)
- if None in keys:
- keys.remove(None)
- # Map jid to info tuple
- # Warning : This for is time critical with big DB
- for key in keys:
- completed = key
- contact = completion_dict[completed]
- if contact:
- info_name = contact.get_shown_name()
- info_completion = info_name
- info_jid = contact.jid
- else:
- # Corrensponding account is offline, we know nothing
- info_name = completed.split('@')[0]
- info_completion = completed
- info_jid = completed
-
- info_acc = self._get_account_for_jid(info_jid)
-
- if gajim.logger.jid_is_room_jid(completed) or\
- gajim.logger.jid_is_from_pm(completed):
- pix = muc_active_pix
- if gajim.logger.jid_is_from_pm(completed):
- # It's PM. Make it easier to find
- room, nick = gajim.get_room_and_nick_from_fjid(completed)
- info_completion = '%s from %s' % (nick, room)
- completed = info_completion
- info_name = nick
- else:
- pix = contact_pix
-
- liststore.append((pix, completed))
- self.completion_dict[key] = (info_jid, info_acc, info_name,
- info_completion)
- self.completion_dict[completed] = (info_jid, info_acc,
- info_name, info_completion)
- if key == actual_jid:
- self._load_history(info_jid, info_acc)
- yield True
- keys.sort()
- yield False
-
- def _get_account_for_jid(self, jid):
- """
- Return the corresponding account of the jid. May be None if an account
- could not be found
- """
- accounts = gajim.contacts.get_accounts()
- account = None
- for acc in accounts:
- jid_list = gajim.contacts.get_jid_list(acc)
- gc_list = gajim.contacts.get_gc_list(acc)
- if jid in jid_list or jid in gc_list:
- account = acc
- break
- return account
-
- def on_history_window_destroy(self, widget):
- self.history_textview.del_handlers()
- del gajim.interface.instances['logs']
-
- def on_history_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.save_state()
- self.window.destroy()
-
- def on_close_button_clicked(self, widget):
- self.save_state()
- self.window.destroy()
-
- def on_jid_entry_activate(self, widget):
- if not self.query_combobox.get_active() < 0:
- # Don't disable querybox when we have changed the combobox
- # to GC or All and hit enter
- return
- jid = self.jid_entry.get_text().decode('utf-8')
- account = None # we don't know the account, could be any. Search for it!
- self._load_history(jid, account)
- self.results_window.set_property('visible', False)
-
- def on_jid_entry_focus(self, widget, event):
- widget.select_region(0, -1) # select text
-
- def _load_history(self, jid_or_name, account=None):
- """
- Load history for the given jid/name and show it
- """
- if jid_or_name and jid_or_name in self.completion_dict:
- # a full qualified jid or a contact name was entered
- info_jid, info_account, info_name, info_completion = self.completion_dict[jid_or_name]
- self.jids_to_search = [info_jid]
- self.jid = info_jid
-
- if account:
- self.account = account
- else:
- self.account = info_account
- if self.account is None:
- # We don't know account. Probably a gc not opened or an
- # account not connected.
- # Disable possibility to say if we want to log or not
- self.checkbutton.set_sensitive(False)
- else:
- # Are log disabled for account ?
- if self.account in gajim.config.get_per('accounts', self.account,
- 'no_log_for').split(' '):
- self.checkbutton.set_active(False)
- self.checkbutton.set_sensitive(False)
- else:
- # Are log disabled for jid ?
- log = True
- if self.jid in gajim.config.get_per('accounts', self.account,
- 'no_log_for').split(' '):
- log = False
- self.checkbutton.set_active(log)
- self.checkbutton.set_sensitive(True)
-
- self.jids_to_search = [info_jid]
-
- # select logs for last date we have logs with contact
- self.calendar.set_sensitive(True)
- last_log = \
- gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
-
- date = time.localtime(last_log)
-
- y, m, d = date[0], date[1], date[2]
- gtk_month = gtkgui_helpers.make_python_month_gtk_month(m)
- self.calendar.select_month(gtk_month, y)
- self.calendar.select_day(d)
-
- self.query_entry.set_sensitive(True)
- self.query_entry.grab_focus()
-
- title = _('Conversation History with %s') % info_name
- self.window.set_title(title)
- self.jid_entry.set_text(info_completion)
-
- else: # neither a valid jid, nor an existing contact name was entered
- # we have got nothing to show or to search in
- self.jid = None
- self.account = None
-
- self.history_buffer.set_text('') # clear the buffer
- self.query_entry.set_sensitive(False)
-
- self.checkbutton.set_sensitive(False)
- self.calendar.set_sensitive(False)
- self.calendar.clear_marks()
-
- self.results_window.set_property('visible', False)
-
- title = _('Conversation History')
- self.window.set_title(title)
-
- def on_calendar_day_selected(self, widget):
- if not self.jid:
- return
- year, month, day = widget.get_date() # integers
- month = gtkgui_helpers.make_gtk_month_python_month(month)
- self._add_lines_for_date(year, month, day)
-
- def on_calendar_month_changed(self, widget):
- """
- Ask for days in this month, if they have logs it bolds them (marks them)
- """
- if not self.jid:
- return
- year, month, day = widget.get_date() # integers
- # in gtk January is 1, in python January is 0,
- # I want the second
- # first day of month is 1 not 0
- widget.clear_marks()
- month = gtkgui_helpers.make_gtk_month_python_month(month)
- days_in_this_month = calendar.monthrange(year, month)[1]
- try:
- log_days = gajim.logger.get_days_with_logs(self.jid, year, month,
- days_in_this_month, self.account)
- except exceptions.PysqliteOperationalError, e:
- dialogs.ErrorDialog(_('Disk Error'), str(e))
- return
- for day in log_days:
- widget.mark_day(day)
-
- def _get_string_show_from_constant_int(self, show):
- if show == constants.SHOW_ONLINE:
- show = 'online'
- elif show == constants.SHOW_CHAT:
- show = 'chat'
- elif show == constants.SHOW_AWAY:
- show = 'away'
- elif show == constants.SHOW_XA:
- show = 'xa'
- elif show == constants.SHOW_DND:
- show = 'dnd'
- elif show == constants.SHOW_OFFLINE:
- show = 'offline'
-
- return show
-
- def _add_lines_for_date(self, year, month, day):
- """
- Add all the lines for given date in textbuffer
- """
- self.history_buffer.set_text('') # clear the buffer first
- self.last_time_printout = 0
-
- lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account)
- # lines holds list with tupples that have:
- # contact_name, time, kind, show, message
- for line in lines:
- # line[0] is contact_name, line[1] is time of message
- # line[2] is kind, line[3] is show, line[4] is message
- self._add_new_line(line[0], line[1], line[2], line[3], line[4],
- line[5])
-
- def _add_new_line(self, contact_name, tim, kind, show, message, subject):
- """
- Add a new line in textbuffer
- """
- if not message and kind not in (constants.KIND_STATUS,
- constants.KIND_GCSTATUS):
- return
- buf = self.history_buffer
- end_iter = buf.get_end_iter()
-
- if gajim.config.get('print_time') == 'always':
- timestamp_str = gajim.config.get('time_stamp')
- timestamp_str = helpers.from_one_line(timestamp_str)
- tim = time.strftime(timestamp_str, time.localtime(float(tim)))
- buf.insert(end_iter, tim) # add time
- elif gajim.config.get('print_time') == 'sometimes':
- every_foo_seconds = 60 * gajim.config.get(
- 'print_ichat_every_foo_minutes')
- seconds_passed = tim - self.last_time_printout
- if seconds_passed > every_foo_seconds:
- self.last_time_printout = tim
- tim = time.strftime('%X ', time.localtime(float(tim)))
- buf.insert_with_tags_by_name(end_iter, tim + '\n',
- 'time_sometimes')
-
- tag_name = ''
- tag_msg = ''
-
- show = self._get_string_show_from_constant_int(show)
-
- if kind == constants.KIND_GC_MSG:
- tag_name = 'incoming'
- elif kind in (constants.KIND_SINGLE_MSG_RECV,
- constants.KIND_CHAT_MSG_RECV):
- contact_name = self.completion_dict[self.jid][C_INFO_NAME]
- tag_name = 'incoming'
- tag_msg = 'incomingtxt'
- elif kind in (constants.KIND_SINGLE_MSG_SENT,
- constants.KIND_CHAT_MSG_SENT):
- if self.account:
- contact_name = gajim.nicks[self.account]
- else:
- # we don't have roster, we don't know our own nick, use first
- # account one (urk!)
- account = gajim.contacts.get_accounts()[0]
- contact_name = gajim.nicks[account]
- tag_name = 'outgoing'
- tag_msg = 'outgoingtxt'
- elif kind == constants.KIND_GCSTATUS:
- # message here (if not None) is status message
- if message:
- message = _('%(nick)s is now %(status)s: %(status_msg)s') %\
- {'nick': contact_name, 'status': helpers.get_uf_show(show),
- 'status_msg': message }
- else:
- message = _('%(nick)s is now %(status)s') % {'nick': contact_name,
- 'status': helpers.get_uf_show(show) }
- tag_msg = 'status'
- else: # 'status'
- # message here (if not None) is status message
- if show is None: # it means error
- if message:
- message = _('Error: %s') % message
- else:
- message = _('Error')
- elif message:
- message = _('Status is now: %(status)s: %(status_msg)s') % \
- {'status': helpers.get_uf_show(show), 'status_msg': message}
- else:
- message = _('Status is now: %(status)s') % { 'status':
- helpers.get_uf_show(show) }
- tag_msg = 'status'
-
- if message.startswith('/me ') or message.startswith('/me\n'):
- tag_msg = tag_name
- else:
- # do not do this if gcstats, avoid dupping contact_name
- # eg. nkour: nkour is now Offline
- if contact_name and kind != constants.KIND_GCSTATUS:
- # add stuff before and after contact name
- before_str = gajim.config.get('before_nickname')
- before_str = helpers.from_one_line(before_str)
- after_str = gajim.config.get('after_nickname')
- after_str = helpers.from_one_line(after_str)
- format = before_str + contact_name + after_str + ' '
- buf.insert_with_tags_by_name(end_iter, format, tag_name)
-
- if subject:
- message = _('Subject: %s\n') % subject + message
- message += '\n'
- if tag_msg:
- self.history_textview.print_real_text(message, [tag_msg],
- name=contact_name)
- else:
- self.history_textview.print_real_text(message, name=contact_name)
-
- def on_query_entry_activate(self, widget):
- text = self.query_entry.get_text()
- model = self.results_treeview.get_model()
- model.clear()
- if text == '':
- self.results_window.set_property('visible', False)
- return
- else:
- self.results_window.set_property('visible', True)
-
- # perform search in preselected jids
- # jids are preselected with the query_combobox (all, single jid...)
- for jid in self.jids_to_search:
- account = self.completion_dict[jid][C_INFO_ACCOUNT]
- if account is None:
- # We do not know an account. This can only happen if the contact is offine,
- # or if we browse a groupchat history. The account is not needed, a dummy can
- # be set.
- # This may leed to wrong self nick in the displayed history (Uggh!)
- account = gajim.contacts.get_accounts()[0]
-
- # contact_name, time, kind, show, message, subject
- results = gajim.logger.get_search_results_for_query(
- jid, text, account)
- #FIXME:
- # add "subject: | message: " in message column if kind is single
- # also do we need show at all? (we do not search on subject)
- for row in results:
- contact_name = row[0]
- if not contact_name:
- kind = row[2]
- if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :)
- contact_name = gajim.nicks[account]
- else:
- contact_name = self.completion_dict[jid][C_INFO_NAME]
- tim = row[1]
- message = row[4]
- local_time = time.localtime(tim)
- date = time.strftime('%Y-%m-%d', local_time)
-
- # jid (to which log is assigned to), name, date, message,
- # time (full unix time)
- model.append((jid, contact_name, date, message, tim))
-
- def on_query_combobox_changed(self, widget):
- if self.query_combobox.get_active() < 0:
- return # custom entry
- self.account = None
- self.jid = None
- self.jids_to_search = []
- self._load_history(None) # clear textview
-
- if self.query_combobox.get_active() == 0:
- # JID or Contact name
- self.query_entry.set_sensitive(False)
- self.jid_entry.grab_focus()
- if self.query_combobox.get_active() == 1:
- # Groupchat Histories
- self.query_entry.set_sensitive(True)
- self.query_entry.grab_focus()
- self.jids_to_search = (jid for jid in gajim.logger.get_jids_in_db()
- if gajim.logger.jid_is_room_jid(jid))
- if self.query_combobox.get_active() == 2:
- # All Chat Histories
- self.query_entry.set_sensitive(True)
- self.query_entry.grab_focus()
- self.jids_to_search = gajim.logger.get_jids_in_db()
-
- def on_results_treeview_row_activated(self, widget, path, column):
- """
- A row was double clicked, get date from row, and select it in calendar
- which results to showing conversation logs for that date
- """
- # get currently selected date
- cur_year, cur_month = self.calendar.get_date()[0:2]
- cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month)
- model = widget.get_model()
- # make it a tupple (Y, M, D, 0, 0, 0...)
- tim = time.strptime(model[path][C_UNIXTIME], '%Y-%m-%d')
- year = tim[0]
- gtk_month = tim[1]
- month = gtkgui_helpers.make_python_month_gtk_month(gtk_month)
- day = tim[2]
-
- # switch to belonging logfile if necessary
- log_jid = model[path][C_LOG_JID]
- if log_jid != self.jid:
- self._load_history(log_jid, None)
-
- # avoid reruning mark days algo if same month and year!
- if year != cur_year or gtk_month != cur_month:
- self.calendar.select_month(month, year)
-
- self.calendar.select_day(day)
- unix_time = model[path][C_TIME]
- self._scroll_to_result(unix_time)
- #FIXME: one day do not search just for unix_time but the whole and user
- # specific format of the textbuffer line [time] nick: message
- # and highlight all that
-
- def _scroll_to_result(self, unix_time):
- """
- Scroll to the result using unix_time and highlight line
- """
- start_iter = self.history_buffer.get_start_iter()
- local_time = time.localtime(float(unix_time))
- tim = time.strftime('%X', local_time)
- result = start_iter.forward_search(tim, gtk.TEXT_SEARCH_VISIBLE_ONLY,
- None)
- if result is not None:
- match_start_iter, match_end_iter = result
- match_start_iter.backward_char() # include '[' or other character before time
- match_end_iter.forward_line() # highlight all message not just time
- self.history_buffer.apply_tag_by_name('highlight', match_start_iter,
- match_end_iter)
-
- match_start_mark = self.history_buffer.create_mark('match_start',
- match_start_iter, True)
- self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True)
-
- def on_log_history_checkbutton_toggled(self, widget):
- # log conversation history?
- oldlog = True
- no_log_for = gajim.config.get_per('accounts', self.account,
- 'no_log_for').split()
- if self.jid in no_log_for:
- oldlog = False
- log = widget.get_active()
- if not log and not self.jid in no_log_for:
- no_log_for.append(self.jid)
- if log and self.jid in no_log_for:
- no_log_for.remove(self.jid)
- if oldlog != log:
- gajim.config.set_per('accounts', self.account, 'no_log_for',
- ' '.join(no_log_for))
-
- def open_history(self, jid, account):
- """
- Load chat history of the specified jid
- """
- self.jid_entry.set_text(jid)
- if account and account not in self.accounts_seen_online:
- # Update dict to not only show bare jid
- gobject.idle_add(self._fill_completion_dict().next)
- else:
- # Only in that case because it's called by self._fill_completion_dict()
- # otherwise
- self._load_history(jid, account)
- self.results_window.set_property('visible', False)
-
- def save_state(self):
- x,y = self.window.window.get_root_origin()
- width, height = self.window.get_size()
-
- gajim.config.set('history_window_x-position', x)
- gajim.config.set('history_window_y-position', y)
- gajim.config.set('history_window_width', width);
- gajim.config.set('history_window_height', height);
-
- gajim.interface.save_config()
-
-# vim: se ts=3:
+ """
+ Class for browsing logs of conversations with contacts
+ """
+
+ def __init__(self, jid = None, account = None):
+ xml = gtkgui_helpers.get_gtk_builder('history_window.ui')
+ self.window = xml.get_object('history_window')
+ self.calendar = xml.get_object('calendar')
+ scrolledwindow = xml.get_object('scrolledwindow')
+ self.history_textview = conversation_textview.ConversationTextview(
+ account, used_in_history_window = True)
+ scrolledwindow.add(self.history_textview.tv)
+ self.history_buffer = self.history_textview.tv.get_buffer()
+ self.history_buffer.create_tag('highlight', background = 'yellow')
+ self.checkbutton = xml.get_object('log_history_checkbutton')
+ self.checkbutton.connect('toggled',
+ self.on_log_history_checkbutton_toggled)
+ self.query_entry = xml.get_object('query_entry')
+ self.query_combobox = xml.get_object('query_combobox')
+ self.jid_entry = self.query_combobox.child
+ self.jid_entry.connect('activate', self.on_jid_entry_activate)
+ self.query_combobox.set_active(0)
+ self.results_treeview = xml.get_object('results_treeview')
+ self.results_window = xml.get_object('results_scrolledwindow')
+
+ # contact_name, date, message, time
+ model = gtk.ListStore(str, str, str, str, str)
+ self.results_treeview.set_model(model)
+ col = gtk.TreeViewColumn(_('Name'))
+ self.results_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = C_CONTACT_NAME)
+ col.set_sort_column_id(C_CONTACT_NAME) # user can click this header and sort
+ col.set_resizable(True)
+
+ col = gtk.TreeViewColumn(_('Date'))
+ self.results_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = C_UNIXTIME)
+ col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
+ col.set_resizable(True)
+
+ col = gtk.TreeViewColumn(_('Message'))
+ self.results_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer)
+ col.set_attributes(renderer, text = C_MESSAGE)
+ col.set_resizable(True)
+
+ self.jid = None # The history we are currently viewing
+ self.account = None
+ self.completion_dict = {}
+ self.accounts_seen_online = [] # Update dict when new accounts connect
+ self.jids_to_search = []
+
+ # This will load history too
+ gobject.idle_add(self._fill_completion_dict().next)
+
+ if jid:
+ self.jid_entry.set_text(jid)
+ else:
+ self._load_history(None)
+
+ gtkgui_helpers.resize_window(self.window,
+ gajim.config.get('history_window_width'),
+ gajim.config.get('history_window_height'))
+ gtkgui_helpers.move_window(self.window,
+ gajim.config.get('history_window_x-position'),
+ gajim.config.get('history_window_y-position'))
+
+ xml.connect_signals(self)
+ self.window.show_all()
+
+ def _fill_completion_dict(self):
+ """
+ Fill completion_dict for key auto completion. Then load history for
+ current jid (by calling another function)
+
+ Key will be either jid or full_completion_name (contact name or long
+ description like "pm-contact from groupchat....").
+
+ {key : (jid, account, nick_name, full_completion_name}
+ This is a generator and does pseudo-threading via idle_add().
+ """
+ liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry)
+
+ # Add all jids in logs.db:
+ db_jids = gajim.logger.get_jids_in_db()
+ completion_dict = dict.fromkeys(db_jids)
+
+ self.accounts_seen_online = gajim.contacts.get_accounts()[:]
+
+ # Enhance contacts of online accounts with contact. Needed for mapping below
+ for account in self.accounts_seen_online:
+ completion_dict.update(helpers.get_contact_dict_for_account(account))
+
+ muc_active_img = gtkgui_helpers.load_icon('muc_active')
+ contact_img = gajim.interface.jabber_state_images['16']['online']
+ muc_active_pix = muc_active_img.get_pixbuf()
+ contact_pix = contact_img.get_pixbuf()
+
+ keys = completion_dict.keys()
+ # Move the actual jid at first so we load history faster
+ actual_jid = self.jid_entry.get_text().decode('utf-8')
+ if actual_jid in keys:
+ keys.remove(actual_jid)
+ keys.insert(0, actual_jid)
+ if None in keys:
+ keys.remove(None)
+ # Map jid to info tuple
+ # Warning : This for is time critical with big DB
+ for key in keys:
+ completed = key
+ contact = completion_dict[completed]
+ if contact:
+ info_name = contact.get_shown_name()
+ info_completion = info_name
+ info_jid = contact.jid
+ else:
+ # Corrensponding account is offline, we know nothing
+ info_name = completed.split('@')[0]
+ info_completion = completed
+ info_jid = completed
+
+ info_acc = self._get_account_for_jid(info_jid)
+
+ if gajim.logger.jid_is_room_jid(completed) or\
+ gajim.logger.jid_is_from_pm(completed):
+ pix = muc_active_pix
+ if gajim.logger.jid_is_from_pm(completed):
+ # It's PM. Make it easier to find
+ room, nick = gajim.get_room_and_nick_from_fjid(completed)
+ info_completion = '%s from %s' % (nick, room)
+ completed = info_completion
+ info_name = nick
+ else:
+ pix = contact_pix
+
+ liststore.append((pix, completed))
+ self.completion_dict[key] = (info_jid, info_acc, info_name,
+ info_completion)
+ self.completion_dict[completed] = (info_jid, info_acc,
+ info_name, info_completion)
+ if key == actual_jid:
+ self._load_history(info_jid, info_acc)
+ yield True
+ keys.sort()
+ yield False
+
+ def _get_account_for_jid(self, jid):
+ """
+ Return the corresponding account of the jid. May be None if an account
+ could not be found
+ """
+ accounts = gajim.contacts.get_accounts()
+ account = None
+ for acc in accounts:
+ jid_list = gajim.contacts.get_jid_list(acc)
+ gc_list = gajim.contacts.get_gc_list(acc)
+ if jid in jid_list or jid in gc_list:
+ account = acc
+ break
+ return account
+
+ def on_history_window_destroy(self, widget):
+ self.history_textview.del_handlers()
+ del gajim.interface.instances['logs']
+
+ def on_history_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.save_state()
+ self.window.destroy()
+
+ def on_close_button_clicked(self, widget):
+ self.save_state()
+ self.window.destroy()
+
+ def on_jid_entry_activate(self, widget):
+ if not self.query_combobox.get_active() < 0:
+ # Don't disable querybox when we have changed the combobox
+ # to GC or All and hit enter
+ return
+ jid = self.jid_entry.get_text().decode('utf-8')
+ account = None # we don't know the account, could be any. Search for it!
+ self._load_history(jid, account)
+ self.results_window.set_property('visible', False)
+
+ def on_jid_entry_focus(self, widget, event):
+ widget.select_region(0, -1) # select text
+
+ def _load_history(self, jid_or_name, account=None):
+ """
+ Load history for the given jid/name and show it
+ """
+ if jid_or_name and jid_or_name in self.completion_dict:
+ # a full qualified jid or a contact name was entered
+ info_jid, info_account, info_name, info_completion = self.completion_dict[jid_or_name]
+ self.jids_to_search = [info_jid]
+ self.jid = info_jid
+
+ if account:
+ self.account = account
+ else:
+ self.account = info_account
+ if self.account is None:
+ # We don't know account. Probably a gc not opened or an
+ # account not connected.
+ # Disable possibility to say if we want to log or not
+ self.checkbutton.set_sensitive(False)
+ else:
+ # Are log disabled for account ?
+ if self.account in gajim.config.get_per('accounts', self.account,
+ 'no_log_for').split(' '):
+ self.checkbutton.set_active(False)
+ self.checkbutton.set_sensitive(False)
+ else:
+ # Are log disabled for jid ?
+ log = True
+ if self.jid in gajim.config.get_per('accounts', self.account,
+ 'no_log_for').split(' '):
+ log = False
+ self.checkbutton.set_active(log)
+ self.checkbutton.set_sensitive(True)
+
+ self.jids_to_search = [info_jid]
+
+ # select logs for last date we have logs with contact
+ self.calendar.set_sensitive(True)
+ last_log = \
+ gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
+
+ date = time.localtime(last_log)
+
+ y, m, d = date[0], date[1], date[2]
+ gtk_month = gtkgui_helpers.make_python_month_gtk_month(m)
+ self.calendar.select_month(gtk_month, y)
+ self.calendar.select_day(d)
+
+ self.query_entry.set_sensitive(True)
+ self.query_entry.grab_focus()
+
+ title = _('Conversation History with %s') % info_name
+ self.window.set_title(title)
+ self.jid_entry.set_text(info_completion)
+
+ else: # neither a valid jid, nor an existing contact name was entered
+ # we have got nothing to show or to search in
+ self.jid = None
+ self.account = None
+
+ self.history_buffer.set_text('') # clear the buffer
+ self.query_entry.set_sensitive(False)
+
+ self.checkbutton.set_sensitive(False)
+ self.calendar.set_sensitive(False)
+ self.calendar.clear_marks()
+
+ self.results_window.set_property('visible', False)
+
+ title = _('Conversation History')
+ self.window.set_title(title)
+
+ def on_calendar_day_selected(self, widget):
+ if not self.jid:
+ return
+ year, month, day = widget.get_date() # integers
+ month = gtkgui_helpers.make_gtk_month_python_month(month)
+ self._add_lines_for_date(year, month, day)
+
+ def on_calendar_month_changed(self, widget):
+ """
+ Ask for days in this month, if they have logs it bolds them (marks them)
+ """
+ if not self.jid:
+ return
+ year, month, day = widget.get_date() # integers
+ # in gtk January is 1, in python January is 0,
+ # I want the second
+ # first day of month is 1 not 0
+ widget.clear_marks()
+ month = gtkgui_helpers.make_gtk_month_python_month(month)
+ days_in_this_month = calendar.monthrange(year, month)[1]
+ try:
+ log_days = gajim.logger.get_days_with_logs(self.jid, year, month,
+ days_in_this_month, self.account)
+ except exceptions.PysqliteOperationalError, e:
+ dialogs.ErrorDialog(_('Disk Error'), str(e))
+ return
+ for day in log_days:
+ widget.mark_day(day)
+
+ def _get_string_show_from_constant_int(self, show):
+ if show == constants.SHOW_ONLINE:
+ show = 'online'
+ elif show == constants.SHOW_CHAT:
+ show = 'chat'
+ elif show == constants.SHOW_AWAY:
+ show = 'away'
+ elif show == constants.SHOW_XA:
+ show = 'xa'
+ elif show == constants.SHOW_DND:
+ show = 'dnd'
+ elif show == constants.SHOW_OFFLINE:
+ show = 'offline'
+
+ return show
+
+ def _add_lines_for_date(self, year, month, day):
+ """
+ Add all the lines for given date in textbuffer
+ """
+ self.history_buffer.set_text('') # clear the buffer first
+ self.last_time_printout = 0
+
+ lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account)
+ # lines holds list with tupples that have:
+ # contact_name, time, kind, show, message
+ for line in lines:
+ # line[0] is contact_name, line[1] is time of message
+ # line[2] is kind, line[3] is show, line[4] is message
+ self._add_new_line(line[0], line[1], line[2], line[3], line[4],
+ line[5])
+
+ def _add_new_line(self, contact_name, tim, kind, show, message, subject):
+ """
+ Add a new line in textbuffer
+ """
+ if not message and kind not in (constants.KIND_STATUS,
+ constants.KIND_GCSTATUS):
+ return
+ buf = self.history_buffer
+ end_iter = buf.get_end_iter()
+
+ if gajim.config.get('print_time') == 'always':
+ timestamp_str = gajim.config.get('time_stamp')
+ timestamp_str = helpers.from_one_line(timestamp_str)
+ tim = time.strftime(timestamp_str, time.localtime(float(tim)))
+ buf.insert(end_iter, tim) # add time
+ elif gajim.config.get('print_time') == 'sometimes':
+ every_foo_seconds = 60 * gajim.config.get(
+ 'print_ichat_every_foo_minutes')
+ seconds_passed = tim - self.last_time_printout
+ if seconds_passed > every_foo_seconds:
+ self.last_time_printout = tim
+ tim = time.strftime('%X ', time.localtime(float(tim)))
+ buf.insert_with_tags_by_name(end_iter, tim + '\n',
+ 'time_sometimes')
+
+ tag_name = ''
+ tag_msg = ''
+
+ show = self._get_string_show_from_constant_int(show)
+
+ if kind == constants.KIND_GC_MSG:
+ tag_name = 'incoming'
+ elif kind in (constants.KIND_SINGLE_MSG_RECV,
+ constants.KIND_CHAT_MSG_RECV):
+ contact_name = self.completion_dict[self.jid][C_INFO_NAME]
+ tag_name = 'incoming'
+ tag_msg = 'incomingtxt'
+ elif kind in (constants.KIND_SINGLE_MSG_SENT,
+ constants.KIND_CHAT_MSG_SENT):
+ if self.account:
+ contact_name = gajim.nicks[self.account]
+ else:
+ # we don't have roster, we don't know our own nick, use first
+ # account one (urk!)
+ account = gajim.contacts.get_accounts()[0]
+ contact_name = gajim.nicks[account]
+ tag_name = 'outgoing'
+ tag_msg = 'outgoingtxt'
+ elif kind == constants.KIND_GCSTATUS:
+ # message here (if not None) is status message
+ if message:
+ message = _('%(nick)s is now %(status)s: %(status_msg)s') %\
+ {'nick': contact_name, 'status': helpers.get_uf_show(show),
+ 'status_msg': message }
+ else:
+ message = _('%(nick)s is now %(status)s') % {'nick': contact_name,
+ 'status': helpers.get_uf_show(show) }
+ tag_msg = 'status'
+ else: # 'status'
+ # message here (if not None) is status message
+ if show is None: # it means error
+ if message:
+ message = _('Error: %s') % message
+ else:
+ message = _('Error')
+ elif message:
+ message = _('Status is now: %(status)s: %(status_msg)s') % \
+ {'status': helpers.get_uf_show(show), 'status_msg': message}
+ else:
+ message = _('Status is now: %(status)s') % { 'status':
+ helpers.get_uf_show(show) }
+ tag_msg = 'status'
+
+ if message.startswith('/me ') or message.startswith('/me\n'):
+ tag_msg = tag_name
+ else:
+ # do not do this if gcstats, avoid dupping contact_name
+ # eg. nkour: nkour is now Offline
+ if contact_name and kind != constants.KIND_GCSTATUS:
+ # add stuff before and after contact name
+ before_str = gajim.config.get('before_nickname')
+ before_str = helpers.from_one_line(before_str)
+ after_str = gajim.config.get('after_nickname')
+ after_str = helpers.from_one_line(after_str)
+ format = before_str + contact_name + after_str + ' '
+ buf.insert_with_tags_by_name(end_iter, format, tag_name)
+
+ if subject:
+ message = _('Subject: %s\n') % subject + message
+ message += '\n'
+ if tag_msg:
+ self.history_textview.print_real_text(message, [tag_msg],
+ name=contact_name)
+ else:
+ self.history_textview.print_real_text(message, name=contact_name)
+
+ def on_query_entry_activate(self, widget):
+ text = self.query_entry.get_text()
+ model = self.results_treeview.get_model()
+ model.clear()
+ if text == '':
+ self.results_window.set_property('visible', False)
+ return
+ else:
+ self.results_window.set_property('visible', True)
+
+ # perform search in preselected jids
+ # jids are preselected with the query_combobox (all, single jid...)
+ for jid in self.jids_to_search:
+ account = self.completion_dict[jid][C_INFO_ACCOUNT]
+ if account is None:
+ # We do not know an account. This can only happen if the contact is offine,
+ # or if we browse a groupchat history. The account is not needed, a dummy can
+ # be set.
+ # This may leed to wrong self nick in the displayed history (Uggh!)
+ account = gajim.contacts.get_accounts()[0]
+
+ # contact_name, time, kind, show, message, subject
+ results = gajim.logger.get_search_results_for_query(
+ jid, text, account)
+ #FIXME:
+ # add "subject: | message: " in message column if kind is single
+ # also do we need show at all? (we do not search on subject)
+ for row in results:
+ contact_name = row[0]
+ if not contact_name:
+ kind = row[2]
+ if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :)
+ contact_name = gajim.nicks[account]
+ else:
+ contact_name = self.completion_dict[jid][C_INFO_NAME]
+ tim = row[1]
+ message = row[4]
+ local_time = time.localtime(tim)
+ date = time.strftime('%Y-%m-%d', local_time)
+
+ # jid (to which log is assigned to), name, date, message,
+ # time (full unix time)
+ model.append((jid, contact_name, date, message, tim))
+
+ def on_query_combobox_changed(self, widget):
+ if self.query_combobox.get_active() < 0:
+ return # custom entry
+ self.account = None
+ self.jid = None
+ self.jids_to_search = []
+ self._load_history(None) # clear textview
+
+ if self.query_combobox.get_active() == 0:
+ # JID or Contact name
+ self.query_entry.set_sensitive(False)
+ self.jid_entry.grab_focus()
+ if self.query_combobox.get_active() == 1:
+ # Groupchat Histories
+ self.query_entry.set_sensitive(True)
+ self.query_entry.grab_focus()
+ self.jids_to_search = (jid for jid in gajim.logger.get_jids_in_db()
+ if gajim.logger.jid_is_room_jid(jid))
+ if self.query_combobox.get_active() == 2:
+ # All Chat Histories
+ self.query_entry.set_sensitive(True)
+ self.query_entry.grab_focus()
+ self.jids_to_search = gajim.logger.get_jids_in_db()
+
+ def on_results_treeview_row_activated(self, widget, path, column):
+ """
+ A row was double clicked, get date from row, and select it in calendar
+ which results to showing conversation logs for that date
+ """
+ # get currently selected date
+ cur_year, cur_month = self.calendar.get_date()[0:2]
+ cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month)
+ model = widget.get_model()
+ # make it a tupple (Y, M, D, 0, 0, 0...)
+ tim = time.strptime(model[path][C_UNIXTIME], '%Y-%m-%d')
+ year = tim[0]
+ gtk_month = tim[1]
+ month = gtkgui_helpers.make_python_month_gtk_month(gtk_month)
+ day = tim[2]
+
+ # switch to belonging logfile if necessary
+ log_jid = model[path][C_LOG_JID]
+ if log_jid != self.jid:
+ self._load_history(log_jid, None)
+
+ # avoid reruning mark days algo if same month and year!
+ if year != cur_year or gtk_month != cur_month:
+ self.calendar.select_month(month, year)
+
+ self.calendar.select_day(day)
+ unix_time = model[path][C_TIME]
+ self._scroll_to_result(unix_time)
+ #FIXME: one day do not search just for unix_time but the whole and user
+ # specific format of the textbuffer line [time] nick: message
+ # and highlight all that
+
+ def _scroll_to_result(self, unix_time):
+ """
+ Scroll to the result using unix_time and highlight line
+ """
+ start_iter = self.history_buffer.get_start_iter()
+ local_time = time.localtime(float(unix_time))
+ tim = time.strftime('%X', local_time)
+ result = start_iter.forward_search(tim, gtk.TEXT_SEARCH_VISIBLE_ONLY,
+ None)
+ if result is not None:
+ match_start_iter, match_end_iter = result
+ match_start_iter.backward_char() # include '[' or other character before time
+ match_end_iter.forward_line() # highlight all message not just time
+ self.history_buffer.apply_tag_by_name('highlight', match_start_iter,
+ match_end_iter)
+
+ match_start_mark = self.history_buffer.create_mark('match_start',
+ match_start_iter, True)
+ self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True)
+
+ def on_log_history_checkbutton_toggled(self, widget):
+ # log conversation history?
+ oldlog = True
+ no_log_for = gajim.config.get_per('accounts', self.account,
+ 'no_log_for').split()
+ if self.jid in no_log_for:
+ oldlog = False
+ log = widget.get_active()
+ if not log and not self.jid in no_log_for:
+ no_log_for.append(self.jid)
+ if log and self.jid in no_log_for:
+ no_log_for.remove(self.jid)
+ if oldlog != log:
+ gajim.config.set_per('accounts', self.account, 'no_log_for',
+ ' '.join(no_log_for))
+
+ def open_history(self, jid, account):
+ """
+ Load chat history of the specified jid
+ """
+ self.jid_entry.set_text(jid)
+ if account and account not in self.accounts_seen_online:
+ # Update dict to not only show bare jid
+ gobject.idle_add(self._fill_completion_dict().next)
+ else:
+ # Only in that case because it's called by self._fill_completion_dict()
+ # otherwise
+ self._load_history(jid, account)
+ self.results_window.set_property('visible', False)
+
+ def save_state(self):
+ x,y = self.window.window.get_root_origin()
+ width, height = self.window.get_size()
+
+ gajim.config.set('history_window_x-position', x)
+ gajim.config.set('history_window_y-position', y)
+ gajim.config.set('history_window_width', width);
+ gajim.config.set('history_window_height', height);
+
+ gajim.interface.save_config()
diff --git a/src/htmltextview.py b/src/htmltextview.py
index e91af9462..973336ffc 100644
--- a/src/htmltextview.py
+++ b/src/htmltextview.py
@@ -48,9 +48,9 @@ import urllib2
import operator
if __name__ == '__main__':
- from common import i18n
- import common.configpaths
- common.configpaths.gajimpaths.init(None)
+ from common import i18n
+ import common.configpaths
+ common.configpaths.gajimpaths.init(None)
from common import gajim
import tooltips
@@ -63,26 +63,26 @@ allwhitespace_rx = re.compile('^\\s*$')
# pixels = points * display_resolution
display_resolution = 0.3514598*(gtk.gdk.screen_height() /
- float(gtk.gdk.screen_height_mm()))
+ float(gtk.gdk.screen_height_mm()))
# embryo of CSS classes
classes = {
- #'system-message':';display: none',
- 'problematic':';color: red',
+ #'system-message':';display: none',
+ 'problematic':';color: red',
}
# styles for elements
element_styles = {
- 'u' : ';text-decoration: underline',
- 'em' : ';font-style: oblique',
- 'cite' : '; background-color:rgb(170,190,250); font-style: oblique',
- 'li' : '; margin-left: 1em; margin-right: 10%',
- 'strong' : ';font-weight: bold',
- 'pre' : '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%',
- 'kbd' : ';background-color:rgb(210,210,210);font-family: monospace',
- 'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%',
- 'dt' : ';font-weight: bold; font-style: oblique',
- 'dd' : ';margin-left: 2em; font-style: oblique'
+ 'u' : ';text-decoration: underline',
+ 'em' : ';font-style: oblique',
+ 'cite' : '; background-color:rgb(170,190,250); font-style: oblique',
+ 'li' : '; margin-left: 1em; margin-right: 10%',
+ 'strong' : ';font-weight: bold',
+ 'pre' : '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%',
+ 'kbd' : ';background-color:rgb(210,210,210);font-family: monospace',
+ 'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%',
+ 'dt' : ';font-weight: bold; font-style: oblique',
+ 'dd' : ';margin-left: 2em; font-style: oblique'
}
# no difference for the moment
element_styles['dfn'] = element_styles['em']
@@ -134,18 +134,18 @@ element_styles['b'] = element_styles['strong']
# Inline.mix
# Character-level elements
# InlineNoAnchor.class
-# Anchor element
+# Anchor element
# InlinePre.mix
# Pre element
#
# XHTML-IM also uses the following Attribute Groups:
#
# Core.extra.attrib
-# TBD
+# TBD
# I18n.extra.attrib
-# TBD
+# TBD
# Common.extra
-# style
+# style
#
#
# ...
@@ -178,906 +178,904 @@ INLINE = INLINE_PHRASAL.union(INLINE_PRES).union(INLINE_STRUCT)
LIST_ELEMS = set( 'dl, ol, ul'.split(', '))
for name in BLOCK_HEAD:
- num = eval(name[1])
- size = (num-1) // 2
- weigth = (num - 1) % 2
- element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size],
- ('font-weight: bold', 'font-style: oblique')[weigth],
- )
+ num = eval(name[1])
+ size = (num-1) // 2
+ weigth = (num - 1) % 2
+ element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size],
+ ('font-weight: bold', 'font-style: oblique')[weigth],
+ )
def _parse_css_color(color):
- if color.startswith('rgb(') and color.endswith(')'):
- r, g, b = [int(c)*257 for c in color[4:-1].split(',')]
- return gtk.gdk.Color(r, g, b)
- else:
- return gtk.gdk.color_parse(color)
+ if color.startswith('rgb(') and color.endswith(')'):
+ r, g, b = [int(c)*257 for c in color[4:-1].split(',')]
+ return gtk.gdk.Color(r, g, b)
+ else:
+ return gtk.gdk.color_parse(color)
def style_iter(style):
- return ([x.strip() for x in item.split(':', 1)] for item in style.split(';')\
- if len(item.strip()))
+ return ([x.strip() for x in item.split(':', 1)] for item in style.split(';')\
+ if len(item.strip()))
class HtmlHandler(xml.sax.handler.ContentHandler):
- """
- A handler to display html to a gtk textview
-
- It keeps a stack of "style spans" (start/end element pairs) and a stack of
- list counters, for nested lists.
- """
- def __init__(self, conv_textview, startiter):
- xml.sax.handler.ContentHandler.__init__(self)
- self.textbuf = conv_textview.tv.get_buffer()
- self.textview = conv_textview.tv
- self.iter = startiter
- self.conv_textview = conv_textview
- self.text = ''
- self.starting=True
- self.preserve = False
- self.styles = [] # a gtk.TextTag or None, for each span level
- self.list_counters = [] # stack (top at head) of list
- # counters, or None for unordered list
-
- def _parse_style_color(self, tag, value):
- color = _parse_css_color(value)
- tag.set_property('foreground-gdk', color)
-
- def _parse_style_background_color(self, tag, value):
- color = _parse_css_color(value)
- tag.set_property('background-gdk', color)
- tag.set_property('paragraph-background-gdk', color)
-
-
- def _get_current_attributes(self):
- attrs = self.textview.get_default_attributes()
- self.iter.backward_char()
- self.iter.get_attributes(attrs)
- self.iter.forward_char()
- return attrs
-
- def __parse_length_frac_size_allocate(self, textview, allocation,
- frac, callback, args):
- callback(allocation.width*frac, *args)
-
- def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args):
- """
- Parse/calc length, converting to pixels, calls callback(length, *args)
- when the length is first computed or changes
- """
- if value.endswith('%'):
- val = float(value[:-1])
- sign = cmp(val,0)
- # limits: 1% to 500%
- val = sign*max(1,min(abs(val),500))
- frac = val/100
- if font_relative:
- attrs = self._get_current_attributes()
- font_size = attrs.font.get_size() / pango.SCALE
- callback(frac*display_resolution*font_size, *args)
- elif block_relative:
- # CSS says 'Percentage values: refer to width of the closest
- # block-level ancestor'
- # This is difficult/impossible to implement, so we use
- # textview width instead; a reasonable approximation..
- alloc = self.textview.get_allocation()
- self.__parse_length_frac_size_allocate(self.textview, alloc,
- frac, callback, args)
- self.textview.connect('size-allocate',
- self.__parse_length_frac_size_allocate,
- frac, callback, args)
- else:
- callback(frac, *args)
- return
-
- def get_val():
- val = float(value[:-2])
- sign = cmp(val,0)
- # validate length
- return sign*max(minl,min(abs(val*display_resolution),maxl))
- if value.endswith('pt'): # points
- callback(get_val()*display_resolution, *args)
-
- elif value.endswith('em'): # ems, the width of the element's font
- attrs = self._get_current_attributes()
- font_size = attrs.font.get_size() / pango.SCALE
- callback(get_val()*display_resolution*font_size, *args)
-
- elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
- # FIXME: figure out how to calculate this correctly
- # for now 'em' size is used as approximation
- attrs = self._get_current_attributes()
- font_size = attrs.font.get_size() / pango.SCALE
- callback(get_val()*display_resolution*font_size, *args)
-
- elif value.endswith('px'): # pixels
- callback(get_val(), *args)
-
- else:
- try:
- # TODO: isn't "no units" interpreted as pixels?
- val = int(value)
- sign = cmp(val,0)
- # validate length
- val = sign*max(minl,min(abs(val),maxl))
- callback(val, *args)
- except Exception:
- warnings.warn('Unable to parse length value "%s"' % value)
-
- def __parse_font_size_cb(length, tag):
- tag.set_property('size-points', length/display_resolution)
- __parse_font_size_cb = staticmethod(__parse_font_size_cb)
-
- def _parse_style_display(self, tag, value):
- if value == 'none':
- tag.set_property('invisible','true')
- # FIXME: display: block, inline
-
- def _parse_style_font_size(self, tag, value):
- try:
- scale = {
- 'xx-small': pango.SCALE_XX_SMALL,
- 'x-small': pango.SCALE_X_SMALL,
- 'small': pango.SCALE_SMALL,
- 'medium': pango.SCALE_MEDIUM,
- 'large': pango.SCALE_LARGE,
- 'x-large': pango.SCALE_X_LARGE,
- 'xx-large': pango.SCALE_XX_LARGE,
- } [value]
- except KeyError:
- pass
- else:
- attrs = self._get_current_attributes()
- tag.set_property('scale', scale / attrs.font_scale)
- return
- if value == 'smaller':
- tag.set_property('scale', pango.SCALE_SMALL)
- return
- if value == 'larger':
- tag.set_property('scale', pango.SCALE_LARGE)
- return
- # font relative (5 ~ 4pt, 110 ~ 72pt)
- self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag)
-
- def _parse_style_font_style(self, tag, value):
- try:
- style = {
- 'normal': pango.STYLE_NORMAL,
- 'italic': pango.STYLE_ITALIC,
- 'oblique': pango.STYLE_OBLIQUE,
- } [value]
- except KeyError:
- warnings.warn('unknown font-style %s' % value)
- else:
- tag.set_property('style', style)
-
- def __frac_length_tag_cb(self,length, tag, propname):
- styles = self._get_style_tags()
- if styles:
- length += styles[-1].get_property(propname)
- tag.set_property(propname, length)
- #__frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
-
- def _parse_style_margin_left(self, tag, value):
- # block relative
- self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
- tag, 'left-margin')
-
- def _parse_style_margin_right(self, tag, value):
- # block relative
- self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
- tag, 'right-margin')
-
- def _parse_style_font_weight(self, tag, value):
- # TODO: missing 'bolder' and 'lighter'
- try:
- weight = {
- '100': pango.WEIGHT_ULTRALIGHT,
- '200': pango.WEIGHT_ULTRALIGHT,
- '300': pango.WEIGHT_LIGHT,
- '400': pango.WEIGHT_NORMAL,
- '500': pango.WEIGHT_NORMAL,
- '600': pango.WEIGHT_BOLD,
- '700': pango.WEIGHT_BOLD,
- '800': pango.WEIGHT_ULTRABOLD,
- '900': pango.WEIGHT_HEAVY,
- 'normal': pango.WEIGHT_NORMAL,
- 'bold': pango.WEIGHT_BOLD,
- } [value]
- except KeyError:
- warnings.warn('unknown font-style %s' % value)
- else:
- tag.set_property('weight', weight)
-
- def _parse_style_font_family(self, tag, value):
- tag.set_property('family', value)
-
- def _parse_style_text_align(self, tag, value):
- try:
- align = {
- 'left': gtk.JUSTIFY_LEFT,
- 'right': gtk.JUSTIFY_RIGHT,
- 'center': gtk.JUSTIFY_CENTER,
- 'justify': gtk.JUSTIFY_FILL,
- } [value]
- except KeyError:
- warnings.warn('Invalid text-align:%s requested' % value)
- else:
- tag.set_property('justification', align)
-
- def _parse_style_text_decoration(self, tag, value):
- values = value.split(' ')
- if 'none' in values:
- tag.set_property('underline', pango.UNDERLINE_NONE)
- tag.set_property('strikethrough', False)
- if 'underline' in values:
- tag.set_property('underline', pango.UNDERLINE_SINGLE)
- else:
- tag.set_property('underline', pango.UNDERLINE_NONE)
- if 'line-through' in values:
- tag.set_property('strikethrough', True)
- else:
- tag.set_property('strikethrough', False)
- if 'blink' in values:
- warnings.warn('text-decoration:blink not implemented')
- if 'overline' in values:
- warnings.warn('text-decoration:overline not implemented')
-
- def _parse_style_white_space(self, tag, value):
- if value == 'pre':
- tag.set_property('wrap_mode', gtk.WRAP_NONE)
- elif value == 'normal':
- tag.set_property('wrap_mode', gtk.WRAP_WORD)
- elif value == 'nowrap':
- tag.set_property('wrap_mode', gtk.WRAP_NONE)
-
- def __length_tag_cb(self, value, tag, propname):
- try:
- tag.set_property(propname, value)
- except Exception:
- gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag))
-
-
- def _parse_style_width(self, tag, value):
- if value == 'auto':
- return
- self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
- tag, "width")
- def _parse_style_height(self, tag, value):
- if value == 'auto':
- return
- self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
- tag, "height")
-
-
- # build a dictionary mapping styles to methods, for greater speed
- __style_methods = dict()
- for style in ('background-color', 'color', 'font-family', 'font-size',
- 'font-style', 'font-weight', 'margin-left', 'margin-right',
- 'text-align', 'text-decoration', 'white-space', 'display',
- 'width', 'height' ):
- try:
- method = locals()['_parse_style_%s' % style.replace('-', '_')]
- except KeyError:
- warnings.warn('Style attribute "%s" not yet implemented' % style)
- else:
- __style_methods[style] = method
- del style
- # --
-
- def _get_style_tags(self):
- return [tag for tag in self.styles if tag is not None]
-
- def _create_url(self, href, title, type_, id_):
- '''Process a url tag.
- '''
- tag = self.textbuf.create_tag(id_)
- if href and href[0] != '#':
- tag.href = href
- tag.type_ = type_ # to be used by the URL handler
- tag.connect('event', self.textview.html_hyperlink_handler, 'url', href)
- tag.set_property('foreground', gajim.config.get('urlmsgcolor'))
- tag.set_property('underline', pango.UNDERLINE_SINGLE)
- tag.is_anchor = True
- if title:
- tag.title = title
- return tag
-
- def _process_img(self, attrs):
- '''Process a img tag.
- '''
- mem = ''
- try:
- # Wait maximum 1s for connection
- socket.setdefaulttimeout(1)
- try:
- req = urllib2.Request(attrs['src'])
- req.add_header('User-Agent', 'Gajim ' + gajim.version)
- f = urllib2.urlopen(req)
- except Exception, ex:
- gajim.log.debug('Error loading image %s ' % attrs['src'] + str(ex))
- pixbuf = None
- alt = attrs.get('alt', 'Broken image')
- else:
- # Wait 0.1s between each byte
- try:
- f.fp._sock.fp._sock.settimeout(0.5)
- except Exception:
- pass
- # Max image size = 2 MB (to try to prevent DoS)
- deadline = time.time() + 3
- while True:
- if time.time() > deadline:
- gajim.log.debug(str('Timeout loading image %s ' % \
- attrs['src'] + ex))
- mem = ''
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Timeout loading image')
- break
- try:
- temp = f.read(100)
- except socket.timeout, ex:
- gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \
- str(ex))
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Timeout loading image')
- break
- if temp:
- mem += temp
- else:
- break
- if len(mem) > 2*1024*1024:
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Image is too big')
- break
- pixbuf = None
- if mem:
- # Caveat: GdkPixbuf is known not to be safe to load
- # images from network... this program is now potentially
- # hackable ;)
- loader = gtk.gdk.PixbufLoader()
- dims = [0,0]
- def height_cb(length):
- dims[1] = length
- def width_cb(length):
- dims[0] = length
- # process width and height attributes
- w = attrs.get('width')
- h = attrs.get('height')
- # override with width and height styles
- for attr, val in style_iter(attrs.get('style', '')):
- if attr == 'width':
- w = val
- elif attr == 'height':
- h = val
- if w:
- self._parse_length(w, False, False, 1, 1000, width_cb)
- if h:
- self._parse_length(h, False, False, 1, 1000, height_cb)
- def set_size(pixbuf, w, h, dims):
- """
- FIXME: Floats should be relative to the whole textview, and
- resize with it. This needs new pifbufs for every resize,
- gtk.gdk.Pixbuf.scale_simple or similar.
- """
- if isinstance(dims[0], float):
- dims[0] = int(dims[0]*w)
- elif not dims[0]:
- dims[0] = w
- if isinstance(dims[1], float):
- dims[1] = int(dims[1]*h)
- if not dims[1]:
- dims[1] = h
- loader.set_size(*dims)
- if w or h:
- loader.connect('size-prepared', set_size, dims)
- loader.write(mem)
- loader.close()
- pixbuf = loader.get_pixbuf()
- alt = attrs.get('alt', '')
- if pixbuf is not None:
- tags = self._get_style_tags()
- if tags:
- tmpmark = self.textbuf.create_mark(None, self.iter, True)
- self.textbuf.insert_pixbuf(self.iter, pixbuf)
- self.starting = False
- if tags:
- start = self.textbuf.get_iter_at_mark(tmpmark)
- for tag in tags:
- self.textbuf.apply_tag(tag, start, self.iter)
- self.textbuf.delete_mark(tmpmark)
- else:
- self._insert_text('[IMG: %s]' % alt)
- except Exception, ex:
- gajim.log.error('Error loading image ' + str(ex))
- pixbuf = None
- alt = attrs.get('alt', 'Broken image')
- try:
- loader.close()
- except Exception:
- pass
- return pixbuf
-
- def _begin_span(self, style, tag=None, id_=None):
- if style is None:
- self.styles.append(tag)
- return None
- if tag is None:
- if id_:
- tag = self.textbuf.create_tag(id_)
- else:
- tag = self.textbuf.create_tag() # we create anonymous tag
- for attr, val in style_iter(style):
- attr = attr.lower()
- val = val
- try:
- method = self.__style_methods[attr]
- except KeyError:
- warnings.warn('Style attribute "%s" requested '
- 'but not yet implemented' % attr)
- else:
- method(self, tag, val)
- self.styles.append(tag)
-
- def _end_span(self):
- self.styles.pop()
-
- def _jump_line(self):
- self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol')
- self.starting = True
-
- def _insert_text(self, text):
- if self.starting and text != '\n':
- self.starting = (text[-1] == '\n')
- tags = self._get_style_tags()
- if tags:
- self.textbuf.insert_with_tags(self.iter, text, *tags)
- else:
- self.textbuf.insert(self.iter, text)
-
- def _starts_line(self):
- return self.starting or self.iter.starts_line()
-
- def _flush_text(self):
- if not self.text: return
- text, self.text = self.text, ''
- if not self.preserve:
- text = text.replace('\n', ' ')
- self.handle_specials(whitespace_rx.sub(' ', text))
- else:
- self._insert_text(text.strip('\n'))
-
- def _anchor_event(self, tag, textview, event, iter_, href, type_):
- if event.type == gtk.gdk.BUTTON_PRESS:
- self.textview.emit('url-clicked', href, type_)
- return True
- return False
-
- def handle_specials(self, text):
- self.iter = self.conv_textview.detect_and_print_special_text(text, self._get_style_tags())
-
- def characters(self, content):
- if self.preserve:
- self.text += content
- return
- if allwhitespace_rx.match(content) is not None and self._starts_line():
- self.text += ' '
- return
- self.text += content
- self.starting = False
-
-
- def startElement(self, name, attrs):
- self._flush_text()
- klass = [i for i in attrs.get('class',' ').split(' ') if i]
- style = ''
- #Add styles defined for classes
- for k in klass:
- if k in classes:
- style += classes[k]
-
- tag = None
- #FIXME: if we want to use id, it needs to be unique across
- # the whole textview, so we need to add something like the
- # message-id to it.
- #id_ = attrs.get('id',None)
- id_ = None
- if name == 'a':
- #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type
- href = attrs.get('href', None)
- if not href:
- href = attrs.get('HREF', None)
- # Gaim sends HREF instead of href
- title = attrs.get('title', attrs.get('rel',href))
- type_ = attrs.get('type', None)
- tag = self._create_url(href, title, type_, id_)
- elif name == 'blockquote':
- cite = attrs.get('cite', None)
- if cite:
- tag = self.textbuf.create_tag(id_)
- tag.title = title
- tag.is_anchor = True
- elif name in LIST_ELEMS:
- style += ';margin-left: 2em'
- elif name == 'img':
- tag = self._process_img(attrs)
- if name in element_styles:
- style += element_styles[name]
- # so that explicit styles override implicit ones,
- # we add the attribute last
- style += ";"+attrs.get('style','')
- if style == '':
- style = None
- self._begin_span(style, tag, id_)
-
- if name == 'br':
- pass # handled in endElement
- elif name == 'hr':
- pass # handled in endElement
- elif name in BLOCK:
- if not self._starts_line():
- self._jump_line()
- if name == 'pre':
- self.preserve = True
- elif name == 'span':
- pass
- elif name in ('dl', 'ul'):
- if not self._starts_line():
- self._jump_line()
- self.list_counters.append(None)
- elif name == 'ol':
- if not self._starts_line():
- self._jump_line()
- self.list_counters.append(0)
- elif name == 'li':
- if self.list_counters[-1] is None:
- li_head = unichr(0x2022)
- else:
- self.list_counters[-1] += 1
- li_head = '%i.' % self.list_counters[-1]
- self.text = ' '*len(self.list_counters)*4 + li_head + ' '
- self._flush_text()
- self.starting = True
- elif name == 'dd':
- self._jump_line()
- elif name == 'dt':
- if not self.starting:
- self._jump_line()
- elif name in ('a', 'img', 'body', 'html'):
- pass
- elif name in INLINE:
- pass
- else:
- warnings.warn('Unhandled element "%s"' % name)
-
- def endElement(self, name):
- endPreserving = False
- newLine = False
- if name == 'br':
- newLine = True
- elif name == 'hr':
- #FIXME: plenty of unused attributes (width, height,...) :)
- self._jump_line()
- try:
- self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf)
- #self._insert_text(u'\u2550'*40)
- self._jump_line()
- except Exception, e:
- gajim.log.debug(str('Error in hr'+e))
- elif name in LIST_ELEMS:
- self.list_counters.pop()
- elif name == 'li':
- newLine = True
- elif name == 'img':
- pass
- elif name == 'body' or name == 'html':
- pass
- elif name == 'a':
- pass
- elif name in INLINE:
- pass
- elif name in ('dd', 'dt', ):
- pass
- elif name in BLOCK:
- if name == 'pre':
- endPreserving = True
- else:
- warnings.warn("Unhandled element '%s'" % name)
- self._flush_text()
- if endPreserving:
- self.preserve = False
- if newLine:
- self._jump_line()
- self._end_span()
- #if not self._starts_line():
- # self.text = ' '
+ """
+ A handler to display html to a gtk textview
+
+ It keeps a stack of "style spans" (start/end element pairs) and a stack of
+ list counters, for nested lists.
+ """
+ def __init__(self, conv_textview, startiter):
+ xml.sax.handler.ContentHandler.__init__(self)
+ self.textbuf = conv_textview.tv.get_buffer()
+ self.textview = conv_textview.tv
+ self.iter = startiter
+ self.conv_textview = conv_textview
+ self.text = ''
+ self.starting=True
+ self.preserve = False
+ self.styles = [] # a gtk.TextTag or None, for each span level
+ self.list_counters = [] # stack (top at head) of list
+ # counters, or None for unordered list
+
+ def _parse_style_color(self, tag, value):
+ color = _parse_css_color(value)
+ tag.set_property('foreground-gdk', color)
+
+ def _parse_style_background_color(self, tag, value):
+ color = _parse_css_color(value)
+ tag.set_property('background-gdk', color)
+ tag.set_property('paragraph-background-gdk', color)
+
+
+ def _get_current_attributes(self):
+ attrs = self.textview.get_default_attributes()
+ self.iter.backward_char()
+ self.iter.get_attributes(attrs)
+ self.iter.forward_char()
+ return attrs
+
+ def __parse_length_frac_size_allocate(self, textview, allocation,
+ frac, callback, args):
+ callback(allocation.width*frac, *args)
+
+ def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args):
+ """
+ Parse/calc length, converting to pixels, calls callback(length, *args)
+ when the length is first computed or changes
+ """
+ if value.endswith('%'):
+ val = float(value[:-1])
+ sign = cmp(val,0)
+ # limits: 1% to 500%
+ val = sign*max(1,min(abs(val),500))
+ frac = val/100
+ if font_relative:
+ attrs = self._get_current_attributes()
+ font_size = attrs.font.get_size() / pango.SCALE
+ callback(frac*display_resolution*font_size, *args)
+ elif block_relative:
+ # CSS says 'Percentage values: refer to width of the closest
+ # block-level ancestor'
+ # This is difficult/impossible to implement, so we use
+ # textview width instead; a reasonable approximation..
+ alloc = self.textview.get_allocation()
+ self.__parse_length_frac_size_allocate(self.textview, alloc,
+ frac, callback, args)
+ self.textview.connect('size-allocate',
+ self.__parse_length_frac_size_allocate,
+ frac, callback, args)
+ else:
+ callback(frac, *args)
+ return
+
+ def get_val():
+ val = float(value[:-2])
+ sign = cmp(val,0)
+ # validate length
+ return sign*max(minl,min(abs(val*display_resolution),maxl))
+ if value.endswith('pt'): # points
+ callback(get_val()*display_resolution, *args)
+
+ elif value.endswith('em'): # ems, the width of the element's font
+ attrs = self._get_current_attributes()
+ font_size = attrs.font.get_size() / pango.SCALE
+ callback(get_val()*display_resolution*font_size, *args)
+
+ elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
+ # FIXME: figure out how to calculate this correctly
+ # for now 'em' size is used as approximation
+ attrs = self._get_current_attributes()
+ font_size = attrs.font.get_size() / pango.SCALE
+ callback(get_val()*display_resolution*font_size, *args)
+
+ elif value.endswith('px'): # pixels
+ callback(get_val(), *args)
+
+ else:
+ try:
+ # TODO: isn't "no units" interpreted as pixels?
+ val = int(value)
+ sign = cmp(val,0)
+ # validate length
+ val = sign*max(minl,min(abs(val),maxl))
+ callback(val, *args)
+ except Exception:
+ warnings.warn('Unable to parse length value "%s"' % value)
+
+ def __parse_font_size_cb(length, tag):
+ tag.set_property('size-points', length/display_resolution)
+ __parse_font_size_cb = staticmethod(__parse_font_size_cb)
+
+ def _parse_style_display(self, tag, value):
+ if value == 'none':
+ tag.set_property('invisible','true')
+ # FIXME: display: block, inline
+
+ def _parse_style_font_size(self, tag, value):
+ try:
+ scale = {
+ 'xx-small': pango.SCALE_XX_SMALL,
+ 'x-small': pango.SCALE_X_SMALL,
+ 'small': pango.SCALE_SMALL,
+ 'medium': pango.SCALE_MEDIUM,
+ 'large': pango.SCALE_LARGE,
+ 'x-large': pango.SCALE_X_LARGE,
+ 'xx-large': pango.SCALE_XX_LARGE,
+ } [value]
+ except KeyError:
+ pass
+ else:
+ attrs = self._get_current_attributes()
+ tag.set_property('scale', scale / attrs.font_scale)
+ return
+ if value == 'smaller':
+ tag.set_property('scale', pango.SCALE_SMALL)
+ return
+ if value == 'larger':
+ tag.set_property('scale', pango.SCALE_LARGE)
+ return
+ # font relative (5 ~ 4pt, 110 ~ 72pt)
+ self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag)
+
+ def _parse_style_font_style(self, tag, value):
+ try:
+ style = {
+ 'normal': pango.STYLE_NORMAL,
+ 'italic': pango.STYLE_ITALIC,
+ 'oblique': pango.STYLE_OBLIQUE,
+ } [value]
+ except KeyError:
+ warnings.warn('unknown font-style %s' % value)
+ else:
+ tag.set_property('style', style)
+
+ def __frac_length_tag_cb(self,length, tag, propname):
+ styles = self._get_style_tags()
+ if styles:
+ length += styles[-1].get_property(propname)
+ tag.set_property(propname, length)
+ #__frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
+
+ def _parse_style_margin_left(self, tag, value):
+ # block relative
+ self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
+ tag, 'left-margin')
+
+ def _parse_style_margin_right(self, tag, value):
+ # block relative
+ self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
+ tag, 'right-margin')
+
+ def _parse_style_font_weight(self, tag, value):
+ # TODO: missing 'bolder' and 'lighter'
+ try:
+ weight = {
+ '100': pango.WEIGHT_ULTRALIGHT,
+ '200': pango.WEIGHT_ULTRALIGHT,
+ '300': pango.WEIGHT_LIGHT,
+ '400': pango.WEIGHT_NORMAL,
+ '500': pango.WEIGHT_NORMAL,
+ '600': pango.WEIGHT_BOLD,
+ '700': pango.WEIGHT_BOLD,
+ '800': pango.WEIGHT_ULTRABOLD,
+ '900': pango.WEIGHT_HEAVY,
+ 'normal': pango.WEIGHT_NORMAL,
+ 'bold': pango.WEIGHT_BOLD,
+ } [value]
+ except KeyError:
+ warnings.warn('unknown font-style %s' % value)
+ else:
+ tag.set_property('weight', weight)
+
+ def _parse_style_font_family(self, tag, value):
+ tag.set_property('family', value)
+
+ def _parse_style_text_align(self, tag, value):
+ try:
+ align = {
+ 'left': gtk.JUSTIFY_LEFT,
+ 'right': gtk.JUSTIFY_RIGHT,
+ 'center': gtk.JUSTIFY_CENTER,
+ 'justify': gtk.JUSTIFY_FILL,
+ } [value]
+ except KeyError:
+ warnings.warn('Invalid text-align:%s requested' % value)
+ else:
+ tag.set_property('justification', align)
+
+ def _parse_style_text_decoration(self, tag, value):
+ values = value.split(' ')
+ if 'none' in values:
+ tag.set_property('underline', pango.UNDERLINE_NONE)
+ tag.set_property('strikethrough', False)
+ if 'underline' in values:
+ tag.set_property('underline', pango.UNDERLINE_SINGLE)
+ else:
+ tag.set_property('underline', pango.UNDERLINE_NONE)
+ if 'line-through' in values:
+ tag.set_property('strikethrough', True)
+ else:
+ tag.set_property('strikethrough', False)
+ if 'blink' in values:
+ warnings.warn('text-decoration:blink not implemented')
+ if 'overline' in values:
+ warnings.warn('text-decoration:overline not implemented')
+
+ def _parse_style_white_space(self, tag, value):
+ if value == 'pre':
+ tag.set_property('wrap_mode', gtk.WRAP_NONE)
+ elif value == 'normal':
+ tag.set_property('wrap_mode', gtk.WRAP_WORD)
+ elif value == 'nowrap':
+ tag.set_property('wrap_mode', gtk.WRAP_NONE)
+
+ def __length_tag_cb(self, value, tag, propname):
+ try:
+ tag.set_property(propname, value)
+ except Exception:
+ gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag))
+
+
+ def _parse_style_width(self, tag, value):
+ if value == 'auto':
+ return
+ self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
+ tag, "width")
+ def _parse_style_height(self, tag, value):
+ if value == 'auto':
+ return
+ self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
+ tag, "height")
+
+
+ # build a dictionary mapping styles to methods, for greater speed
+ __style_methods = dict()
+ for style in ('background-color', 'color', 'font-family', 'font-size',
+ 'font-style', 'font-weight', 'margin-left', 'margin-right',
+ 'text-align', 'text-decoration', 'white-space', 'display',
+ 'width', 'height' ):
+ try:
+ method = locals()['_parse_style_%s' % style.replace('-', '_')]
+ except KeyError:
+ warnings.warn('Style attribute "%s" not yet implemented' % style)
+ else:
+ __style_methods[style] = method
+ del style
+ # --
+
+ def _get_style_tags(self):
+ return [tag for tag in self.styles if tag is not None]
+
+ def _create_url(self, href, title, type_, id_):
+ '''Process a url tag.
+ '''
+ tag = self.textbuf.create_tag(id_)
+ if href and href[0] != '#':
+ tag.href = href
+ tag.type_ = type_ # to be used by the URL handler
+ tag.connect('event', self.textview.html_hyperlink_handler, 'url', href)
+ tag.set_property('foreground', gajim.config.get('urlmsgcolor'))
+ tag.set_property('underline', pango.UNDERLINE_SINGLE)
+ tag.is_anchor = True
+ if title:
+ tag.title = title
+ return tag
+
+ def _process_img(self, attrs):
+ '''Process a img tag.
+ '''
+ mem = ''
+ try:
+ # Wait maximum 1s for connection
+ socket.setdefaulttimeout(1)
+ try:
+ req = urllib2.Request(attrs['src'])
+ req.add_header('User-Agent', 'Gajim ' + gajim.version)
+ f = urllib2.urlopen(req)
+ except Exception, ex:
+ gajim.log.debug('Error loading image %s ' % attrs['src'] + str(ex))
+ pixbuf = None
+ alt = attrs.get('alt', 'Broken image')
+ else:
+ # Wait 0.1s between each byte
+ try:
+ f.fp._sock.fp._sock.settimeout(0.5)
+ except Exception:
+ pass
+ # Max image size = 2 MB (to try to prevent DoS)
+ deadline = time.time() + 3
+ while True:
+ if time.time() > deadline:
+ gajim.log.debug(str('Timeout loading image %s ' % \
+ attrs['src'] + ex))
+ mem = ''
+ alt = attrs.get('alt', '')
+ if alt:
+ alt += '\n'
+ alt += _('Timeout loading image')
+ break
+ try:
+ temp = f.read(100)
+ except socket.timeout, ex:
+ gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \
+ str(ex))
+ alt = attrs.get('alt', '')
+ if alt:
+ alt += '\n'
+ alt += _('Timeout loading image')
+ break
+ if temp:
+ mem += temp
+ else:
+ break
+ if len(mem) > 2*1024*1024:
+ alt = attrs.get('alt', '')
+ if alt:
+ alt += '\n'
+ alt += _('Image is too big')
+ break
+ pixbuf = None
+ if mem:
+ # Caveat: GdkPixbuf is known not to be safe to load
+ # images from network... this program is now potentially
+ # hackable ;)
+ loader = gtk.gdk.PixbufLoader()
+ dims = [0,0]
+ def height_cb(length):
+ dims[1] = length
+ def width_cb(length):
+ dims[0] = length
+ # process width and height attributes
+ w = attrs.get('width')
+ h = attrs.get('height')
+ # override with width and height styles
+ for attr, val in style_iter(attrs.get('style', '')):
+ if attr == 'width':
+ w = val
+ elif attr == 'height':
+ h = val
+ if w:
+ self._parse_length(w, False, False, 1, 1000, width_cb)
+ if h:
+ self._parse_length(h, False, False, 1, 1000, height_cb)
+ def set_size(pixbuf, w, h, dims):
+ """
+ FIXME: Floats should be relative to the whole textview, and
+ resize with it. This needs new pifbufs for every resize,
+ gtk.gdk.Pixbuf.scale_simple or similar.
+ """
+ if isinstance(dims[0], float):
+ dims[0] = int(dims[0]*w)
+ elif not dims[0]:
+ dims[0] = w
+ if isinstance(dims[1], float):
+ dims[1] = int(dims[1]*h)
+ if not dims[1]:
+ dims[1] = h
+ loader.set_size(*dims)
+ if w or h:
+ loader.connect('size-prepared', set_size, dims)
+ loader.write(mem)
+ loader.close()
+ pixbuf = loader.get_pixbuf()
+ alt = attrs.get('alt', '')
+ if pixbuf is not None:
+ tags = self._get_style_tags()
+ if tags:
+ tmpmark = self.textbuf.create_mark(None, self.iter, True)
+ self.textbuf.insert_pixbuf(self.iter, pixbuf)
+ self.starting = False
+ if tags:
+ start = self.textbuf.get_iter_at_mark(tmpmark)
+ for tag in tags:
+ self.textbuf.apply_tag(tag, start, self.iter)
+ self.textbuf.delete_mark(tmpmark)
+ else:
+ self._insert_text('[IMG: %s]' % alt)
+ except Exception, ex:
+ gajim.log.error('Error loading image ' + str(ex))
+ pixbuf = None
+ alt = attrs.get('alt', 'Broken image')
+ try:
+ loader.close()
+ except Exception:
+ pass
+ return pixbuf
+
+ def _begin_span(self, style, tag=None, id_=None):
+ if style is None:
+ self.styles.append(tag)
+ return None
+ if tag is None:
+ if id_:
+ tag = self.textbuf.create_tag(id_)
+ else:
+ tag = self.textbuf.create_tag() # we create anonymous tag
+ for attr, val in style_iter(style):
+ attr = attr.lower()
+ val = val
+ try:
+ method = self.__style_methods[attr]
+ except KeyError:
+ warnings.warn('Style attribute "%s" requested '
+ 'but not yet implemented' % attr)
+ else:
+ method(self, tag, val)
+ self.styles.append(tag)
+
+ def _end_span(self):
+ self.styles.pop()
+
+ def _jump_line(self):
+ self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol')
+ self.starting = True
+
+ def _insert_text(self, text):
+ if self.starting and text != '\n':
+ self.starting = (text[-1] == '\n')
+ tags = self._get_style_tags()
+ if tags:
+ self.textbuf.insert_with_tags(self.iter, text, *tags)
+ else:
+ self.textbuf.insert(self.iter, text)
+
+ def _starts_line(self):
+ return self.starting or self.iter.starts_line()
+
+ def _flush_text(self):
+ if not self.text: return
+ text, self.text = self.text, ''
+ if not self.preserve:
+ text = text.replace('\n', ' ')
+ self.handle_specials(whitespace_rx.sub(' ', text))
+ else:
+ self._insert_text(text.strip('\n'))
+
+ def _anchor_event(self, tag, textview, event, iter_, href, type_):
+ if event.type == gtk.gdk.BUTTON_PRESS:
+ self.textview.emit('url-clicked', href, type_)
+ return True
+ return False
+
+ def handle_specials(self, text):
+ self.iter = self.conv_textview.detect_and_print_special_text(text, self._get_style_tags())
+
+ def characters(self, content):
+ if self.preserve:
+ self.text += content
+ return
+ if allwhitespace_rx.match(content) is not None and self._starts_line():
+ self.text += ' '
+ return
+ self.text += content
+ self.starting = False
+
+
+ def startElement(self, name, attrs):
+ self._flush_text()
+ klass = [i for i in attrs.get('class',' ').split(' ') if i]
+ style = ''
+ #Add styles defined for classes
+ for k in klass:
+ if k in classes:
+ style += classes[k]
+
+ tag = None
+ #FIXME: if we want to use id, it needs to be unique across
+ # the whole textview, so we need to add something like the
+ # message-id to it.
+ #id_ = attrs.get('id',None)
+ id_ = None
+ if name == 'a':
+ #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type
+ href = attrs.get('href', None)
+ if not href:
+ href = attrs.get('HREF', None)
+ # Gaim sends HREF instead of href
+ title = attrs.get('title', attrs.get('rel',href))
+ type_ = attrs.get('type', None)
+ tag = self._create_url(href, title, type_, id_)
+ elif name == 'blockquote':
+ cite = attrs.get('cite', None)
+ if cite:
+ tag = self.textbuf.create_tag(id_)
+ tag.title = title
+ tag.is_anchor = True
+ elif name in LIST_ELEMS:
+ style += ';margin-left: 2em'
+ elif name == 'img':
+ tag = self._process_img(attrs)
+ if name in element_styles:
+ style += element_styles[name]
+ # so that explicit styles override implicit ones,
+ # we add the attribute last
+ style += ";"+attrs.get('style','')
+ if style == '':
+ style = None
+ self._begin_span(style, tag, id_)
+
+ if name == 'br':
+ pass # handled in endElement
+ elif name == 'hr':
+ pass # handled in endElement
+ elif name in BLOCK:
+ if not self._starts_line():
+ self._jump_line()
+ if name == 'pre':
+ self.preserve = True
+ elif name == 'span':
+ pass
+ elif name in ('dl', 'ul'):
+ if not self._starts_line():
+ self._jump_line()
+ self.list_counters.append(None)
+ elif name == 'ol':
+ if not self._starts_line():
+ self._jump_line()
+ self.list_counters.append(0)
+ elif name == 'li':
+ if self.list_counters[-1] is None:
+ li_head = unichr(0x2022)
+ else:
+ self.list_counters[-1] += 1
+ li_head = '%i.' % self.list_counters[-1]
+ self.text = ' '*len(self.list_counters)*4 + li_head + ' '
+ self._flush_text()
+ self.starting = True
+ elif name == 'dd':
+ self._jump_line()
+ elif name == 'dt':
+ if not self.starting:
+ self._jump_line()
+ elif name in ('a', 'img', 'body', 'html'):
+ pass
+ elif name in INLINE:
+ pass
+ else:
+ warnings.warn('Unhandled element "%s"' % name)
+
+ def endElement(self, name):
+ endPreserving = False
+ newLine = False
+ if name == 'br':
+ newLine = True
+ elif name == 'hr':
+ #FIXME: plenty of unused attributes (width, height,...) :)
+ self._jump_line()
+ try:
+ self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf)
+ #self._insert_text(u'\u2550'*40)
+ self._jump_line()
+ except Exception, e:
+ gajim.log.debug(str('Error in hr'+e))
+ elif name in LIST_ELEMS:
+ self.list_counters.pop()
+ elif name == 'li':
+ newLine = True
+ elif name == 'img':
+ pass
+ elif name == 'body' or name == 'html':
+ pass
+ elif name == 'a':
+ pass
+ elif name in INLINE:
+ pass
+ elif name in ('dd', 'dt', ):
+ pass
+ elif name in BLOCK:
+ if name == 'pre':
+ endPreserving = True
+ else:
+ warnings.warn("Unhandled element '%s'" % name)
+ self._flush_text()
+ if endPreserving:
+ self.preserve = False
+ if newLine:
+ self._jump_line()
+ self._end_span()
+ #if not self._starts_line():
+ # self.text = ' '
class HtmlTextView(gtk.TextView):
- def __init__(self):
- gobject.GObject.__init__(self)
- self.set_wrap_mode(gtk.WRAP_CHAR)
- self.set_editable(False)
- self._changed_cursor = False
- self.connect('destroy', self.__destroy_event)
- self.connect('motion-notify-event', self.__motion_notify_event)
- self.connect('leave-notify-event', self.__leave_event)
- self.connect('enter-notify-event', self.__motion_notify_event)
- self.connect('realize', self.on_html_text_view_realized)
- self.connect('unrealize', self.on_html_text_view_unrealized)
- self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
- self.get_buffer().connect_after('mark-set', self.on_text_buffer_mark_set)
- self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL)
- self.tooltip = tooltips.BaseTooltip()
- self.config = gajim.config
- self.interface = gajim.interface
- # end big hack
-
- def __destroy_event(self, widget):
- if self.tooltip.timeout != 0:
- self.tooltip.hide_tooltip()
-
- def __leave_event(self, widget, event):
- if self._changed_cursor:
- window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
- window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
- self._changed_cursor = False
-
- def show_tooltip(self, tag):
- if not self.tooltip.win:
- # check if the current pointer is still over the line
- x, y, _ = self.window.get_pointer()
- x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
- tags = self.get_iter_at_location(x, y).get_tags()
- is_over_anchor = False
- for tag_ in tags:
- if getattr(tag_, 'is_anchor', False):
- is_over_anchor = True
- break
- if not is_over_anchor:
- return
- text = getattr(tag, 'title', False)
- if text:
- pointer = self.get_pointer()
- position = self.window.get_origin()
- self.tooltip.show_tooltip(text, 8, position[1] + pointer[1])
-
- def __motion_notify_event(self, widget, event):
- x, y, _ = widget.window.get_pointer()
- x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
- tags = widget.get_iter_at_location(x, y).get_tags()
- anchor_tags = [tag for tag in tags if getattr(tag, 'is_anchor', False)]
- if self.tooltip.timeout != 0:
- # Check if we should hide the line tooltip
- if not anchor_tags:
- self.tooltip.hide_tooltip()
- if not self._changed_cursor and anchor_tags:
- window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
- window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
- self._changed_cursor = True
- self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip, anchor_tags[0])
- elif self._changed_cursor and not anchor_tags:
- window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
- window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
- self._changed_cursor = False
- return False
-
- def display_html(self, html, conv_textview):
- buffer_ = self.get_buffer()
- eob = buffer_.get_end_iter()
- ## this works too if libxml2 is not available
- # parser = xml.sax.make_parser(['drv_libxml2'])
- # parser.setFeature(xml.sax.handler.feature_validation, True)
- parser = xml.sax.make_parser()
- parser.setContentHandler(HtmlHandler(conv_textview, eob))
- parser.parse(StringIO(html))
-
- # too much space after :)
- #if not eob.starts_line():
- # buffer_.insert(eob, '\n')
-
- def on_html_text_view_copy_clipboard(self, unused_data):
- clipboard = self.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD)
- clipboard.set_text(self.get_selected_text())
- self.emit_stop_by_name('copy-clipboard')
-
- def on_html_text_view_realized(self, unused_data):
- self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
-
- def on_html_text_view_unrealized(self, unused_data):
- self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
-
- def on_text_buffer_mark_set(self, location, mark, unused_data):
- bounds = self.get_buffer().get_selection_bounds()
- if bounds:
- # textview can be hidden while we add a new line in it.
- if self.has_screen():
- clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)
- clipboard.set_text(self.get_selected_text())
-
- def get_selected_text(self):
- bounds = self.get_buffer().get_selection_bounds()
- selection = ''
- if bounds:
- (search_iter, end) = bounds
-
- while (search_iter.compare(end)):
- character = search_iter.get_char()
- if character == u'\ufffc':
- anchor = search_iter.get_child_anchor()
- if anchor:
- text = anchor.get_data('plaintext')
- if text:
- selection+=text
- else:
- selection+=character
- else:
- selection+=character
- search_iter.forward_char()
- return selection
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self.set_wrap_mode(gtk.WRAP_CHAR)
+ self.set_editable(False)
+ self._changed_cursor = False
+ self.connect('destroy', self.__destroy_event)
+ self.connect('motion-notify-event', self.__motion_notify_event)
+ self.connect('leave-notify-event', self.__leave_event)
+ self.connect('enter-notify-event', self.__motion_notify_event)
+ self.connect('realize', self.on_html_text_view_realized)
+ self.connect('unrealize', self.on_html_text_view_unrealized)
+ self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
+ self.get_buffer().connect_after('mark-set', self.on_text_buffer_mark_set)
+ self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL)
+ self.tooltip = tooltips.BaseTooltip()
+ self.config = gajim.config
+ self.interface = gajim.interface
+ # end big hack
+
+ def __destroy_event(self, widget):
+ if self.tooltip.timeout != 0:
+ self.tooltip.hide_tooltip()
+
+ def __leave_event(self, widget, event):
+ if self._changed_cursor:
+ window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+ window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+ self._changed_cursor = False
+
+ def show_tooltip(self, tag):
+ if not self.tooltip.win:
+ # check if the current pointer is still over the line
+ x, y, _ = self.window.get_pointer()
+ x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
+ tags = self.get_iter_at_location(x, y).get_tags()
+ is_over_anchor = False
+ for tag_ in tags:
+ if getattr(tag_, 'is_anchor', False):
+ is_over_anchor = True
+ break
+ if not is_over_anchor:
+ return
+ text = getattr(tag, 'title', False)
+ if text:
+ pointer = self.get_pointer()
+ position = self.window.get_origin()
+ self.tooltip.show_tooltip(text, 8, position[1] + pointer[1])
+
+ def __motion_notify_event(self, widget, event):
+ x, y, _ = widget.window.get_pointer()
+ x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
+ tags = widget.get_iter_at_location(x, y).get_tags()
+ anchor_tags = [tag for tag in tags if getattr(tag, 'is_anchor', False)]
+ if self.tooltip.timeout != 0:
+ # Check if we should hide the line tooltip
+ if not anchor_tags:
+ self.tooltip.hide_tooltip()
+ if not self._changed_cursor and anchor_tags:
+ window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+ window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
+ self._changed_cursor = True
+ self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip, anchor_tags[0])
+ elif self._changed_cursor and not anchor_tags:
+ window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+ window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+ self._changed_cursor = False
+ return False
+
+ def display_html(self, html, conv_textview):
+ buffer_ = self.get_buffer()
+ eob = buffer_.get_end_iter()
+ ## this works too if libxml2 is not available
+ # parser = xml.sax.make_parser(['drv_libxml2'])
+ # parser.setFeature(xml.sax.handler.feature_validation, True)
+ parser = xml.sax.make_parser()
+ parser.setContentHandler(HtmlHandler(conv_textview, eob))
+ parser.parse(StringIO(html))
+
+ # too much space after :)
+ #if not eob.starts_line():
+ # buffer_.insert(eob, '\n')
+
+ def on_html_text_view_copy_clipboard(self, unused_data):
+ clipboard = self.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD)
+ clipboard.set_text(self.get_selected_text())
+ self.emit_stop_by_name('copy-clipboard')
+
+ def on_html_text_view_realized(self, unused_data):
+ self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
+
+ def on_html_text_view_unrealized(self, unused_data):
+ self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
+
+ def on_text_buffer_mark_set(self, location, mark, unused_data):
+ bounds = self.get_buffer().get_selection_bounds()
+ if bounds:
+ # textview can be hidden while we add a new line in it.
+ if self.has_screen():
+ clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY)
+ clipboard.set_text(self.get_selected_text())
+
+ def get_selected_text(self):
+ bounds = self.get_buffer().get_selection_bounds()
+ selection = ''
+ if bounds:
+ (search_iter, end) = bounds
+
+ while (search_iter.compare(end)):
+ character = search_iter.get_char()
+ if character == u'\ufffc':
+ anchor = search_iter.get_child_anchor()
+ if anchor:
+ text = anchor.get_data('plaintext')
+ if text:
+ selection+=text
+ else:
+ selection+=character
+ else:
+ selection+=character
+ search_iter.forward_char()
+ return selection
change_cursor = None
if __name__ == '__main__':
- import os
-
- from conversation_textview import ConversationTextview
- import gajim as gaj
-
- class log(object):
-
- def debug(self, text):
- print "debug:", text
- def warn(self, text):
- print "warn;", text
- def error(self,text):
- print "error;", text
-
- gajim.log=log()
-
- gaj.Interface()
-
- htmlview = ConversationTextview(None)
-
- path = gtkgui_helpers.get_icon_path('gajim-muc_separator')
- # use this for hr
- htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path)
-
- tooltip = tooltips.BaseTooltip()
-
- def on_textview_motion_notify_event(widget, event):
- """
- Change the cursor to a hand when we are over a mail or an url
- """
- global change_cursor
- pointer_x, pointer_y = htmlview.tv.window.get_pointer()[0:2]
- x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
- pointer_y)
- tags = htmlview.tv.get_iter_at_location(x, y).get_tags()
- if change_cursor:
- htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
- gtk.gdk.Cursor(gtk.gdk.XTERM))
- change_cursor = None
- tag_table = htmlview.tv.get_buffer().get_tag_table()
- for tag in tags:
- try:
- if tag.is_anchor:
- htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
- gtk.gdk.Cursor(gtk.gdk.HAND2))
- change_cursor = tag
- elif tag == tag_table.lookup('focus-out-line'):
- over_line = True
- except Exception:
- pass
-
- #if line_tooltip.timeout != 0:
- # Check if we should hide the line tooltip
- # if not over_line:
- # line_tooltip.hide_tooltip()
- #if over_line and not line_tooltip.win:
- # line_tooltip.timeout = gobject.timeout_add(500,
- # show_line_tooltip)
- # htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
- # gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
- # change_cursor = tag
-
- htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event)
-
- def handler(texttag, widget, event, iter_, kind, href):
- if event.type == gtk.gdk.BUTTON_PRESS:
- print href
-
- htmlview.tv.html_hyperlink_handler = handler
-
- htmlview.print_real_text(None, xhtml='<div><span style="color: red; text-decoration:underline">Hello</span><br/>\n'
- ' <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
- ' <span style="font-size: 500%; font-family: serif">World</span>\n'
- '</div>\n')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''<body xmlns='http://www.w3.org/1999/xhtml'><p xmlns='http://www.w3.org/1999/xhtml'>a:b<a href='http://google.com/' xmlns='http://www.w3.org/1999/xhtml'>Google</a></p><br/></body>''')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p style='font-size:large'>
- <span style='font-style: italic'>O<span style='font-size:larger'>M</span>G</span>,
- I&apos;m <span style='color:green'>green</span>
- with <span style='font-weight: bold'>envy</span>!
- </p>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- http://test.com/ testing links autolinkifying
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p>As Emerson said in his essay <span style='font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
- <p style='margin-left: 5px; margin-right: 2%'>
- &quot;A foolish consistency is the hobgoblin of little minds.&quot;
- </p>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
- <p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
- alt='A License to Jabber'
- width='50%' height='50%'
- /></p>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <ul style='background-color:rgb(120,140,100)'>
- <li> One </li>
- <li> Two </li>
- <li> Three </li>
- </ul><hr /><pre style="background-color:rgb(120,120,120)">def fac(n):
- def faciter(n,acc):
- if n==0: return acc
- return faciter(n-1, acc*n)
- if n&lt;0: raise ValueError('Must be non-negative')
- return faciter(n,1)</pre>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <ol style='background-color:rgb(120,140,100)'>
- <li> One </li>
- <li> Two is nested: <ul style='background-color:rgb(200,200,100)'>
- <li> One </li>
- <li style='font-size:50%'> Two </li>
- <li style='font-size:200%'> Three </li>
- <li style='font-size:9999pt'> Four </li>
- </ul></li>
- <li> Three </li></ol>
- </body>
- ''')
- htmlview.tv.show()
- sw = gtk.ScrolledWindow()
- sw.set_property('hscrollbar-policy', gtk.POLICY_AUTOMATIC)
- sw.set_property('vscrollbar-policy', gtk.POLICY_AUTOMATIC)
- sw.set_property('border-width', 0)
- sw.add(htmlview.tv)
- sw.show()
- frame = gtk.Frame()
- frame.set_shadow_type(gtk.SHADOW_IN)
- frame.show()
- frame.add(sw)
- w = gtk.Window()
- w.add(frame)
- w.set_default_size(400, 300)
- w.show_all()
- w.connect('destroy', lambda w: gtk.main_quit())
- gtk.main()
-
-# vim: se ts=3:
+ import os
+
+ from conversation_textview import ConversationTextview
+ import gajim as gaj
+
+ class log(object):
+
+ def debug(self, text):
+ print "debug:", text
+ def warn(self, text):
+ print "warn;", text
+ def error(self,text):
+ print "error;", text
+
+ gajim.log=log()
+
+ gaj.Interface()
+
+ htmlview = ConversationTextview(None)
+
+ path = gtkgui_helpers.get_icon_path('gajim-muc_separator')
+ # use this for hr
+ htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path)
+
+ tooltip = tooltips.BaseTooltip()
+
+ def on_textview_motion_notify_event(widget, event):
+ """
+ Change the cursor to a hand when we are over a mail or an url
+ """
+ global change_cursor
+ pointer_x, pointer_y = htmlview.tv.window.get_pointer()[0:2]
+ x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
+ pointer_y)
+ tags = htmlview.tv.get_iter_at_location(x, y).get_tags()
+ if change_cursor:
+ htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.XTERM))
+ change_cursor = None
+ tag_table = htmlview.tv.get_buffer().get_tag_table()
+ for tag in tags:
+ try:
+ if tag.is_anchor:
+ htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.HAND2))
+ change_cursor = tag
+ elif tag == tag_table.lookup('focus-out-line'):
+ over_line = True
+ except Exception:
+ pass
+
+ #if line_tooltip.timeout != 0:
+ # Check if we should hide the line tooltip
+ # if not over_line:
+ # line_tooltip.hide_tooltip()
+ #if over_line and not line_tooltip.win:
+ # line_tooltip.timeout = gobject.timeout_add(500,
+ # show_line_tooltip)
+ # htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+ # gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
+ # change_cursor = tag
+
+ htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event)
+
+ def handler(texttag, widget, event, iter_, kind, href):
+ if event.type == gtk.gdk.BUTTON_PRESS:
+ print href
+
+ htmlview.tv.html_hyperlink_handler = handler
+
+ htmlview.print_real_text(None, xhtml='<div><span style="color: red; text-decoration:underline">Hello</span><br/>\n'
+ ' <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
+ ' <span style="font-size: 500%; font-family: serif">World</span>\n'
+ '</div>\n')
+ htmlview.print_real_text(None, xhtml='<hr />')
+ htmlview.print_real_text(None, xhtml='''<body xmlns='http://www.w3.org/1999/xhtml'><p xmlns='http://www.w3.org/1999/xhtml'>a:b<a href='http://google.com/' xmlns='http://www.w3.org/1999/xhtml'>Google</a></p><br/></body>''')
+ htmlview.print_real_text(None, xhtml='''
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <p style='font-size:large'>
+ <span style='font-style: italic'>O<span style='font-size:larger'>M</span>G</span>,
+ I&apos;m <span style='color:green'>green</span>
+ with <span style='font-weight: bold'>envy</span>!
+ </p>
+ </body>
+ ''')
+ htmlview.print_real_text(None, xhtml='<hr />')
+ htmlview.print_real_text(None, xhtml='''
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ http://test.com/ testing links autolinkifying
+ </body>
+ ''')
+ htmlview.print_real_text(None, xhtml='<hr />')
+ htmlview.print_real_text(None, xhtml='''
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <p>As Emerson said in his essay <span style='font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
+ <p style='margin-left: 5px; margin-right: 2%'>
+ &quot;A foolish consistency is the hobgoblin of little minds.&quot;
+ </p>
+ </body>
+ ''')
+ htmlview.print_real_text(None, xhtml='<hr />')
+ htmlview.print_real_text(None, xhtml='''
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
+ <p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
+ alt='A License to Jabber'
+ width='50%' height='50%'
+ /></p>
+ </body>
+ ''')
+ htmlview.print_real_text(None, xhtml='<hr />')
+ htmlview.print_real_text(None, xhtml='''
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <ul style='background-color:rgb(120,140,100)'>
+ <li> One </li>
+ <li> Two </li>
+ <li> Three </li>
+ </ul><hr /><pre style="background-color:rgb(120,120,120)">def fac(n):
+def faciter(n,acc):
+ if n==0: return acc
+ return faciter(n-1, acc*n)
+if n&lt;0: raise ValueError('Must be non-negative')
+return faciter(n,1)</pre>
+ </body>
+ ''')
+ htmlview.print_real_text(None, xhtml='<hr />')
+ htmlview.print_real_text(None, xhtml='''
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <ol style='background-color:rgb(120,140,100)'>
+ <li> One </li>
+ <li> Two is nested: <ul style='background-color:rgb(200,200,100)'>
+ <li> One </li>
+ <li style='font-size:50%'> Two </li>
+ <li style='font-size:200%'> Three </li>
+ <li style='font-size:9999pt'> Four </li>
+ </ul></li>
+ <li> Three </li></ol>
+ </body>
+ ''')
+ htmlview.tv.show()
+ sw = gtk.ScrolledWindow()
+ sw.set_property('hscrollbar-policy', gtk.POLICY_AUTOMATIC)
+ sw.set_property('vscrollbar-policy', gtk.POLICY_AUTOMATIC)
+ sw.set_property('border-width', 0)
+ sw.add(htmlview.tv)
+ sw.show()
+ frame = gtk.Frame()
+ frame.set_shadow_type(gtk.SHADOW_IN)
+ frame.show()
+ frame.add(sw)
+ w = gtk.Window()
+ w.add(frame)
+ w.set_default_size(400, 300)
+ w.show_all()
+ w.connect('destroy', lambda w: gtk.main_quit())
+ gtk.main()
diff --git a/src/ipython_view.py b/src/ipython_view.py
index a901643f6..bfc50d21a 100644
--- a/src/ipython_view.py
+++ b/src/ipython_view.py
@@ -50,490 +50,488 @@ import pango
from StringIO import StringIO
try:
- import IPython
+ import IPython
except ImportError:
- IPython = None
+ IPython = None
class IterableIPShell:
- """
- Create an IPython instance. Does not start a blocking event loop,
- instead allow single iterations. This allows embedding in GTK+
- without blockage
-
- @ivar IP: IPython instance.
- @type IP: IPython.iplib.InteractiveShell
- @ivar iter_more: Indicates if the line executed was a complete command,
- or we should wait for more.
- @type iter_more: integer
- @ivar history_level: The place in history where we currently are
- when pressing up/down.
- @type history_level: integer
- @ivar complete_sep: Seperation delimeters for completion function.
- @type complete_sep: _sre.SRE_Pattern
- """
- def __init__(self,argv=[],user_ns=None,user_global_ns=None, cin=None,
- cout=None,cerr=None, input_func=None):
"""
- @param argv: Command line options for IPython
- @type argv: list
- @param user_ns: User namespace.
- @type user_ns: dictionary
- @param user_global_ns: User global namespace.
- @type user_global_ns: dictionary.
- @param cin: Console standard input.
- @type cin: IO stream
- @param cout: Console standard output.
- @type cout: IO stream
- @param cerr: Console standard error.
- @type cerr: IO stream
- @param input_func: Replacement for builtin raw_input()
- @type input_func: function
- """
- if input_func:
- IPython.iplib.raw_input_original = input_func
- if cin:
- IPython.Shell.Term.cin = cin
- if cout:
- IPython.Shell.Term.cout = cout
- if cerr:
- IPython.Shell.Term.cerr = cerr
-
- # This is to get rid of the blockage that accurs during
- # IPython.Shell.InteractiveShell.user_setup()
- IPython.iplib.raw_input = lambda x: None
-
- self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr)
- os.environ['TERM'] = 'dumb'
- excepthook = sys.excepthook
- self.IP = IPython.Shell.make_IPython(
- argv,user_ns=user_ns,
- user_global_ns=user_global_ns,
- embedded=True,
- shell_class=IPython.Shell.InteractiveShell)
- self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
- header='IPython system call: ',
- verbose=self.IP.rc.system_verbose)
- sys.excepthook = excepthook
- self.iter_more = 0
- self.history_level = 0
- self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
-
- def execute(self):
- """
- Execute the current line provided by the shell object
- """
- self.history_level = 0
- orig_stdout = sys.stdout
- sys.stdout = IPython.Shell.Term.cout
- try:
- line = self.IP.raw_input(None, self.iter_more)
- if self.IP.autoindent:
- self.IP.readline_startup_hook(None)
- except KeyboardInterrupt:
- self.IP.write('\nKeyboardInterrupt\n')
- self.IP.resetbuffer()
- # keep cache in sync with the prompt counter:
- self.IP.outputcache.prompt_count -= 1
-
- if self.IP.autoindent:
- self.IP.indent_current_nsp = 0
- self.iter_more = 0
- except:
- self.IP.showtraceback()
- else:
- self.iter_more = self.IP.push(line)
- if (self.IP.SyntaxTB.last_syntax_error and
- self.IP.rc.autoedit_syntax):
- self.IP.edit_syntax_error()
- if self.iter_more:
- self.prompt = str(self.IP.outputcache.prompt2).strip()
- if self.IP.autoindent:
- self.IP.readline_startup_hook(self.IP.pre_readline)
- else:
- self.prompt = str(self.IP.outputcache.prompt1).strip()
- sys.stdout = orig_stdout
-
- def historyBack(self):
- """
- Provide one history command back
-
- @return: The command string.
- @rtype: string
- """
- self.history_level -= 1
- return self._getHistory()
-
- def historyForward(self):
- """
- Provide one history command forward
-
- @return: The command string.
- @rtype: string
- """
- self.history_level += 1
- return self._getHistory()
+ Create an IPython instance. Does not start a blocking event loop,
+ instead allow single iterations. This allows embedding in GTK+
+ without blockage
+
+ @ivar IP: IPython instance.
+ @type IP: IPython.iplib.InteractiveShell
+ @ivar iter_more: Indicates if the line executed was a complete command,
+ or we should wait for more.
+ @type iter_more: integer
+ @ivar history_level: The place in history where we currently are
+ when pressing up/down.
+ @type history_level: integer
+ @ivar complete_sep: Seperation delimeters for completion function.
+ @type complete_sep: _sre.SRE_Pattern
+ """
+ def __init__(self,argv=[],user_ns=None,user_global_ns=None, cin=None,
+ cout=None,cerr=None, input_func=None):
+ """
+ @param argv: Command line options for IPython
+ @type argv: list
+ @param user_ns: User namespace.
+ @type user_ns: dictionary
+ @param user_global_ns: User global namespace.
+ @type user_global_ns: dictionary.
+ @param cin: Console standard input.
+ @type cin: IO stream
+ @param cout: Console standard output.
+ @type cout: IO stream
+ @param cerr: Console standard error.
+ @type cerr: IO stream
+ @param input_func: Replacement for builtin raw_input()
+ @type input_func: function
+ """
+ if input_func:
+ IPython.iplib.raw_input_original = input_func
+ if cin:
+ IPython.Shell.Term.cin = cin
+ if cout:
+ IPython.Shell.Term.cout = cout
+ if cerr:
+ IPython.Shell.Term.cerr = cerr
+
+ # This is to get rid of the blockage that accurs during
+ # IPython.Shell.InteractiveShell.user_setup()
+ IPython.iplib.raw_input = lambda x: None
+
+ self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr)
+ os.environ['TERM'] = 'dumb'
+ excepthook = sys.excepthook
+ self.IP = IPython.Shell.make_IPython(
+ argv,user_ns=user_ns,
+ user_global_ns=user_global_ns,
+ embedded=True,
+ shell_class=IPython.Shell.InteractiveShell)
+ self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
+ header='IPython system call: ',
+ verbose=self.IP.rc.system_verbose)
+ sys.excepthook = excepthook
+ self.iter_more = 0
+ self.history_level = 0
+ self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
+
+ def execute(self):
+ """
+ Execute the current line provided by the shell object
+ """
+ self.history_level = 0
+ orig_stdout = sys.stdout
+ sys.stdout = IPython.Shell.Term.cout
+ try:
+ line = self.IP.raw_input(None, self.iter_more)
+ if self.IP.autoindent:
+ self.IP.readline_startup_hook(None)
+ except KeyboardInterrupt:
+ self.IP.write('\nKeyboardInterrupt\n')
+ self.IP.resetbuffer()
+ # keep cache in sync with the prompt counter:
+ self.IP.outputcache.prompt_count -= 1
+
+ if self.IP.autoindent:
+ self.IP.indent_current_nsp = 0
+ self.iter_more = 0
+ except:
+ self.IP.showtraceback()
+ else:
+ self.iter_more = self.IP.push(line)
+ if (self.IP.SyntaxTB.last_syntax_error and
+ self.IP.rc.autoedit_syntax):
+ self.IP.edit_syntax_error()
+ if self.iter_more:
+ self.prompt = str(self.IP.outputcache.prompt2).strip()
+ if self.IP.autoindent:
+ self.IP.readline_startup_hook(self.IP.pre_readline)
+ else:
+ self.prompt = str(self.IP.outputcache.prompt1).strip()
+ sys.stdout = orig_stdout
+
+ def historyBack(self):
+ """
+ Provide one history command back
- def _getHistory(self):
- """
- Get the command string of the current history level
+ @return: The command string.
+ @rtype: string
+ """
+ self.history_level -= 1
+ return self._getHistory()
- @return: Historic command string.
- @rtype: string
- """
- try:
- rv = self.IP.user_ns['In'][self.history_level].strip('\n')
- except IndexError:
- self.history_level = 0
- rv = ''
- return rv
-
- def updateNamespace(self, ns_dict):
- """
- Add the current dictionary to the shell namespace
+ def historyForward(self):
+ """
+ Provide one history command forward
- @param ns_dict: A dictionary of symbol-values.
- @type ns_dict: dictionary
- """
- self.IP.user_ns.update(ns_dict)
+ @return: The command string.
+ @rtype: string
+ """
+ self.history_level += 1
+ return self._getHistory()
- def complete(self, line):
- """
- Returns an auto completed line and/or posibilities for completion
+ def _getHistory(self):
+ """
+ Get the command string of the current history level
- @param line: Given line so far.
- @type line: string
+ @return: Historic command string.
+ @rtype: string
+ """
+ try:
+ rv = self.IP.user_ns['In'][self.history_level].strip('\n')
+ except IndexError:
+ self.history_level = 0
+ rv = ''
+ return rv
+
+ def updateNamespace(self, ns_dict):
+ """
+ Add the current dictionary to the shell namespace
- @return: Line completed as for as possible,
- and possible further completions.
- @rtype: tuple
- """
- split_line = self.complete_sep.split(line)
- possibilities = self.IP.complete(split_line[-1])
-
- try:
- __builtins__.all()
- except AttributeError:
- def all(iterable):
- for element in iterable:
- if not element:
- return False
- return True
+ @param ns_dict: A dictionary of symbol-values.
+ @type ns_dict: dictionary
+ """
+ self.IP.user_ns.update(ns_dict)
- def common_prefix(seq):
+ def complete(self, line):
"""
- Return the common prefix of a sequence of strings
- """
- return "".join(c for i, c in enumerate(seq[0])
- if all(s.startswith(c, i) for s in seq))
- if possibilities:
- completed = line[:-len(split_line[-1])]+common_prefix(possibilities)
- else:
- completed = line
- return completed, possibilities
+ Returns an auto completed line and/or posibilities for completion
+ @param line: Given line so far.
+ @type line: string
- def shell(self, cmd,verbose=0,debug=0,header=''):
- """
- Replacement method to allow shell commands without them blocking
-
- @param cmd: Shell command to execute.
- @type cmd: string
- @param verbose: Verbosity
- @type verbose: integer
- @param debug: Debug level
- @type debug: integer
- @param header: Header to be printed before output
- @type header: string
- """
- if verbose or debug: print header+cmd
- # flush stdout so we don't mangle python's buffering
- if not debug:
- input_, output = os.popen4(cmd)
- print output.read()
- output.close()
- input_.close()
+ @return: Line completed as for as possible,
+ and possible further completions.
+ @rtype: tuple
+ """
+ split_line = self.complete_sep.split(line)
+ possibilities = self.IP.complete(split_line[-1])
+
+ try:
+ __builtins__.all()
+ except AttributeError:
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+
+ def common_prefix(seq):
+ """
+ Return the common prefix of a sequence of strings
+ """
+ return "".join(c for i, c in enumerate(seq[0])
+ if all(s.startswith(c, i) for s in seq))
+ if possibilities:
+ completed = line[:-len(split_line[-1])]+common_prefix(possibilities)
+ else:
+ completed = line
+ return completed, possibilities
+
+
+ def shell(self, cmd,verbose=0,debug=0,header=''):
+ """
+ Replacement method to allow shell commands without them blocking
+
+ @param cmd: Shell command to execute.
+ @type cmd: string
+ @param verbose: Verbosity
+ @type verbose: integer
+ @param debug: Debug level
+ @type debug: integer
+ @param header: Header to be printed before output
+ @type header: string
+ """
+ if verbose or debug: print header+cmd
+ # flush stdout so we don't mangle python's buffering
+ if not debug:
+ input_, output = os.popen4(cmd)
+ print output.read()
+ output.close()
+ input_.close()
class ConsoleView(gtk.TextView):
- """
- Specialized text view for console-like workflow
-
- @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
- @type ANSI_COLORS: dictionary
-
- @ivar text_buffer: Widget's text buffer.
- @type text_buffer: gtk.TextBuffer
- @ivar color_pat: Regex of terminal color pattern
- @type color_pat: _sre.SRE_Pattern
- @ivar mark: Scroll mark for automatic scrolling on input.
- @type mark: gtk.TextMark
- @ivar line_start: Start of command line mark.
- @type line_start: gtk.TextMark
- """
-
- ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red',
- '0;32': 'Green', '0;33': 'Brown',
- '0;34': 'Blue', '0;35': 'Purple',
- '0;36': 'Cyan', '0;37': 'LightGray',
- '1;30': 'DarkGray', '1;31': 'DarkRed',
- '1;32': 'SeaGreen', '1;33': 'Yellow',
- '1;34': 'LightBlue', '1;35': 'MediumPurple',
- '1;36': 'LightCyan', '1;37': 'White'}
-
- def __init__(self):
- """
- Initialize console view
"""
- gtk.TextView.__init__(self)
- self.modify_font(pango.FontDescription('Mono'))
- self.set_cursor_visible(True)
- self.text_buffer = self.get_buffer()
- self.mark = self.text_buffer.create_mark('scroll_mark',
- self.text_buffer.get_end_iter(),
- False)
- for code in self.ANSI_COLORS:
- self.text_buffer.create_tag(code,
- foreground=self.ANSI_COLORS[code],
- weight=700)
- self.text_buffer.create_tag('0')
- self.text_buffer.create_tag('notouch', editable=False)
- self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
- self.line_start = \
- self.text_buffer.create_mark('line_start',
- self.text_buffer.get_end_iter(), True)
- self.connect('key-press-event', self.onKeyPress)
-
- def write(self, text, editable=False):
- gobject.idle_add(self._write, text, editable)
-
- def _write(self, text, editable=False):
- """
- Write given text to buffer
+ Specialized text view for console-like workflow
- @param text: Text to append.
- @type text: string
- @param editable: If true, added text is editable.
- @type editable: boolean
- """
- segments = self.color_pat.split(text)
- segment = segments.pop(0)
- start_mark = self.text_buffer.create_mark(None,
- self.text_buffer.get_end_iter(),
- True)
- self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
-
- if segments:
- ansi_tags = self.color_pat.findall(text)
- for tag in ansi_tags:
- i = segments.index(tag)
- self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(),
- segments[i+1], tag)
- segments.pop(i)
- if not editable:
- self.text_buffer.apply_tag_by_name('notouch',
- self.text_buffer.get_iter_at_mark(start_mark),
- self.text_buffer.get_end_iter())
- self.text_buffer.delete_mark(start_mark)
- self.scroll_mark_onscreen(self.mark)
-
-
- def showPrompt(self, prompt):
- gobject.idle_add(self._showPrompt, prompt)
-
- def _showPrompt(self, prompt):
- """
- Print prompt at start of line
+ @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
+ @type ANSI_COLORS: dictionary
- @param prompt: Prompt to print.
- @type prompt: string
+ @ivar text_buffer: Widget's text buffer.
+ @type text_buffer: gtk.TextBuffer
+ @ivar color_pat: Regex of terminal color pattern
+ @type color_pat: _sre.SRE_Pattern
+ @ivar mark: Scroll mark for automatic scrolling on input.
+ @type mark: gtk.TextMark
+ @ivar line_start: Start of command line mark.
+ @type line_start: gtk.TextMark
"""
- self._write(prompt)
- self.text_buffer.move_mark(self.line_start,
- self.text_buffer.get_end_iter())
- def changeLine(self, text):
- gobject.idle_add(self._changeLine, text)
+ ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red',
+ '0;32': 'Green', '0;33': 'Brown',
+ '0;34': 'Blue', '0;35': 'Purple',
+ '0;36': 'Cyan', '0;37': 'LightGray',
+ '1;30': 'DarkGray', '1;31': 'DarkRed',
+ '1;32': 'SeaGreen', '1;33': 'Yellow',
+ '1;34': 'LightBlue', '1;35': 'MediumPurple',
+ '1;36': 'LightCyan', '1;37': 'White'}
- def _changeLine(self, text):
- """
- Replace currently entered command line with given text
+ def __init__(self):
+ """
+ Initialize console view
+ """
+ gtk.TextView.__init__(self)
+ self.modify_font(pango.FontDescription('Mono'))
+ self.set_cursor_visible(True)
+ self.text_buffer = self.get_buffer()
+ self.mark = self.text_buffer.create_mark('scroll_mark',
+ self.text_buffer.get_end_iter(),
+ False)
+ for code in self.ANSI_COLORS:
+ self.text_buffer.create_tag(code,
+ foreground=self.ANSI_COLORS[code],
+ weight=700)
+ self.text_buffer.create_tag('0')
+ self.text_buffer.create_tag('notouch', editable=False)
+ self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
+ self.line_start = \
+ self.text_buffer.create_mark('line_start',
+ self.text_buffer.get_end_iter(), True)
+ self.connect('key-press-event', self.onKeyPress)
+
+ def write(self, text, editable=False):
+ gobject.idle_add(self._write, text, editable)
+
+ def _write(self, text, editable=False):
+ """
+ Write given text to buffer
- @param text: Text to use as replacement.
- @type text: string
- """
- iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
- iter_.forward_to_line_end()
- self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter_)
- self._write(text, True)
+ @param text: Text to append.
+ @type text: string
+ @param editable: If true, added text is editable.
+ @type editable: boolean
+ """
+ segments = self.color_pat.split(text)
+ segment = segments.pop(0)
+ start_mark = self.text_buffer.create_mark(None,
+ self.text_buffer.get_end_iter(),
+ True)
+ self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
+
+ if segments:
+ ansi_tags = self.color_pat.findall(text)
+ for tag in ansi_tags:
+ i = segments.index(tag)
+ self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(),
+ segments[i+1], tag)
+ segments.pop(i)
+ if not editable:
+ self.text_buffer.apply_tag_by_name('notouch',
+ self.text_buffer.get_iter_at_mark(start_mark),
+ self.text_buffer.get_end_iter())
+ self.text_buffer.delete_mark(start_mark)
+ self.scroll_mark_onscreen(self.mark)
+
+
+ def showPrompt(self, prompt):
+ gobject.idle_add(self._showPrompt, prompt)
+
+ def _showPrompt(self, prompt):
+ """
+ Print prompt at start of line
- def getCurrentLine(self):
- """
- Get text in current command line
+ @param prompt: Prompt to print.
+ @type prompt: string
+ """
+ self._write(prompt)
+ self.text_buffer.move_mark(self.line_start,
+ self.text_buffer.get_end_iter())
- @return: Text of current command line.
- @rtype: string
- """
- rv = self.text_buffer.get_slice(
- self.text_buffer.get_iter_at_mark(self.line_start),
- self.text_buffer.get_end_iter(), False)
- return rv
+ def changeLine(self, text):
+ gobject.idle_add(self._changeLine, text)
- def showReturned(self, text):
- gobject.idle_add(self._showReturned, text)
+ def _changeLine(self, text):
+ """
+ Replace currently entered command line with given text
- def _showReturned(self, text):
- """
- Show returned text from last command and print new prompt
+ @param text: Text to use as replacement.
+ @type text: string
+ """
+ iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
+ iter_.forward_to_line_end()
+ self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter_)
+ self._write(text, True)
- @param text: Text to show.
- @type text: string
- """
- iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
- iter_.forward_to_line_end()
- self.text_buffer.apply_tag_by_name(
- 'notouch',
- self.text_buffer.get_iter_at_mark(self.line_start),
- iter_)
- self._write('\n'+text)
- if text:
- self._write('\n')
- self._showPrompt(self.prompt)
- self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter())
- self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
-
- def onKeyPress(self, widget, event):
- """
- Key press callback used for correcting behavior for console-like
- interfaces. For example 'home' should go to prompt, not to begining of
- line
+ def getCurrentLine(self):
+ """
+ Get text in current command line
- @param widget: Widget that key press accored in.
- @type widget: gtk.Widget
- @param event: Event object
- @type event: gtk.gdk.Event
+ @return: Text of current command line.
+ @rtype: string
+ """
+ rv = self.text_buffer.get_slice(
+ self.text_buffer.get_iter_at_mark(self.line_start),
+ self.text_buffer.get_end_iter(), False)
+ return rv
- @return: Return True if event should not trickle.
- @rtype: boolean
- """
- insert_mark = self.text_buffer.get_insert()
- insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
- selection_mark = self.text_buffer.get_selection_bound()
- selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
- start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
- if event.keyval == gtk.keysyms.Home:
- if event.state == 0:
- self.text_buffer.place_cursor(start_iter)
- return True
- elif event.state == gtk.gdk.SHIFT_MASK:
- self.text_buffer.move_mark(insert_mark, start_iter)
- return True
- elif event.keyval == gtk.keysyms.Left:
- insert_iter.backward_cursor_position()
- if not insert_iter.editable(True):
- return True
- elif not event.string:
- pass
- elif start_iter.compare(insert_iter) <= 0 and \
- start_iter.compare(selection_iter) <= 0:
- pass
- elif start_iter.compare(insert_iter) > 0 and \
- start_iter.compare(selection_iter) > 0:
- self.text_buffer.place_cursor(start_iter)
- elif insert_iter.compare(selection_iter) < 0:
- self.text_buffer.move_mark(insert_mark, start_iter)
- elif insert_iter.compare(selection_iter) > 0:
- self.text_buffer.move_mark(selection_mark, start_iter)
-
- return self.onKeyPressExtend(event)
-
- def onKeyPressExtend(self, event):
- """
- For some reason we can't extend onKeyPress directly (bug #500900)
- """
- pass
+ def showReturned(self, text):
+ gobject.idle_add(self._showReturned, text)
+
+ def _showReturned(self, text):
+ """
+ Show returned text from last command and print new prompt
+
+ @param text: Text to show.
+ @type text: string
+ """
+ iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
+ iter_.forward_to_line_end()
+ self.text_buffer.apply_tag_by_name(
+ 'notouch',
+ self.text_buffer.get_iter_at_mark(self.line_start),
+ iter_)
+ self._write('\n'+text)
+ if text:
+ self._write('\n')
+ self._showPrompt(self.prompt)
+ self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter())
+ self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
+
+ def onKeyPress(self, widget, event):
+ """
+ Key press callback used for correcting behavior for console-like
+ interfaces. For example 'home' should go to prompt, not to begining of
+ line
+
+ @param widget: Widget that key press accored in.
+ @type widget: gtk.Widget
+ @param event: Event object
+ @type event: gtk.gdk.Event
+
+ @return: Return True if event should not trickle.
+ @rtype: boolean
+ """
+ insert_mark = self.text_buffer.get_insert()
+ insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
+ selection_mark = self.text_buffer.get_selection_bound()
+ selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
+ start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
+ if event.keyval == gtk.keysyms.Home:
+ if event.state == 0:
+ self.text_buffer.place_cursor(start_iter)
+ return True
+ elif event.state == gtk.gdk.SHIFT_MASK:
+ self.text_buffer.move_mark(insert_mark, start_iter)
+ return True
+ elif event.keyval == gtk.keysyms.Left:
+ insert_iter.backward_cursor_position()
+ if not insert_iter.editable(True):
+ return True
+ elif not event.string:
+ pass
+ elif start_iter.compare(insert_iter) <= 0 and \
+ start_iter.compare(selection_iter) <= 0:
+ pass
+ elif start_iter.compare(insert_iter) > 0 and \
+ start_iter.compare(selection_iter) > 0:
+ self.text_buffer.place_cursor(start_iter)
+ elif insert_iter.compare(selection_iter) < 0:
+ self.text_buffer.move_mark(insert_mark, start_iter)
+ elif insert_iter.compare(selection_iter) > 0:
+ self.text_buffer.move_mark(selection_mark, start_iter)
+
+ return self.onKeyPressExtend(event)
+
+ def onKeyPressExtend(self, event):
+ """
+ For some reason we can't extend onKeyPress directly (bug #500900)
+ """
+ pass
class IPythonView(ConsoleView, IterableIPShell):
- '''
- Sub-class of both modified IPython shell and L{ConsoleView} this makes
- a GTK+ IPython console.
- '''
- def __init__(self):
- """
- Initialize. Redirect I/O to console
- """
- ConsoleView.__init__(self)
- self.cout = StringIO()
- IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout,
- input_func=self.raw_input)
+ '''
+ Sub-class of both modified IPython shell and L{ConsoleView} this makes
+ a GTK+ IPython console.
+ '''
+ def __init__(self):
+ """
+ Initialize. Redirect I/O to console
+ """
+ ConsoleView.__init__(self)
+ self.cout = StringIO()
+ IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout,
+ input_func=self.raw_input)
# self.connect('key_press_event', self.keyPress)
- self.execute()
- self.cout.truncate(0)
- self.showPrompt(self.prompt)
- self.interrupt = False
+ self.execute()
+ self.cout.truncate(0)
+ self.showPrompt(self.prompt)
+ self.interrupt = False
- def raw_input(self, prompt=''):
- """
- Custom raw_input() replacement. Get's current line from console buffer
+ def raw_input(self, prompt=''):
+ """
+ Custom raw_input() replacement. Get's current line from console buffer
- @param prompt: Prompt to print. Here for compatability as replacement.
- @type prompt: string
+ @param prompt: Prompt to print. Here for compatability as replacement.
+ @type prompt: string
- @return: The current command line text.
- @rtype: string
- """
- if self.interrupt:
- self.interrupt = False
- raise KeyboardInterrupt
- return self.getCurrentLine()
+ @return: The current command line text.
+ @rtype: string
+ """
+ if self.interrupt:
+ self.interrupt = False
+ raise KeyboardInterrupt
+ return self.getCurrentLine()
- def onKeyPressExtend(self, event):
- """
- Key press callback with plenty of shell goodness, like history,
- autocompletions, etc
+ def onKeyPressExtend(self, event):
+ """
+ Key press callback with plenty of shell goodness, like history,
+ autocompletions, etc
- @param widget: Widget that key press occured in.
- @type widget: gtk.Widget
- @param event: Event object.
- @type event: gtk.gdk.Event
+ @param widget: Widget that key press occured in.
+ @type widget: gtk.Widget
+ @param event: Event object.
+ @type event: gtk.gdk.Event
- @return: True if event should not trickle.
- @rtype: boolean
- """
- if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
- self.interrupt = True
- self._processLine()
- return True
- elif event.keyval == gtk.keysyms.Return:
- self._processLine()
- return True
- elif event.keyval == gtk.keysyms.Up:
- self.changeLine(self.historyBack())
- return True
- elif event.keyval == gtk.keysyms.Down:
- self.changeLine(self.historyForward())
- return True
- elif event.keyval == gtk.keysyms.Tab:
- if not self.getCurrentLine().strip():
- return False
- completed, possibilities = self.complete(self.getCurrentLine())
- if len(possibilities) > 1:
- slice_ = self.getCurrentLine()
- self.write('\n')
- for symbol in possibilities:
- self.write(symbol+'\n')
- self.showPrompt(self.prompt)
- self.changeLine(completed or slice_)
- return True
-
- def _processLine(self):
- """
- Process current command line
- """
- self.history_pos = 0
- self.execute()
- rv = self.cout.getvalue()
- if rv: rv = rv.strip('\n')
- self.showReturned(rv)
- self.cout.truncate(0)
+ @return: True if event should not trickle.
+ @rtype: boolean
+ """
+ if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
+ self.interrupt = True
+ self._processLine()
+ return True
+ elif event.keyval == gtk.keysyms.Return:
+ self._processLine()
+ return True
+ elif event.keyval == gtk.keysyms.Up:
+ self.changeLine(self.historyBack())
+ return True
+ elif event.keyval == gtk.keysyms.Down:
+ self.changeLine(self.historyForward())
+ return True
+ elif event.keyval == gtk.keysyms.Tab:
+ if not self.getCurrentLine().strip():
+ return False
+ completed, possibilities = self.complete(self.getCurrentLine())
+ if len(possibilities) > 1:
+ slice_ = self.getCurrentLine()
+ self.write('\n')
+ for symbol in possibilities:
+ self.write(symbol+'\n')
+ self.showPrompt(self.prompt)
+ self.changeLine(completed or slice_)
+ return True
+ def _processLine(self):
+ """
+ Process current command line
+ """
+ self.history_pos = 0
+ self.execute()
+ rv = self.cout.getvalue()
+ if rv: rv = rv.strip('\n')
+ self.showReturned(rv)
+ self.cout.truncate(0)
-# vim: se ts=3:
diff --git a/src/message_control.py b/src/message_control.py
index 4d83ce763..881f1b7f0 100644
--- a/src/message_control.py
+++ b/src/message_control.py
@@ -39,209 +39,207 @@ TYPE_PM = 'pm'
####################
class MessageControl:
- """
- An abstract base widget that can embed in the gtk.Notebook of a
- MessageWindow
- """
-
- def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None):
- # dict { cb id : widget}
- # keep all registered callbacks of widgets, created by self.xml
- self.handlers = {}
- self.type_id = type_id
- self.parent_win = parent_win
- self.widget_name = widget_name
- self.contact = contact
- self.account = account
- self.hide_chat_buttons = False
- self.resource = resource
-
- self.session = None
-
- gajim.last_message_time[self.account][self.get_full_jid()] = 0
-
- self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % widget_name)
- self.widget = self.xml.get_object('%s_vbox' % widget_name)
-
- def get_full_jid(self):
- fjid = self.contact.jid
- if self.resource:
- fjid += '/' + self.resource
- return fjid
-
- def set_control_active(self, state):
- """
- Called when the control becomes active (state is True) or inactive (state
- is False)
- """
- pass # Derived classes MUST implement this method
-
- def minimizable(self):
- """
- Called to check if control can be minimized
-
- Derived classes MAY implement this.
- """
- return False
-
- def safe_shutdown(self):
- """
- Called to check if control can be closed without loosing data.
- returns True if control can be closed safely else False
-
- Derived classes MAY implement this.
- """
- return True
-
- def allow_shutdown(self, method, on_response_yes, on_response_no,
- on_response_minimize):
- """
- Called to check is a control is allowed to shutdown.
- If a control is not in a suitable shutdown state this method
- should call on_response_no, else on_response_yes or
- on_response_minimize
-
- Derived classes MAY implement this.
- """
- on_response_yes(self)
-
- def shutdown(self):
- """
- Derived classes MUST implement this
- """
- pass
-
- def repaint_themed_widgets(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def update_ui(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def toggle_emoticons(self):
- """
- Derived classes MAY implement this
- """
- pass
-
- def update_font(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def update_tags(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def get_tab_label(self, chatstate):
- """
- Return a suitable tab label string. Returns a tuple such as: (label_str,
- color) either of which can be None if chatstate is given that means we
- have HE SENT US a chatstate and we want it displayed
-
- Derivded classes MUST implement this.
- """
- # Return a markup'd label and optional gtk.Color in a tupple like:
- # return (label_str, None)
- pass
-
- def get_tab_image(self, count_unread=True):
- # Return a suitable tab image for display.
- # None clears any current label.
- return None
-
- def prepare_context_menu(self):
- """
- Derived classes SHOULD implement this
- """
- return None
-
- def chat_buttons_set_visible(self, state):
- """
- Derived classes MAY implement this
- """
- self.hide_chat_buttons = state
-
- def got_connected(self):
- pass
-
- def got_disconnected(self):
- pass
-
- def get_specific_unread(self):
- return len(gajim.events.get_events(self.account,
- self.contact.jid))
-
- def set_session(self, session):
- oldsession = None
- if hasattr(self, 'session'):
- oldsession = self.session
-
- if oldsession and session == oldsession:
- return
-
- self.session = session
-
- if session:
- session.control = self
-
- if oldsession:
- oldsession.control = None
-
- jid = self.contact.jid
- if self.resource:
- jid += '/' + self.resource
-
- crypto_changed = bool(session and session.enable_encryption) != \
- bool(oldsession and oldsession.enable_encryption)
-
- if crypto_changed:
- self.print_esession_details()
-
- def send_message(self, message, keyID='', type_='chat', chatstate=None,
- msg_id=None, composing_xep=None, resource=None, user_nick=None,
- xhtml=None, callback=None, callback_args=[]):
- # Send the given message to the active tab.
- # Doesn't return None if error
- jid = self.contact.jid
-
- message = helpers.remove_invalid_xml_chars(message)
-
- original_message = message
- conn = gajim.connections[self.account]
-
- if not self.session:
- if not resource:
- if self.resource:
- resource = self.resource
- else:
- resource = self.contact.resource
- sess = conn.find_controlless_session(jid, resource=resource)
-
- if self.resource:
- jid += '/' + self.resource
-
- if not sess:
- if self.type_id == TYPE_PM:
- sess = conn.make_new_session(jid, type_='pm')
- else:
- sess = conn.make_new_session(jid)
-
- self.set_session(sess)
-
- # Send and update history
- conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate,
- msg_id=msg_id, composing_xep=composing_xep, resource=self.resource,
- user_nick=user_nick, session=self.session,
- original_message=original_message, xhtml=xhtml, callback=callback,
- callback_args=callback_args)
-
-# vim: se ts=3:
+ """
+ An abstract base widget that can embed in the gtk.Notebook of a
+ MessageWindow
+ """
+
+ def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None):
+ # dict { cb id : widget}
+ # keep all registered callbacks of widgets, created by self.xml
+ self.handlers = {}
+ self.type_id = type_id
+ self.parent_win = parent_win
+ self.widget_name = widget_name
+ self.contact = contact
+ self.account = account
+ self.hide_chat_buttons = False
+ self.resource = resource
+
+ self.session = None
+
+ gajim.last_message_time[self.account][self.get_full_jid()] = 0
+
+ self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % widget_name)
+ self.widget = self.xml.get_object('%s_vbox' % widget_name)
+
+ def get_full_jid(self):
+ fjid = self.contact.jid
+ if self.resource:
+ fjid += '/' + self.resource
+ return fjid
+
+ def set_control_active(self, state):
+ """
+ Called when the control becomes active (state is True) or inactive (state
+ is False)
+ """
+ pass # Derived classes MUST implement this method
+
+ def minimizable(self):
+ """
+ Called to check if control can be minimized
+
+ Derived classes MAY implement this.
+ """
+ return False
+
+ def safe_shutdown(self):
+ """
+ Called to check if control can be closed without loosing data.
+ returns True if control can be closed safely else False
+
+ Derived classes MAY implement this.
+ """
+ return True
+
+ def allow_shutdown(self, method, on_response_yes, on_response_no,
+ on_response_minimize):
+ """
+ Called to check is a control is allowed to shutdown.
+ If a control is not in a suitable shutdown state this method
+ should call on_response_no, else on_response_yes or
+ on_response_minimize
+
+ Derived classes MAY implement this.
+ """
+ on_response_yes(self)
+
+ def shutdown(self):
+ """
+ Derived classes MUST implement this
+ """
+ pass
+
+ def repaint_themed_widgets(self):
+ """
+ Derived classes SHOULD implement this
+ """
+ pass
+
+ def update_ui(self):
+ """
+ Derived classes SHOULD implement this
+ """
+ pass
+
+ def toggle_emoticons(self):
+ """
+ Derived classes MAY implement this
+ """
+ pass
+
+ def update_font(self):
+ """
+ Derived classes SHOULD implement this
+ """
+ pass
+
+ def update_tags(self):
+ """
+ Derived classes SHOULD implement this
+ """
+ pass
+
+ def get_tab_label(self, chatstate):
+ """
+ Return a suitable tab label string. Returns a tuple such as: (label_str,
+ color) either of which can be None if chatstate is given that means we
+ have HE SENT US a chatstate and we want it displayed
+
+ Derivded classes MUST implement this.
+ """
+ # Return a markup'd label and optional gtk.Color in a tupple like:
+ # return (label_str, None)
+ pass
+
+ def get_tab_image(self, count_unread=True):
+ # Return a suitable tab image for display.
+ # None clears any current label.
+ return None
+
+ def prepare_context_menu(self):
+ """
+ Derived classes SHOULD implement this
+ """
+ return None
+
+ def chat_buttons_set_visible(self, state):
+ """
+ Derived classes MAY implement this
+ """
+ self.hide_chat_buttons = state
+
+ def got_connected(self):
+ pass
+
+ def got_disconnected(self):
+ pass
+
+ def get_specific_unread(self):
+ return len(gajim.events.get_events(self.account,
+ self.contact.jid))
+
+ def set_session(self, session):
+ oldsession = None
+ if hasattr(self, 'session'):
+ oldsession = self.session
+
+ if oldsession and session == oldsession:
+ return
+
+ self.session = session
+
+ if session:
+ session.control = self
+
+ if oldsession:
+ oldsession.control = None
+
+ jid = self.contact.jid
+ if self.resource:
+ jid += '/' + self.resource
+
+ crypto_changed = bool(session and session.enable_encryption) != \
+ bool(oldsession and oldsession.enable_encryption)
+
+ if crypto_changed:
+ self.print_esession_details()
+
+ def send_message(self, message, keyID='', type_='chat', chatstate=None,
+ msg_id=None, composing_xep=None, resource=None, user_nick=None,
+ xhtml=None, callback=None, callback_args=[]):
+ # Send the given message to the active tab.
+ # Doesn't return None if error
+ jid = self.contact.jid
+
+ message = helpers.remove_invalid_xml_chars(message)
+
+ original_message = message
+ conn = gajim.connections[self.account]
+
+ if not self.session:
+ if not resource:
+ if self.resource:
+ resource = self.resource
+ else:
+ resource = self.contact.resource
+ sess = conn.find_controlless_session(jid, resource=resource)
+
+ if self.resource:
+ jid += '/' + self.resource
+
+ if not sess:
+ if self.type_id == TYPE_PM:
+ sess = conn.make_new_session(jid, type_='pm')
+ else:
+ sess = conn.make_new_session(jid)
+
+ self.set_session(sess)
+
+ # Send and update history
+ conn.send_message(jid, message, keyID, type_=type_, chatstate=chatstate,
+ msg_id=msg_id, composing_xep=composing_xep, resource=self.resource,
+ user_nick=user_nick, session=self.session,
+ original_message=original_message, xhtml=xhtml, callback=callback,
+ callback_args=callback_args)
diff --git a/src/message_textview.py b/src/message_textview.py
index 37c865043..1b568af6b 100644
--- a/src/message_textview.py
+++ b/src/message_textview.py
@@ -31,281 +31,281 @@ import gtkgui_helpers
from common import gajim
class MessageTextView(gtk.TextView):
- """
- Class for the message textview (where user writes new messages) for
- chat/groupchat windows
- """
- UNDO_LIMIT = 20
- __gsignals__ = dict(
- mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
- None, # return value
- (int, gtk.gdk.ModifierType ) # arguments
- )
- )
-
- def __init__(self):
- gtk.TextView.__init__(self)
-
- # set properties
- self.set_border_width(1)
- self.set_accepts_tab(True)
- self.set_editable(True)
- self.set_cursor_visible(True)
- self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
- self.set_left_margin(2)
- self.set_right_margin(2)
- self.set_pixels_above_lines(2)
- self.set_pixels_below_lines(2)
-
- # set undo list
- self.undo_list = []
- # needed to know if we undid something
- self.undo_pressed = False
- self.lang = None # Lang used for spell checking
- _buffer = self.get_buffer()
- self.begin_tags = {}
- self.end_tags = {}
- self.color_tags = []
- self.fonts_tags = []
- self.other_tags = {}
- self.other_tags['bold'] = _buffer.create_tag('bold')
- self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD)
- self.begin_tags['bold'] = '<strong>'
- self.end_tags['bold'] = '</strong>'
- self.other_tags['italic'] = _buffer.create_tag('italic')
- self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC)
- self.begin_tags['italic'] = '<em>'
- self.end_tags['italic'] = '</em>'
- self.other_tags['underline'] = _buffer.create_tag('underline')
- self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE)
- self.begin_tags['underline'] = '<span style="text-decoration: underline;">'
- self.end_tags['underline'] = '</span>'
- self.other_tags['strike'] = _buffer.create_tag('strike')
- self.other_tags['strike'].set_property('strikethrough', True)
- self.begin_tags['strike'] = '<span style="text-decoration: line-through;">'
- self.end_tags['strike'] = '</span>'
-
- def make_clickable_urls(self, text):
- _buffer = self.get_buffer()
-
- start = 0
- end = 0
- index = 0
-
- new_text = ''
- iterator = gajim.interface.link_pattern_re.finditer(text)
- for match in iterator:
- start, end = match.span()
- url = text[start:end]
- if start != 0:
- text_before_special_text = text[index:start]
- else:
- text_before_special_text = ''
- end_iter = _buffer.get_end_iter()
- # we insert normal text
- new_text += text_before_special_text + \
- '<a href="'+ url +'">' + url + '</a>'
-
- index = end # update index
-
- if end < len(text):
- new_text += text[end:]
-
- return new_text # the position after *last* special text
-
- def get_active_tags(self):
- start, finish = self.get_active_iters()
- active_tags = []
- for tag in start.get_tags():
- active_tags.append(tag.get_property('name'))
- return active_tags
-
- def get_active_iters(self):
- _buffer = self.get_buffer()
- return_val = _buffer.get_selection_bounds()
- if return_val: # if sth was selected
- start, finish = return_val[0], return_val[1]
- else:
- start, finish = _buffer.get_bounds()
- return (start, finish)
-
- def set_tag(self, widget, tag):
- _buffer = self.get_buffer()
- start, finish = self.get_active_iters()
- if start.has_tag(self.other_tags[tag]):
- _buffer.remove_tag_by_name(tag, start, finish)
- else:
- if tag == 'underline':
- _buffer.remove_tag_by_name('strike', start, finish)
- elif tag == 'strike':
- _buffer.remove_tag_by_name('underline', start, finish)
- _buffer.apply_tag_by_name(tag, start, finish)
-
- def clear_tags(self, widget):
- _buffer = self.get_buffer()
- start, finish = self.get_active_iters()
- _buffer.remove_all_tags(start, finish)
-
- def color_set(self, widget, response, color):
- if response == -6:
- widget.destroy()
- return
- _buffer = self.get_buffer()
- color = color.get_current_color()
- widget.destroy()
- color_string = gtkgui_helpers.make_color_string(color)
- tag_name = 'color' + color_string
- if not tag_name in self.color_tags:
- tagColor = _buffer.create_tag(tag_name)
- tagColor.set_property('foreground', color_string)
- self.begin_tags[tag_name] = '<span style="color: ' + color_string + ';">'
- self.end_tags[tag_name] = '</span>'
- self.color_tags.append(tag_name)
-
- start, finish = self.get_active_iters()
-
- for tag in self.color_tags:
- _buffer.remove_tag_by_name(tag, start, finish)
-
- _buffer.apply_tag_by_name(tag_name, start, finish)
-
- def font_set(self, widget, response, font):
- if response == -6:
- widget.destroy()
- return
-
- _buffer = self.get_buffer()
-
- font = font.get_font_name()
- font_desc = pango.FontDescription(font)
- family = font_desc.get_family()
- size = font_desc.get_size()
- size = size / pango.SCALE
- weight = font_desc.get_weight()
- style = font_desc.get_style()
-
- widget.destroy()
-
- tag_name = 'font' + font
- if not tag_name in self.fonts_tags:
- tagFont = _buffer.create_tag(tag_name)
- tagFont.set_property('font', family + ' ' + str(size))
- self.begin_tags[tag_name] = \
- '<span style="font-family: ' + family + '; ' + \
- 'font-size: ' + str(size) + 'px">'
- self.end_tags[tag_name] = '</span>'
- self.fonts_tags.append(tag_name)
-
- start, finish = self.get_active_iters()
-
- for tag in self.fonts_tags:
- _buffer.remove_tag_by_name(tag, start, finish)
-
- _buffer.apply_tag_by_name(tag_name, start, finish)
-
- if weight == pango.WEIGHT_BOLD:
- _buffer.apply_tag_by_name('bold', start, finish)
- else:
- _buffer.remove_tag_by_name('bold', start, finish)
-
- if style == pango.STYLE_ITALIC:
- _buffer.apply_tag_by_name('italic', start, finish)
- else:
- _buffer.remove_tag_by_name('italic', start, finish)
-
- def get_xhtml(self):
- _buffer = self.get_buffer()
- old = _buffer.get_start_iter()
- tags = {}
- tags['bold'] = False
- iter = _buffer.get_start_iter()
- old = _buffer.get_start_iter()
- text = ''
- modified = False
-
- def xhtml_special(text):
- text = text.replace('<', '&lt;')
- text = text.replace('>', '&gt;')
- text = text.replace('&', '&amp;')
- text = text.replace('\n', '<br />')
- return text
-
- for tag in iter.get_toggled_tags(True):
- tag_name = tag.get_property('name')
- if tag_name not in self.begin_tags:
- continue
- text += self.begin_tags[tag_name]
- modified = True
- while (iter.forward_to_tag_toggle(None) and not iter.is_end()):
- text += xhtml_special(_buffer.get_text(old, iter))
- old.forward_to_tag_toggle(None)
- new_tags, old_tags, end_tags = [], [], []
- for tag in iter.get_toggled_tags(True):
- tag_name = tag.get_property('name')
- if tag_name not in self.begin_tags:
- continue
- new_tags.append(tag_name)
- modified = True
-
- for tag in iter.get_tags():
- tag_name = tag.get_property('name')
- if tag_name not in self.begin_tags or tag_name not in self.end_tags:
- continue
- if tag_name not in new_tags:
- old_tags.append(tag_name)
-
- for tag in iter.get_toggled_tags(False):
- tag_name = tag.get_property('name')
- if tag_name not in self.end_tags:
- continue
- end_tags.append(tag_name)
-
- for tag in old_tags:
- text += self.end_tags[tag]
- for tag in end_tags:
- text += self.end_tags[tag]
- for tag in new_tags:
- text += self.begin_tags[tag]
- for tag in old_tags:
- text += self.begin_tags[tag]
-
- text += xhtml_special(_buffer.get_text(old, _buffer.get_end_iter()))
- for tag in iter.get_toggled_tags(False):
- tag_name = tag.get_property('name')
- if tag_name not in self.end_tags:
- continue
- text += self.end_tags[tag_name]
-
- if modified:
- return '<p>' + self.make_clickable_urls(text) + '</p>'
- else:
- return None
-
- def destroy(self):
- gobject.idle_add(gc.collect)
-
- def clear(self, widget = None):
- """
- Clear text in the textview
- """
- _buffer = self.get_buffer()
- start, end = _buffer.get_bounds()
- _buffer.delete(start, end)
-
- def save_undo(self, text):
- self.undo_list.append(text)
- if len(self.undo_list) > self.UNDO_LIMIT:
- del self.undo_list[0]
- self.undo_pressed = False
-
- def undo(self, widget=None):
- """
- Undo text in the textview
- """
- _buffer = self.get_buffer()
- if self.undo_list:
- _buffer.set_text(self.undo_list.pop())
- self.undo_pressed = True
+ """
+ Class for the message textview (where user writes new messages) for
+ chat/groupchat windows
+ """
+ UNDO_LIMIT = 20
+ __gsignals__ = dict(
+ mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+ None, # return value
+ (int, gtk.gdk.ModifierType ) # arguments
+ )
+ )
+
+ def __init__(self):
+ gtk.TextView.__init__(self)
+
+ # set properties
+ self.set_border_width(1)
+ self.set_accepts_tab(True)
+ self.set_editable(True)
+ self.set_cursor_visible(True)
+ self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+ self.set_left_margin(2)
+ self.set_right_margin(2)
+ self.set_pixels_above_lines(2)
+ self.set_pixels_below_lines(2)
+
+ # set undo list
+ self.undo_list = []
+ # needed to know if we undid something
+ self.undo_pressed = False
+ self.lang = None # Lang used for spell checking
+ _buffer = self.get_buffer()
+ self.begin_tags = {}
+ self.end_tags = {}
+ self.color_tags = []
+ self.fonts_tags = []
+ self.other_tags = {}
+ self.other_tags['bold'] = _buffer.create_tag('bold')
+ self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD)
+ self.begin_tags['bold'] = '<strong>'
+ self.end_tags['bold'] = '</strong>'
+ self.other_tags['italic'] = _buffer.create_tag('italic')
+ self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC)
+ self.begin_tags['italic'] = '<em>'
+ self.end_tags['italic'] = '</em>'
+ self.other_tags['underline'] = _buffer.create_tag('underline')
+ self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE)
+ self.begin_tags['underline'] = '<span style="text-decoration: underline;">'
+ self.end_tags['underline'] = '</span>'
+ self.other_tags['strike'] = _buffer.create_tag('strike')
+ self.other_tags['strike'].set_property('strikethrough', True)
+ self.begin_tags['strike'] = '<span style="text-decoration: line-through;">'
+ self.end_tags['strike'] = '</span>'
+
+ def make_clickable_urls(self, text):
+ _buffer = self.get_buffer()
+
+ start = 0
+ end = 0
+ index = 0
+
+ new_text = ''
+ iterator = gajim.interface.link_pattern_re.finditer(text)
+ for match in iterator:
+ start, end = match.span()
+ url = text[start:end]
+ if start != 0:
+ text_before_special_text = text[index:start]
+ else:
+ text_before_special_text = ''
+ end_iter = _buffer.get_end_iter()
+ # we insert normal text
+ new_text += text_before_special_text + \
+ '<a href="'+ url +'">' + url + '</a>'
+
+ index = end # update index
+
+ if end < len(text):
+ new_text += text[end:]
+
+ return new_text # the position after *last* special text
+
+ def get_active_tags(self):
+ start, finish = self.get_active_iters()
+ active_tags = []
+ for tag in start.get_tags():
+ active_tags.append(tag.get_property('name'))
+ return active_tags
+
+ def get_active_iters(self):
+ _buffer = self.get_buffer()
+ return_val = _buffer.get_selection_bounds()
+ if return_val: # if sth was selected
+ start, finish = return_val[0], return_val[1]
+ else:
+ start, finish = _buffer.get_bounds()
+ return (start, finish)
+
+ def set_tag(self, widget, tag):
+ _buffer = self.get_buffer()
+ start, finish = self.get_active_iters()
+ if start.has_tag(self.other_tags[tag]):
+ _buffer.remove_tag_by_name(tag, start, finish)
+ else:
+ if tag == 'underline':
+ _buffer.remove_tag_by_name('strike', start, finish)
+ elif tag == 'strike':
+ _buffer.remove_tag_by_name('underline', start, finish)
+ _buffer.apply_tag_by_name(tag, start, finish)
+
+ def clear_tags(self, widget):
+ _buffer = self.get_buffer()
+ start, finish = self.get_active_iters()
+ _buffer.remove_all_tags(start, finish)
+
+ def color_set(self, widget, response, color):
+ if response == -6:
+ widget.destroy()
+ return
+ _buffer = self.get_buffer()
+ color = color.get_current_color()
+ widget.destroy()
+ color_string = gtkgui_helpers.make_color_string(color)
+ tag_name = 'color' + color_string
+ if not tag_name in self.color_tags:
+ tagColor = _buffer.create_tag(tag_name)
+ tagColor.set_property('foreground', color_string)
+ self.begin_tags[tag_name] = '<span style="color: ' + color_string + ';">'
+ self.end_tags[tag_name] = '</span>'
+ self.color_tags.append(tag_name)
+
+ start, finish = self.get_active_iters()
+
+ for tag in self.color_tags:
+ _buffer.remove_tag_by_name(tag, start, finish)
+
+ _buffer.apply_tag_by_name(tag_name, start, finish)
+
+ def font_set(self, widget, response, font):
+ if response == -6:
+ widget.destroy()
+ return
+
+ _buffer = self.get_buffer()
+
+ font = font.get_font_name()
+ font_desc = pango.FontDescription(font)
+ family = font_desc.get_family()
+ size = font_desc.get_size()
+ size = size / pango.SCALE
+ weight = font_desc.get_weight()
+ style = font_desc.get_style()
+
+ widget.destroy()
+
+ tag_name = 'font' + font
+ if not tag_name in self.fonts_tags:
+ tagFont = _buffer.create_tag(tag_name)
+ tagFont.set_property('font', family + ' ' + str(size))
+ self.begin_tags[tag_name] = \
+ '<span style="font-family: ' + family + '; ' + \
+ 'font-size: ' + str(size) + 'px">'
+ self.end_tags[tag_name] = '</span>'
+ self.fonts_tags.append(tag_name)
+
+ start, finish = self.get_active_iters()
+
+ for tag in self.fonts_tags:
+ _buffer.remove_tag_by_name(tag, start, finish)
+
+ _buffer.apply_tag_by_name(tag_name, start, finish)
+
+ if weight == pango.WEIGHT_BOLD:
+ _buffer.apply_tag_by_name('bold', start, finish)
+ else:
+ _buffer.remove_tag_by_name('bold', start, finish)
+
+ if style == pango.STYLE_ITALIC:
+ _buffer.apply_tag_by_name('italic', start, finish)
+ else:
+ _buffer.remove_tag_by_name('italic', start, finish)
+
+ def get_xhtml(self):
+ _buffer = self.get_buffer()
+ old = _buffer.get_start_iter()
+ tags = {}
+ tags['bold'] = False
+ iter = _buffer.get_start_iter()
+ old = _buffer.get_start_iter()
+ text = ''
+ modified = False
+
+ def xhtml_special(text):
+ text = text.replace('<', '&lt;')
+ text = text.replace('>', '&gt;')
+ text = text.replace('&', '&amp;')
+ text = text.replace('\n', '<br />')
+ return text
+
+ for tag in iter.get_toggled_tags(True):
+ tag_name = tag.get_property('name')
+ if tag_name not in self.begin_tags:
+ continue
+ text += self.begin_tags[tag_name]
+ modified = True
+ while (iter.forward_to_tag_toggle(None) and not iter.is_end()):
+ text += xhtml_special(_buffer.get_text(old, iter))
+ old.forward_to_tag_toggle(None)
+ new_tags, old_tags, end_tags = [], [], []
+ for tag in iter.get_toggled_tags(True):
+ tag_name = tag.get_property('name')
+ if tag_name not in self.begin_tags:
+ continue
+ new_tags.append(tag_name)
+ modified = True
+
+ for tag in iter.get_tags():
+ tag_name = tag.get_property('name')
+ if tag_name not in self.begin_tags or tag_name not in self.end_tags:
+ continue
+ if tag_name not in new_tags:
+ old_tags.append(tag_name)
+
+ for tag in iter.get_toggled_tags(False):
+ tag_name = tag.get_property('name')
+ if tag_name not in self.end_tags:
+ continue
+ end_tags.append(tag_name)
+
+ for tag in old_tags:
+ text += self.end_tags[tag]
+ for tag in end_tags:
+ text += self.end_tags[tag]
+ for tag in new_tags:
+ text += self.begin_tags[tag]
+ for tag in old_tags:
+ text += self.begin_tags[tag]
+
+ text += xhtml_special(_buffer.get_text(old, _buffer.get_end_iter()))
+ for tag in iter.get_toggled_tags(False):
+ tag_name = tag.get_property('name')
+ if tag_name not in self.end_tags:
+ continue
+ text += self.end_tags[tag_name]
+
+ if modified:
+ return '<p>' + self.make_clickable_urls(text) + '</p>'
+ else:
+ return None
+
+ def destroy(self):
+ gobject.idle_add(gc.collect)
+
+ def clear(self, widget = None):
+ """
+ Clear text in the textview
+ """
+ _buffer = self.get_buffer()
+ start, end = _buffer.get_bounds()
+ _buffer.delete(start, end)
+
+ def save_undo(self, text):
+ self.undo_list.append(text)
+ if len(self.undo_list) > self.UNDO_LIMIT:
+ del self.undo_list[0]
+ self.undo_pressed = False
+
+ def undo(self, widget=None):
+ """
+ Undo text in the textview
+ """
+ _buffer = self.get_buffer()
+ if self.undo_list:
+ _buffer.set_text(self.undo_list.pop())
+ self.undo_pressed = True
# We register depending on keysym and modifier some bindings
# but we also pass those as param so we can construct fake Event
@@ -317,50 +317,49 @@ class MessageTextView(gtk.TextView):
# CTRL + SHIFT + TAB
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.ISO_Left_Tab,
- gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.ISO_Left_Tab,
- gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
+ gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.ISO_Left_Tab,
+ gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
# CTRL + TAB
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab,
- gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Tab,
- gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
+ gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Tab,
+ gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
# TAB
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab,
- 0, 'mykeypress', int, gtk.keysyms.Tab, gtk.gdk.ModifierType, 0)
+ 0, 'mykeypress', int, gtk.keysyms.Tab, gtk.gdk.ModifierType, 0)
# CTRL + UP
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Up,
- gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Up,
- gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
+ gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Up,
+ gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
# CTRL + DOWN
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Down,
- gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Down,
- gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
+ gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Down,
+ gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
# ENTER
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return,
- 0, 'mykeypress', int, gtk.keysyms.Return,
- gtk.gdk.ModifierType, 0)
+ 0, 'mykeypress', int, gtk.keysyms.Return,
+ gtk.gdk.ModifierType, 0)
# Ctrl + Enter
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return,
- gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Return,
- gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
+ gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Return,
+ gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
# Keypad Enter
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter,
- 0, 'mykeypress', int, gtk.keysyms.KP_Enter,
- gtk.gdk.ModifierType, 0)
+ 0, 'mykeypress', int, gtk.keysyms.KP_Enter,
+ gtk.gdk.ModifierType, 0)
# Ctrl + Keypad Enter
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter,
- gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.KP_Enter,
- gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
+ gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.KP_Enter,
+ gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
# Ctrl + z
gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.z,
- gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.z,
- gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
-# vim: se ts=3:
+ gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.z,
+ gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
diff --git a/src/message_window.py b/src/message_window.py
index 82417fd34..22c14c327 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -42,1167 +42,1165 @@ from common import gajim
####################
class MessageWindow(object):
- """
- Class for windows which contain message like things; chats, groupchats, etc
- """
-
- # DND_TARGETS is the targets needed by drag_source_set and drag_dest_set
- DND_TARGETS = [('GAJIM_TAB', 0, 81)]
- hid = 0 # drag_data_received handler id
- (
- CLOSE_TAB_MIDDLE_CLICK,
- CLOSE_ESC,
- CLOSE_CLOSE_BUTTON,
- CLOSE_COMMAND,
- CLOSE_CTRL_KEY
- ) = range(5)
-
- def __init__(self, acct, type_, parent_window=None, parent_paned=None):
- # A dictionary of dictionaries
- # where _contacts[account][jid] == A MessageControl
- self._controls = {}
-
- # If None, the window is not tied to any specific account
- self.account = acct
- # If None, the window is not tied to any specific type
- self.type_ = type_
- # dict { handler id: widget}. Keeps callbacks, which
- # lead to cylcular references
- self.handlers = {}
- # Don't show warning dialogs when we want to delete the window
- self.dont_warn_on_delete = False
-
- self.widget_name = 'message_window'
- self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % self.widget_name)
- self.window = self.xml.get_object(self.widget_name)
- self.notebook = self.xml.get_object('notebook')
- self.parent_paned = None
-
- if parent_window:
- orig_window = self.window
- self.window = parent_window
- self.parent_paned = parent_paned
- self.notebook.reparent(self.parent_paned)
- self.parent_paned.pack2(self.notebook, resize=True, shrink=True)
- orig_window.destroy()
- del orig_window
-
- # NOTE: we use 'connect_after' here because in
- # MessageWindowMgr._new_window we register handler that saves window
- # state when closing it, and it should be called before
- # MessageWindow._on_window_delete, which manually destroys window
- # through win.destroy() - this means no additional handlers for
- # 'delete-event' are called.
- id_ = self.window.connect_after('delete-event', self._on_window_delete)
- self.handlers[id_] = self.window
- id_ = self.window.connect('destroy', self._on_window_destroy)
- self.handlers[id_] = self.window
- id_ = self.window.connect('focus-in-event', self._on_window_focus)
- self.handlers[id_] = self.window
-
- keys=['<Control>f', '<Control>g', '<Control>h', '<Control>i',
- '<Control>l', '<Control>L', '<Control><Shift>n', '<Control>u',
- '<Control>b', '<Control>F4',
- '<Control>w', '<Control>Page_Up', '<Control>Page_Down', '<Alt>Right',
- '<Alt>Left', '<Alt>d', '<Alt>c', '<Alt>m', '<Alt>t', 'Escape'] + \
- ['<Alt>'+str(i) for i in xrange(10)]
- accel_group = gtk.AccelGroup()
- for key in keys:
- keyval, mod = gtk.accelerator_parse(key)
- accel_group.connect_group(keyval, mod, gtk.ACCEL_VISIBLE,
- self.accel_group_func)
- self.window.add_accel_group(accel_group)
-
- # gtk+ doesn't make use of the motion notify on gtkwindow by default
- # so this line adds that
- self.window.add_events(gtk.gdk.POINTER_MOTION_MASK)
-
- id_ = self.notebook.connect('switch-page',
- self._on_notebook_switch_page)
- self.handlers[id_] = self.notebook
- id_ = self.notebook.connect('key-press-event',
- self._on_notebook_key_press)
- self.handlers[id_] = self.notebook
-
- # Tab customizations
- pref_pos = gajim.config.get('tabs_position')
- if pref_pos == 'bottom':
- nb_pos = gtk.POS_BOTTOM
- elif pref_pos == 'left':
- nb_pos = gtk.POS_LEFT
- elif pref_pos == 'right':
- nb_pos = gtk.POS_RIGHT
- else:
- nb_pos = gtk.POS_TOP
- self.notebook.set_tab_pos(nb_pos)
- window_mode = gajim.interface.msg_win_mgr.mode
- if gajim.config.get('tabs_always_visible') or \
- window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- self.notebook.set_show_tabs(True)
- else:
- self.notebook.set_show_tabs(False)
- self.notebook.set_show_border(gajim.config.get('tabs_border'))
- self.show_icon()
-
- def change_account_name(self, old_name, new_name):
- if old_name in self._controls:
- self._controls[new_name] = self._controls[old_name]
- del self._controls[old_name]
-
- for ctrl in self.controls():
- if ctrl.account == old_name:
- ctrl.account = new_name
- if self.account == old_name:
- self.account = new_name
-
- def change_jid(self, account, old_jid, new_jid):
- """
- Called when the full jid of the control is changed
- """
- if account not in self._controls:
- return
- if old_jid not in self._controls[account]:
- return
- if old_jid == new_jid:
- return
- self._controls[account][new_jid] = self._controls[account][old_jid]
- del self._controls[account][old_jid]
-
- def get_num_controls(self):
- return sum(len(d) for d in self._controls.values())
-
- def resize(self, width, height):
- gtkgui_helpers.resize_window(self.window, width, height)
-
- def _on_window_focus(self, widget, event):
- # window received focus, so if we had urgency REMOVE IT
- # NOTE: we do not have to read the message (it maybe in a bg tab)
- # to remove urgency hint so this functions does that
- gtkgui_helpers.set_unset_urgency_hint(self.window, False)
-
- ctrl = self.get_active_control()
- if ctrl:
- ctrl.set_control_active(True)
- # Undo "unread" state display, etc.
- if ctrl.type_id == message_control.TYPE_GC:
- self.redraw_tab(ctrl, 'active')
- else:
- # NOTE: we do not send any chatstate to preserve
- # inactive, gone, etc.
- self.redraw_tab(ctrl)
-
- def _on_window_delete(self, win, event):
- if self.dont_warn_on_delete:
- # Destroy the window
- return False
-
- # Number of controls that will be closed and for which we'll loose data:
- # chat, pm, gc that won't go in roster
- number_of_closed_control = 0
- for ctrl in self.controls():
- if not ctrl.safe_shutdown():
- number_of_closed_control += 1
-
- if number_of_closed_control > 1:
- def on_yes1(checked):
- if checked:
- gajim.config.set('confirm_close_multiple_tabs', False)
- self.dont_warn_on_delete = True
- for ctrl in self.controls():
- if ctrl.minimizable():
- ctrl.minimize()
- win.destroy()
-
- if not gajim.config.get('confirm_close_multiple_tabs'):
- # destroy window
- return False
- dialogs.YesNoDialog(
- _('You are going to close several tabs'),
- _('Do you really want to close them all?'),
- checktext=_('Do _not ask me again'), on_response_yes=on_yes1)
- return True
-
- def on_yes(ctrl):
- if self.on_delete_ok == 1:
- self.dont_warn_on_delete = True
- win.destroy()
- self.on_delete_ok -= 1
-
- def on_no(ctrl):
- return
-
- def on_minimize(ctrl):
- ctrl.minimize()
- if self.on_delete_ok == 1:
- self.dont_warn_on_delete = True
- win.destroy()
- self.on_delete_ok -= 1
-
- # Make sure all controls are okay with being deleted
- self.on_delete_ok = self.get_nb_controls()
- for ctrl in self.controls():
- ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON, on_yes, on_no,
- on_minimize)
- return True # halt the delete for the moment
-
- def _on_window_destroy(self, win):
- for ctrl in self.controls():
- ctrl.shutdown()
- self._controls.clear()
- # Clean up handlers connected to the parent window, this is important since
- # self.window may be the RosterWindow
- for i in self.handlers.keys():
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers[i]
- del self.handlers
-
- def new_tab(self, control):
- fjid = control.get_full_jid()
-
- if control.account not in self._controls:
- self._controls[control.account] = {}
-
- self._controls[control.account][fjid] = control
-
- if self.get_num_controls() == 2:
- # is first conversation_textview scrolled down ?
- scrolled = False
- first_widget = self.notebook.get_nth_page(0)
- ctrl = self._widget_to_control(first_widget)
- conv_textview = ctrl.conv_textview
- if conv_textview.at_the_end():
- scrolled = True
- self.notebook.set_show_tabs(True)
- if scrolled:
- gobject.idle_add(conv_textview.scroll_to_end_iter)
-
- # Add notebook page and connect up to the tab's close button
- xml = gtkgui_helpers.get_gtk_builder('message_window.ui', 'chat_tab_ebox')
- tab_label_box = xml.get_object('chat_tab_ebox')
- widget = xml.get_object('tab_close_button')
- id_ = widget.connect('clicked', self._on_close_button_clicked, control)
- control.handlers[id_] = widget
-
- id_ = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event,
- control.widget)
- control.handlers[id_] = tab_label_box
- self.notebook.append_page(control.widget, tab_label_box)
-
- self.notebook.set_tab_reorderable(control.widget, True)
-
- self.redraw_tab(control)
- if self.parent_paned:
- self.notebook.show_all()
- else:
- self.window.show_all()
- # NOTE: we do not call set_control_active(True) since we don't know whether
- # the tab is the active one.
- self.show_title()
-
- def on_tab_eventbox_button_press_event(self, widget, event, child):
- if event.button == 3: # right click
- n = self.notebook.page_num(child)
- self.notebook.set_current_page(n)
- self.popup_menu(event)
- elif event.button == 2: # middle click
- ctrl = self._widget_to_control(child)
- self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK)
-
- def _on_message_textview_mykeypress_event(self, widget, event_keyval,
- event_keymod):
- # NOTE: handles mykeypress which is custom signal; see message_textview.py
-
- # construct event instance from binding
- event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
- event.keyval = event_keyval
- event.state = event_keymod
- event.time = 0 # assign current time
-
- if event.state & gtk.gdk.CONTROL_MASK:
- # Tab switch bindings
- if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
- self.move_to_next_unread_tab(True)
- elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB
- self.move_to_next_unread_tab(False)
- elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN
- self.notebook.emit('key_press_event', event)
- elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP
- self.notebook.emit('key_press_event', event)
-
- def accel_group_func(self, accel_group, acceleratable, keyval, modifier):
- st = '1234567890' # alt+1 means the first tab (tab 0)
- control = self.get_active_control()
- if not control:
- # No more control in this window
- return
-
- # CTRL mask
- if modifier & gtk.gdk.CONTROL_MASK:
- if keyval == gtk.keysyms.h: # CTRL + h
- control._on_history_menuitem_activate()
- elif control.type_id == message_control.TYPE_CHAT and \
- keyval == gtk.keysyms.f: # CTRL + f
- control._on_send_file_menuitem_activate(None)
- elif control.type_id == message_control.TYPE_CHAT and \
- keyval == gtk.keysyms.g: # CTRL + g
- control._on_convert_to_gc_menuitem_activate(None)
- elif control.type_id in (message_control.TYPE_CHAT,
- message_control.TYPE_PM) and keyval == gtk.keysyms.i: # CTRL + i
- control._on_contact_information_menuitem_activate(None)
- elif keyval == gtk.keysyms.l or keyval == gtk.keysyms.L: # CTRL + l|L
- control.conv_textview.clear()
- elif keyval == gtk.keysyms.u: # CTRL + u: emacs style clear line
- control.clear(control.msg_textview)
- elif control.type_id == message_control.TYPE_GC and \
- keyval == gtk.keysyms.b: # CTRL + b
- control._on_bookmark_room_menuitem_activate(None)
- # Tab switch bindings
- elif keyval == gtk.keysyms.F4: # CTRL + F4
- self.remove_tab(control, self.CLOSE_CTRL_KEY)
- elif keyval == gtk.keysyms.w: # CTRL + w
- # CTRL + w removes latest word before sursor when User uses emacs
- # theme
- if not gtk.settings_get_default().get_property(
- 'gtk-key-theme-name') == 'Emacs':
- self.remove_tab(control, self.CLOSE_CTRL_KEY)
- elif keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down):
- # CTRL + PageUp | PageDown
- # Create event and send it to notebook
- event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
- event.window = self.window.window
- event.time = int(time.time())
- event.state = gtk.gdk.CONTROL_MASK
- event.keyval = int(keyval)
- self.notebook.emit('key_press_event', event)
-
- if modifier & gtk.gdk.SHIFT_MASK:
- # CTRL + SHIFT
- if control.type_id == message_control.TYPE_GC and \
- keyval == gtk.keysyms.n: # CTRL + SHIFT + n
- control._on_change_nick_menuitem_activate(None)
- # MOD1 (ALT) mask
- elif modifier & gtk.gdk.MOD1_MASK:
- # Tab switch bindings
- if keyval == gtk.keysyms.Right: # ALT + RIGHT
- new = self.notebook.get_current_page() + 1
- if new >= self.notebook.get_n_pages():
- new = 0
- self.notebook.set_current_page(new)
- elif keyval == gtk.keysyms.Left: # ALT + LEFT
- new = self.notebook.get_current_page() - 1
- if new < 0:
- new = self.notebook.get_n_pages() - 1
- self.notebook.set_current_page(new)
- elif chr(keyval) in st: # ALT + 1,2,3..
- self.notebook.set_current_page(st.index(chr(keyval)))
- elif keyval == gtk.keysyms.c: # ALT + C toggles chat buttons
- control.chat_buttons_set_visible(not control.hide_chat_buttons)
- elif keyval == gtk.keysyms.m: # ALT + M show emoticons menu
- control.show_emoticons_menu()
- elif keyval == gtk.keysyms.d: # ALT + D show actions menu
- control.on_actions_button_clicked(control.actions_button)
- elif control.type_id == message_control.TYPE_GC and \
- keyval == gtk.keysyms.t: # ALT + t
- control._on_change_subject_menuitem_activate(None)
- # Close tab bindings
- elif keyval == gtk.keysyms.Escape and \
- gajim.config.get('escape_key_closes'): # Escape
- self.remove_tab(control, self.CLOSE_ESC)
- return True
-
- def _on_close_button_clicked(self, button, control):
- """
- When close button is pressed: close a tab
- """
- self.remove_tab(control, self.CLOSE_CLOSE_BUTTON)
-
- def show_icon(self):
- window_mode = gajim.interface.msg_win_mgr.mode
- icon = None
- if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_NEVER:
- ctrl = self.get_active_control()
- if not ctrl:
- return
- icon = ctrl.get_tab_image(count_unread=False)
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS:
- pass # keep default icon
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- pass # keep default icon
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT:
- pass # keep default icon
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
- if self.type_ == 'gc':
- icon = gtkgui_helpers.load_icon('muc_active')
- else:
- # chat, pm
- icon = gtkgui_helpers.load_icon('online')
- if icon:
- self.window.set_icon(icon.get_pixbuf())
-
- def show_title(self, urgent=True, control=None):
- """
- Redraw the window's title
- """
- if not control:
- control = self.get_active_control()
- if not control:
- # No more control in this window
- return
- unread = 0
- for ctrl in self.controls():
- if ctrl.type_id == message_control.TYPE_GC and not \
- gajim.config.get('notify_on_all_muc_messages') and not \
- ctrl.attention_flag:
- # count only pm messages
- unread += ctrl.get_nb_unread_pm()
- continue
- unread += ctrl.get_nb_unread()
-
- unread_str = ''
- if unread > 1:
- unread_str = '[' + unicode(unread) + '] '
- elif unread == 1:
- unread_str = '* '
- else:
- urgent = False
-
- if control.type_id == message_control.TYPE_GC:
- name = control.room_jid.split('@')[0]
- urgent = control.attention_flag
- else:
- name = control.contact.get_shown_name()
- if control.resource:
- name += '/' + control.resource
-
- window_mode = gajim.interface.msg_win_mgr.mode
- if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
- # Show the plural form since number of tabs > 1
- if self.type_ == 'chat':
- label = _('Chats')
- elif self.type_ == 'gc':
- label = _('Group Chats')
- else:
- label = _('Private Chats')
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- label = None
- elif self.get_num_controls() == 1:
- label = name
- else:
- label = _('Messages')
-
- title = 'Gajim'
- if label:
- title = '%s - %s' % (label, title)
-
- if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT:
- title = title + ": " + control.account
-
- self.window.set_title(unread_str + title)
-
- if urgent:
- gtkgui_helpers.set_unset_urgency_hint(self.window, unread)
- else:
- gtkgui_helpers.set_unset_urgency_hint(self.window, False)
-
- def set_active_tab(self, ctrl):
- ctrl_page = self.notebook.page_num(ctrl.widget)
- self.notebook.set_current_page(ctrl_page)
- self.window.present()
-
- def remove_tab(self, ctrl, method, reason = None, force = False):
- """
- Reason is only for gc (offline status message) if force is True, do not
- ask any confirmation
- """
- def close(ctrl):
- if reason is not None: # We are leaving gc with a status message
- ctrl.shutdown(reason)
- else: # We are leaving gc without status message or it's a chat
- ctrl.shutdown()
- # Update external state
- gajim.events.remove_events(ctrl.account, ctrl.get_full_jid,
- types = ['printed_msg', 'chat', 'gc_msg'])
-
- fjid = ctrl.get_full_jid()
- jid = gajim.get_jid_without_resource(fjid)
-
- fctrl = self.get_control(fjid, ctrl.account)
- bctrl = self.get_control(jid, ctrl.account)
- # keep last_message_time around unless this was our last control with
- # that jid
- if not fctrl and not bctrl and \
- fjid in gajim.last_message_time[ctrl.account]:
- del gajim.last_message_time[ctrl.account][fjid]
-
- self.notebook.remove_page(self.notebook.page_num(ctrl.widget))
-
- del self._controls[ctrl.account][fjid]
-
- if len(self._controls[ctrl.account]) == 0:
- del self._controls[ctrl.account]
-
- self.check_tabs()
- self.show_title()
-
- def on_yes(ctrl):
- close(ctrl)
-
- def on_no(ctrl):
- return
-
- def on_minimize(ctrl):
- if method != self.CLOSE_COMMAND:
- ctrl.minimize()
- self.check_tabs()
- return
- close(ctrl)
-
- # Shutdown the MessageControl
- if force:
- close(ctrl)
- else:
- ctrl.allow_shutdown(method, on_yes, on_no, on_minimize)
-
- def check_tabs(self):
- if self.get_num_controls() == 0:
- # These are not called when the window is destroyed like this, fake it
- gajim.interface.msg_win_mgr._on_window_delete(self.window, None)
- gajim.interface.msg_win_mgr._on_window_destroy(self.window)
- # dnd clean up
- self.notebook.drag_dest_unset()
- if self.parent_paned:
- # Don't close parent window, just remove the child
- child = self.parent_paned.get_child2()
- self.parent_paned.remove(child)
- else:
- self.window.destroy()
- return # don't show_title, we are dead
- elif self.get_num_controls() == 1: # we are going from two tabs to one
- window_mode = gajim.interface.msg_win_mgr.mode
- show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') or \
- window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER
- self.notebook.set_show_tabs(show_tabs_if_one_tab)
-
- def redraw_tab(self, ctrl, chatstate = None):
- hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0]
- status_img = hbox.get_children()[0]
- nick_label = hbox.get_children()[1]
-
- # Optionally hide close button
- close_button = hbox.get_children()[2]
- if gajim.config.get('tabs_close_button'):
- close_button.show()
- else:
- close_button.hide()
-
- # Update nick
- nick_label.set_max_width_chars(10)
- (tab_label_str, tab_label_color) = ctrl.get_tab_label(chatstate)
- nick_label.set_markup(tab_label_str)
- if tab_label_color:
- nick_label.modify_fg(gtk.STATE_NORMAL, tab_label_color)
- nick_label.modify_fg(gtk.STATE_ACTIVE, tab_label_color)
-
- tab_img = ctrl.get_tab_image()
- if tab_img:
- if tab_img.get_storage_type() == gtk.IMAGE_ANIMATION:
- status_img.set_from_animation(tab_img.get_animation())
- else:
- status_img.set_from_pixbuf(tab_img.get_pixbuf())
-
- self.show_icon()
-
- def repaint_themed_widgets(self):
- """
- Repaint controls in the window with theme color
- """
- # iterate through controls and repaint
- for ctrl in self.controls():
- ctrl.repaint_themed_widgets()
-
- def _widget_to_control(self, widget):
- for ctrl in self.controls():
- if ctrl.widget == widget:
- return ctrl
- return None
-
- def get_active_control(self):
- notebook = self.notebook
- active_widget = notebook.get_nth_page(notebook.get_current_page())
- return self._widget_to_control(active_widget)
-
- def get_active_contact(self):
- ctrl = self.get_active_control()
- if ctrl:
- return ctrl.contact
- return None
-
- def get_active_jid(self):
- contact = self.get_active_contact()
- if contact:
- return contact.jid
- return None
-
- def is_active(self):
- return self.window.is_active()
-
- def get_origin(self):
- return self.window.window.get_origin()
-
- def get_control(self, key, acct):
- """
- Return the MessageControl for jid or n, where n is a notebook page index.
- When key is an int index acct may be None
- """
- if isinstance(key, str):
- key = unicode(key, 'utf-8')
-
- if isinstance(key, unicode):
- jid = key
- try:
- return self._controls[acct][jid]
- except Exception:
- return None
- else:
- page_num = key
- notebook = self.notebook
- if page_num is None:
- page_num = notebook.get_current_page()
- nth_child = notebook.get_nth_page(page_num)
- return self._widget_to_control(nth_child)
-
- def has_control(self, jid, acct):
- return (acct in self._controls and jid in self._controls[acct])
-
- def change_key(self, old_jid, new_jid, acct):
- """
- Change the JID key of a control
- """
- try:
- # Check if controls exists
- ctrl = self._controls[acct][old_jid]
- except KeyError:
- return
-
- self._controls[acct][new_jid] = ctrl
- del self._controls[acct][old_jid]
-
- if old_jid in gajim.last_message_time[acct]:
- gajim.last_message_time[acct][new_jid] = \
- gajim.last_message_time[acct][old_jid]
- del gajim.last_message_time[acct][old_jid]
-
- def controls(self):
- for jid_dict in self._controls.values():
- for ctrl in jid_dict.values():
- yield ctrl
-
- def get_nb_controls(self):
- return sum(len(jid_dict) for jid_dict in self._controls.values())
-
- def move_to_next_unread_tab(self, forward):
- ind = self.notebook.get_current_page()
- current = ind
- found = False
- first_composing_ind = -1 # id of first composing ctrl to switch to
- # if no others controls have awaiting events
- # loop until finding an unread tab or having done a complete cycle
- while True:
- if forward == True: # look for the first unread tab on the right
- ind = ind + 1
- if ind >= self.notebook.get_n_pages():
- ind = 0
- else: # look for the first unread tab on the right
- ind = ind - 1
- if ind < 0:
- ind = self.notebook.get_n_pages() - 1
- ctrl = self.get_control(ind, None)
- if ctrl.get_nb_unread() > 0:
- found = True
- break # found
- elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact
- contact = ctrl.contact
- if first_composing_ind == -1 and contact.chatstate == 'composing':
- # If no composing contact found yet, check if this one is composing
- first_composing_ind = ind
- if ind == current:
- break # a complete cycle without finding an unread tab
- if found:
- self.notebook.set_current_page(ind)
- elif first_composing_ind != -1:
- self.notebook.set_current_page(first_composing_ind)
- else: # not found and nobody composing
- if forward: # CTRL + TAB
- if current < (self.notebook.get_n_pages() - 1):
- self.notebook.next_page()
- else: # traverse for ever (eg. don't stop at last tab)
- self.notebook.set_current_page(0)
- else: # CTRL + SHIFT + TAB
- if current > 0:
- self.notebook.prev_page()
- else: # traverse for ever (eg. don't stop at first tab)
- self.notebook.set_current_page(
- self.notebook.get_n_pages() - 1)
-
- def popup_menu(self, event):
- menu = self.get_active_control().prepare_context_menu()
- # show the menu
- menu.popup(None, None, None, event.button, event.time)
- menu.show_all()
-
- def _on_notebook_switch_page(self, notebook, page, page_num):
- old_no = notebook.get_current_page()
- if old_no >= 0:
- old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no))
- old_ctrl.set_control_active(False)
-
- new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num))
- new_ctrl.set_control_active(True)
- self.show_title(control = new_ctrl)
-
- control = self.get_active_control()
- if isinstance(control, ChatControlBase):
- control.msg_textview.grab_focus()
-
- def _on_notebook_key_press(self, widget, event):
- # when tab itself is selected, make sure <- and -> are allowed for navigating between tabs
- if event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right):
- return False
-
- control = self.get_active_control()
-
- if event.state & gtk.gdk.SHIFT_MASK:
- # CTRL + SHIFT + TAB
- if event.state & gtk.gdk.CONTROL_MASK and \
- event.keyval == gtk.keysyms.ISO_Left_Tab:
- self.move_to_next_unread_tab(False)
- return True
- # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
- elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up):
- control.conv_textview.tv.emit('key_press_event', event)
- return True
- elif event.state & gtk.gdk.CONTROL_MASK:
- if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
- self.move_to_next_unread_tab(True)
- return True
- # Ctrl+PageUP / DOWN has to be handled by notebook
- elif event.keyval == gtk.keysyms.Page_Down:
- self.move_to_next_unread_tab(True)
- return True
- elif event.keyval == gtk.keysyms.Page_Up:
- self.move_to_next_unread_tab(False)
- return True
- if event.keyval in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R,
- gtk.keysyms.Control_L, gtk.keysyms.Control_R, gtk.keysyms.Caps_Lock,
- gtk.keysyms.Shift_Lock, gtk.keysyms.Meta_L, gtk.keysyms.Meta_R,
- gtk.keysyms.Alt_L, gtk.keysyms.Alt_R, gtk.keysyms.Super_L,
- gtk.keysyms.Super_R, gtk.keysyms.Hyper_L, gtk.keysyms.Hyper_R):
- return True
-
- if isinstance(control, ChatControlBase):
- # we forwarded it to message textview
- control.msg_textview.emit('key_press_event', event)
- control.msg_textview.grab_focus()
-
- def get_tab_at_xy(self, x, y):
- """
- Return the tab under xy and if its nearer from left or right side of the
- tab
- """
- page_num = -1
- to_right = False
- horiz = self.notebook.get_tab_pos() == gtk.POS_TOP or \
- self.notebook.get_tab_pos() == gtk.POS_BOTTOM
- for i in xrange(self.notebook.get_n_pages()):
- page = self.notebook.get_nth_page(i)
- tab = self.notebook.get_tab_label(page)
- tab_alloc = tab.get_allocation()
- if horiz:
- if (x >= tab_alloc.x) and \
- (x <= (tab_alloc.x + tab_alloc.width)):
- page_num = i
- if x >= tab_alloc.x + (tab_alloc.width / 2.0):
- to_right = True
- break
- else:
- if (y >= tab_alloc.y) and \
- (y <= (tab_alloc.y + tab_alloc.height)):
- page_num = i
-
- if y > tab_alloc.y + (tab_alloc.height / 2.0):
- to_right = True
- break
- return (page_num, to_right)
-
- def find_page_num_according_to_tab_label(self, tab_label):
- """
- Find the page num of the tab label
- """
- page_num = -1
- for i in xrange(self.notebook.get_n_pages()):
- page = self.notebook.get_nth_page(i)
- tab = self.notebook.get_tab_label(page)
- if tab == tab_label:
- page_num = i
- break
- return page_num
+ """
+ Class for windows which contain message like things; chats, groupchats, etc
+ """
+
+ # DND_TARGETS is the targets needed by drag_source_set and drag_dest_set
+ DND_TARGETS = [('GAJIM_TAB', 0, 81)]
+ hid = 0 # drag_data_received handler id
+ (
+ CLOSE_TAB_MIDDLE_CLICK,
+ CLOSE_ESC,
+ CLOSE_CLOSE_BUTTON,
+ CLOSE_COMMAND,
+ CLOSE_CTRL_KEY
+ ) = range(5)
+
+ def __init__(self, acct, type_, parent_window=None, parent_paned=None):
+ # A dictionary of dictionaries
+ # where _contacts[account][jid] == A MessageControl
+ self._controls = {}
+
+ # If None, the window is not tied to any specific account
+ self.account = acct
+ # If None, the window is not tied to any specific type
+ self.type_ = type_
+ # dict { handler id: widget}. Keeps callbacks, which
+ # lead to cylcular references
+ self.handlers = {}
+ # Don't show warning dialogs when we want to delete the window
+ self.dont_warn_on_delete = False
+
+ self.widget_name = 'message_window'
+ self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % self.widget_name)
+ self.window = self.xml.get_object(self.widget_name)
+ self.notebook = self.xml.get_object('notebook')
+ self.parent_paned = None
+
+ if parent_window:
+ orig_window = self.window
+ self.window = parent_window
+ self.parent_paned = parent_paned
+ self.notebook.reparent(self.parent_paned)
+ self.parent_paned.pack2(self.notebook, resize=True, shrink=True)
+ orig_window.destroy()
+ del orig_window
+
+ # NOTE: we use 'connect_after' here because in
+ # MessageWindowMgr._new_window we register handler that saves window
+ # state when closing it, and it should be called before
+ # MessageWindow._on_window_delete, which manually destroys window
+ # through win.destroy() - this means no additional handlers for
+ # 'delete-event' are called.
+ id_ = self.window.connect_after('delete-event', self._on_window_delete)
+ self.handlers[id_] = self.window
+ id_ = self.window.connect('destroy', self._on_window_destroy)
+ self.handlers[id_] = self.window
+ id_ = self.window.connect('focus-in-event', self._on_window_focus)
+ self.handlers[id_] = self.window
+
+ keys=['<Control>f', '<Control>g', '<Control>h', '<Control>i',
+ '<Control>l', '<Control>L', '<Control><Shift>n', '<Control>u',
+ '<Control>b', '<Control>F4',
+ '<Control>w', '<Control>Page_Up', '<Control>Page_Down', '<Alt>Right',
+ '<Alt>Left', '<Alt>d', '<Alt>c', '<Alt>m', '<Alt>t', 'Escape'] + \
+ ['<Alt>'+str(i) for i in xrange(10)]
+ accel_group = gtk.AccelGroup()
+ for key in keys:
+ keyval, mod = gtk.accelerator_parse(key)
+ accel_group.connect_group(keyval, mod, gtk.ACCEL_VISIBLE,
+ self.accel_group_func)
+ self.window.add_accel_group(accel_group)
+
+ # gtk+ doesn't make use of the motion notify on gtkwindow by default
+ # so this line adds that
+ self.window.add_events(gtk.gdk.POINTER_MOTION_MASK)
+
+ id_ = self.notebook.connect('switch-page',
+ self._on_notebook_switch_page)
+ self.handlers[id_] = self.notebook
+ id_ = self.notebook.connect('key-press-event',
+ self._on_notebook_key_press)
+ self.handlers[id_] = self.notebook
+
+ # Tab customizations
+ pref_pos = gajim.config.get('tabs_position')
+ if pref_pos == 'bottom':
+ nb_pos = gtk.POS_BOTTOM
+ elif pref_pos == 'left':
+ nb_pos = gtk.POS_LEFT
+ elif pref_pos == 'right':
+ nb_pos = gtk.POS_RIGHT
+ else:
+ nb_pos = gtk.POS_TOP
+ self.notebook.set_tab_pos(nb_pos)
+ window_mode = gajim.interface.msg_win_mgr.mode
+ if gajim.config.get('tabs_always_visible') or \
+ window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
+ self.notebook.set_show_tabs(True)
+ else:
+ self.notebook.set_show_tabs(False)
+ self.notebook.set_show_border(gajim.config.get('tabs_border'))
+ self.show_icon()
+
+ def change_account_name(self, old_name, new_name):
+ if old_name in self._controls:
+ self._controls[new_name] = self._controls[old_name]
+ del self._controls[old_name]
+
+ for ctrl in self.controls():
+ if ctrl.account == old_name:
+ ctrl.account = new_name
+ if self.account == old_name:
+ self.account = new_name
+
+ def change_jid(self, account, old_jid, new_jid):
+ """
+ Called when the full jid of the control is changed
+ """
+ if account not in self._controls:
+ return
+ if old_jid not in self._controls[account]:
+ return
+ if old_jid == new_jid:
+ return
+ self._controls[account][new_jid] = self._controls[account][old_jid]
+ del self._controls[account][old_jid]
+
+ def get_num_controls(self):
+ return sum(len(d) for d in self._controls.values())
+
+ def resize(self, width, height):
+ gtkgui_helpers.resize_window(self.window, width, height)
+
+ def _on_window_focus(self, widget, event):
+ # window received focus, so if we had urgency REMOVE IT
+ # NOTE: we do not have to read the message (it maybe in a bg tab)
+ # to remove urgency hint so this functions does that
+ gtkgui_helpers.set_unset_urgency_hint(self.window, False)
+
+ ctrl = self.get_active_control()
+ if ctrl:
+ ctrl.set_control_active(True)
+ # Undo "unread" state display, etc.
+ if ctrl.type_id == message_control.TYPE_GC:
+ self.redraw_tab(ctrl, 'active')
+ else:
+ # NOTE: we do not send any chatstate to preserve
+ # inactive, gone, etc.
+ self.redraw_tab(ctrl)
+
+ def _on_window_delete(self, win, event):
+ if self.dont_warn_on_delete:
+ # Destroy the window
+ return False
+
+ # Number of controls that will be closed and for which we'll loose data:
+ # chat, pm, gc that won't go in roster
+ number_of_closed_control = 0
+ for ctrl in self.controls():
+ if not ctrl.safe_shutdown():
+ number_of_closed_control += 1
+
+ if number_of_closed_control > 1:
+ def on_yes1(checked):
+ if checked:
+ gajim.config.set('confirm_close_multiple_tabs', False)
+ self.dont_warn_on_delete = True
+ for ctrl in self.controls():
+ if ctrl.minimizable():
+ ctrl.minimize()
+ win.destroy()
+
+ if not gajim.config.get('confirm_close_multiple_tabs'):
+ # destroy window
+ return False
+ dialogs.YesNoDialog(
+ _('You are going to close several tabs'),
+_('Do you really want to close them all?'),
+ checktext=_('Do _not ask me again'), on_response_yes=on_yes1)
+ return True
+
+ def on_yes(ctrl):
+ if self.on_delete_ok == 1:
+ self.dont_warn_on_delete = True
+ win.destroy()
+ self.on_delete_ok -= 1
+
+ def on_no(ctrl):
+ return
+
+ def on_minimize(ctrl):
+ ctrl.minimize()
+ if self.on_delete_ok == 1:
+ self.dont_warn_on_delete = True
+ win.destroy()
+ self.on_delete_ok -= 1
+
+ # Make sure all controls are okay with being deleted
+ self.on_delete_ok = self.get_nb_controls()
+ for ctrl in self.controls():
+ ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON, on_yes, on_no,
+ on_minimize)
+ return True # halt the delete for the moment
+
+ def _on_window_destroy(self, win):
+ for ctrl in self.controls():
+ ctrl.shutdown()
+ self._controls.clear()
+ # Clean up handlers connected to the parent window, this is important since
+ # self.window may be the RosterWindow
+ for i in self.handlers.keys():
+ if self.handlers[i].handler_is_connected(i):
+ self.handlers[i].disconnect(i)
+ del self.handlers[i]
+ del self.handlers
+
+ def new_tab(self, control):
+ fjid = control.get_full_jid()
+
+ if control.account not in self._controls:
+ self._controls[control.account] = {}
+
+ self._controls[control.account][fjid] = control
+
+ if self.get_num_controls() == 2:
+ # is first conversation_textview scrolled down ?
+ scrolled = False
+ first_widget = self.notebook.get_nth_page(0)
+ ctrl = self._widget_to_control(first_widget)
+ conv_textview = ctrl.conv_textview
+ if conv_textview.at_the_end():
+ scrolled = True
+ self.notebook.set_show_tabs(True)
+ if scrolled:
+ gobject.idle_add(conv_textview.scroll_to_end_iter)
+
+ # Add notebook page and connect up to the tab's close button
+ xml = gtkgui_helpers.get_gtk_builder('message_window.ui', 'chat_tab_ebox')
+ tab_label_box = xml.get_object('chat_tab_ebox')
+ widget = xml.get_object('tab_close_button')
+ id_ = widget.connect('clicked', self._on_close_button_clicked, control)
+ control.handlers[id_] = widget
+
+ id_ = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event,
+ control.widget)
+ control.handlers[id_] = tab_label_box
+ self.notebook.append_page(control.widget, tab_label_box)
+
+ self.notebook.set_tab_reorderable(control.widget, True)
+
+ self.redraw_tab(control)
+ if self.parent_paned:
+ self.notebook.show_all()
+ else:
+ self.window.show_all()
+ # NOTE: we do not call set_control_active(True) since we don't know whether
+ # the tab is the active one.
+ self.show_title()
+
+ def on_tab_eventbox_button_press_event(self, widget, event, child):
+ if event.button == 3: # right click
+ n = self.notebook.page_num(child)
+ self.notebook.set_current_page(n)
+ self.popup_menu(event)
+ elif event.button == 2: # middle click
+ ctrl = self._widget_to_control(child)
+ self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK)
+
+ def _on_message_textview_mykeypress_event(self, widget, event_keyval,
+ event_keymod):
+ # NOTE: handles mykeypress which is custom signal; see message_textview.py
+
+ # construct event instance from binding
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
+ event.keyval = event_keyval
+ event.state = event_keymod
+ event.time = 0 # assign current time
+
+ if event.state & gtk.gdk.CONTROL_MASK:
+ # Tab switch bindings
+ if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
+ self.move_to_next_unread_tab(True)
+ elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB
+ self.move_to_next_unread_tab(False)
+ elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN
+ self.notebook.emit('key_press_event', event)
+ elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP
+ self.notebook.emit('key_press_event', event)
+
+ def accel_group_func(self, accel_group, acceleratable, keyval, modifier):
+ st = '1234567890' # alt+1 means the first tab (tab 0)
+ control = self.get_active_control()
+ if not control:
+ # No more control in this window
+ return
+
+ # CTRL mask
+ if modifier & gtk.gdk.CONTROL_MASK:
+ if keyval == gtk.keysyms.h: # CTRL + h
+ control._on_history_menuitem_activate()
+ elif control.type_id == message_control.TYPE_CHAT and \
+ keyval == gtk.keysyms.f: # CTRL + f
+ control._on_send_file_menuitem_activate(None)
+ elif control.type_id == message_control.TYPE_CHAT and \
+ keyval == gtk.keysyms.g: # CTRL + g
+ control._on_convert_to_gc_menuitem_activate(None)
+ elif control.type_id in (message_control.TYPE_CHAT,
+ message_control.TYPE_PM) and keyval == gtk.keysyms.i: # CTRL + i
+ control._on_contact_information_menuitem_activate(None)
+ elif keyval == gtk.keysyms.l or keyval == gtk.keysyms.L: # CTRL + l|L
+ control.conv_textview.clear()
+ elif keyval == gtk.keysyms.u: # CTRL + u: emacs style clear line
+ control.clear(control.msg_textview)
+ elif control.type_id == message_control.TYPE_GC and \
+ keyval == gtk.keysyms.b: # CTRL + b
+ control._on_bookmark_room_menuitem_activate(None)
+ # Tab switch bindings
+ elif keyval == gtk.keysyms.F4: # CTRL + F4
+ self.remove_tab(control, self.CLOSE_CTRL_KEY)
+ elif keyval == gtk.keysyms.w: # CTRL + w
+ # CTRL + w removes latest word before sursor when User uses emacs
+ # theme
+ if not gtk.settings_get_default().get_property(
+ 'gtk-key-theme-name') == 'Emacs':
+ self.remove_tab(control, self.CLOSE_CTRL_KEY)
+ elif keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down):
+ # CTRL + PageUp | PageDown
+ # Create event and send it to notebook
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
+ event.window = self.window.window
+ event.time = int(time.time())
+ event.state = gtk.gdk.CONTROL_MASK
+ event.keyval = int(keyval)
+ self.notebook.emit('key_press_event', event)
+
+ if modifier & gtk.gdk.SHIFT_MASK:
+ # CTRL + SHIFT
+ if control.type_id == message_control.TYPE_GC and \
+ keyval == gtk.keysyms.n: # CTRL + SHIFT + n
+ control._on_change_nick_menuitem_activate(None)
+ # MOD1 (ALT) mask
+ elif modifier & gtk.gdk.MOD1_MASK:
+ # Tab switch bindings
+ if keyval == gtk.keysyms.Right: # ALT + RIGHT
+ new = self.notebook.get_current_page() + 1
+ if new >= self.notebook.get_n_pages():
+ new = 0
+ self.notebook.set_current_page(new)
+ elif keyval == gtk.keysyms.Left: # ALT + LEFT
+ new = self.notebook.get_current_page() - 1
+ if new < 0:
+ new = self.notebook.get_n_pages() - 1
+ self.notebook.set_current_page(new)
+ elif chr(keyval) in st: # ALT + 1,2,3..
+ self.notebook.set_current_page(st.index(chr(keyval)))
+ elif keyval == gtk.keysyms.c: # ALT + C toggles chat buttons
+ control.chat_buttons_set_visible(not control.hide_chat_buttons)
+ elif keyval == gtk.keysyms.m: # ALT + M show emoticons menu
+ control.show_emoticons_menu()
+ elif keyval == gtk.keysyms.d: # ALT + D show actions menu
+ control.on_actions_button_clicked(control.actions_button)
+ elif control.type_id == message_control.TYPE_GC and \
+ keyval == gtk.keysyms.t: # ALT + t
+ control._on_change_subject_menuitem_activate(None)
+ # Close tab bindings
+ elif keyval == gtk.keysyms.Escape and \
+ gajim.config.get('escape_key_closes'): # Escape
+ self.remove_tab(control, self.CLOSE_ESC)
+ return True
+
+ def _on_close_button_clicked(self, button, control):
+ """
+ When close button is pressed: close a tab
+ """
+ self.remove_tab(control, self.CLOSE_CLOSE_BUTTON)
+
+ def show_icon(self):
+ window_mode = gajim.interface.msg_win_mgr.mode
+ icon = None
+ if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_NEVER:
+ ctrl = self.get_active_control()
+ if not ctrl:
+ return
+ icon = ctrl.get_tab_image(count_unread=False)
+ elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS:
+ pass # keep default icon
+ elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
+ pass # keep default icon
+ elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT:
+ pass # keep default icon
+ elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
+ if self.type_ == 'gc':
+ icon = gtkgui_helpers.load_icon('muc_active')
+ else:
+ # chat, pm
+ icon = gtkgui_helpers.load_icon('online')
+ if icon:
+ self.window.set_icon(icon.get_pixbuf())
+
+ def show_title(self, urgent=True, control=None):
+ """
+ Redraw the window's title
+ """
+ if not control:
+ control = self.get_active_control()
+ if not control:
+ # No more control in this window
+ return
+ unread = 0
+ for ctrl in self.controls():
+ if ctrl.type_id == message_control.TYPE_GC and not \
+ gajim.config.get('notify_on_all_muc_messages') and not \
+ ctrl.attention_flag:
+ # count only pm messages
+ unread += ctrl.get_nb_unread_pm()
+ continue
+ unread += ctrl.get_nb_unread()
+
+ unread_str = ''
+ if unread > 1:
+ unread_str = '[' + unicode(unread) + '] '
+ elif unread == 1:
+ unread_str = '* '
+ else:
+ urgent = False
+
+ if control.type_id == message_control.TYPE_GC:
+ name = control.room_jid.split('@')[0]
+ urgent = control.attention_flag
+ else:
+ name = control.contact.get_shown_name()
+ if control.resource:
+ name += '/' + control.resource
+
+ window_mode = gajim.interface.msg_win_mgr.mode
+ if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
+ # Show the plural form since number of tabs > 1
+ if self.type_ == 'chat':
+ label = _('Chats')
+ elif self.type_ == 'gc':
+ label = _('Group Chats')
+ else:
+ label = _('Private Chats')
+ elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
+ label = None
+ elif self.get_num_controls() == 1:
+ label = name
+ else:
+ label = _('Messages')
+
+ title = 'Gajim'
+ if label:
+ title = '%s - %s' % (label, title)
+
+ if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT:
+ title = title + ": " + control.account
+
+ self.window.set_title(unread_str + title)
+
+ if urgent:
+ gtkgui_helpers.set_unset_urgency_hint(self.window, unread)
+ else:
+ gtkgui_helpers.set_unset_urgency_hint(self.window, False)
+
+ def set_active_tab(self, ctrl):
+ ctrl_page = self.notebook.page_num(ctrl.widget)
+ self.notebook.set_current_page(ctrl_page)
+ self.window.present()
+
+ def remove_tab(self, ctrl, method, reason = None, force = False):
+ """
+ Reason is only for gc (offline status message) if force is True, do not
+ ask any confirmation
+ """
+ def close(ctrl):
+ if reason is not None: # We are leaving gc with a status message
+ ctrl.shutdown(reason)
+ else: # We are leaving gc without status message or it's a chat
+ ctrl.shutdown()
+ # Update external state
+ gajim.events.remove_events(ctrl.account, ctrl.get_full_jid,
+ types = ['printed_msg', 'chat', 'gc_msg'])
+
+ fjid = ctrl.get_full_jid()
+ jid = gajim.get_jid_without_resource(fjid)
+
+ fctrl = self.get_control(fjid, ctrl.account)
+ bctrl = self.get_control(jid, ctrl.account)
+ # keep last_message_time around unless this was our last control with
+ # that jid
+ if not fctrl and not bctrl and \
+ fjid in gajim.last_message_time[ctrl.account]:
+ del gajim.last_message_time[ctrl.account][fjid]
+
+ self.notebook.remove_page(self.notebook.page_num(ctrl.widget))
+
+ del self._controls[ctrl.account][fjid]
+
+ if len(self._controls[ctrl.account]) == 0:
+ del self._controls[ctrl.account]
+
+ self.check_tabs()
+ self.show_title()
+
+ def on_yes(ctrl):
+ close(ctrl)
+
+ def on_no(ctrl):
+ return
+
+ def on_minimize(ctrl):
+ if method != self.CLOSE_COMMAND:
+ ctrl.minimize()
+ self.check_tabs()
+ return
+ close(ctrl)
+
+ # Shutdown the MessageControl
+ if force:
+ close(ctrl)
+ else:
+ ctrl.allow_shutdown(method, on_yes, on_no, on_minimize)
+
+ def check_tabs(self):
+ if self.get_num_controls() == 0:
+ # These are not called when the window is destroyed like this, fake it
+ gajim.interface.msg_win_mgr._on_window_delete(self.window, None)
+ gajim.interface.msg_win_mgr._on_window_destroy(self.window)
+ # dnd clean up
+ self.notebook.drag_dest_unset()
+ if self.parent_paned:
+ # Don't close parent window, just remove the child
+ child = self.parent_paned.get_child2()
+ self.parent_paned.remove(child)
+ else:
+ self.window.destroy()
+ return # don't show_title, we are dead
+ elif self.get_num_controls() == 1: # we are going from two tabs to one
+ window_mode = gajim.interface.msg_win_mgr.mode
+ show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') or \
+ window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER
+ self.notebook.set_show_tabs(show_tabs_if_one_tab)
+
+ def redraw_tab(self, ctrl, chatstate = None):
+ hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0]
+ status_img = hbox.get_children()[0]
+ nick_label = hbox.get_children()[1]
+
+ # Optionally hide close button
+ close_button = hbox.get_children()[2]
+ if gajim.config.get('tabs_close_button'):
+ close_button.show()
+ else:
+ close_button.hide()
+
+ # Update nick
+ nick_label.set_max_width_chars(10)
+ (tab_label_str, tab_label_color) = ctrl.get_tab_label(chatstate)
+ nick_label.set_markup(tab_label_str)
+ if tab_label_color:
+ nick_label.modify_fg(gtk.STATE_NORMAL, tab_label_color)
+ nick_label.modify_fg(gtk.STATE_ACTIVE, tab_label_color)
+
+ tab_img = ctrl.get_tab_image()
+ if tab_img:
+ if tab_img.get_storage_type() == gtk.IMAGE_ANIMATION:
+ status_img.set_from_animation(tab_img.get_animation())
+ else:
+ status_img.set_from_pixbuf(tab_img.get_pixbuf())
+
+ self.show_icon()
+
+ def repaint_themed_widgets(self):
+ """
+ Repaint controls in the window with theme color
+ """
+ # iterate through controls and repaint
+ for ctrl in self.controls():
+ ctrl.repaint_themed_widgets()
+
+ def _widget_to_control(self, widget):
+ for ctrl in self.controls():
+ if ctrl.widget == widget:
+ return ctrl
+ return None
+
+ def get_active_control(self):
+ notebook = self.notebook
+ active_widget = notebook.get_nth_page(notebook.get_current_page())
+ return self._widget_to_control(active_widget)
+
+ def get_active_contact(self):
+ ctrl = self.get_active_control()
+ if ctrl:
+ return ctrl.contact
+ return None
+
+ def get_active_jid(self):
+ contact = self.get_active_contact()
+ if contact:
+ return contact.jid
+ return None
+
+ def is_active(self):
+ return self.window.is_active()
+
+ def get_origin(self):
+ return self.window.window.get_origin()
+
+ def get_control(self, key, acct):
+ """
+ Return the MessageControl for jid or n, where n is a notebook page index.
+ When key is an int index acct may be None
+ """
+ if isinstance(key, str):
+ key = unicode(key, 'utf-8')
+
+ if isinstance(key, unicode):
+ jid = key
+ try:
+ return self._controls[acct][jid]
+ except Exception:
+ return None
+ else:
+ page_num = key
+ notebook = self.notebook
+ if page_num is None:
+ page_num = notebook.get_current_page()
+ nth_child = notebook.get_nth_page(page_num)
+ return self._widget_to_control(nth_child)
+
+ def has_control(self, jid, acct):
+ return (acct in self._controls and jid in self._controls[acct])
+
+ def change_key(self, old_jid, new_jid, acct):
+ """
+ Change the JID key of a control
+ """
+ try:
+ # Check if controls exists
+ ctrl = self._controls[acct][old_jid]
+ except KeyError:
+ return
+
+ self._controls[acct][new_jid] = ctrl
+ del self._controls[acct][old_jid]
+
+ if old_jid in gajim.last_message_time[acct]:
+ gajim.last_message_time[acct][new_jid] = \
+ gajim.last_message_time[acct][old_jid]
+ del gajim.last_message_time[acct][old_jid]
+
+ def controls(self):
+ for jid_dict in self._controls.values():
+ for ctrl in jid_dict.values():
+ yield ctrl
+
+ def get_nb_controls(self):
+ return sum(len(jid_dict) for jid_dict in self._controls.values())
+
+ def move_to_next_unread_tab(self, forward):
+ ind = self.notebook.get_current_page()
+ current = ind
+ found = False
+ first_composing_ind = -1 # id of first composing ctrl to switch to
+ # if no others controls have awaiting events
+ # loop until finding an unread tab or having done a complete cycle
+ while True:
+ if forward == True: # look for the first unread tab on the right
+ ind = ind + 1
+ if ind >= self.notebook.get_n_pages():
+ ind = 0
+ else: # look for the first unread tab on the right
+ ind = ind - 1
+ if ind < 0:
+ ind = self.notebook.get_n_pages() - 1
+ ctrl = self.get_control(ind, None)
+ if ctrl.get_nb_unread() > 0:
+ found = True
+ break # found
+ elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact
+ contact = ctrl.contact
+ if first_composing_ind == -1 and contact.chatstate == 'composing':
+ # If no composing contact found yet, check if this one is composing
+ first_composing_ind = ind
+ if ind == current:
+ break # a complete cycle without finding an unread tab
+ if found:
+ self.notebook.set_current_page(ind)
+ elif first_composing_ind != -1:
+ self.notebook.set_current_page(first_composing_ind)
+ else: # not found and nobody composing
+ if forward: # CTRL + TAB
+ if current < (self.notebook.get_n_pages() - 1):
+ self.notebook.next_page()
+ else: # traverse for ever (eg. don't stop at last tab)
+ self.notebook.set_current_page(0)
+ else: # CTRL + SHIFT + TAB
+ if current > 0:
+ self.notebook.prev_page()
+ else: # traverse for ever (eg. don't stop at first tab)
+ self.notebook.set_current_page(
+ self.notebook.get_n_pages() - 1)
+
+ def popup_menu(self, event):
+ menu = self.get_active_control().prepare_context_menu()
+ # show the menu
+ menu.popup(None, None, None, event.button, event.time)
+ menu.show_all()
+
+ def _on_notebook_switch_page(self, notebook, page, page_num):
+ old_no = notebook.get_current_page()
+ if old_no >= 0:
+ old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no))
+ old_ctrl.set_control_active(False)
+
+ new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num))
+ new_ctrl.set_control_active(True)
+ self.show_title(control = new_ctrl)
+
+ control = self.get_active_control()
+ if isinstance(control, ChatControlBase):
+ control.msg_textview.grab_focus()
+
+ def _on_notebook_key_press(self, widget, event):
+ # when tab itself is selected, make sure <- and -> are allowed for navigating between tabs
+ if event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right):
+ return False
+
+ control = self.get_active_control()
+
+ if event.state & gtk.gdk.SHIFT_MASK:
+ # CTRL + SHIFT + TAB
+ if event.state & gtk.gdk.CONTROL_MASK and \
+ event.keyval == gtk.keysyms.ISO_Left_Tab:
+ self.move_to_next_unread_tab(False)
+ return True
+ # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
+ elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up):
+ control.conv_textview.tv.emit('key_press_event', event)
+ return True
+ elif event.state & gtk.gdk.CONTROL_MASK:
+ if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
+ self.move_to_next_unread_tab(True)
+ return True
+ # Ctrl+PageUP / DOWN has to be handled by notebook
+ elif event.keyval == gtk.keysyms.Page_Down:
+ self.move_to_next_unread_tab(True)
+ return True
+ elif event.keyval == gtk.keysyms.Page_Up:
+ self.move_to_next_unread_tab(False)
+ return True
+ if event.keyval in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R,
+ gtk.keysyms.Control_L, gtk.keysyms.Control_R, gtk.keysyms.Caps_Lock,
+ gtk.keysyms.Shift_Lock, gtk.keysyms.Meta_L, gtk.keysyms.Meta_R,
+ gtk.keysyms.Alt_L, gtk.keysyms.Alt_R, gtk.keysyms.Super_L,
+ gtk.keysyms.Super_R, gtk.keysyms.Hyper_L, gtk.keysyms.Hyper_R):
+ return True
+
+ if isinstance(control, ChatControlBase):
+ # we forwarded it to message textview
+ control.msg_textview.emit('key_press_event', event)
+ control.msg_textview.grab_focus()
+
+ def get_tab_at_xy(self, x, y):
+ """
+ Return the tab under xy and if its nearer from left or right side of the
+ tab
+ """
+ page_num = -1
+ to_right = False
+ horiz = self.notebook.get_tab_pos() == gtk.POS_TOP or \
+ self.notebook.get_tab_pos() == gtk.POS_BOTTOM
+ for i in xrange(self.notebook.get_n_pages()):
+ page = self.notebook.get_nth_page(i)
+ tab = self.notebook.get_tab_label(page)
+ tab_alloc = tab.get_allocation()
+ if horiz:
+ if (x >= tab_alloc.x) and \
+ (x <= (tab_alloc.x + tab_alloc.width)):
+ page_num = i
+ if x >= tab_alloc.x + (tab_alloc.width / 2.0):
+ to_right = True
+ break
+ else:
+ if (y >= tab_alloc.y) and \
+ (y <= (tab_alloc.y + tab_alloc.height)):
+ page_num = i
+
+ if y > tab_alloc.y + (tab_alloc.height / 2.0):
+ to_right = True
+ break
+ return (page_num, to_right)
+
+ def find_page_num_according_to_tab_label(self, tab_label):
+ """
+ Find the page num of the tab label
+ """
+ page_num = -1
+ for i in xrange(self.notebook.get_n_pages()):
+ page = self.notebook.get_nth_page(i)
+ tab = self.notebook.get_tab_label(page)
+ if tab == tab_label:
+ page_num = i
+ break
+ return page_num
################################################################################
class MessageWindowMgr(gobject.GObject):
- """
- A manager and factory for MessageWindow objects
- """
-
- __gsignals__ = {
- 'window-delete': (gobject.SIGNAL_RUN_LAST, None, (object,)),
- }
-
- # These constants map to common.config.opt_one_window_types indices
- (
- ONE_MSG_WINDOW_NEVER,
- ONE_MSG_WINDOW_ALWAYS,
- ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER,
- ONE_MSG_WINDOW_PERACCT,
- ONE_MSG_WINDOW_PERTYPE,
- ) = range(5)
- # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode
- MAIN_WIN = 'main'
- # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode
- ROSTER_MAIN_WIN = 'roster'
-
- def __init__(self, parent_window, parent_paned):
- """
- A dictionary of windows; the key depends on the config:
- ONE_MSG_WINDOW_NEVER: The key is the contact JID
- ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN
- ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN
- ONE_MSG_WINDOW_PERACCT: The key is the account name
- ONE_MSG_WINDOW_PERTYPE: The key is a message type constant
- """
- gobject.GObject.__init__(self)
- self._windows = {}
-
- # Map the mode to a int constant for frequent compares
- mode = gajim.config.get('one_message_window')
- self.mode = common.config.opt_one_window_types.index(mode)
-
- self.parent_win = parent_window
- self.parent_paned = parent_paned
-
- def change_account_name(self, old_name, new_name):
- for win in self.windows():
- win.change_account_name(old_name, new_name)
-
- def _new_window(self, acct, type_):
- parent_win = None
- parent_paned = None
- if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- parent_win = self.parent_win
- parent_paned = self.parent_paned
- win = MessageWindow(acct, type_, parent_win, parent_paned)
- # we track the lifetime of this window
- win.window.connect('delete-event', self._on_window_delete)
- win.window.connect('destroy', self._on_window_destroy)
- return win
-
- def _gtk_win_to_msg_win(self, gtk_win):
- for w in self.windows():
- if w.window == gtk_win:
- return w
- return None
-
- def get_window(self, jid, acct):
- for win in self.windows():
- if win.has_control(jid, acct):
- return win
-
- return None
-
- def has_window(self, jid, acct):
- return self.get_window(jid, acct) is not None
-
- def one_window_opened(self, contact=None, acct=None, type_=None):
- try:
- return \
- self._windows[self._mode_to_key(contact, acct, type_)] is not None
- except KeyError:
- return False
-
- def _resize_window(self, win, acct, type_):
- """
- Resizes window according to config settings
- """
- if self.mode in (self.ONE_MSG_WINDOW_ALWAYS,
- self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER):
- size = (gajim.config.get('msgwin-width'),
- gajim.config.get('msgwin-height'))
- if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- parent_size = win.window.get_size()
- # Need to add the size of the now visible paned handle, otherwise
- # the saved width of the message window decreases by this amount
- handle_size = win.parent_paned.style_get_property('handle-size')
- size = (parent_size[0] + size[0] + handle_size, size[1])
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- size = (gajim.config.get_per('accounts', acct, 'msgwin-width'),
- gajim.config.get_per('accounts', acct, 'msgwin-height'))
- elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE):
- if type_ == message_control.TYPE_PM:
- type_ = message_control.TYPE_CHAT
- opt_width = type_ + '-msgwin-width'
- opt_height = type_ + '-msgwin-height'
- size = (gajim.config.get(opt_width), gajim.config.get(opt_height))
- else:
- return
- win.resize(size[0], size[1])
- if win.parent_paned:
- win.parent_paned.set_position(parent_size[0])
-
- def _position_window(self, win, acct, type_):
- """
- Moves window according to config settings
- """
- if (self.mode in [self.ONE_MSG_WINDOW_NEVER,
- self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]):
- return
-
- if self.mode == self.ONE_MSG_WINDOW_ALWAYS:
- pos = (gajim.config.get('msgwin-x-position'),
- gajim.config.get('msgwin-y-position'))
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- pos = (gajim.config.get_per('accounts', acct, 'msgwin-x-position'),
- gajim.config.get_per('accounts', acct, 'msgwin-y-position'))
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- pos = (gajim.config.get(type_ + '-msgwin-x-position'),
- gajim.config.get(type_ + '-msgwin-y-position'))
- else:
- return
-
- gtkgui_helpers.move_window(win.window, pos[0], pos[1])
-
- def _mode_to_key(self, contact, acct, type_, resource = None):
- if self.mode == self.ONE_MSG_WINDOW_NEVER:
- key = acct + contact.jid
- if resource:
- key += '/' + resource
- return key
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
- return self.MAIN_WIN
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- return self.ROSTER_MAIN_WIN
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- return acct
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- return type_
-
- def create_window(self, contact, acct, type_, resource = None):
- win_acct = None
- win_type = None
- win_role = None # X11 window role
-
- win_key = self._mode_to_key(contact, acct, type_, resource)
- if self.mode == self.ONE_MSG_WINDOW_PERACCT:
- win_acct = acct
- win_role = acct
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- win_type = type_
- win_role = type_
- elif self.mode == self.ONE_MSG_WINDOW_NEVER:
- win_type = type_
- win_role = contact.jid
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
- win_role = 'messages'
-
- win = None
- try:
- win = self._windows[win_key]
- except KeyError:
- win = self._new_window(win_acct, win_type)
-
- if win_role:
- win.window.set_role(win_role)
-
- # Position and size window based on saved state and window mode
- if not self.one_window_opened(contact, acct, type_):
- if gajim.config.get('msgwin-max-state'):
- win.window.maximize()
- else:
- self._resize_window(win, acct, type_)
- self._position_window(win, acct, type_)
-
- self._windows[win_key] = win
- return win
-
- def change_key(self, old_jid, new_jid, acct):
- win = self.get_window(old_jid, acct)
- if self.mode == self.ONE_MSG_WINDOW_NEVER:
- old_key = acct + old_jid
- if old_jid not in self._windows:
- return
- new_key = acct + new_jid
- self._windows[new_key] = self._windows[old_key]
- del self._windows[old_key]
- win.change_key(old_jid, new_jid, acct)
-
- def _on_window_delete(self, win, event):
- self.save_state(self._gtk_win_to_msg_win(win))
- gajim.interface.save_config()
- return False
-
- def _on_window_destroy(self, win):
- for k in self._windows.keys():
- if self._windows[k].window == win:
- self.emit('window-delete', self._windows[k])
- del self._windows[k]
- return
-
- def get_control(self, jid, acct):
- """
- Amongst all windows, return the MessageControl for jid
- """
- win = self.get_window(jid, acct)
- if win:
- return win.get_control(jid, acct)
- return None
-
- def get_gc_control(self, jid, acct):
- """
- Same as get_control. Was briefly required, is not any more. May be useful
- some day in the future?
- """
- ctrl = self.get_control(jid, acct)
- if ctrl and ctrl.type_id == message_control.TYPE_GC:
- return ctrl
- return None
-
- def get_controls(self, type_=None, acct=None):
- ctrls = []
- for c in self.controls():
- if acct and c.account != acct:
- continue
- if not type_ or c.type_id == type_:
- ctrls.append(c)
- return ctrls
-
- def windows(self):
- for w in self._windows.values():
- yield w
-
- def controls(self):
- for w in self._windows.values():
- for c in w.controls():
- yield c
-
- def shutdown(self, width_adjust=0):
- for w in self.windows():
- self.save_state(w, width_adjust)
- if not w.parent_paned:
- w.window.hide()
- w.window.destroy()
-
- gajim.interface.save_config()
-
- def save_state(self, msg_win, width_adjust=0):
- # Save window size and position
- max_win_key = 'msgwin-max-state'
- pos_x_key = 'msgwin-x-position'
- pos_y_key = 'msgwin-y-position'
- size_width_key = 'msgwin-width'
- size_height_key = 'msgwin-height'
-
- acct = None
- x, y = msg_win.window.get_position()
- width, height = msg_win.window.get_size()
-
- # If any of these values seem bogus don't update.
- if x < 0 or y < 0 or width < 0 or height < 0:
- return
-
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- acct = msg_win.account
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- type_ = msg_win.type_
- pos_x_key = type_ + '-msgwin-x-position'
- pos_y_key = type_ + '-msgwin-y-position'
- size_width_key = type_ + '-msgwin-width'
- size_height_key = type_ + '-msgwin-height'
- elif self.mode == self.ONE_MSG_WINDOW_NEVER:
- type_ = msg_win.type_
- size_width_key = type_ + '-msgwin-width'
- size_height_key = type_ + '-msgwin-height'
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- # Ignore any hpaned width
- width = msg_win.notebook.allocation.width
-
- if acct:
- gajim.config.set_per('accounts', acct, size_width_key, width)
- gajim.config.set_per('accounts', acct, size_height_key, height)
-
- if self.mode != self.ONE_MSG_WINDOW_NEVER:
- gajim.config.set_per('accounts', acct, pos_x_key, x)
- gajim.config.set_per('accounts', acct, pos_y_key, y)
-
- else:
- win_maximized = msg_win.window.window.get_state() == \
- gtk.gdk.WINDOW_STATE_MAXIMIZED
- gajim.config.set(max_win_key, win_maximized)
- width += width_adjust
- gajim.config.set(size_width_key, width)
- gajim.config.set(size_height_key, height)
-
- if self.mode != self.ONE_MSG_WINDOW_NEVER:
- gajim.config.set(pos_x_key, x)
- gajim.config.set(pos_y_key, y)
-
- def reconfig(self):
- for w in self.windows():
- self.save_state(w)
- gajim.interface.save_config()
- mode = gajim.config.get('one_message_window')
- if self.mode == common.config.opt_one_window_types.index(mode):
- # No change
- return
- self.mode = common.config.opt_one_window_types.index(mode)
-
- controls = []
- for w in self.windows():
- # Note, we are taking care not to hide/delete the roster window when the
- # MessageWindow is embedded.
- if not w.parent_paned:
- w.window.hide()
- else:
- # Stash current size so it can be restored if the MessageWindow
- # is not longer embedded
- roster_width = w.parent_paned.get_child1().allocation.width
- gajim.config.set('roster_width', roster_width)
-
- while w.notebook.get_n_pages():
- page = w.notebook.get_nth_page(0)
- ctrl = w._widget_to_control(page)
- w.notebook.remove_page(0)
- page.unparent()
- controls.append(ctrl)
-
- # Must clear _controls to prevent MessageControl.shutdown calls
- w._controls = {}
- if not w.parent_paned:
- w.window.destroy()
- else:
- # Don't close parent window, just remove the child
- child = w.parent_paned.get_child2()
- w.parent_paned.remove(child)
- gtkgui_helpers.resize_window(w.window,
- gajim.config.get('roster_width'),
- gajim.config.get('roster_height'))
-
- self._windows = {}
-
- for ctrl in controls:
- mw = self.get_window(ctrl.contact.jid, ctrl.account)
- if not mw:
- mw = self.create_window(ctrl.contact, ctrl.account,
- ctrl.type_id)
- ctrl.parent_win = mw
- mw.new_tab(ctrl)
-
-# vim: se ts=3:
+ """
+ A manager and factory for MessageWindow objects
+ """
+
+ __gsignals__ = {
+ 'window-delete': (gobject.SIGNAL_RUN_LAST, None, (object,)),
+ }
+
+ # These constants map to common.config.opt_one_window_types indices
+ (
+ ONE_MSG_WINDOW_NEVER,
+ ONE_MSG_WINDOW_ALWAYS,
+ ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER,
+ ONE_MSG_WINDOW_PERACCT,
+ ONE_MSG_WINDOW_PERTYPE,
+ ) = range(5)
+ # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode
+ MAIN_WIN = 'main'
+ # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode
+ ROSTER_MAIN_WIN = 'roster'
+
+ def __init__(self, parent_window, parent_paned):
+ """
+ A dictionary of windows; the key depends on the config:
+ ONE_MSG_WINDOW_NEVER: The key is the contact JID
+ ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN
+ ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN
+ ONE_MSG_WINDOW_PERACCT: The key is the account name
+ ONE_MSG_WINDOW_PERTYPE: The key is a message type constant
+ """
+ gobject.GObject.__init__(self)
+ self._windows = {}
+
+ # Map the mode to a int constant for frequent compares
+ mode = gajim.config.get('one_message_window')
+ self.mode = common.config.opt_one_window_types.index(mode)
+
+ self.parent_win = parent_window
+ self.parent_paned = parent_paned
+
+ def change_account_name(self, old_name, new_name):
+ for win in self.windows():
+ win.change_account_name(old_name, new_name)
+
+ def _new_window(self, acct, type_):
+ parent_win = None
+ parent_paned = None
+ if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
+ parent_win = self.parent_win
+ parent_paned = self.parent_paned
+ win = MessageWindow(acct, type_, parent_win, parent_paned)
+ # we track the lifetime of this window
+ win.window.connect('delete-event', self._on_window_delete)
+ win.window.connect('destroy', self._on_window_destroy)
+ return win
+
+ def _gtk_win_to_msg_win(self, gtk_win):
+ for w in self.windows():
+ if w.window == gtk_win:
+ return w
+ return None
+
+ def get_window(self, jid, acct):
+ for win in self.windows():
+ if win.has_control(jid, acct):
+ return win
+
+ return None
+
+ def has_window(self, jid, acct):
+ return self.get_window(jid, acct) is not None
+
+ def one_window_opened(self, contact=None, acct=None, type_=None):
+ try:
+ return \
+ self._windows[self._mode_to_key(contact, acct, type_)] is not None
+ except KeyError:
+ return False
+
+ def _resize_window(self, win, acct, type_):
+ """
+ Resizes window according to config settings
+ """
+ if self.mode in (self.ONE_MSG_WINDOW_ALWAYS,
+ self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER):
+ size = (gajim.config.get('msgwin-width'),
+ gajim.config.get('msgwin-height'))
+ if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
+ parent_size = win.window.get_size()
+ # Need to add the size of the now visible paned handle, otherwise
+ # the saved width of the message window decreases by this amount
+ handle_size = win.parent_paned.style_get_property('handle-size')
+ size = (parent_size[0] + size[0] + handle_size, size[1])
+ elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
+ size = (gajim.config.get_per('accounts', acct, 'msgwin-width'),
+ gajim.config.get_per('accounts', acct, 'msgwin-height'))
+ elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE):
+ if type_ == message_control.TYPE_PM:
+ type_ = message_control.TYPE_CHAT
+ opt_width = type_ + '-msgwin-width'
+ opt_height = type_ + '-msgwin-height'
+ size = (gajim.config.get(opt_width), gajim.config.get(opt_height))
+ else:
+ return
+ win.resize(size[0], size[1])
+ if win.parent_paned:
+ win.parent_paned.set_position(parent_size[0])
+
+ def _position_window(self, win, acct, type_):
+ """
+ Moves window according to config settings
+ """
+ if (self.mode in [self.ONE_MSG_WINDOW_NEVER,
+ self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]):
+ return
+
+ if self.mode == self.ONE_MSG_WINDOW_ALWAYS:
+ pos = (gajim.config.get('msgwin-x-position'),
+ gajim.config.get('msgwin-y-position'))
+ elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
+ pos = (gajim.config.get_per('accounts', acct, 'msgwin-x-position'),
+ gajim.config.get_per('accounts', acct, 'msgwin-y-position'))
+ elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
+ pos = (gajim.config.get(type_ + '-msgwin-x-position'),
+ gajim.config.get(type_ + '-msgwin-y-position'))
+ else:
+ return
+
+ gtkgui_helpers.move_window(win.window, pos[0], pos[1])
+
+ def _mode_to_key(self, contact, acct, type_, resource = None):
+ if self.mode == self.ONE_MSG_WINDOW_NEVER:
+ key = acct + contact.jid
+ if resource:
+ key += '/' + resource
+ return key
+ elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
+ return self.MAIN_WIN
+ elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
+ return self.ROSTER_MAIN_WIN
+ elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
+ return acct
+ elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
+ return type_
+
+ def create_window(self, contact, acct, type_, resource = None):
+ win_acct = None
+ win_type = None
+ win_role = None # X11 window role
+
+ win_key = self._mode_to_key(contact, acct, type_, resource)
+ if self.mode == self.ONE_MSG_WINDOW_PERACCT:
+ win_acct = acct
+ win_role = acct
+ elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
+ win_type = type_
+ win_role = type_
+ elif self.mode == self.ONE_MSG_WINDOW_NEVER:
+ win_type = type_
+ win_role = contact.jid
+ elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
+ win_role = 'messages'
+
+ win = None
+ try:
+ win = self._windows[win_key]
+ except KeyError:
+ win = self._new_window(win_acct, win_type)
+
+ if win_role:
+ win.window.set_role(win_role)
+
+ # Position and size window based on saved state and window mode
+ if not self.one_window_opened(contact, acct, type_):
+ if gajim.config.get('msgwin-max-state'):
+ win.window.maximize()
+ else:
+ self._resize_window(win, acct, type_)
+ self._position_window(win, acct, type_)
+
+ self._windows[win_key] = win
+ return win
+
+ def change_key(self, old_jid, new_jid, acct):
+ win = self.get_window(old_jid, acct)
+ if self.mode == self.ONE_MSG_WINDOW_NEVER:
+ old_key = acct + old_jid
+ if old_jid not in self._windows:
+ return
+ new_key = acct + new_jid
+ self._windows[new_key] = self._windows[old_key]
+ del self._windows[old_key]
+ win.change_key(old_jid, new_jid, acct)
+
+ def _on_window_delete(self, win, event):
+ self.save_state(self._gtk_win_to_msg_win(win))
+ gajim.interface.save_config()
+ return False
+
+ def _on_window_destroy(self, win):
+ for k in self._windows.keys():
+ if self._windows[k].window == win:
+ self.emit('window-delete', self._windows[k])
+ del self._windows[k]
+ return
+
+ def get_control(self, jid, acct):
+ """
+ Amongst all windows, return the MessageControl for jid
+ """
+ win = self.get_window(jid, acct)
+ if win:
+ return win.get_control(jid, acct)
+ return None
+
+ def get_gc_control(self, jid, acct):
+ """
+ Same as get_control. Was briefly required, is not any more. May be useful
+ some day in the future?
+ """
+ ctrl = self.get_control(jid, acct)
+ if ctrl and ctrl.type_id == message_control.TYPE_GC:
+ return ctrl
+ return None
+
+ def get_controls(self, type_=None, acct=None):
+ ctrls = []
+ for c in self.controls():
+ if acct and c.account != acct:
+ continue
+ if not type_ or c.type_id == type_:
+ ctrls.append(c)
+ return ctrls
+
+ def windows(self):
+ for w in self._windows.values():
+ yield w
+
+ def controls(self):
+ for w in self._windows.values():
+ for c in w.controls():
+ yield c
+
+ def shutdown(self, width_adjust=0):
+ for w in self.windows():
+ self.save_state(w, width_adjust)
+ if not w.parent_paned:
+ w.window.hide()
+ w.window.destroy()
+
+ gajim.interface.save_config()
+
+ def save_state(self, msg_win, width_adjust=0):
+ # Save window size and position
+ max_win_key = 'msgwin-max-state'
+ pos_x_key = 'msgwin-x-position'
+ pos_y_key = 'msgwin-y-position'
+ size_width_key = 'msgwin-width'
+ size_height_key = 'msgwin-height'
+
+ acct = None
+ x, y = msg_win.window.get_position()
+ width, height = msg_win.window.get_size()
+
+ # If any of these values seem bogus don't update.
+ if x < 0 or y < 0 or width < 0 or height < 0:
+ return
+
+ elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
+ acct = msg_win.account
+ elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
+ type_ = msg_win.type_
+ pos_x_key = type_ + '-msgwin-x-position'
+ pos_y_key = type_ + '-msgwin-y-position'
+ size_width_key = type_ + '-msgwin-width'
+ size_height_key = type_ + '-msgwin-height'
+ elif self.mode == self.ONE_MSG_WINDOW_NEVER:
+ type_ = msg_win.type_
+ size_width_key = type_ + '-msgwin-width'
+ size_height_key = type_ + '-msgwin-height'
+ elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
+ # Ignore any hpaned width
+ width = msg_win.notebook.allocation.width
+
+ if acct:
+ gajim.config.set_per('accounts', acct, size_width_key, width)
+ gajim.config.set_per('accounts', acct, size_height_key, height)
+
+ if self.mode != self.ONE_MSG_WINDOW_NEVER:
+ gajim.config.set_per('accounts', acct, pos_x_key, x)
+ gajim.config.set_per('accounts', acct, pos_y_key, y)
+
+ else:
+ win_maximized = msg_win.window.window.get_state() == \
+ gtk.gdk.WINDOW_STATE_MAXIMIZED
+ gajim.config.set(max_win_key, win_maximized)
+ width += width_adjust
+ gajim.config.set(size_width_key, width)
+ gajim.config.set(size_height_key, height)
+
+ if self.mode != self.ONE_MSG_WINDOW_NEVER:
+ gajim.config.set(pos_x_key, x)
+ gajim.config.set(pos_y_key, y)
+
+ def reconfig(self):
+ for w in self.windows():
+ self.save_state(w)
+ gajim.interface.save_config()
+ mode = gajim.config.get('one_message_window')
+ if self.mode == common.config.opt_one_window_types.index(mode):
+ # No change
+ return
+ self.mode = common.config.opt_one_window_types.index(mode)
+
+ controls = []
+ for w in self.windows():
+ # Note, we are taking care not to hide/delete the roster window when the
+ # MessageWindow is embedded.
+ if not w.parent_paned:
+ w.window.hide()
+ else:
+ # Stash current size so it can be restored if the MessageWindow
+ # is not longer embedded
+ roster_width = w.parent_paned.get_child1().allocation.width
+ gajim.config.set('roster_width', roster_width)
+
+ while w.notebook.get_n_pages():
+ page = w.notebook.get_nth_page(0)
+ ctrl = w._widget_to_control(page)
+ w.notebook.remove_page(0)
+ page.unparent()
+ controls.append(ctrl)
+
+ # Must clear _controls to prevent MessageControl.shutdown calls
+ w._controls = {}
+ if not w.parent_paned:
+ w.window.destroy()
+ else:
+ # Don't close parent window, just remove the child
+ child = w.parent_paned.get_child2()
+ w.parent_paned.remove(child)
+ gtkgui_helpers.resize_window(w.window,
+ gajim.config.get('roster_width'),
+ gajim.config.get('roster_height'))
+
+ self._windows = {}
+
+ for ctrl in controls:
+ mw = self.get_window(ctrl.contact.jid, ctrl.account)
+ if not mw:
+ mw = self.create_window(ctrl.contact, ctrl.account,
+ ctrl.type_id)
+ ctrl.parent_win = mw
+ mw.new_tab(ctrl)
diff --git a/src/music_track_listener.py b/src/music_track_listener.py
index 07105a947..9de01dd02 100644
--- a/src/music_track_listener.py
+++ b/src/music_track_listener.py
@@ -25,282 +25,280 @@
import gobject
if __name__ == '__main__':
- # install _() func before importing dbus_support
- from common import i18n
+ # install _() func before importing dbus_support
+ from common import i18n
from common import dbus_support
if dbus_support.supported:
- import dbus
- import dbus.glib
+ import dbus
+ import dbus.glib
class MusicTrackInfo(object):
- __slots__ = ['title', 'album', 'artist', 'duration', 'track_number',
- 'paused']
+ __slots__ = ['title', 'album', 'artist', 'duration', 'track_number',
+ 'paused']
class MusicTrackListener(gobject.GObject):
- __gsignals__ = {
- 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)),
- }
-
- _instance = None
- @classmethod
- def get(cls):
- if cls._instance is None:
- cls._instance = cls()
- return cls._instance
-
- def __init__(self):
- super(MusicTrackListener, self).__init__()
- self._last_playing_music = None
-
- bus = dbus.SessionBus()
-
- ## MPRIS
- bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange',
- 'org.freedesktop.MediaPlayer')
- bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange',
- 'org.freedesktop.MediaPlayer')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='org.freedesktop.MediaPlayer')
-
- ## Muine
- bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged',
- 'org.gnome.Muine.Player')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine')
- bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged',
- 'org.gnome.Muine.Player')
-
- ## Rhythmbox
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
- bus.add_signal_receiver(self._rhythmbox_playing_changed_cb,
- 'playingChanged', 'org.gnome.Rhythmbox.Player')
- bus.add_signal_receiver(self._player_playing_song_property_changed_cb,
- 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player')
-
- ## Banshee
- bus.add_signal_receiver(self._banshee_state_changed_cb,
- 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='org.bansheeproject.Banshee')
-
- ## Quod Libet
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
- 'SongStarted', 'net.sacredchao.QuodLibet')
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
- 'Paused', 'net.sacredchao.QuodLibet')
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
- 'Unpaused', 'net.sacredchao.QuodLibet')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='net.sacredchao.QuodLibet')
-
- def _player_name_owner_changed(self, name, old, new):
- if not new:
- self.emit('music-track-changed', None)
-
- def _player_playing_changed_cb(self, playing):
- if playing:
- self.emit('music-track-changed', self._last_playing_music)
- else:
- self.emit('music-track-changed', None)
-
- def _player_playing_song_property_changed_cb(self, a, b, c, d):
- if b == 'rb:stream-song-title':
- self.emit('music-track-changed', self._last_playing_music)
-
- def _mpris_properties_extract(self, song):
- info = MusicTrackInfo()
- info.title = song.get('title', '')
- info.album = song.get('album', '')
- info.artist = song.get('artist', '')
- info.duration = int(song.get('length', 0))
- return info
-
- def _mpris_playing_changed_cb(self, playing):
- if type(playing) is dbus.Struct:
- if playing[0]:
- self.emit('music-track-changed', None)
- else:
- self.emit('music-track-changed', self._last_playing_music)
- else: # Workaround for e.g. Audacious
- if playing:
- self.emit('music-track-changed', None)
- else:
- self.emit('music-track-changed', self._last_playing_music)
-
- def _mpris_music_track_change_cb(self, arg):
- self._last_playing_music = self._mpris_properties_extract(arg)
- self.emit('music-track-changed', self._last_playing_music)
-
- def _muine_properties_extract(self, song_string):
- d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \
- song_string.split('\n'))
- info = MusicTrackInfo()
- info.title = d['title']
- info.album = d['album']
- info.artist = d['artist']
- info.duration = int(d['duration'])
- info.track_number = int(d['track_number'])
- return info
-
- def _muine_music_track_change_cb(self, arg):
- info = self._muine_properties_extract(arg)
- self.emit('music-track-changed', info)
-
- def _rhythmbox_playing_changed_cb(self, playing):
- if playing:
- info = self.get_playing_track()
- self.emit('music-track-changed', info)
- else:
- self.emit('music-track-changed', None)
-
- def _rhythmbox_properties_extract(self, props):
- info = MusicTrackInfo()
- info.title = props.get('title', None)
- info.album = props.get('album', None)
- info.artist = props.get('artist', None)
- info.duration = int(props.get('duration', 0))
- info.track_number = int(props.get('track-number', 0))
- return info
-
- def _banshee_state_changed_cb(self, state):
- if state == 'playing':
- bus = dbus.SessionBus()
- banshee = bus.get_object('org.bansheeproject.Banshee',
- '/org/bansheeproject/Banshee/PlayerEngine')
- currentTrack = banshee.GetCurrentTrack()
- self._last_playing_music = self._banshee_properties_extract(
- currentTrack)
- self.emit('music-track-changed', self._last_playing_music)
- elif state == 'paused':
- self.emit('music-track-changed', None)
-
- def _banshee_properties_extract(self, props):
- info = MusicTrackInfo()
- info.title = props.get('name', None)
- info.album = props.get('album', None)
- info.artist = props.get('artist', None)
- info.duration = int(props.get('length', 0))
- return info
-
- def _quodlibet_state_change_cb(self, state=None):
- info = self.get_playing_track()
- if info:
- self.emit('music-track-changed', info)
- else:
- self.emit('music-track-changed', None)
-
- def _quodlibet_properties_extract(self, props):
- info = MusicTrackInfo()
- info.title = props.get('title', None)
- info.album = props.get('album', None)
- info.artist = props.get('artist', None)
- info.duration = int(props.get('~#length', 0))
- return info
-
- def get_playing_track(self):
- '''Return a MusicTrackInfo for the currently playing
- song, or None if no song is playing'''
-
- bus = dbus.SessionBus()
-
- ## Check Muine playing track
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('org.gnome.Muine'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'org.gnome.Muine'):
- test = True
- if test:
- obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player')
- player = dbus.Interface(obj, 'org.gnome.Muine.Player')
- if player.GetPlaying():
- song_string = player.GetCurrentSong()
- song = self._muine_properties_extract(song_string)
- self._last_playing_music = song
- return song
-
- ## Check Rhythmbox playing song
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('org.gnome.Rhythmbox'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'org.gnome.Rhythmbox'):
- test = True
- if test:
- rbshellobj = bus.get_object('org.gnome.Rhythmbox',
- '/org/gnome/Rhythmbox/Shell')
- player = dbus.Interface(
- bus.get_object('org.gnome.Rhythmbox',
- '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player')
- rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
- try:
- uri = player.getPlayingUri()
- except dbus.DBusException:
- uri = None
- if not uri:
- return None
- props = rbshell.getSongProperties(uri)
- info = self._rhythmbox_properties_extract(props)
- self._last_playing_music = info
- return info
-
- ## Check Banshee playing track
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('org.bansheeproject.Banshee'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'org.bansheeproject.Banshee'):
- test = True
- if test:
- banshee = bus.get_object('org.bansheeproject.Banshee',
- '/org/bansheeproject/Banshee/PlayerEngine')
- currentTrack = banshee.GetCurrentTrack()
- if currentTrack:
- song = self._banshee_properties_extract(currentTrack)
- self._last_playing_music = song
- return song
-
- ## Check Quod Libet playing track
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('net.sacredchao.QuodLibet'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'net.sacredchao.QuodLibet'):
- test = True
- if test:
- quodlibet = bus.get_object('net.sacredchao.QuodLibet',
- '/net/sacredchao/QuodLibet')
- if quodlibet.IsPlaying():
- currentTrack = quodlibet.CurrentSong()
- song = self._quodlibet_properties_extract(currentTrack)
- self._last_playing_music = song
- return song
-
- return None
+ __gsignals__ = {
+ 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)),
+ }
+
+ _instance = None
+ @classmethod
+ def get(cls):
+ if cls._instance is None:
+ cls._instance = cls()
+ return cls._instance
+
+ def __init__(self):
+ super(MusicTrackListener, self).__init__()
+ self._last_playing_music = None
+
+ bus = dbus.SessionBus()
+
+ ## MPRIS
+ bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange',
+ 'org.freedesktop.MediaPlayer')
+ bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange',
+ 'org.freedesktop.MediaPlayer')
+ bus.add_signal_receiver(self._player_name_owner_changed,
+ 'NameOwnerChanged', 'org.freedesktop.DBus',
+ arg0='org.freedesktop.MediaPlayer')
+
+ ## Muine
+ bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged',
+ 'org.gnome.Muine.Player')
+ bus.add_signal_receiver(self._player_name_owner_changed,
+ 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine')
+ bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged',
+ 'org.gnome.Muine.Player')
+
+ ## Rhythmbox
+ bus.add_signal_receiver(self._player_name_owner_changed,
+ 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
+ bus.add_signal_receiver(self._rhythmbox_playing_changed_cb,
+ 'playingChanged', 'org.gnome.Rhythmbox.Player')
+ bus.add_signal_receiver(self._player_playing_song_property_changed_cb,
+ 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player')
+
+ ## Banshee
+ bus.add_signal_receiver(self._banshee_state_changed_cb,
+ 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine')
+ bus.add_signal_receiver(self._player_name_owner_changed,
+ 'NameOwnerChanged', 'org.freedesktop.DBus',
+ arg0='org.bansheeproject.Banshee')
+
+ ## Quod Libet
+ bus.add_signal_receiver(self._quodlibet_state_change_cb,
+ 'SongStarted', 'net.sacredchao.QuodLibet')
+ bus.add_signal_receiver(self._quodlibet_state_change_cb,
+ 'Paused', 'net.sacredchao.QuodLibet')
+ bus.add_signal_receiver(self._quodlibet_state_change_cb,
+ 'Unpaused', 'net.sacredchao.QuodLibet')
+ bus.add_signal_receiver(self._player_name_owner_changed,
+ 'NameOwnerChanged', 'org.freedesktop.DBus',
+ arg0='net.sacredchao.QuodLibet')
+
+ def _player_name_owner_changed(self, name, old, new):
+ if not new:
+ self.emit('music-track-changed', None)
+
+ def _player_playing_changed_cb(self, playing):
+ if playing:
+ self.emit('music-track-changed', self._last_playing_music)
+ else:
+ self.emit('music-track-changed', None)
+
+ def _player_playing_song_property_changed_cb(self, a, b, c, d):
+ if b == 'rb:stream-song-title':
+ self.emit('music-track-changed', self._last_playing_music)
+
+ def _mpris_properties_extract(self, song):
+ info = MusicTrackInfo()
+ info.title = song.get('title', '')
+ info.album = song.get('album', '')
+ info.artist = song.get('artist', '')
+ info.duration = int(song.get('length', 0))
+ return info
+
+ def _mpris_playing_changed_cb(self, playing):
+ if type(playing) is dbus.Struct:
+ if playing[0]:
+ self.emit('music-track-changed', None)
+ else:
+ self.emit('music-track-changed', self._last_playing_music)
+ else: # Workaround for e.g. Audacious
+ if playing:
+ self.emit('music-track-changed', None)
+ else:
+ self.emit('music-track-changed', self._last_playing_music)
+
+ def _mpris_music_track_change_cb(self, arg):
+ self._last_playing_music = self._mpris_properties_extract(arg)
+ self.emit('music-track-changed', self._last_playing_music)
+
+ def _muine_properties_extract(self, song_string):
+ d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \
+ song_string.split('\n'))
+ info = MusicTrackInfo()
+ info.title = d['title']
+ info.album = d['album']
+ info.artist = d['artist']
+ info.duration = int(d['duration'])
+ info.track_number = int(d['track_number'])
+ return info
+
+ def _muine_music_track_change_cb(self, arg):
+ info = self._muine_properties_extract(arg)
+ self.emit('music-track-changed', info)
+
+ def _rhythmbox_playing_changed_cb(self, playing):
+ if playing:
+ info = self.get_playing_track()
+ self.emit('music-track-changed', info)
+ else:
+ self.emit('music-track-changed', None)
+
+ def _rhythmbox_properties_extract(self, props):
+ info = MusicTrackInfo()
+ info.title = props.get('title', None)
+ info.album = props.get('album', None)
+ info.artist = props.get('artist', None)
+ info.duration = int(props.get('duration', 0))
+ info.track_number = int(props.get('track-number', 0))
+ return info
+
+ def _banshee_state_changed_cb(self, state):
+ if state == 'playing':
+ bus = dbus.SessionBus()
+ banshee = bus.get_object('org.bansheeproject.Banshee',
+ '/org/bansheeproject/Banshee/PlayerEngine')
+ currentTrack = banshee.GetCurrentTrack()
+ self._last_playing_music = self._banshee_properties_extract(
+ currentTrack)
+ self.emit('music-track-changed', self._last_playing_music)
+ elif state == 'paused':
+ self.emit('music-track-changed', None)
+
+ def _banshee_properties_extract(self, props):
+ info = MusicTrackInfo()
+ info.title = props.get('name', None)
+ info.album = props.get('album', None)
+ info.artist = props.get('artist', None)
+ info.duration = int(props.get('length', 0))
+ return info
+
+ def _quodlibet_state_change_cb(self, state=None):
+ info = self.get_playing_track()
+ if info:
+ self.emit('music-track-changed', info)
+ else:
+ self.emit('music-track-changed', None)
+
+ def _quodlibet_properties_extract(self, props):
+ info = MusicTrackInfo()
+ info.title = props.get('title', None)
+ info.album = props.get('album', None)
+ info.artist = props.get('artist', None)
+ info.duration = int(props.get('~#length', 0))
+ return info
+
+ def get_playing_track(self):
+ '''Return a MusicTrackInfo for the currently playing
+ song, or None if no song is playing'''
+
+ bus = dbus.SessionBus()
+
+ ## Check Muine playing track
+ test = False
+ if hasattr(bus, 'name_has_owner'):
+ if bus.name_has_owner('org.gnome.Muine'):
+ test = True
+ elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
+ 'org.gnome.Muine'):
+ test = True
+ if test:
+ obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player')
+ player = dbus.Interface(obj, 'org.gnome.Muine.Player')
+ if player.GetPlaying():
+ song_string = player.GetCurrentSong()
+ song = self._muine_properties_extract(song_string)
+ self._last_playing_music = song
+ return song
+
+ ## Check Rhythmbox playing song
+ test = False
+ if hasattr(bus, 'name_has_owner'):
+ if bus.name_has_owner('org.gnome.Rhythmbox'):
+ test = True
+ elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
+ 'org.gnome.Rhythmbox'):
+ test = True
+ if test:
+ rbshellobj = bus.get_object('org.gnome.Rhythmbox',
+ '/org/gnome/Rhythmbox/Shell')
+ player = dbus.Interface(
+ bus.get_object('org.gnome.Rhythmbox',
+ '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player')
+ rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
+ try:
+ uri = player.getPlayingUri()
+ except dbus.DBusException:
+ uri = None
+ if not uri:
+ return None
+ props = rbshell.getSongProperties(uri)
+ info = self._rhythmbox_properties_extract(props)
+ self._last_playing_music = info
+ return info
+
+ ## Check Banshee playing track
+ test = False
+ if hasattr(bus, 'name_has_owner'):
+ if bus.name_has_owner('org.bansheeproject.Banshee'):
+ test = True
+ elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
+ 'org.bansheeproject.Banshee'):
+ test = True
+ if test:
+ banshee = bus.get_object('org.bansheeproject.Banshee',
+ '/org/bansheeproject/Banshee/PlayerEngine')
+ currentTrack = banshee.GetCurrentTrack()
+ if currentTrack:
+ song = self._banshee_properties_extract(currentTrack)
+ self._last_playing_music = song
+ return song
+
+ ## Check Quod Libet playing track
+ test = False
+ if hasattr(bus, 'name_has_owner'):
+ if bus.name_has_owner('net.sacredchao.QuodLibet'):
+ test = True
+ elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
+ 'net.sacredchao.QuodLibet'):
+ test = True
+ if test:
+ quodlibet = bus.get_object('net.sacredchao.QuodLibet',
+ '/net/sacredchao/QuodLibet')
+ if quodlibet.IsPlaying():
+ currentTrack = quodlibet.CurrentSong()
+ song = self._quodlibet_properties_extract(currentTrack)
+ self._last_playing_music = song
+ return song
+
+ return None
# here we test :)
if __name__ == '__main__':
- def music_track_change_cb(listener, music_track_info):
- if music_track_info is None:
- print 'Stop!'
- else:
- print music_track_info.title
- listener = MusicTrackListener.get()
- listener.connect('music-track-changed', music_track_change_cb)
- track = listener.get_playing_track()
- if track is None:
- print 'Now not playing anything'
- else:
- print 'Now playing: "%s" by %s' % (track.title, track.artist)
- gobject.MainLoop().run()
-
-# vim: se ts=3:
+ def music_track_change_cb(listener, music_track_info):
+ if music_track_info is None:
+ print 'Stop!'
+ else:
+ print music_track_info.title
+ listener = MusicTrackListener.get()
+ listener.connect('music-track-changed', music_track_change_cb)
+ track = listener.get_playing_track()
+ if track is None:
+ print 'Now not playing anything'
+ else:
+ print 'Now playing: "%s" by %s' % (track.title, track.artist)
+ gobject.MainLoop().run()
diff --git a/src/negotiation.py b/src/negotiation.py
index 6e8161983..d5d974b79 100644
--- a/src/negotiation.py
+++ b/src/negotiation.py
@@ -27,62 +27,60 @@ from common import gajim
from common import xmpp
def describe_features(features):
- """
- A human-readable description of the features that have been negotiated
- """
- if features['logging'] == 'may':
- return _('- messages will be logged')
- elif features['logging'] == 'mustnot':
- return _('- messages will not be logged')
+ """
+ A human-readable description of the features that have been negotiated
+ """
+ if features['logging'] == 'may':
+ return _('- messages will be logged')
+ elif features['logging'] == 'mustnot':
+ return _('- messages will not be logged')
class FeatureNegotiationWindow:
- def __init__(self, account, jid, session, form):
- self.account = account
- self.jid = jid
- self.form = form
- self.session = session
+ def __init__(self, account, jid, session, form):
+ self.account = account
+ self.jid = jid
+ self.form = form
+ self.session = session
- self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window')
- self.window = self.xml.get_object('data_form_window')
+ self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window')
+ self.window = self.xml.get_object('data_form_window')
- config_vbox = self.xml.get_object('config_vbox')
- dataform = dataforms.ExtendForm(node = self.form)
- self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
- self.data_form_widget.show()
- config_vbox.pack_start(self.data_form_widget)
+ config_vbox = self.xml.get_object('config_vbox')
+ dataform = dataforms.ExtendForm(node = self.form)
+ self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
+ self.data_form_widget.show()
+ config_vbox.pack_start(self.data_form_widget)
- self.xml.connect_signals(self)
- self.window.show_all()
+ self.xml.connect_signals(self)
+ self.window.show_all()
- def on_ok_button_clicked(self, widget):
- acceptance = xmpp.Message(self.jid)
- acceptance.setThread(self.session.thread_id)
- feature = acceptance.NT.feature
- feature.setNamespace(xmpp.NS_FEATURE)
+ def on_ok_button_clicked(self, widget):
+ acceptance = xmpp.Message(self.jid)
+ acceptance.setThread(self.session.thread_id)
+ feature = acceptance.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
- form = self.data_form_widget.data_form
- form.setAttr('type', 'submit')
+ form = self.data_form_widget.data_form
+ form.setAttr('type', 'submit')
- feature.addChild(node=form)
+ feature.addChild(node=form)
- gajim.connections[self.account].send_stanza(acceptance)
+ gajim.connections[self.account].send_stanza(acceptance)
- self.window.destroy()
+ self.window.destroy()
- def on_cancel_button_clicked(self, widget):
- rejection = xmpp.Message(self.jid)
- rejection.setThread(self.session.thread_id)
- feature = rejection.NT.feature
- feature.setNamespace(xmpp.NS_FEATURE)
+ def on_cancel_button_clicked(self, widget):
+ rejection = xmpp.Message(self.jid)
+ rejection.setThread(self.session.thread_id)
+ feature = rejection.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
- x = xmpp.DataForm(typ='submit')
- x.addChild(node=xmpp.DataField('FORM_TYPE', value='urn:xmpp:ssn'))
- x.addChild(node=xmpp.DataField('accept', value='false', typ='boolean'))
+ x = xmpp.DataForm(typ='submit')
+ x.addChild(node=xmpp.DataField('FORM_TYPE', value='urn:xmpp:ssn'))
+ x.addChild(node=xmpp.DataField('accept', value='false', typ='boolean'))
- feature.addChild(node=x)
+ feature.addChild(node=x)
- gajim.connections[self.account].send_stanza(rejection)
+ gajim.connections[self.account].send_stanza(rejection)
- self.window.destroy()
-
-# vim: se ts=3:
+ self.window.destroy()
diff --git a/src/network_manager_listener.py b/src/network_manager_listener.py
index 3f1f774ca..9cf4aaa84 100644
--- a/src/network_manager_listener.py
+++ b/src/network_manager_listener.py
@@ -26,79 +26,77 @@ from common import gajim
def device_now_active(self, *args):
- """
- For Network Manager 0.6
- """
- for connection in gajim.connections.itervalues():
- if gajim.config.get_per('accounts', connection.name,
- 'listen_to_network_manager') and connection.time_to_reconnect:
- connection._reconnect()
+ """
+ For Network Manager 0.6
+ """
+ for connection in gajim.connections.itervalues():
+ if gajim.config.get_per('accounts', connection.name,
+ 'listen_to_network_manager') and connection.time_to_reconnect:
+ connection._reconnect()
def device_no_longer_active(self, *args):
- """
- For Network Manager 0.6
- """
- for connection in gajim.connections.itervalues():
- if gajim.config.get_per('accounts', connection.name,
- 'listen_to_network_manager') and connection.connected > 1:
- connection._disconnectedReconnCB()
+ """
+ For Network Manager 0.6
+ """
+ for connection in gajim.connections.itervalues():
+ if gajim.config.get_per('accounts', connection.name,
+ 'listen_to_network_manager') and connection.connected > 1:
+ connection._disconnectedReconnCB()
def state_changed(state):
- """
- For Network Manager 0.7
- """
- if props.Get("org.freedesktop.NetworkManager", "State") == 3:
- for connection in gajim.connections.itervalues():
- if gajim.config.get_per('accounts', connection.name,
- 'listen_to_network_manager') and connection.time_to_reconnect:
- connection._reconnect()
- else:
- for connection in gajim.connections.itervalues():
- if gajim.config.get_per('accounts', connection.name,
- 'listen_to_network_manager') and connection.connected > 1:
- connection._disconnectedReconnCB()
+ """
+ For Network Manager 0.7
+ """
+ if props.Get("org.freedesktop.NetworkManager", "State") == 3:
+ for connection in gajim.connections.itervalues():
+ if gajim.config.get_per('accounts', connection.name,
+ 'listen_to_network_manager') and connection.time_to_reconnect:
+ connection._reconnect()
+ else:
+ for connection in gajim.connections.itervalues():
+ if gajim.config.get_per('accounts', connection.name,
+ 'listen_to_network_manager') and connection.connected > 1:
+ connection._disconnectedReconnCB()
supported = False
from common import dbus_support
if dbus_support.supported:
- import dbus
- import dbus.glib
+ import dbus
+ import dbus.glib
- try:
- from common.dbus_support import system_bus
+ try:
+ from common.dbus_support import system_bus
- bus = system_bus.bus()
+ bus = system_bus.bus()
- if 'org.freedesktop.NetworkManager' in bus.list_names():
- nm_object = bus.get_object('org.freedesktop.NetworkManager',
- '/org/freedesktop/NetworkManager')
- props = dbus.Interface(nm_object,"org.freedesktop.DBus.Properties")
- bus.add_signal_receiver(state_changed,
- 'StateChanged',
- 'org.freedesktop.NetworkManager',
- 'org.freedesktop.NetworkManager',
- '/org/freedesktop/NetworkManager')
- supported = True
+ if 'org.freedesktop.NetworkManager' in bus.list_names():
+ nm_object = bus.get_object('org.freedesktop.NetworkManager',
+ '/org/freedesktop/NetworkManager')
+ props = dbus.Interface(nm_object,"org.freedesktop.DBus.Properties")
+ bus.add_signal_receiver(state_changed,
+ 'StateChanged',
+ 'org.freedesktop.NetworkManager',
+ 'org.freedesktop.NetworkManager',
+ '/org/freedesktop/NetworkManager')
+ supported = True
- except dbus.DBusException:
- try:
- if 'org.freedesktop.NetworkManager' in bus.list_names():
- supported = True
+ except dbus.DBusException:
+ try:
+ if 'org.freedesktop.NetworkManager' in bus.list_names():
+ supported = True
- bus.add_signal_receiver(device_no_longer_active,
- 'DeviceNoLongerActive',
- 'org.freedesktop.NetworkManager',
- 'org.freedesktop.NetworkManager',
- '/org/freedesktop/NetworkManager')
+ bus.add_signal_receiver(device_no_longer_active,
+ 'DeviceNoLongerActive',
+ 'org.freedesktop.NetworkManager',
+ 'org.freedesktop.NetworkManager',
+ '/org/freedesktop/NetworkManager')
- bus.add_signal_receiver(device_now_active,
- 'DeviceNowActive',
- 'org.freedesktop.NetworkManager',
- 'org.freedesktop.NetworkManager',
- '/org/freedesktop/NetworkManager')
- except Exception:
- pass
-
-# vim: se ts=3:
+ bus.add_signal_receiver(device_now_active,
+ 'DeviceNowActive',
+ 'org.freedesktop.NetworkManager',
+ 'org.freedesktop.NetworkManager',
+ '/org/freedesktop/NetworkManager')
+ except Exception:
+ pass
diff --git a/src/notify.py b/src/notify.py
index 37a7fd267..b9634ec6f 100644
--- a/src/notify.py
+++ b/src/notify.py
@@ -39,644 +39,642 @@ from common import helpers
from common import dbus_support
if dbus_support.supported:
- import dbus
- import dbus.glib
+ import dbus
+ import dbus.glib
USER_HAS_PYNOTIFY = True # user has pynotify module
try:
- import pynotify
- pynotify.init('Gajim Notification')
+ import pynotify
+ pynotify.init('Gajim Notification')
except ImportError:
- USER_HAS_PYNOTIFY = False
+ USER_HAS_PYNOTIFY = False
if gajim.HAVE_INDICATOR:
- import indicate
+ import indicate
def setup_indicator_server():
- server = indicate.indicate_server_ref_default()
- server.set_type('message.im')
- server.set_desktop_file('/usr/share/applications/gajim.desktop')
- server.connect('server-display', server_display)
- server.show()
+ server = indicate.indicate_server_ref_default()
+ server.set_type('message.im')
+ server.set_desktop_file('/usr/share/applications/gajim.desktop')
+ server.connect('server-display', server_display)
+ server.show()
def display(indicator, account, jid, msg_type):
- gajim.interface.handle_event(account, jid, msg_type)
- indicator.hide()
+ gajim.interface.handle_event(account, jid, msg_type)
+ indicator.hide()
def server_display(server):
- win = gajim.interface.roster.window
- win.present()
+ win = gajim.interface.roster.window
+ win.present()
def get_show_in_roster(event, account, contact, session=None):
- """
- Return True if this event must be shown in roster, else False
- """
- if event == 'gc_message_received':
- return True
- num = get_advanced_notification(event, account, contact)
- if num is not None:
- if gajim.config.get_per('notifications', str(num), 'roster') == 'yes':
- return True
- if gajim.config.get_per('notifications', str(num), 'roster') == 'no':
- return False
- if event == 'message_received':
- if session and session.control:
- return False
- return True
+ """
+ Return True if this event must be shown in roster, else False
+ """
+ if event == 'gc_message_received':
+ return True
+ num = get_advanced_notification(event, account, contact)
+ if num is not None:
+ if gajim.config.get_per('notifications', str(num), 'roster') == 'yes':
+ return True
+ if gajim.config.get_per('notifications', str(num), 'roster') == 'no':
+ return False
+ if event == 'message_received':
+ if session and session.control:
+ return False
+ return True
def get_show_in_systray(event, account, contact, type_=None):
- """
- Return True if this event must be shown in systray, else False
- """
- num = get_advanced_notification(event, account, contact)
- if num is not None:
- if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
- return True
- if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
- return False
- if type_ == 'printed_gc_msg' and not gajim.config.get(
- 'notify_on_all_muc_messages'):
- # it's not an highlighted message, don't show in systray
- return False
- return gajim.config.get('trayicon_notification_on_events')
+ """
+ Return True if this event must be shown in systray, else False
+ """
+ num = get_advanced_notification(event, account, contact)
+ if num is not None:
+ if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
+ return True
+ if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
+ return False
+ if type_ == 'printed_gc_msg' and not gajim.config.get(
+ 'notify_on_all_muc_messages'):
+ # it's not an highlighted message, don't show in systray
+ return False
+ return gajim.config.get('trayicon_notification_on_events')
def get_advanced_notification(event, account, contact):
- """
- Returns the number of the first (top most) advanced notification else None
- """
- num = 0
- notif = gajim.config.get_per('notifications', str(num))
- while notif:
- recipient_ok = False
- status_ok = False
- tab_opened_ok = False
- # test event
- if gajim.config.get_per('notifications', str(num), 'event') == event:
- # test recipient
- recipient_type = gajim.config.get_per('notifications', str(num),
- 'recipient_type')
- recipients = gajim.config.get_per('notifications', str(num),
- 'recipients').split()
- if recipient_type == 'all':
- recipient_ok = True
- elif recipient_type == 'contact' and contact.jid in recipients:
- recipient_ok = True
- elif recipient_type == 'group':
- for group in contact.groups:
- if group in contact.groups:
- recipient_ok = True
- break
- if recipient_ok:
- # test status
- our_status = gajim.SHOW_LIST[gajim.connections[account].connected]
- status = gajim.config.get_per('notifications', str(num), 'status')
- if status == 'all' or our_status in status.split():
- status_ok = True
- if status_ok:
- # test window_opened
- tab_opened = gajim.config.get_per('notifications', str(num),
- 'tab_opened')
- if tab_opened == 'both':
- tab_opened_ok = True
- else:
- chat_control = helpers.get_chat_control(account, contact)
- if (chat_control and tab_opened == 'yes') or (not chat_control and \
- tab_opened == 'no'):
- tab_opened_ok = True
- if tab_opened_ok:
- return num
-
- num += 1
- notif = gajim.config.get_per('notifications', str(num))
+ """
+ Returns the number of the first (top most) advanced notification else None
+ """
+ num = 0
+ notif = gajim.config.get_per('notifications', str(num))
+ while notif:
+ recipient_ok = False
+ status_ok = False
+ tab_opened_ok = False
+ # test event
+ if gajim.config.get_per('notifications', str(num), 'event') == event:
+ # test recipient
+ recipient_type = gajim.config.get_per('notifications', str(num),
+ 'recipient_type')
+ recipients = gajim.config.get_per('notifications', str(num),
+ 'recipients').split()
+ if recipient_type == 'all':
+ recipient_ok = True
+ elif recipient_type == 'contact' and contact.jid in recipients:
+ recipient_ok = True
+ elif recipient_type == 'group':
+ for group in contact.groups:
+ if group in contact.groups:
+ recipient_ok = True
+ break
+ if recipient_ok:
+ # test status
+ our_status = gajim.SHOW_LIST[gajim.connections[account].connected]
+ status = gajim.config.get_per('notifications', str(num), 'status')
+ if status == 'all' or our_status in status.split():
+ status_ok = True
+ if status_ok:
+ # test window_opened
+ tab_opened = gajim.config.get_per('notifications', str(num),
+ 'tab_opened')
+ if tab_opened == 'both':
+ tab_opened_ok = True
+ else:
+ chat_control = helpers.get_chat_control(account, contact)
+ if (chat_control and tab_opened == 'yes') or (not chat_control and \
+ tab_opened == 'no'):
+ tab_opened_ok = True
+ if tab_opened_ok:
+ return num
+
+ num += 1
+ notif = gajim.config.get_per('notifications', str(num))
def notify(event, jid, account, parameters, advanced_notif_num=None):
- """
- Check what type of notifications we want, depending on basic and the advanced
- configuration of notifications and do these notifications; advanced_notif_num
- holds the number of the first (top most) advanced notification
- """
- # First, find what notifications we want
- do_popup = False
- do_sound = False
- do_cmd = False
- if event == 'status_change':
- new_show = parameters[0]
- status_message = parameters[1]
- # Default: No popup for status change
- elif event == 'contact_connected':
- status_message = parameters
- j = gajim.get_jid_without_resource(jid)
- server = gajim.get_server_from_jid(j)
- account_server = account + '/' + server
- block_transport = False
- if account_server in gajim.block_signed_in_notifications and \
- gajim.block_signed_in_notifications[account_server]:
- block_transport = True
- if helpers.allow_showing_notification(account, 'notify_on_signin') and \
- not gajim.block_signed_in_notifications[account] and not block_transport:
- do_popup = True
- if gajim.config.get_per('soundevents', 'contact_connected',
- 'enabled') and not gajim.block_signed_in_notifications[account] and \
- not block_transport:
- do_sound = True
- elif event == 'contact_disconnected':
- status_message = parameters
- if helpers.allow_showing_notification(account, 'notify_on_signout'):
- do_popup = True
- if gajim.config.get_per('soundevents', 'contact_disconnected',
- 'enabled'):
- do_sound = True
- elif event == 'new_message':
- message_type = parameters[0]
- is_first_message = parameters[1]
- nickname = parameters[2]
- if gajim.config.get('notification_preview_message'):
- message = parameters[3]
- if message.startswith('/me ') or message.startswith('/me\n'):
- message = '* ' + nickname + message[3:]
- else:
- # We don't want message preview, do_preview = False
- message = ''
- focused = parameters[4]
- if helpers.allow_showing_notification(account, 'notify_on_new_message',
- advanced_notif_num, is_first_message):
- do_popup = True
- if is_first_message and helpers.allow_sound_notification(account,
- 'first_message_received', advanced_notif_num):
- do_sound = True
- elif not is_first_message and focused and \
- helpers.allow_sound_notification(account, 'next_message_received_focused',
- advanced_notif_num):
- do_sound = True
- elif not is_first_message and not focused and \
- helpers.allow_sound_notification(account,
- 'next_message_received_unfocused', advanced_notif_num):
- do_sound = True
- else:
- print '*Event not implemeted yet*'
-
- if advanced_notif_num is not None and gajim.config.get_per('notifications',
- str(advanced_notif_num), 'run_command'):
- do_cmd = True
-
- # Do the wanted notifications
- if do_popup:
- if event in ('contact_connected', 'contact_disconnected',
- 'status_change'): # Common code for popup for these three events
- if event == 'contact_disconnected':
- show_image = 'offline.png'
- suffix = '_notif_size_bw'
- else: #Status Change or Connected
- # FIXME: for status change,
- # we don't always 'online.png', but we
- # first need 48x48 for all status
- show_image = 'online.png'
- suffix = '_notif_size_colored'
- transport_name = gajim.get_transport_name_from_jid(jid)
- img_path = None
- if transport_name:
- img_path = os.path.join(helpers.get_transport_path(transport_name),
- '48x48', show_image)
- if not img_path or not os.path.isfile(img_path):
- iconset = gajim.config.get('iconset')
- img_path = os.path.join(helpers.get_iconset_path(iconset), '48x48',
- show_image)
- path = gtkgui_helpers.get_path_to_generic_or_avatar(img_path, jid=jid,
- suffix=suffix)
- if event == 'status_change':
- title = _('%(nick)s Changed Status') % \
- {'nick': gajim.get_name_from_jid(account, jid)}
- text = _('%(nick)s is now %(status)s') % \
- {'nick': gajim.get_name_from_jid(account, jid),\
- 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])}
- if status_message:
- text = text + " : " + status_message
- popup(_('Contact Changed Status'), jid, account,
- path_to_image=path, title=title, text=text)
- elif event == 'contact_connected':
- title = _('%(nickname)s Signed In') % \
- {'nickname': gajim.get_name_from_jid(account, jid)}
- text = ''
- if status_message:
- text = status_message
- popup(_('Contact Signed In'), jid, account,
- path_to_image=path, title=title, text=text)
- elif event == 'contact_disconnected':
- title = _('%(nickname)s Signed Out') % \
- {'nickname': gajim.get_name_from_jid(account, jid)}
- text = ''
- if status_message:
- text = status_message
- popup(_('Contact Signed Out'), jid, account,
- path_to_image=path, title=title, text=text)
- elif event == 'new_message':
- if message_type == 'normal': # single message
- event_type = _('New Single Message')
- img_name = 'gajim-single_msg_recv'
- title = _('New Single Message from %(nickname)s') % \
- {'nickname': nickname}
- text = message
- elif message_type == 'pm': # private message
- event_type = _('New Private Message')
- room_name = gajim.get_nick_from_jid(jid)
- img_name = 'gajim-priv_msg_recv'
- title = _('New Private Message from group chat %s') % room_name
- if message:
- text = _('%(nickname)s: %(message)s') % {'nickname': nickname,
- 'message': message}
- else:
- text = _('Messaged by %(nickname)s') % {'nickname': nickname}
-
- else: # chat message
- event_type = _('New Message')
- img_name = 'gajim-chat_msg_recv'
- title = _('New Message from %(nickname)s') % \
- {'nickname': nickname}
- text = message
- img_path = gtkgui_helpers.get_icon_path(img_name, 48)
- popup(event_type, jid, account, message_type,
- path_to_image=img_path, title=title, text=text)
-
- if do_sound:
- snd_file = None
- snd_event = None # If not snd_file, play the event
- if event == 'new_message':
- if advanced_notif_num is not None and gajim.config.get_per(
- 'notifications', str(advanced_notif_num), 'sound') == 'yes':
- snd_file = gajim.config.get_per('notifications',
- str(advanced_notif_num), 'sound_file')
- elif advanced_notif_num is not None and gajim.config.get_per(
- 'notifications', str(advanced_notif_num), 'sound') == 'no':
- pass # do not set snd_event
- elif is_first_message:
- snd_event = 'first_message_received'
- elif focused:
- snd_event = 'next_message_received_focused'
- else:
- snd_event = 'next_message_received_unfocused'
- elif event in ('contact_connected', 'contact_disconnected'):
- snd_event = event
- if snd_file:
- helpers.play_sound_file(snd_file)
- if snd_event:
- helpers.play_sound(snd_event)
-
- if do_cmd:
- command = gajim.config.get_per('notifications', str(advanced_notif_num),
- 'command')
- try:
- helpers.exec_command(command)
- except Exception:
- pass
+ """
+ Check what type of notifications we want, depending on basic and the advanced
+ configuration of notifications and do these notifications; advanced_notif_num
+ holds the number of the first (top most) advanced notification
+ """
+ # First, find what notifications we want
+ do_popup = False
+ do_sound = False
+ do_cmd = False
+ if event == 'status_change':
+ new_show = parameters[0]
+ status_message = parameters[1]
+ # Default: No popup for status change
+ elif event == 'contact_connected':
+ status_message = parameters
+ j = gajim.get_jid_without_resource(jid)
+ server = gajim.get_server_from_jid(j)
+ account_server = account + '/' + server
+ block_transport = False
+ if account_server in gajim.block_signed_in_notifications and \
+ gajim.block_signed_in_notifications[account_server]:
+ block_transport = True
+ if helpers.allow_showing_notification(account, 'notify_on_signin') and \
+ not gajim.block_signed_in_notifications[account] and not block_transport:
+ do_popup = True
+ if gajim.config.get_per('soundevents', 'contact_connected',
+ 'enabled') and not gajim.block_signed_in_notifications[account] and \
+ not block_transport:
+ do_sound = True
+ elif event == 'contact_disconnected':
+ status_message = parameters
+ if helpers.allow_showing_notification(account, 'notify_on_signout'):
+ do_popup = True
+ if gajim.config.get_per('soundevents', 'contact_disconnected',
+ 'enabled'):
+ do_sound = True
+ elif event == 'new_message':
+ message_type = parameters[0]
+ is_first_message = parameters[1]
+ nickname = parameters[2]
+ if gajim.config.get('notification_preview_message'):
+ message = parameters[3]
+ if message.startswith('/me ') or message.startswith('/me\n'):
+ message = '* ' + nickname + message[3:]
+ else:
+ # We don't want message preview, do_preview = False
+ message = ''
+ focused = parameters[4]
+ if helpers.allow_showing_notification(account, 'notify_on_new_message',
+ advanced_notif_num, is_first_message):
+ do_popup = True
+ if is_first_message and helpers.allow_sound_notification(account,
+ 'first_message_received', advanced_notif_num):
+ do_sound = True
+ elif not is_first_message and focused and \
+ helpers.allow_sound_notification(account, 'next_message_received_focused',
+ advanced_notif_num):
+ do_sound = True
+ elif not is_first_message and not focused and \
+ helpers.allow_sound_notification(account,
+ 'next_message_received_unfocused', advanced_notif_num):
+ do_sound = True
+ else:
+ print '*Event not implemeted yet*'
+
+ if advanced_notif_num is not None and gajim.config.get_per('notifications',
+ str(advanced_notif_num), 'run_command'):
+ do_cmd = True
+
+ # Do the wanted notifications
+ if do_popup:
+ if event in ('contact_connected', 'contact_disconnected',
+ 'status_change'): # Common code for popup for these three events
+ if event == 'contact_disconnected':
+ show_image = 'offline.png'
+ suffix = '_notif_size_bw'
+ else: #Status Change or Connected
+ # FIXME: for status change,
+ # we don't always 'online.png', but we
+ # first need 48x48 for all status
+ show_image = 'online.png'
+ suffix = '_notif_size_colored'
+ transport_name = gajim.get_transport_name_from_jid(jid)
+ img_path = None
+ if transport_name:
+ img_path = os.path.join(helpers.get_transport_path(transport_name),
+ '48x48', show_image)
+ if not img_path or not os.path.isfile(img_path):
+ iconset = gajim.config.get('iconset')
+ img_path = os.path.join(helpers.get_iconset_path(iconset), '48x48',
+ show_image)
+ path = gtkgui_helpers.get_path_to_generic_or_avatar(img_path, jid=jid,
+ suffix=suffix)
+ if event == 'status_change':
+ title = _('%(nick)s Changed Status') % \
+ {'nick': gajim.get_name_from_jid(account, jid)}
+ text = _('%(nick)s is now %(status)s') % \
+ {'nick': gajim.get_name_from_jid(account, jid),\
+ 'status': helpers.get_uf_show(gajim.SHOW_LIST[new_show])}
+ if status_message:
+ text = text + " : " + status_message
+ popup(_('Contact Changed Status'), jid, account,
+ path_to_image=path, title=title, text=text)
+ elif event == 'contact_connected':
+ title = _('%(nickname)s Signed In') % \
+ {'nickname': gajim.get_name_from_jid(account, jid)}
+ text = ''
+ if status_message:
+ text = status_message
+ popup(_('Contact Signed In'), jid, account,
+ path_to_image=path, title=title, text=text)
+ elif event == 'contact_disconnected':
+ title = _('%(nickname)s Signed Out') % \
+ {'nickname': gajim.get_name_from_jid(account, jid)}
+ text = ''
+ if status_message:
+ text = status_message
+ popup(_('Contact Signed Out'), jid, account,
+ path_to_image=path, title=title, text=text)
+ elif event == 'new_message':
+ if message_type == 'normal': # single message
+ event_type = _('New Single Message')
+ img_name = 'gajim-single_msg_recv'
+ title = _('New Single Message from %(nickname)s') % \
+ {'nickname': nickname}
+ text = message
+ elif message_type == 'pm': # private message
+ event_type = _('New Private Message')
+ room_name = gajim.get_nick_from_jid(jid)
+ img_name = 'gajim-priv_msg_recv'
+ title = _('New Private Message from group chat %s') % room_name
+ if message:
+ text = _('%(nickname)s: %(message)s') % {'nickname': nickname,
+ 'message': message}
+ else:
+ text = _('Messaged by %(nickname)s') % {'nickname': nickname}
+
+ else: # chat message
+ event_type = _('New Message')
+ img_name = 'gajim-chat_msg_recv'
+ title = _('New Message from %(nickname)s') % \
+ {'nickname': nickname}
+ text = message
+ img_path = gtkgui_helpers.get_icon_path(img_name, 48)
+ popup(event_type, jid, account, message_type,
+ path_to_image=img_path, title=title, text=text)
+
+ if do_sound:
+ snd_file = None
+ snd_event = None # If not snd_file, play the event
+ if event == 'new_message':
+ if advanced_notif_num is not None and gajim.config.get_per(
+ 'notifications', str(advanced_notif_num), 'sound') == 'yes':
+ snd_file = gajim.config.get_per('notifications',
+ str(advanced_notif_num), 'sound_file')
+ elif advanced_notif_num is not None and gajim.config.get_per(
+ 'notifications', str(advanced_notif_num), 'sound') == 'no':
+ pass # do not set snd_event
+ elif is_first_message:
+ snd_event = 'first_message_received'
+ elif focused:
+ snd_event = 'next_message_received_focused'
+ else:
+ snd_event = 'next_message_received_unfocused'
+ elif event in ('contact_connected', 'contact_disconnected'):
+ snd_event = event
+ if snd_file:
+ helpers.play_sound_file(snd_file)
+ if snd_event:
+ helpers.play_sound(snd_event)
+
+ if do_cmd:
+ command = gajim.config.get_per('notifications', str(advanced_notif_num),
+ 'command')
+ try:
+ helpers.exec_command(command)
+ except Exception:
+ pass
def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None,
- text=None):
- """
- Notify a user of an event. It first tries to a valid implementation of
- the Desktop Notification Specification. If that fails, then we fall back to
- the older style PopupNotificationWindow method
- """
- # default image
- if not path_to_image:
- path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
-
- if gajim.HAVE_INDICATOR and event_type in (_('New Message'),
- _('New Single Message'), _('New Private Message')):
- indicator = indicate.Indicator()
- indicator.set_property('subtype', 'im')
- indicator.set_property('sender', jid)
- indicator.set_property('body', text)
- indicator.set_property_time('time', time.time())
- pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image)
- indicator.set_property_icon('icon', pixbuf)
- indicator.connect('user-display', display, account, jid, msg_type)
- indicator.show()
-
- # Try to show our popup via D-Bus and notification daemon
- if gajim.config.get('use_notif_daemon') and dbus_support.supported:
- try:
- DesktopNotification(event_type, jid, account, msg_type,
- path_to_image, title, gobject.markup_escape_text(text))
- return # sucessfully did D-Bus Notification procedure!
- except dbus.DBusException, e:
- # Connection to D-Bus failed
- gajim.log.debug(str(e))
- except TypeError, e:
- # This means that we sent the message incorrectly
- gajim.log.debug(str(e))
-
- # Ok, that failed. Let's try pynotify, which also uses notification daemon
- if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY:
- if not text and event_type == 'new_message':
- # empty text for new_message means do_preview = False
- # -> default value for text
- _text = gobject.markup_escape_text(
- gajim.get_name_from_jid(account, jid))
- else:
- _text = gobject.markup_escape_text(text)
-
- if not title:
- _title = ''
- else:
- _title = title
-
- notification = pynotify.Notification(_title, _text)
- timeout = gajim.config.get('notification_timeout') * 1000 # make it ms
- notification.set_timeout(timeout)
-
- notification.set_category(event_type)
- notification.set_data('event_type', event_type)
- notification.set_data('jid', jid)
- notification.set_data('account', account)
- notification.set_data('msg_type', msg_type)
- notification.set_property('icon-name', path_to_image)
- if 'actions' in pynotify.get_server_caps():
- notification.add_action('default', 'Default Action',
- on_pynotify_notification_clicked)
-
- try:
- notification.show()
- return
- except gobject.GError, e:
- # Connection to notification-daemon failed, see #2893
- gajim.log.debug(str(e))
-
- # Either nothing succeeded or the user wants old-style notifications
- instance = dialogs.PopupNotificationWindow(event_type, jid, account,
- msg_type, path_to_image, title, text)
- gajim.interface.roster.popup_notification_windows.append(instance)
+ text=None):
+ """
+ Notify a user of an event. It first tries to a valid implementation of
+ the Desktop Notification Specification. If that fails, then we fall back to
+ the older style PopupNotificationWindow method
+ """
+ # default image
+ if not path_to_image:
+ path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
+
+ if gajim.HAVE_INDICATOR and event_type in (_('New Message'),
+ _('New Single Message'), _('New Private Message')):
+ indicator = indicate.Indicator()
+ indicator.set_property('subtype', 'im')
+ indicator.set_property('sender', jid)
+ indicator.set_property('body', text)
+ indicator.set_property_time('time', time.time())
+ pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_image)
+ indicator.set_property_icon('icon', pixbuf)
+ indicator.connect('user-display', display, account, jid, msg_type)
+ indicator.show()
+
+ # Try to show our popup via D-Bus and notification daemon
+ if gajim.config.get('use_notif_daemon') and dbus_support.supported:
+ try:
+ DesktopNotification(event_type, jid, account, msg_type,
+ path_to_image, title, gobject.markup_escape_text(text))
+ return # sucessfully did D-Bus Notification procedure!
+ except dbus.DBusException, e:
+ # Connection to D-Bus failed
+ gajim.log.debug(str(e))
+ except TypeError, e:
+ # This means that we sent the message incorrectly
+ gajim.log.debug(str(e))
+
+ # Ok, that failed. Let's try pynotify, which also uses notification daemon
+ if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY:
+ if not text and event_type == 'new_message':
+ # empty text for new_message means do_preview = False
+ # -> default value for text
+ _text = gobject.markup_escape_text(
+ gajim.get_name_from_jid(account, jid))
+ else:
+ _text = gobject.markup_escape_text(text)
+
+ if not title:
+ _title = ''
+ else:
+ _title = title
+
+ notification = pynotify.Notification(_title, _text)
+ timeout = gajim.config.get('notification_timeout') * 1000 # make it ms
+ notification.set_timeout(timeout)
+
+ notification.set_category(event_type)
+ notification.set_data('event_type', event_type)
+ notification.set_data('jid', jid)
+ notification.set_data('account', account)
+ notification.set_data('msg_type', msg_type)
+ notification.set_property('icon-name', path_to_image)
+ if 'actions' in pynotify.get_server_caps():
+ notification.add_action('default', 'Default Action',
+ on_pynotify_notification_clicked)
+
+ try:
+ notification.show()
+ return
+ except gobject.GError, e:
+ # Connection to notification-daemon failed, see #2893
+ gajim.log.debug(str(e))
+
+ # Either nothing succeeded or the user wants old-style notifications
+ instance = dialogs.PopupNotificationWindow(event_type, jid, account,
+ msg_type, path_to_image, title, text)
+ gajim.interface.roster.popup_notification_windows.append(instance)
def on_pynotify_notification_clicked(notification, action):
- jid = notification.get_data('jid')
- account = notification.get_data('account')
- msg_type = notification.get_data('msg_type')
+ jid = notification.get_data('jid')
+ account = notification.get_data('account')
+ msg_type = notification.get_data('msg_type')
- notification.close()
- gajim.interface.handle_event(account, jid, msg_type)
+ notification.close()
+ gajim.interface.handle_event(account, jid, msg_type)
class NotificationResponseManager:
- """
- Collect references to pending DesktopNotifications and manages there
- signalling. This is necessary due to a bug in DBus where you can't remove a
- signal from an interface once it's connected
- """
-
- def __init__(self):
- self.pending = {}
- self.received = []
- self.interface = None
-
- def attach_to_interface(self):
- if self.interface is not None:
- return
- self.interface = dbus_support.get_notifications_interface()
- self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked)
- self.interface.connect_to_signal('NotificationClosed', self.on_closed)
-
- def on_action_invoked(self, id_, reason):
- self.received.append((id_, time.time(), reason))
- if id_ in self.pending:
- notification = self.pending[id_]
- notification.on_action_invoked(id_, reason)
- del self.pending[id_]
- if len(self.received) > 20:
- curt = time.time()
- for rec in self.received:
- diff = curt - rec[1]
- if diff > 10:
- self.received.remove(rec)
-
- def on_closed(self, id_, reason=None):
- if id_ in self.pending:
- del self.pending[id_]
-
- def add_pending(self, id_, object_):
- # Check to make sure that we handle an event immediately if we're adding
- # an id that's already been triggered
- for rec in self.received:
- if rec[0] == id_:
- object_.on_action_invoked(id_, rec[2])
- self.received.remove(rec)
- return
- if id_ not in self.pending:
- # Add it
- self.pending[id_] = object_
- else:
- # We've triggered an event that has a duplicate ID!
- gajim.log.debug('Duplicate ID of notification. Can\'t handle this.')
+ """
+ Collect references to pending DesktopNotifications and manages there
+ signalling. This is necessary due to a bug in DBus where you can't remove a
+ signal from an interface once it's connected
+ """
+
+ def __init__(self):
+ self.pending = {}
+ self.received = []
+ self.interface = None
+
+ def attach_to_interface(self):
+ if self.interface is not None:
+ return
+ self.interface = dbus_support.get_notifications_interface()
+ self.interface.connect_to_signal('ActionInvoked', self.on_action_invoked)
+ self.interface.connect_to_signal('NotificationClosed', self.on_closed)
+
+ def on_action_invoked(self, id_, reason):
+ self.received.append((id_, time.time(), reason))
+ if id_ in self.pending:
+ notification = self.pending[id_]
+ notification.on_action_invoked(id_, reason)
+ del self.pending[id_]
+ if len(self.received) > 20:
+ curt = time.time()
+ for rec in self.received:
+ diff = curt - rec[1]
+ if diff > 10:
+ self.received.remove(rec)
+
+ def on_closed(self, id_, reason=None):
+ if id_ in self.pending:
+ del self.pending[id_]
+
+ def add_pending(self, id_, object_):
+ # Check to make sure that we handle an event immediately if we're adding
+ # an id that's already been triggered
+ for rec in self.received:
+ if rec[0] == id_:
+ object_.on_action_invoked(id_, rec[2])
+ self.received.remove(rec)
+ return
+ if id_ not in self.pending:
+ # Add it
+ self.pending[id_] = object_
+ else:
+ # We've triggered an event that has a duplicate ID!
+ gajim.log.debug('Duplicate ID of notification. Can\'t handle this.')
notification_response_manager = NotificationResponseManager()
class DesktopNotification:
- """
- A DesktopNotification that interfaces with D-Bus via the Desktop Notification
- specification
- """
-
- def __init__(self, event_type, jid, account, msg_type='',
- path_to_image=None, title=None, text=None):
- self.path_to_image = path_to_image
- self.event_type = event_type
- self.title = title
- self.text = text
- # 0.3.1 is the only version of notification daemon that has no way
- # to determine which version it is. If no method exists, it means
- # they're using that one.
- self.default_version = [0, 3, 1]
- self.account = account
- self.jid = jid
- self.msg_type = msg_type
-
- # default value of text
- if not text and event_type == 'new_message':
- # empty text for new_message means do_preview = False
- self.text = gajim.get_name_from_jid(account, jid)
-
- if not title:
- self.title = event_type # default value
-
- if event_type == _('Contact Signed In'):
- ntype = 'presence.online'
- elif event_type == _('Contact Signed Out'):
- ntype = 'presence.offline'
- elif event_type in (_('New Message'), _('New Single Message'),
- _('New Private Message')):
- ntype = 'im.received'
- elif event_type == _('File Transfer Request'):
- ntype = 'transfer'
- elif event_type == _('File Transfer Error'):
- ntype = 'transfer.error'
- elif event_type in (_('File Transfer Completed'),
- _('File Transfer Stopped')):
- ntype = 'transfer.complete'
- elif event_type == _('New E-mail'):
- ntype = 'email.arrived'
- elif event_type == _('Groupchat Invitation'):
- ntype = 'im.invitation'
- elif event_type == _('Contact Changed Status'):
- ntype = 'presence.status'
- elif event_type == _('Connection Failed'):
- ntype = 'connection.failed'
- elif event_type == _('Subscription request'):
- ntype = 'subscription.request'
- elif event_type == _('Unsubscribed'):
- ntype = 'unsubscribed'
- else:
- # default failsafe values
- self.path_to_image = gtkgui_helpers.get_icon_path(
- 'gajim-chat_msg_recv', 48)
- ntype = 'im' # Notification Type
-
- self.notif = dbus_support.get_notifications_interface(self)
- if self.notif is None:
- raise dbus.DBusException('unable to get notifications interface')
- self.ntype = ntype
-
- if self.kde_notifications:
- self.attempt_notify()
- else:
- self.capabilities = self.notif.GetCapabilities()
- if self.capabilities is None:
- self.capabilities = ['actions']
- self.get_version()
-
- def attempt_notify(self):
- timeout = gajim.config.get('notification_timeout') # in seconds
- ntype = self.ntype
- if self.kde_notifications:
- notification_text = ('<html><img src="%(image)s" align=left />' \
- '%(title)s<br/>%(text)s</html>') % {'title': self.title,
- 'text': self.text, 'image': self.path_to_image}
- gajim_icon = gtkgui_helpers.get_icon_path('gajim', 48)
- self.notif.Notify(
- dbus.String(_('Gajim')), # app_name (string)
- dbus.UInt32(0), # replaces_id (uint)
- ntype, # event_id (string)
- dbus.String(gajim_icon), # app_icon (string)
- dbus.String(''), # summary (string)
- dbus.String(notification_text), # body (string)
- # actions (stringlist)
- (dbus.String('default'), dbus.String(self.event_type),
- dbus.String('ignore'), dbus.String(_('Ignore'))),
- [], # hints (not used in KDE yet)
- dbus.UInt32(timeout*1000), # timeout (int), in ms
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
- return
- version = self.version
- if version[:2] == [0, 2]:
- actions = {}
- if 'actions' in self.capabilities:
- actions = {'default': 0}
- try:
- self.notif.Notify(
- dbus.String(_('Gajim')),
- dbus.String(self.path_to_image),
- dbus.UInt32(0),
- ntype,
- dbus.Byte(0),
- dbus.String(self.title),
- dbus.String(self.text),
- [dbus.String(self.path_to_image)],
- actions,
- [''],
- True,
- dbus.UInt32(timeout),
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
- except AttributeError:
- version = [0, 3, 1] # we're actually dealing with the newer version
- if version > [0, 3]:
- if gajim.interface.systray_enabled and \
- gajim.config.get('attach_notifications_to_systray'):
- status_icon = gajim.interface.systray.status_icon
- x, y, width, height = status_icon.get_geometry()[1]
- pos_x = x + (width / 2)
- pos_y = y + (height / 2)
- hints = {'x': pos_x, 'y': pos_y}
- else:
- hints = {}
- if version >= [0, 3, 2]:
- hints['urgency'] = dbus.Byte(0) # Low Urgency
- hints['category'] = dbus.String(ntype)
- # it seems notification-daemon doesn't like empty text
- if self.text:
- text = self.text
- else:
- text = ' '
- actions = ()
- if 'actions' in self.capabilities:
- actions = (dbus.String('default'), dbus.String(self.event_type))
- self.notif.Notify(
- dbus.String(_('Gajim')),
- dbus.UInt32(0), # this notification does not replace other
- dbus.String(self.path_to_image),
- dbus.String(self.title),
- dbus.String(text),
- actions,
- hints,
- dbus.UInt32(timeout*1000),
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
- else:
- self.notif.Notify(
- dbus.String(_('Gajim')),
- dbus.String(self.path_to_image),
- dbus.UInt32(0),
- dbus.String(self.title),
- dbus.String(self.text),
- dbus.String(''),
- hints,
- dbus.UInt32(timeout*1000),
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
-
- def attach_by_id(self, id_):
- self.id = id_
- notification_response_manager.attach_to_interface()
- notification_response_manager.add_pending(self.id, self)
-
- def notify_another_way(self,e):
- gajim.log.debug(str(e))
- gajim.log.debug('Need to implement a new way of falling back')
-
- def on_action_invoked(self, id_, reason):
- if self.notif is None:
- return
- self.notif.CloseNotification(dbus.UInt32(id_))
- self.notif = None
-
- if reason == 'ignore':
- return
-
- gajim.interface.handle_event(self.account, self.jid, self.msg_type)
-
- def version_reply_handler(self, name, vendor, version, spec_version=None):
- if spec_version:
- version = spec_version
- elif vendor == 'Xfce' and version.startswith('0.1.0'):
- version = '0.9'
- version_list = version.split('.')
- self.version = []
- try:
- while len(version_list):
- self.version.append(int(version_list.pop(0)))
- except ValueError:
- self.version_error_handler_3_x_try(None)
- self.attempt_notify()
-
- def get_version(self):
- self.notif.GetServerInfo(
- reply_handler=self.version_reply_handler,
- error_handler=self.version_error_handler_2_x_try)
-
- def version_error_handler_2_x_try(self, e):
- self.notif.GetServerInformation(reply_handler=self.version_reply_handler,
- error_handler=self.version_error_handler_3_x_try)
-
- def version_error_handler_3_x_try(self, e):
- self.version = self.default_version
- self.attempt_notify()
-
-# vim: se ts=3:
+ """
+ A DesktopNotification that interfaces with D-Bus via the Desktop Notification
+ specification
+ """
+
+ def __init__(self, event_type, jid, account, msg_type='',
+ path_to_image=None, title=None, text=None):
+ self.path_to_image = path_to_image
+ self.event_type = event_type
+ self.title = title
+ self.text = text
+ # 0.3.1 is the only version of notification daemon that has no way
+ # to determine which version it is. If no method exists, it means
+ # they're using that one.
+ self.default_version = [0, 3, 1]
+ self.account = account
+ self.jid = jid
+ self.msg_type = msg_type
+
+ # default value of text
+ if not text and event_type == 'new_message':
+ # empty text for new_message means do_preview = False
+ self.text = gajim.get_name_from_jid(account, jid)
+
+ if not title:
+ self.title = event_type # default value
+
+ if event_type == _('Contact Signed In'):
+ ntype = 'presence.online'
+ elif event_type == _('Contact Signed Out'):
+ ntype = 'presence.offline'
+ elif event_type in (_('New Message'), _('New Single Message'),
+ _('New Private Message')):
+ ntype = 'im.received'
+ elif event_type == _('File Transfer Request'):
+ ntype = 'transfer'
+ elif event_type == _('File Transfer Error'):
+ ntype = 'transfer.error'
+ elif event_type in (_('File Transfer Completed'),
+ _('File Transfer Stopped')):
+ ntype = 'transfer.complete'
+ elif event_type == _('New E-mail'):
+ ntype = 'email.arrived'
+ elif event_type == _('Groupchat Invitation'):
+ ntype = 'im.invitation'
+ elif event_type == _('Contact Changed Status'):
+ ntype = 'presence.status'
+ elif event_type == _('Connection Failed'):
+ ntype = 'connection.failed'
+ elif event_type == _('Subscription request'):
+ ntype = 'subscription.request'
+ elif event_type == _('Unsubscribed'):
+ ntype = 'unsubscribed'
+ else:
+ # default failsafe values
+ self.path_to_image = gtkgui_helpers.get_icon_path(
+ 'gajim-chat_msg_recv', 48)
+ ntype = 'im' # Notification Type
+
+ self.notif = dbus_support.get_notifications_interface(self)
+ if self.notif is None:
+ raise dbus.DBusException('unable to get notifications interface')
+ self.ntype = ntype
+
+ if self.kde_notifications:
+ self.attempt_notify()
+ else:
+ self.capabilities = self.notif.GetCapabilities()
+ if self.capabilities is None:
+ self.capabilities = ['actions']
+ self.get_version()
+
+ def attempt_notify(self):
+ timeout = gajim.config.get('notification_timeout') # in seconds
+ ntype = self.ntype
+ if self.kde_notifications:
+ notification_text = ('<html><img src="%(image)s" align=left />' \
+ '%(title)s<br/>%(text)s</html>') % {'title': self.title,
+ 'text': self.text, 'image': self.path_to_image}
+ gajim_icon = gtkgui_helpers.get_icon_path('gajim', 48)
+ self.notif.Notify(
+ dbus.String(_('Gajim')), # app_name (string)
+ dbus.UInt32(0), # replaces_id (uint)
+ ntype, # event_id (string)
+ dbus.String(gajim_icon), # app_icon (string)
+ dbus.String(''), # summary (string)
+ dbus.String(notification_text), # body (string)
+ # actions (stringlist)
+ (dbus.String('default'), dbus.String(self.event_type),
+ dbus.String('ignore'), dbus.String(_('Ignore'))),
+ [], # hints (not used in KDE yet)
+ dbus.UInt32(timeout*1000), # timeout (int), in ms
+ reply_handler=self.attach_by_id,
+ error_handler=self.notify_another_way)
+ return
+ version = self.version
+ if version[:2] == [0, 2]:
+ actions = {}
+ if 'actions' in self.capabilities:
+ actions = {'default': 0}
+ try:
+ self.notif.Notify(
+ dbus.String(_('Gajim')),
+ dbus.String(self.path_to_image),
+ dbus.UInt32(0),
+ ntype,
+ dbus.Byte(0),
+ dbus.String(self.title),
+ dbus.String(self.text),
+ [dbus.String(self.path_to_image)],
+ actions,
+ [''],
+ True,
+ dbus.UInt32(timeout),
+ reply_handler=self.attach_by_id,
+ error_handler=self.notify_another_way)
+ except AttributeError:
+ version = [0, 3, 1] # we're actually dealing with the newer version
+ if version > [0, 3]:
+ if gajim.interface.systray_enabled and \
+ gajim.config.get('attach_notifications_to_systray'):
+ status_icon = gajim.interface.systray.status_icon
+ x, y, width, height = status_icon.get_geometry()[1]
+ pos_x = x + (width / 2)
+ pos_y = y + (height / 2)
+ hints = {'x': pos_x, 'y': pos_y}
+ else:
+ hints = {}
+ if version >= [0, 3, 2]:
+ hints['urgency'] = dbus.Byte(0) # Low Urgency
+ hints['category'] = dbus.String(ntype)
+ # it seems notification-daemon doesn't like empty text
+ if self.text:
+ text = self.text
+ else:
+ text = ' '
+ actions = ()
+ if 'actions' in self.capabilities:
+ actions = (dbus.String('default'), dbus.String(self.event_type))
+ self.notif.Notify(
+ dbus.String(_('Gajim')),
+ dbus.UInt32(0), # this notification does not replace other
+ dbus.String(self.path_to_image),
+ dbus.String(self.title),
+ dbus.String(text),
+ actions,
+ hints,
+ dbus.UInt32(timeout*1000),
+ reply_handler=self.attach_by_id,
+ error_handler=self.notify_another_way)
+ else:
+ self.notif.Notify(
+ dbus.String(_('Gajim')),
+ dbus.String(self.path_to_image),
+ dbus.UInt32(0),
+ dbus.String(self.title),
+ dbus.String(self.text),
+ dbus.String(''),
+ hints,
+ dbus.UInt32(timeout*1000),
+ reply_handler=self.attach_by_id,
+ error_handler=self.notify_another_way)
+
+ def attach_by_id(self, id_):
+ self.id = id_
+ notification_response_manager.attach_to_interface()
+ notification_response_manager.add_pending(self.id, self)
+
+ def notify_another_way(self,e):
+ gajim.log.debug(str(e))
+ gajim.log.debug('Need to implement a new way of falling back')
+
+ def on_action_invoked(self, id_, reason):
+ if self.notif is None:
+ return
+ self.notif.CloseNotification(dbus.UInt32(id_))
+ self.notif = None
+
+ if reason == 'ignore':
+ return
+
+ gajim.interface.handle_event(self.account, self.jid, self.msg_type)
+
+ def version_reply_handler(self, name, vendor, version, spec_version=None):
+ if spec_version:
+ version = spec_version
+ elif vendor == 'Xfce' and version.startswith('0.1.0'):
+ version = '0.9'
+ version_list = version.split('.')
+ self.version = []
+ try:
+ while len(version_list):
+ self.version.append(int(version_list.pop(0)))
+ except ValueError:
+ self.version_error_handler_3_x_try(None)
+ self.attempt_notify()
+
+ def get_version(self):
+ self.notif.GetServerInfo(
+ reply_handler=self.version_reply_handler,
+ error_handler=self.version_error_handler_2_x_try)
+
+ def version_error_handler_2_x_try(self, e):
+ self.notif.GetServerInformation(reply_handler=self.version_reply_handler,
+ error_handler=self.version_error_handler_3_x_try)
+
+ def version_error_handler_3_x_try(self, e):
+ self.version = self.default_version
+ self.attempt_notify()
diff --git a/src/profile_window.py b/src/profile_window.py
index 834f1e59e..577f50fa4 100644
--- a/src/profile_window.py
+++ b/src/profile_window.py
@@ -36,333 +36,331 @@ from common import gajim
class ProfileWindow:
- """
- Class for our information window
- """
-
- def __init__(self, account):
- self.xml = gtkgui_helpers.get_gtk_builder('profile_window.ui')
- self.window = self.xml.get_object('profile_window')
- self.progressbar = self.xml.get_object('progressbar')
- self.statusbar = self.xml.get_object('statusbar')
- self.context_id = self.statusbar.get_context_id('profile')
-
- self.account = account
- self.jid = gajim.get_jid_from_account(account)
-
- self.dialog = None
- self.avatar_mime_type = None
- self.avatar_encoded = None
- self.message_id = self.statusbar.push(self.context_id,
- _('Retrieving profile...'))
- self.update_progressbar_timeout_id = gobject.timeout_add(100,
- self.update_progressbar)
- self.remove_statusbar_timeout_id = None
-
- # Create Image for avatar button
- image = gtk.Image()
- self.xml.get_object('PHOTO_button').set_image(image)
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def update_progressbar(self):
- self.progressbar.pulse()
- return True # loop forever
-
- def remove_statusbar(self, message_id):
- self.statusbar.remove_message(self.context_id, message_id)
- self.remove_statusbar_timeout_id = None
-
- def on_profile_window_destroy(self, widget):
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
- if self.remove_statusbar_timeout_id is not None:
- gobject.source_remove(self.remove_statusbar_timeout_id)
- del gajim.interface.instances[self.account]['profile']
- if self.dialog: # Image chooser dialog
- self.dialog.destroy()
-
- def on_profile_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.window.destroy()
-
- def on_clear_button_clicked(self, widget):
- # empty the image
- button = self.xml.get_object('PHOTO_button')
- image = button.get_image()
- image.set_from_pixbuf(None)
- button.hide()
- text_button = self.xml.get_object('NOPHOTO_button')
- text_button.show()
- self.avatar_encoded = None
- self.avatar_mime_type = None
-
- def on_set_avatar_button_clicked(self, widget):
- def on_ok(widget, path_to_file):
- must_delete = False
- filesize = os.path.getsize(path_to_file) # in bytes
- invalid_file = False
- msg = ''
- if os.path.isfile(path_to_file):
- stat = os.stat(path_to_file)
- if stat[6] == 0:
- invalid_file = True
- msg = _('File is empty')
- else:
- invalid_file = True
- msg = _('File does not exist')
- if not invalid_file and filesize > 16384: # 16 kb
- try:
- pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
- # get the image at 'notification size'
- # and hope that user did not specify in ACE crazy size
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf,
- 'tooltip')
- except gobject.GError, msg: # unknown format
- # msg should be string, not object instance
- msg = str(msg)
- invalid_file = True
- if invalid_file:
- if True: # keep identation
- dialogs.ErrorDialog(_('Could not load image'), msg)
- return
- if filesize > 16384:
- if scaled_pixbuf:
- path_to_file = os.path.join(gajim.TMP,
- 'avatar_scaled.png')
- scaled_pixbuf.save(path_to_file, 'png')
- must_delete = True
-
- fd = open(path_to_file, 'rb')
- data = fd.read()
- pixbuf = gtkgui_helpers.get_pixbuf_from_data(data)
- try:
- # rescale it
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
- except AttributeError: # unknown format
- dialogs.ErrorDialog(_('Could not load image'))
- return
- self.dialog.destroy()
- self.dialog = None
- button = self.xml.get_object('PHOTO_button')
- image = button.get_image()
- image.set_from_pixbuf(pixbuf)
- button.show()
- text_button = self.xml.get_object('NOPHOTO_button')
- text_button.hide()
- self.avatar_encoded = base64.encodestring(data)
- # returns None if unknown type
- self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0]
- if must_delete:
- try:
- os.remove(path_to_file)
- except OSError:
- gajim.log.debug('Cannot remove %s' % path_to_file)
-
- def on_clear(widget):
- self.dialog.destroy()
- self.dialog = None
- self.on_clear_button_clicked(widget)
-
- def on_cancel(widget):
- self.dialog.destroy()
- self.dialog = None
-
- if self.dialog:
- self.dialog.present()
- else:
- self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok,
- on_response_cancel = on_cancel, on_response_clear = on_clear)
-
- def on_PHOTO_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3 and self.avatar_encoded: # right click
- menu = gtk.Menu()
-
- # Try to get pixbuf
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid,
- use_local=False)
-
- if pixbuf not in (None, 'ask'):
- nick = gajim.config.get_per('accounts', self.account, 'name')
- menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
- menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.jid, self.account, nick)
- menu.append(menuitem)
- # show clear
- menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
- menuitem.connect('activate', self.on_clear_button_clicked)
- menu.append(menuitem)
- menu.connect('selection-done', lambda w:w.destroy())
- # show the menu
- menu.show_all()
- menu.popup(None, None, None, event.button, event.time)
- elif event.button == 1: # left click
- self.on_set_avatar_button_clicked(widget)
-
- def set_value(self, entry_name, value):
- try:
- self.xml.get_object(entry_name).set_text(value)
- except AttributeError:
- pass
-
- def set_values(self, vcard_):
- button = self.xml.get_object('PHOTO_button')
- image = button.get_image()
- text_button = self.xml.get_object('NOPHOTO_button')
- if not 'PHOTO' in vcard_:
- # set default image
- image.set_from_pixbuf(None)
- button.hide()
- text_button.show()
- for i in vcard_.keys():
- if i == 'PHOTO':
- pixbuf, self.avatar_encoded, self.avatar_mime_type = \
- vcard.get_avatar_pixbuf_encoded_mime(vcard_[i])
- if not pixbuf:
- image.set_from_pixbuf(None)
- button.hide()
- text_button.show()
- continue
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
- image.set_from_pixbuf(pixbuf)
- button.show()
- text_button.hide()
- continue
- if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
- for entry in vcard_[i]:
- add_on = '_HOME'
- if 'WORK' in entry:
- add_on = '_WORK'
- for j in entry.keys():
- self.set_value(i + add_on + '_' + j + '_entry', entry[j])
- if isinstance(vcard_[i], dict):
- for j in vcard_[i].keys():
- self.set_value(i + '_' + j + '_entry', vcard_[i][j])
- else:
- if i == 'DESC':
- self.xml.get_object('DESC_textview').get_buffer().set_text(
- vcard_[i], 0)
- else:
- self.set_value(i + '_entry', vcard_[i])
- if self.update_progressbar_timeout_id is not None:
- if self.message_id:
- self.statusbar.remove_message(self.context_id, self.message_id)
- self.message_id = self.statusbar.push(self.context_id,
- _('Information received'))
- self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3,
- self.remove_statusbar, self.message_id)
- gobject.source_remove(self.update_progressbar_timeout_id)
- self.progressbar.hide()
- self.progressbar.set_fraction(0)
- self.update_progressbar_timeout_id = None
-
- def add_to_vcard(self, vcard_, entry, txt):
- """
- Add an information to the vCard dictionary
- """
- entries = entry.split('_')
- loc = vcard_
- if len(entries) == 3: # We need to use lists
- if entries[0] not in loc:
- loc[entries[0]] = []
- found = False
- for e in loc[entries[0]]:
- if entries[1] in e:
- e[entries[2]] = txt
- break
- else:
- loc[entries[0]].append({entries[1]: '', entries[2]: txt})
- return vcard_
- while len(entries) > 1:
- if entries[0] not in loc:
- loc[entries[0]] = {}
- loc = loc[entries[0]]
- del entries[0]
- loc[entries[0]] = txt
- return vcard_
-
- def make_vcard(self):
- """
- Make the vCard dictionary
- """
- entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
- 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
- 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
- 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
- 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
- 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
- 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
- vcard_ = {}
- for e in entries:
- txt = self.xml.get_object(e + '_entry').get_text().decode('utf-8')
- if txt != '':
- vcard_ = self.add_to_vcard(vcard_, e, txt)
-
- # DESC textview
- buff = self.xml.get_object('DESC_textview').get_buffer()
- start_iter = buff.get_start_iter()
- end_iter = buff.get_end_iter()
- txt = buff.get_text(start_iter, end_iter, 0)
- if txt != '':
- vcard_['DESC'] = txt.decode('utf-8')
-
- # Avatar
- if self.avatar_encoded:
- vcard_['PHOTO'] = {'BINVAL': self.avatar_encoded}
- if self.avatar_mime_type:
- vcard_['PHOTO']['TYPE'] = self.avatar_mime_type
- return vcard_
-
- def on_ok_button_clicked(self, widget):
- if self.update_progressbar_timeout_id:
- # Operation in progress
- return
- if gajim.connections[self.account].connected < 2:
- dialogs.ErrorDialog(_('You are not connected to the server'),
- _('Without a connection you can not publish your contact '
- 'information.'))
- return
- vcard_ = self.make_vcard()
- nick = ''
- if 'NICKNAME' in vcard_:
- nick = vcard_['NICKNAME']
- gajim.connections[self.account].send_nickname(nick)
- if nick == '':
- nick = gajim.config.get_per('accounts', self.account, 'name')
- gajim.nicks[self.account] = nick
- gajim.connections[self.account].send_vcard(vcard_)
- self.message_id = self.statusbar.push(self.context_id,
- _('Sending profile...'))
- self.progressbar.show()
- self.update_progressbar_timeout_id = gobject.timeout_add(100,
- self.update_progressbar)
-
- def vcard_published(self):
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
- self.update_progressbar_timeout_id = None
- self.window.destroy()
-
- def vcard_not_published(self):
- if self.message_id:
- self.statusbar.remove_message(self.context_id, self.message_id)
- self.message_id = self.statusbar.push(self.context_id,
- _('Information NOT published'))
- self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3,
- self.remove_statusbar, self.message_id)
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
- self.progressbar.set_fraction(0)
- self.update_progressbar_timeout_id = None
- dialogs.InformationDialog(_('vCard publication failed'),
- _('There was an error while publishing your personal information, '
- 'try again later.'))
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
-# vim: se ts=3:
+ """
+ Class for our information window
+ """
+
+ def __init__(self, account):
+ self.xml = gtkgui_helpers.get_gtk_builder('profile_window.ui')
+ self.window = self.xml.get_object('profile_window')
+ self.progressbar = self.xml.get_object('progressbar')
+ self.statusbar = self.xml.get_object('statusbar')
+ self.context_id = self.statusbar.get_context_id('profile')
+
+ self.account = account
+ self.jid = gajim.get_jid_from_account(account)
+
+ self.dialog = None
+ self.avatar_mime_type = None
+ self.avatar_encoded = None
+ self.message_id = self.statusbar.push(self.context_id,
+ _('Retrieving profile...'))
+ self.update_progressbar_timeout_id = gobject.timeout_add(100,
+ self.update_progressbar)
+ self.remove_statusbar_timeout_id = None
+
+ # Create Image for avatar button
+ image = gtk.Image()
+ self.xml.get_object('PHOTO_button').set_image(image)
+ self.xml.connect_signals(self)
+ self.window.show_all()
+
+ def update_progressbar(self):
+ self.progressbar.pulse()
+ return True # loop forever
+
+ def remove_statusbar(self, message_id):
+ self.statusbar.remove_message(self.context_id, message_id)
+ self.remove_statusbar_timeout_id = None
+
+ def on_profile_window_destroy(self, widget):
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ if self.remove_statusbar_timeout_id is not None:
+ gobject.source_remove(self.remove_statusbar_timeout_id)
+ del gajim.interface.instances[self.account]['profile']
+ if self.dialog: # Image chooser dialog
+ self.dialog.destroy()
+
+ def on_profile_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def on_clear_button_clicked(self, widget):
+ # empty the image
+ button = self.xml.get_object('PHOTO_button')
+ image = button.get_image()
+ image.set_from_pixbuf(None)
+ button.hide()
+ text_button = self.xml.get_object('NOPHOTO_button')
+ text_button.show()
+ self.avatar_encoded = None
+ self.avatar_mime_type = None
+
+ def on_set_avatar_button_clicked(self, widget):
+ def on_ok(widget, path_to_file):
+ must_delete = False
+ filesize = os.path.getsize(path_to_file) # in bytes
+ invalid_file = False
+ msg = ''
+ if os.path.isfile(path_to_file):
+ stat = os.stat(path_to_file)
+ if stat[6] == 0:
+ invalid_file = True
+ msg = _('File is empty')
+ else:
+ invalid_file = True
+ msg = _('File does not exist')
+ if not invalid_file and filesize > 16384: # 16 kb
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
+ # get the image at 'notification size'
+ # and hope that user did not specify in ACE crazy size
+ scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf,
+ 'tooltip')
+ except gobject.GError, msg: # unknown format
+ # msg should be string, not object instance
+ msg = str(msg)
+ invalid_file = True
+ if invalid_file:
+ if True: # keep identation
+ dialogs.ErrorDialog(_('Could not load image'), msg)
+ return
+ if filesize > 16384:
+ if scaled_pixbuf:
+ path_to_file = os.path.join(gajim.TMP,
+ 'avatar_scaled.png')
+ scaled_pixbuf.save(path_to_file, 'png')
+ must_delete = True
+
+ fd = open(path_to_file, 'rb')
+ data = fd.read()
+ pixbuf = gtkgui_helpers.get_pixbuf_from_data(data)
+ try:
+ # rescale it
+ pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
+ except AttributeError: # unknown format
+ dialogs.ErrorDialog(_('Could not load image'))
+ return
+ self.dialog.destroy()
+ self.dialog = None
+ button = self.xml.get_object('PHOTO_button')
+ image = button.get_image()
+ image.set_from_pixbuf(pixbuf)
+ button.show()
+ text_button = self.xml.get_object('NOPHOTO_button')
+ text_button.hide()
+ self.avatar_encoded = base64.encodestring(data)
+ # returns None if unknown type
+ self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0]
+ if must_delete:
+ try:
+ os.remove(path_to_file)
+ except OSError:
+ gajim.log.debug('Cannot remove %s' % path_to_file)
+
+ def on_clear(widget):
+ self.dialog.destroy()
+ self.dialog = None
+ self.on_clear_button_clicked(widget)
+
+ def on_cancel(widget):
+ self.dialog.destroy()
+ self.dialog = None
+
+ if self.dialog:
+ self.dialog.present()
+ else:
+ self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok,
+ on_response_cancel = on_cancel, on_response_clear = on_clear)
+
+ def on_PHOTO_button_press_event(self, widget, event):
+ """
+ If right-clicked, show popup
+ """
+ if event.button == 3 and self.avatar_encoded: # right click
+ menu = gtk.Menu()
+
+ # Try to get pixbuf
+ pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid,
+ use_local=False)
+
+ if pixbuf not in (None, 'ask'):
+ nick = gajim.config.get_per('accounts', self.account, 'name')
+ menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
+ menuitem.connect('activate',
+ gtkgui_helpers.on_avatar_save_as_menuitem_activate,
+ self.jid, self.account, nick)
+ menu.append(menuitem)
+ # show clear
+ menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+ menuitem.connect('activate', self.on_clear_button_clicked)
+ menu.append(menuitem)
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+ elif event.button == 1: # left click
+ self.on_set_avatar_button_clicked(widget)
+
+ def set_value(self, entry_name, value):
+ try:
+ self.xml.get_object(entry_name).set_text(value)
+ except AttributeError:
+ pass
+
+ def set_values(self, vcard_):
+ button = self.xml.get_object('PHOTO_button')
+ image = button.get_image()
+ text_button = self.xml.get_object('NOPHOTO_button')
+ if not 'PHOTO' in vcard_:
+ # set default image
+ image.set_from_pixbuf(None)
+ button.hide()
+ text_button.show()
+ for i in vcard_.keys():
+ if i == 'PHOTO':
+ pixbuf, self.avatar_encoded, self.avatar_mime_type = \
+ vcard.get_avatar_pixbuf_encoded_mime(vcard_[i])
+ if not pixbuf:
+ image.set_from_pixbuf(None)
+ button.hide()
+ text_button.show()
+ continue
+ pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
+ image.set_from_pixbuf(pixbuf)
+ button.show()
+ text_button.hide()
+ continue
+ if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
+ for entry in vcard_[i]:
+ add_on = '_HOME'
+ if 'WORK' in entry:
+ add_on = '_WORK'
+ for j in entry.keys():
+ self.set_value(i + add_on + '_' + j + '_entry', entry[j])
+ if isinstance(vcard_[i], dict):
+ for j in vcard_[i].keys():
+ self.set_value(i + '_' + j + '_entry', vcard_[i][j])
+ else:
+ if i == 'DESC':
+ self.xml.get_object('DESC_textview').get_buffer().set_text(
+ vcard_[i], 0)
+ else:
+ self.set_value(i + '_entry', vcard_[i])
+ if self.update_progressbar_timeout_id is not None:
+ if self.message_id:
+ self.statusbar.remove_message(self.context_id, self.message_id)
+ self.message_id = self.statusbar.push(self.context_id,
+ _('Information received'))
+ self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3,
+ self.remove_statusbar, self.message_id)
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ self.progressbar.hide()
+ self.progressbar.set_fraction(0)
+ self.update_progressbar_timeout_id = None
+
+ def add_to_vcard(self, vcard_, entry, txt):
+ """
+ Add an information to the vCard dictionary
+ """
+ entries = entry.split('_')
+ loc = vcard_
+ if len(entries) == 3: # We need to use lists
+ if entries[0] not in loc:
+ loc[entries[0]] = []
+ found = False
+ for e in loc[entries[0]]:
+ if entries[1] in e:
+ e[entries[2]] = txt
+ break
+ else:
+ loc[entries[0]].append({entries[1]: '', entries[2]: txt})
+ return vcard_
+ while len(entries) > 1:
+ if entries[0] not in loc:
+ loc[entries[0]] = {}
+ loc = loc[entries[0]]
+ del entries[0]
+ loc[entries[0]] = txt
+ return vcard_
+
+ def make_vcard(self):
+ """
+ Make the vCard dictionary
+ """
+ entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
+ 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
+ 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
+ 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
+ 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
+ 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
+ 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
+ vcard_ = {}
+ for e in entries:
+ txt = self.xml.get_object(e + '_entry').get_text().decode('utf-8')
+ if txt != '':
+ vcard_ = self.add_to_vcard(vcard_, e, txt)
+
+ # DESC textview
+ buff = self.xml.get_object('DESC_textview').get_buffer()
+ start_iter = buff.get_start_iter()
+ end_iter = buff.get_end_iter()
+ txt = buff.get_text(start_iter, end_iter, 0)
+ if txt != '':
+ vcard_['DESC'] = txt.decode('utf-8')
+
+ # Avatar
+ if self.avatar_encoded:
+ vcard_['PHOTO'] = {'BINVAL': self.avatar_encoded}
+ if self.avatar_mime_type:
+ vcard_['PHOTO']['TYPE'] = self.avatar_mime_type
+ return vcard_
+
+ def on_ok_button_clicked(self, widget):
+ if self.update_progressbar_timeout_id:
+ # Operation in progress
+ return
+ if gajim.connections[self.account].connected < 2:
+ dialogs.ErrorDialog(_('You are not connected to the server'),
+ _('Without a connection you can not publish your contact '
+ 'information.'))
+ return
+ vcard_ = self.make_vcard()
+ nick = ''
+ if 'NICKNAME' in vcard_:
+ nick = vcard_['NICKNAME']
+ gajim.connections[self.account].send_nickname(nick)
+ if nick == '':
+ nick = gajim.config.get_per('accounts', self.account, 'name')
+ gajim.nicks[self.account] = nick
+ gajim.connections[self.account].send_vcard(vcard_)
+ self.message_id = self.statusbar.push(self.context_id,
+ _('Sending profile...'))
+ self.progressbar.show()
+ self.update_progressbar_timeout_id = gobject.timeout_add(100,
+ self.update_progressbar)
+
+ def vcard_published(self):
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ self.update_progressbar_timeout_id = None
+ self.window.destroy()
+
+ def vcard_not_published(self):
+ if self.message_id:
+ self.statusbar.remove_message(self.context_id, self.message_id)
+ self.message_id = self.statusbar.push(self.context_id,
+ _('Information NOT published'))
+ self.remove_statusbar_timeout_id = gobject.timeout_add_seconds(3,
+ self.remove_statusbar, self.message_id)
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ self.progressbar.set_fraction(0)
+ self.update_progressbar_timeout_id = None
+ dialogs.InformationDialog(_('vCard publication failed'),
+ _('There was an error while publishing your personal information, '
+ 'try again later.'))
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
diff --git a/src/remote_control.py b/src/remote_control.py
index 377b9e1e8..71cab3aa8 100644
--- a/src/remote_control.py
+++ b/src/remote_control.py
@@ -39,10 +39,10 @@ from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
from common import dbus_support
if dbus_support.supported:
- import dbus
- if dbus_support:
- import dbus.service
- import dbus.glib
+ import dbus
+ if dbus_support:
+ import dbus.service
+ import dbus.glib
INTERFACE = 'org.gajim.dbus.RemoteInterface'
OBJ_PATH = '/org/gajim/dbus/RemoteObject'
@@ -66,729 +66,727 @@ DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
DBUS_NONE = lambda : dbus.Int32(0)
def get_dbus_struct(obj):
- """
- Recursively go through all the items and replace them with their casted dbus
- equivalents
- """
- if obj is None:
- return DBUS_NONE()
- if isinstance(obj, (unicode, str)):
- return DBUS_STRING(obj)
- if isinstance(obj, int):
- return DBUS_INT32(obj)
- if isinstance(obj, float):
- return DBUS_DOUBLE(obj)
- if isinstance(obj, bool):
- return DBUS_BOOLEAN(obj)
- if isinstance(obj, (list, tuple)):
- result = dbus.Array([get_dbus_struct(i) for i in obj],
- signature='v')
- if result == []:
- return DBUS_NONE()
- return result
- if isinstance(obj, dict):
- result = DBUS_DICT_SV()
- for key, value in obj.items():
- result[DBUS_STRING(key)] = get_dbus_struct(value)
- if result == {}:
- return DBUS_NONE()
- return result
- # unknown type
- return DBUS_NONE()
+ """
+ Recursively go through all the items and replace them with their casted dbus
+ equivalents
+ """
+ if obj is None:
+ return DBUS_NONE()
+ if isinstance(obj, (unicode, str)):
+ return DBUS_STRING(obj)
+ if isinstance(obj, int):
+ return DBUS_INT32(obj)
+ if isinstance(obj, float):
+ return DBUS_DOUBLE(obj)
+ if isinstance(obj, bool):
+ return DBUS_BOOLEAN(obj)
+ if isinstance(obj, (list, tuple)):
+ result = dbus.Array([get_dbus_struct(i) for i in obj],
+ signature='v')
+ if result == []:
+ return DBUS_NONE()
+ return result
+ if isinstance(obj, dict):
+ result = DBUS_DICT_SV()
+ for key, value in obj.items():
+ result[DBUS_STRING(key)] = get_dbus_struct(value)
+ if result == {}:
+ return DBUS_NONE()
+ return result
+ # unknown type
+ return DBUS_NONE()
class Remote:
- def __init__(self):
- self.signal_object = None
- session_bus = dbus_support.session_bus.SessionBus()
+ def __init__(self):
+ self.signal_object = None
+ session_bus = dbus_support.session_bus.SessionBus()
- bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
- self.signal_object = SignalObject(bus_name)
+ bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
+ self.signal_object = SignalObject(bus_name)
- def raise_signal(self, signal, arg):
- if self.signal_object:
- try:
- getattr(self.signal_object, signal)(get_dbus_struct(arg))
- except UnicodeDecodeError:
- pass # ignore error when we fail to announce on dbus
+ def raise_signal(self, signal, arg):
+ if self.signal_object:
+ try:
+ getattr(self.signal_object, signal)(get_dbus_struct(arg))
+ except UnicodeDecodeError:
+ pass # ignore error when we fail to announce on dbus
class SignalObject(dbus.service.Object):
- """
- Local object definition for /org/gajim/dbus/RemoteObject
-
- This docstring is not be visible, because the clients can access only the
- remote object.
- """
-
- def __init__(self, bus_name):
- self.first_show = True
- self.vcard_account = None
-
- # register our dbus API
- dbus.service.Object.__init__(self, bus_name, OBJ_PATH)
-
- @dbus.service.signal(INTERFACE, signature='av')
- def Roster(self, account_and_data):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def AccountPresence(self, status_and_account):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def ContactPresence(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def ContactAbsence(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def ContactStatus(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def NewMessage(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def Subscribe(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def Subscribed(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def Unsubscribed(self, account_and_jid):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def NewAccount(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def VcardInfo(self, account_and_vcard):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def LastStatusTime(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def OsInfo(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def EntityTime(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def GCPresence(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def GCMessage(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def RosterInfo(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def NewGmail(self, account_and_array):
- pass
-
- def raise_signal(self, signal, arg):
- """
- Raise a signal, with a single argument of unspecified type Instead of
- obj.raise_signal("Foo", bar), use obj.Foo(bar)
- """
- getattr(self, signal)(arg)
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
- def get_status(self, account):
- """
- Return status (show to be exact) which is the global one unless account is
- given
- """
- if not account:
- # If user did not ask for account, returns the global status
- return DBUS_STRING(helpers.get_global_show())
- # return show for the given account
- index = gajim.connections[account].connected
- return DBUS_STRING(gajim.SHOW_LIST[index])
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
- def get_status_message(self, account):
- """
- Return status which is the global one unless account is given
- """
- if not account:
- # If user did not ask for account, returns the global status
- return DBUS_STRING(str(helpers.get_global_status()))
- # return show for the given account
- status = gajim.connections[account].status
- return DBUS_STRING(status)
-
- def _get_account_and_contact(self, account, jid):
- """
- Get the account (if not given) and contact instance from jid
- """
- connected_account = None
- contact = None
- accounts = gajim.contacts.get_accounts()
- # if there is only one account in roster, take it as default
- # if user did not ask for account
- if not account and len(accounts) == 1:
- account = accounts[0]
- if account:
- if gajim.connections[account].connected > 1: # account is connected
- connected_account = account
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- else:
- for account in accounts:
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if contact and gajim.connections[account].connected > 1:
- # account is connected
- connected_account = account
- break
- if not contact:
- contact = jid
-
- return connected_account, contact
-
- def _get_account_for_groupchat(self, account, room_jid):
- """
- Get the account which is connected to groupchat (if not given)
- or check if the given account is connected to the groupchat
- """
- connected_account = None
- accounts = gajim.contacts.get_accounts()
- # if there is only one account in roster, take it as default
- # if user did not ask for account
- if not account and len(accounts) == 1:
- account = accounts[0]
- if account:
- if gajim.connections[account].connected > 1 and \
- room_jid in gajim.gc_connected[account] and \
- gajim.gc_connected[account][room_jid]:
- # account and groupchat are connected
- connected_account = account
- else:
- for account in accounts:
- if gajim.connections[account].connected > 1 and \
- room_jid in gajim.gc_connected[account] and \
- gajim.gc_connected[account][room_jid]:
- # account and groupchat are connected
- connected_account = account
- break
- return connected_account
-
- @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
- def send_file(self, file_path, jid, account):
- """
- Send file, located at 'file_path' to 'jid', using account (optional)
- 'account'
- """
- jid = self._get_real_jid(jid, account)
- connected_account, contact = self._get_account_and_contact(account, jid)
-
- if connected_account:
- if file_path.startswith('file://'):
- file_path=file_path[7:]
- if os.path.isfile(file_path): # is it file?
- gajim.interface.instances['file_transfers'].send_file(
- connected_account, contact, file_path)
- return DBUS_BOOLEAN(True)
- return DBUS_BOOLEAN(False)
-
- def _send_message(self, jid, message, keyID, account, type_ = 'chat',
- subject = None):
- """
- Can be called from send_chat_message (default when send_message) or
- send_single_message
- """
- if not jid or not message:
- return DBUS_BOOLEAN(False)
- if not keyID:
- keyID = ''
-
- connected_account, contact = self._get_account_and_contact(account, jid)
- if connected_account:
- connection = gajim.connections[connected_account]
- connection.send_message(jid, message, keyID, type_, subject)
- return DBUS_BOOLEAN(True)
- return DBUS_BOOLEAN(False)
-
- @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b')
- def send_chat_message(self, jid, message, keyID, account):
- """
- Send chat 'message' to 'jid', using account (optional) 'account'. If keyID
- is specified, encrypt the message with the pgp key
- """
- jid = self._get_real_jid(jid, account)
- return self._send_message(jid, message, keyID, account)
-
- @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b')
- def send_single_message(self, jid, subject, message, keyID, account):
- """
- Send single 'message' to 'jid', using account (optional) 'account'. If
- keyID is specified, encrypt the message with the pgp key
- """
- jid = self._get_real_jid(jid, account)
- return self._send_message(jid, message, keyID, account, type, subject)
-
- @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
- def send_groupchat_message(self, room_jid, message, account):
- """
- Send 'message' to groupchat 'room_jid', using account (optional) 'account'
- """
- if not room_jid or not message:
- return DBUS_BOOLEAN(False)
- connected_account = self._get_account_for_groupchat(account, room_jid)
- if connected_account:
- connection = gajim.connections[connected_account]
- connection.send_gc_message(room_jid, message)
- return DBUS_BOOLEAN(True)
- return DBUS_BOOLEAN(False)
-
- @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
- def open_chat(self, jid, account, message):
- """
- Shows the tabbed window for new message to 'jid', using account (optional)
- 'account'
- """
- if not jid:
- raise dbus_support.MissingArgument()
- jid = self._get_real_jid(jid, account)
- try:
- jid = helpers.parse_jid(jid)
- except Exception:
- # Jid is not conform, ignore it
- return DBUS_BOOLEAN(False)
-
- if account:
- accounts = [account]
- else:
- accounts = gajim.connections.keys()
- if len(accounts) == 1:
- account = accounts[0]
- connected_account = None
- first_connected_acct = None
- for acct in accounts:
- if gajim.connections[acct].connected > 1: # account is online
- contact = gajim.contacts.get_first_contact_from_jid(acct, jid)
- if gajim.interface.msg_win_mgr.has_window(jid, acct):
- connected_account = acct
- break
- # jid is in roster
- elif contact:
- connected_account = acct
- break
- # we send the message to jid not in roster, because account is
- # specified, or there is only one account
- elif account:
- connected_account = acct
- elif first_connected_acct is None:
- first_connected_acct = acct
-
- # if jid is not a conntact, open-chat with first connected account
- if connected_account is None and first_connected_acct:
- connected_account = first_connected_acct
-
- if connected_account:
- gajim.interface.new_chat_from_jid(connected_account, jid, message)
- # preserve the 'steal focus preservation'
- win = gajim.interface.msg_win_mgr.get_window(jid,
- connected_account).window
- if win.get_property('visible'):
- win.window.focus(gtk.get_current_event_time())
- return DBUS_BOOLEAN(True)
- return DBUS_BOOLEAN(False)
-
- @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
- def change_status(self, status, message, account):
- """
- change_status(status, message, account). Account is optional - if not
- specified status is changed for all accounts
- """
- if status not in ('offline', 'online', 'chat',
- 'away', 'xa', 'dnd', 'invisible'):
- status = ''
- if account:
- if not status:
- if account not in gajim.connections:
- return DBUS_BOOLEAN(False)
- status = gajim.SHOW_LIST[gajim.connections[account].connected]
- gobject.idle_add(gajim.interface.roster.send_status, account,
- status, message)
- else:
- # account not specified, so change the status of all accounts
- for acc in gajim.contacts.get_accounts():
- if not gajim.config.get_per('accounts', acc,
- 'sync_with_global_status'):
- continue
- if status:
- status_ = status
- else:
- if acc not in gajim.connections:
- continue
- status_ = gajim.SHOW_LIST[gajim.connections[acc].connected]
- gobject.idle_add(gajim.interface.roster.send_status, acc,
- status_, message)
- return DBUS_BOOLEAN(False)
-
- @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
- def set_priority(self, prio, account):
- """
- set_priority(prio, account). Account is optional - if not specified
- priority is changed for all accounts. That are synced with global status
- """
- if account:
- gajim.config.set_per('accounts', account, 'priority', prio)
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- status = gajim.connections[account].status
- gobject.idle_add(gajim.connections[account].change_status, show,
- status)
- else:
- # account not specified, so change prio of all accounts
- for acc in gajim.contacts.get_accounts():
- if not gajim.account_is_connected(acc):
- continue
- if not gajim.config.get_per('accounts', acc,
- 'sync_with_global_status'):
- continue
- gajim.config.set_per('accounts', acc, 'priority', prio)
- show = gajim.SHOW_LIST[gajim.connections[acc].connected]
- status = gajim.connections[acc].status
- gobject.idle_add(gajim.connections[acc].change_status, show,
- status)
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='')
- def show_next_pending_event(self):
- """
- Show the window(s) with next pending event in tabbed/group chats
- """
- if gajim.events.get_nb_events():
- gajim.interface.systray.handle_first_event()
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}')
- def contact_info(self, jid):
- """
- Get vcard info for a contact. Return cached value of the vcard
- """
- if not isinstance(jid, unicode):
- jid = unicode(jid)
- if not jid:
- raise dbus_support.MissingArgument()
- jid = self._get_real_jid(jid)
-
- cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
- if cached_vcard:
- return get_dbus_struct(cached_vcard)
-
- # return empty dict
- return DBUS_DICT_SV()
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='as')
- def list_accounts(self):
- """
- List register accounts
- """
- result = gajim.contacts.get_accounts()
- result_array = dbus.Array([], signature='s')
- if result and len(result) > 0:
- for account in result:
- result_array.append(DBUS_STRING(account))
- return result_array
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}')
- def account_info(self, account):
- """
- Show info on account: resource, jid, nick, prio, message
- """
- result = DBUS_DICT_SS()
- if account in gajim.connections:
- # account is valid
- con = gajim.connections[account]
- index = con.connected
- result['status'] = DBUS_STRING(gajim.SHOW_LIST[index])
- result['name'] = DBUS_STRING(con.name)
- result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name))
- result['message'] = DBUS_STRING(con.status)
- result['priority'] = DBUS_STRING(unicode(con.priority))
- result['resource'] = DBUS_STRING(unicode(gajim.config.get_per(
- 'accounts', con.name, 'resource')))
- return result
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}')
- def list_contacts(self, account):
- """
- List all contacts in the roster. If the first argument is specified, then
- return the contacts for the specified account
- """
- result = dbus.Array([], signature='aa{sv}')
- accounts = gajim.contacts.get_accounts()
- if len(accounts) == 0:
- return result
- if account:
- accounts_to_search = [account]
- else:
- accounts_to_search = accounts
- for acct in accounts_to_search:
- if acct in accounts:
- for jid in gajim.contacts.get_jid_list(acct):
- item = self._contacts_as_dbus_structure(
- gajim.contacts.get_contacts(acct, jid))
- if item:
- result.append(item)
- return result
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='')
- def toggle_roster_appearance(self):
- """
- Show/hide the roster window
- """
- win = gajim.interface.roster.window
- if win.get_property('visible'):
- gobject.idle_add(win.hide)
- else:
- win.present()
- # preserve the 'steal focus preservation'
- if self._is_first():
- win.window.focus(gtk.get_current_event_time())
- else:
- win.window.focus(long(time()))
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='')
- def toggle_ipython(self):
- """
- Show/hide the ipython window
- """
- win = gajim.ipython_window
- if win:
- if win.window.is_visible():
- gobject.idle_add(win.hide)
- else:
- win.show_all()
- win.present()
- else:
- gajim.interface.create_ipython_window()
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}')
- def prefs_list(self):
- prefs_dict = DBUS_DICT_SS()
- def get_prefs(data, name, path, value):
- if value is None:
- return
- key = ''
- if path is not None:
- for node in path:
- key += node + '#'
- key += name
- prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1])
- gajim.config.foreach(get_prefs)
- return prefs_dict
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='b')
- def prefs_store(self):
- try:
- gajim.interface.save_config()
- except Exception, e:
- return DBUS_BOOLEAN(False)
- return DBUS_BOOLEAN(True)
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
- def prefs_del(self, key):
- if not key:
- return DBUS_BOOLEAN(False)
- key_path = key.split('#', 2)
- if len(key_path) != 3:
- return DBUS_BOOLEAN(False)
- if key_path[2] == '*':
- gajim.config.del_per(key_path[0], key_path[1])
- else:
- gajim.config.del_per(key_path[0], key_path[1], key_path[2])
- return DBUS_BOOLEAN(True)
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
- def prefs_put(self, key):
- if not key:
- return DBUS_BOOLEAN(False)
- key_path = key.split('#', 2)
- if len(key_path) < 3:
- subname, value = key.split('=', 1)
- gajim.config.set(subname, value)
- return DBUS_BOOLEAN(True)
- subname, value = key_path[2].split('=', 1)
- gajim.config.set_per(key_path[0], key_path[1], subname, value)
- return DBUS_BOOLEAN(True)
-
- @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
- def add_contact(self, jid, account):
- if account:
- if account in gajim.connections and \
- gajim.connections[account].connected > 1:
- # if given account is active, use it
- AddNewContactWindow(account = account, jid = jid)
- else:
- # wrong account
- return DBUS_BOOLEAN(False)
- else:
- # if account is not given, show account combobox
- AddNewContactWindow(account = None, jid = jid)
- return DBUS_BOOLEAN(True)
-
- @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
- def remove_contact(self, jid, account):
- jid = self._get_real_jid(jid, account)
- accounts = gajim.contacts.get_accounts()
-
- # if there is only one account in roster, take it as default
- if account:
- accounts = [account]
- contact_exists = False
- for account in accounts:
- contacts = gajim.contacts.get_contacts(account, jid)
- if contacts:
- gajim.connections[account].unsubscribe(jid)
- for contact in contacts:
- gajim.interface.roster.remove_contact(contact, account)
- gajim.contacts.remove_jid(account, jid)
- contact_exists = True
- return DBUS_BOOLEAN(contact_exists)
-
- def _is_first(self):
- if self.first_show:
- self.first_show = False
- return True
- return False
-
- def _get_real_jid(self, jid, account = None):
- """
- Get the real jid from the given one: removes xmpp: or get jid from nick if
- account is specified, search only in this account
- """
- if account:
- accounts = [account]
- else:
- accounts = gajim.connections.keys()
- if jid.startswith('xmpp:'):
- return jid[5:] # len('xmpp:') = 5
- nick_in_roster = None # Is jid a nick ?
- for account in accounts:
- # Does jid exists in roster of one account ?
- if gajim.contacts.get_contacts(account, jid):
- return jid
- if not nick_in_roster:
- # look in all contact if one has jid as nick
- for jid_ in gajim.contacts.get_jid_list(account):
- c = gajim.contacts.get_contacts(account, jid_)
- if c[0].name == jid:
- nick_in_roster = jid_
- break
- if nick_in_roster:
- # We have not found jid in roster, but we found is as a nick
- return nick_in_roster
- # We have not found it as jid nor as nick, probably a not in roster jid
- return jid
-
- def _contacts_as_dbus_structure(self, contacts):
- """
- Get info from list of Contact objects and create dbus dict
- """
- if not contacts:
- return None
- prim_contact = None # primary contact
- for contact in contacts:
- if prim_contact is None or contact.priority > prim_contact.priority:
- prim_contact = contact
- contact_dict = DBUS_DICT_SV()
- contact_dict['name'] = DBUS_STRING(prim_contact.name)
- contact_dict['show'] = DBUS_STRING(prim_contact.show)
- contact_dict['jid'] = DBUS_STRING(prim_contact.jid)
- if prim_contact.keyID:
- keyID = None
- if len(prim_contact.keyID) == 8:
- keyID = prim_contact.keyID
- elif len(prim_contact.keyID) == 16:
- keyID = prim_contact.keyID[8:]
- if keyID:
- contact_dict['openpgp'] = keyID
- contact_dict['resources'] = dbus.Array([], signature='(sis)')
- for contact in contacts:
- resource_props = dbus.Struct((DBUS_STRING(contact.resource),
- dbus.Int32(contact.priority), DBUS_STRING(contact.status)))
- contact_dict['resources'].append(resource_props)
- contact_dict['groups'] = dbus.Array([], signature='(s)')
- for group in prim_contact.groups:
- contact_dict['groups'].append((DBUS_STRING(group),))
- return contact_dict
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='s')
- def get_unread_msgs_number(self):
- return DBUS_STRING(str(gajim.events.get_nb_events()))
-
- @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
- def start_chat(self, account):
- if not account:
- # error is shown in gajim-remote check_arguments(..)
- return DBUS_BOOLEAN(False)
- NewChatDialog(account)
- return DBUS_BOOLEAN(True)
-
- @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
- def send_xml(self, xml, account):
- if account:
- gajim.connections[account].send_stanza(str(xml))
- else:
- for acc in gajim.contacts.get_accounts():
- gajim.connections[acc].send_stanza(str(xml))
-
- @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
- def change_avatar(self, picture, account):
- filesize = os.path.getsize(picture)
- invalid_file = False
- if os.path.isfile(picture):
- stat = os.stat(picture)
- if stat[6] == 0:
- invalid_file = True
- else:
- invalid_file = True
- if not invalid_file and filesize < 16384:
- fd = open(picture, 'rb')
- data = fd.read()
- avatar = base64.encodestring(data)
- avatar_mime_type = mimetypes.guess_type(picture)[0]
- vcard={}
- vcard['PHOTO'] = {'BINVAL': avatar}
- if avatar_mime_type:
- vcard['PHOTO']['TYPE'] = avatar_mime_type
- if account:
- gajim.connections[account].send_vcard(vcard)
- else:
- for acc in gajim.connections:
- gajim.connections[acc].send_vcard(vcard)
-
- @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='')
- def join_room(self, room_jid, nick, password, account):
- if not account:
- # get the first connected account
- accounts = gajim.connections.keys()
- for acct in accounts:
- if gajim.account_is_connected(acct):
- account = acct
- break
- if not account:
- return
- if not nick:
- nick = ''
- gajim.interface.instances[account]['join_gc'] = \
- JoinGroupchatWindow(account, room_jid, nick)
- else:
- gajim.interface.join_gc_room(account, room_jid, nick, password)
-
-# vim: se ts=3:
+ """
+ Local object definition for /org/gajim/dbus/RemoteObject
+
+ This docstring is not be visible, because the clients can access only the
+ remote object.
+ """
+
+ def __init__(self, bus_name):
+ self.first_show = True
+ self.vcard_account = None
+
+ # register our dbus API
+ dbus.service.Object.__init__(self, bus_name, OBJ_PATH)
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def Roster(self, account_and_data):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def AccountPresence(self, status_and_account):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def ContactPresence(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def ContactAbsence(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def ContactStatus(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def NewMessage(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def Subscribe(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def Subscribed(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def Unsubscribed(self, account_and_jid):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def NewAccount(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def VcardInfo(self, account_and_vcard):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def LastStatusTime(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def OsInfo(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def EntityTime(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def GCPresence(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def GCMessage(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def RosterInfo(self, account_and_array):
+ pass
+
+ @dbus.service.signal(INTERFACE, signature='av')
+ def NewGmail(self, account_and_array):
+ pass
+
+ def raise_signal(self, signal, arg):
+ """
+ Raise a signal, with a single argument of unspecified type Instead of
+ obj.raise_signal("Foo", bar), use obj.Foo(bar)
+ """
+ getattr(self, signal)(arg)
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
+ def get_status(self, account):
+ """
+ Return status (show to be exact) which is the global one unless account is
+ given
+ """
+ if not account:
+ # If user did not ask for account, returns the global status
+ return DBUS_STRING(helpers.get_global_show())
+ # return show for the given account
+ index = gajim.connections[account].connected
+ return DBUS_STRING(gajim.SHOW_LIST[index])
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
+ def get_status_message(self, account):
+ """
+ Return status which is the global one unless account is given
+ """
+ if not account:
+ # If user did not ask for account, returns the global status
+ return DBUS_STRING(str(helpers.get_global_status()))
+ # return show for the given account
+ status = gajim.connections[account].status
+ return DBUS_STRING(status)
+
+ def _get_account_and_contact(self, account, jid):
+ """
+ Get the account (if not given) and contact instance from jid
+ """
+ connected_account = None
+ contact = None
+ accounts = gajim.contacts.get_accounts()
+ # if there is only one account in roster, take it as default
+ # if user did not ask for account
+ if not account and len(accounts) == 1:
+ account = accounts[0]
+ if account:
+ if gajim.connections[account].connected > 1: # account is connected
+ connected_account = account
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ else:
+ for account in accounts:
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ if contact and gajim.connections[account].connected > 1:
+ # account is connected
+ connected_account = account
+ break
+ if not contact:
+ contact = jid
+
+ return connected_account, contact
+
+ def _get_account_for_groupchat(self, account, room_jid):
+ """
+ Get the account which is connected to groupchat (if not given)
+ or check if the given account is connected to the groupchat
+ """
+ connected_account = None
+ accounts = gajim.contacts.get_accounts()
+ # if there is only one account in roster, take it as default
+ # if user did not ask for account
+ if not account and len(accounts) == 1:
+ account = accounts[0]
+ if account:
+ if gajim.connections[account].connected > 1 and \
+ room_jid in gajim.gc_connected[account] and \
+ gajim.gc_connected[account][room_jid]:
+ # account and groupchat are connected
+ connected_account = account
+ else:
+ for account in accounts:
+ if gajim.connections[account].connected > 1 and \
+ room_jid in gajim.gc_connected[account] and \
+ gajim.gc_connected[account][room_jid]:
+ # account and groupchat are connected
+ connected_account = account
+ break
+ return connected_account
+
+ @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
+ def send_file(self, file_path, jid, account):
+ """
+ Send file, located at 'file_path' to 'jid', using account (optional)
+ 'account'
+ """
+ jid = self._get_real_jid(jid, account)
+ connected_account, contact = self._get_account_and_contact(account, jid)
+
+ if connected_account:
+ if file_path.startswith('file://'):
+ file_path=file_path[7:]
+ if os.path.isfile(file_path): # is it file?
+ gajim.interface.instances['file_transfers'].send_file(
+ connected_account, contact, file_path)
+ return DBUS_BOOLEAN(True)
+ return DBUS_BOOLEAN(False)
+
+ def _send_message(self, jid, message, keyID, account, type_ = 'chat',
+ subject = None):
+ """
+ Can be called from send_chat_message (default when send_message) or
+ send_single_message
+ """
+ if not jid or not message:
+ return DBUS_BOOLEAN(False)
+ if not keyID:
+ keyID = ''
+
+ connected_account, contact = self._get_account_and_contact(account, jid)
+ if connected_account:
+ connection = gajim.connections[connected_account]
+ connection.send_message(jid, message, keyID, type_, subject)
+ return DBUS_BOOLEAN(True)
+ return DBUS_BOOLEAN(False)
+
+ @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b')
+ def send_chat_message(self, jid, message, keyID, account):
+ """
+ Send chat 'message' to 'jid', using account (optional) 'account'. If keyID
+ is specified, encrypt the message with the pgp key
+ """
+ jid = self._get_real_jid(jid, account)
+ return self._send_message(jid, message, keyID, account)
+
+ @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b')
+ def send_single_message(self, jid, subject, message, keyID, account):
+ """
+ Send single 'message' to 'jid', using account (optional) 'account'. If
+ keyID is specified, encrypt the message with the pgp key
+ """
+ jid = self._get_real_jid(jid, account)
+ return self._send_message(jid, message, keyID, account, type, subject)
+
+ @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
+ def send_groupchat_message(self, room_jid, message, account):
+ """
+ Send 'message' to groupchat 'room_jid', using account (optional) 'account'
+ """
+ if not room_jid or not message:
+ return DBUS_BOOLEAN(False)
+ connected_account = self._get_account_for_groupchat(account, room_jid)
+ if connected_account:
+ connection = gajim.connections[connected_account]
+ connection.send_gc_message(room_jid, message)
+ return DBUS_BOOLEAN(True)
+ return DBUS_BOOLEAN(False)
+
+ @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
+ def open_chat(self, jid, account, message):
+ """
+ Shows the tabbed window for new message to 'jid', using account (optional)
+ 'account'
+ """
+ if not jid:
+ raise dbus_support.MissingArgument()
+ jid = self._get_real_jid(jid, account)
+ try:
+ jid = helpers.parse_jid(jid)
+ except Exception:
+ # Jid is not conform, ignore it
+ return DBUS_BOOLEAN(False)
+
+ if account:
+ accounts = [account]
+ else:
+ accounts = gajim.connections.keys()
+ if len(accounts) == 1:
+ account = accounts[0]
+ connected_account = None
+ first_connected_acct = None
+ for acct in accounts:
+ if gajim.connections[acct].connected > 1: # account is online
+ contact = gajim.contacts.get_first_contact_from_jid(acct, jid)
+ if gajim.interface.msg_win_mgr.has_window(jid, acct):
+ connected_account = acct
+ break
+ # jid is in roster
+ elif contact:
+ connected_account = acct
+ break
+ # we send the message to jid not in roster, because account is
+ # specified, or there is only one account
+ elif account:
+ connected_account = acct
+ elif first_connected_acct is None:
+ first_connected_acct = acct
+
+ # if jid is not a conntact, open-chat with first connected account
+ if connected_account is None and first_connected_acct:
+ connected_account = first_connected_acct
+
+ if connected_account:
+ gajim.interface.new_chat_from_jid(connected_account, jid, message)
+ # preserve the 'steal focus preservation'
+ win = gajim.interface.msg_win_mgr.get_window(jid,
+ connected_account).window
+ if win.get_property('visible'):
+ win.window.focus(gtk.get_current_event_time())
+ return DBUS_BOOLEAN(True)
+ return DBUS_BOOLEAN(False)
+
+ @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
+ def change_status(self, status, message, account):
+ """
+ change_status(status, message, account). Account is optional - if not
+ specified status is changed for all accounts
+ """
+ if status not in ('offline', 'online', 'chat',
+ 'away', 'xa', 'dnd', 'invisible'):
+ status = ''
+ if account:
+ if not status:
+ if account not in gajim.connections:
+ return DBUS_BOOLEAN(False)
+ status = gajim.SHOW_LIST[gajim.connections[account].connected]
+ gobject.idle_add(gajim.interface.roster.send_status, account,
+ status, message)
+ else:
+ # account not specified, so change the status of all accounts
+ for acc in gajim.contacts.get_accounts():
+ if not gajim.config.get_per('accounts', acc,
+ 'sync_with_global_status'):
+ continue
+ if status:
+ status_ = status
+ else:
+ if acc not in gajim.connections:
+ continue
+ status_ = gajim.SHOW_LIST[gajim.connections[acc].connected]
+ gobject.idle_add(gajim.interface.roster.send_status, acc,
+ status_, message)
+ return DBUS_BOOLEAN(False)
+
+ @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
+ def set_priority(self, prio, account):
+ """
+ set_priority(prio, account). Account is optional - if not specified
+ priority is changed for all accounts. That are synced with global status
+ """
+ if account:
+ gajim.config.set_per('accounts', account, 'priority', prio)
+ show = gajim.SHOW_LIST[gajim.connections[account].connected]
+ status = gajim.connections[account].status
+ gobject.idle_add(gajim.connections[account].change_status, show,
+ status)
+ else:
+ # account not specified, so change prio of all accounts
+ for acc in gajim.contacts.get_accounts():
+ if not gajim.account_is_connected(acc):
+ continue
+ if not gajim.config.get_per('accounts', acc,
+ 'sync_with_global_status'):
+ continue
+ gajim.config.set_per('accounts', acc, 'priority', prio)
+ show = gajim.SHOW_LIST[gajim.connections[acc].connected]
+ status = gajim.connections[acc].status
+ gobject.idle_add(gajim.connections[acc].change_status, show,
+ status)
+
+ @dbus.service.method(INTERFACE, in_signature='', out_signature='')
+ def show_next_pending_event(self):
+ """
+ Show the window(s) with next pending event in tabbed/group chats
+ """
+ if gajim.events.get_nb_events():
+ gajim.interface.systray.handle_first_event()
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}')
+ def contact_info(self, jid):
+ """
+ Get vcard info for a contact. Return cached value of the vcard
+ """
+ if not isinstance(jid, unicode):
+ jid = unicode(jid)
+ if not jid:
+ raise dbus_support.MissingArgument()
+ jid = self._get_real_jid(jid)
+
+ cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
+ if cached_vcard:
+ return get_dbus_struct(cached_vcard)
+
+ # return empty dict
+ return DBUS_DICT_SV()
+
+ @dbus.service.method(INTERFACE, in_signature='', out_signature='as')
+ def list_accounts(self):
+ """
+ List register accounts
+ """
+ result = gajim.contacts.get_accounts()
+ result_array = dbus.Array([], signature='s')
+ if result and len(result) > 0:
+ for account in result:
+ result_array.append(DBUS_STRING(account))
+ return result_array
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}')
+ def account_info(self, account):
+ """
+ Show info on account: resource, jid, nick, prio, message
+ """
+ result = DBUS_DICT_SS()
+ if account in gajim.connections:
+ # account is valid
+ con = gajim.connections[account]
+ index = con.connected
+ result['status'] = DBUS_STRING(gajim.SHOW_LIST[index])
+ result['name'] = DBUS_STRING(con.name)
+ result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name))
+ result['message'] = DBUS_STRING(con.status)
+ result['priority'] = DBUS_STRING(unicode(con.priority))
+ result['resource'] = DBUS_STRING(unicode(gajim.config.get_per(
+ 'accounts', con.name, 'resource')))
+ return result
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}')
+ def list_contacts(self, account):
+ """
+ List all contacts in the roster. If the first argument is specified, then
+ return the contacts for the specified account
+ """
+ result = dbus.Array([], signature='aa{sv}')
+ accounts = gajim.contacts.get_accounts()
+ if len(accounts) == 0:
+ return result
+ if account:
+ accounts_to_search = [account]
+ else:
+ accounts_to_search = accounts
+ for acct in accounts_to_search:
+ if acct in accounts:
+ for jid in gajim.contacts.get_jid_list(acct):
+ item = self._contacts_as_dbus_structure(
+ gajim.contacts.get_contacts(acct, jid))
+ if item:
+ result.append(item)
+ return result
+
+ @dbus.service.method(INTERFACE, in_signature='', out_signature='')
+ def toggle_roster_appearance(self):
+ """
+ Show/hide the roster window
+ """
+ win = gajim.interface.roster.window
+ if win.get_property('visible'):
+ gobject.idle_add(win.hide)
+ else:
+ win.present()
+ # preserve the 'steal focus preservation'
+ if self._is_first():
+ win.window.focus(gtk.get_current_event_time())
+ else:
+ win.window.focus(long(time()))
+
+ @dbus.service.method(INTERFACE, in_signature='', out_signature='')
+ def toggle_ipython(self):
+ """
+ Show/hide the ipython window
+ """
+ win = gajim.ipython_window
+ if win:
+ if win.window.is_visible():
+ gobject.idle_add(win.hide)
+ else:
+ win.show_all()
+ win.present()
+ else:
+ gajim.interface.create_ipython_window()
+
+ @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}')
+ def prefs_list(self):
+ prefs_dict = DBUS_DICT_SS()
+ def get_prefs(data, name, path, value):
+ if value is None:
+ return
+ key = ''
+ if path is not None:
+ for node in path:
+ key += node + '#'
+ key += name
+ prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1])
+ gajim.config.foreach(get_prefs)
+ return prefs_dict
+
+ @dbus.service.method(INTERFACE, in_signature='', out_signature='b')
+ def prefs_store(self):
+ try:
+ gajim.interface.save_config()
+ except Exception, e:
+ return DBUS_BOOLEAN(False)
+ return DBUS_BOOLEAN(True)
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
+ def prefs_del(self, key):
+ if not key:
+ return DBUS_BOOLEAN(False)
+ key_path = key.split('#', 2)
+ if len(key_path) != 3:
+ return DBUS_BOOLEAN(False)
+ if key_path[2] == '*':
+ gajim.config.del_per(key_path[0], key_path[1])
+ else:
+ gajim.config.del_per(key_path[0], key_path[1], key_path[2])
+ return DBUS_BOOLEAN(True)
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
+ def prefs_put(self, key):
+ if not key:
+ return DBUS_BOOLEAN(False)
+ key_path = key.split('#', 2)
+ if len(key_path) < 3:
+ subname, value = key.split('=', 1)
+ gajim.config.set(subname, value)
+ return DBUS_BOOLEAN(True)
+ subname, value = key_path[2].split('=', 1)
+ gajim.config.set_per(key_path[0], key_path[1], subname, value)
+ return DBUS_BOOLEAN(True)
+
+ @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
+ def add_contact(self, jid, account):
+ if account:
+ if account in gajim.connections and \
+ gajim.connections[account].connected > 1:
+ # if given account is active, use it
+ AddNewContactWindow(account = account, jid = jid)
+ else:
+ # wrong account
+ return DBUS_BOOLEAN(False)
+ else:
+ # if account is not given, show account combobox
+ AddNewContactWindow(account = None, jid = jid)
+ return DBUS_BOOLEAN(True)
+
+ @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
+ def remove_contact(self, jid, account):
+ jid = self._get_real_jid(jid, account)
+ accounts = gajim.contacts.get_accounts()
+
+ # if there is only one account in roster, take it as default
+ if account:
+ accounts = [account]
+ contact_exists = False
+ for account in accounts:
+ contacts = gajim.contacts.get_contacts(account, jid)
+ if contacts:
+ gajim.connections[account].unsubscribe(jid)
+ for contact in contacts:
+ gajim.interface.roster.remove_contact(contact, account)
+ gajim.contacts.remove_jid(account, jid)
+ contact_exists = True
+ return DBUS_BOOLEAN(contact_exists)
+
+ def _is_first(self):
+ if self.first_show:
+ self.first_show = False
+ return True
+ return False
+
+ def _get_real_jid(self, jid, account = None):
+ """
+ Get the real jid from the given one: removes xmpp: or get jid from nick if
+ account is specified, search only in this account
+ """
+ if account:
+ accounts = [account]
+ else:
+ accounts = gajim.connections.keys()
+ if jid.startswith('xmpp:'):
+ return jid[5:] # len('xmpp:') = 5
+ nick_in_roster = None # Is jid a nick ?
+ for account in accounts:
+ # Does jid exists in roster of one account ?
+ if gajim.contacts.get_contacts(account, jid):
+ return jid
+ if not nick_in_roster:
+ # look in all contact if one has jid as nick
+ for jid_ in gajim.contacts.get_jid_list(account):
+ c = gajim.contacts.get_contacts(account, jid_)
+ if c[0].name == jid:
+ nick_in_roster = jid_
+ break
+ if nick_in_roster:
+ # We have not found jid in roster, but we found is as a nick
+ return nick_in_roster
+ # We have not found it as jid nor as nick, probably a not in roster jid
+ return jid
+
+ def _contacts_as_dbus_structure(self, contacts):
+ """
+ Get info from list of Contact objects and create dbus dict
+ """
+ if not contacts:
+ return None
+ prim_contact = None # primary contact
+ for contact in contacts:
+ if prim_contact is None or contact.priority > prim_contact.priority:
+ prim_contact = contact
+ contact_dict = DBUS_DICT_SV()
+ contact_dict['name'] = DBUS_STRING(prim_contact.name)
+ contact_dict['show'] = DBUS_STRING(prim_contact.show)
+ contact_dict['jid'] = DBUS_STRING(prim_contact.jid)
+ if prim_contact.keyID:
+ keyID = None
+ if len(prim_contact.keyID) == 8:
+ keyID = prim_contact.keyID
+ elif len(prim_contact.keyID) == 16:
+ keyID = prim_contact.keyID[8:]
+ if keyID:
+ contact_dict['openpgp'] = keyID
+ contact_dict['resources'] = dbus.Array([], signature='(sis)')
+ for contact in contacts:
+ resource_props = dbus.Struct((DBUS_STRING(contact.resource),
+ dbus.Int32(contact.priority), DBUS_STRING(contact.status)))
+ contact_dict['resources'].append(resource_props)
+ contact_dict['groups'] = dbus.Array([], signature='(s)')
+ for group in prim_contact.groups:
+ contact_dict['groups'].append((DBUS_STRING(group),))
+ return contact_dict
+
+ @dbus.service.method(INTERFACE, in_signature='', out_signature='s')
+ def get_unread_msgs_number(self):
+ return DBUS_STRING(str(gajim.events.get_nb_events()))
+
+ @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
+ def start_chat(self, account):
+ if not account:
+ # error is shown in gajim-remote check_arguments(..)
+ return DBUS_BOOLEAN(False)
+ NewChatDialog(account)
+ return DBUS_BOOLEAN(True)
+
+ @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
+ def send_xml(self, xml, account):
+ if account:
+ gajim.connections[account].send_stanza(str(xml))
+ else:
+ for acc in gajim.contacts.get_accounts():
+ gajim.connections[acc].send_stanza(str(xml))
+
+ @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
+ def change_avatar(self, picture, account):
+ filesize = os.path.getsize(picture)
+ invalid_file = False
+ if os.path.isfile(picture):
+ stat = os.stat(picture)
+ if stat[6] == 0:
+ invalid_file = True
+ else:
+ invalid_file = True
+ if not invalid_file and filesize < 16384:
+ fd = open(picture, 'rb')
+ data = fd.read()
+ avatar = base64.encodestring(data)
+ avatar_mime_type = mimetypes.guess_type(picture)[0]
+ vcard={}
+ vcard['PHOTO'] = {'BINVAL': avatar}
+ if avatar_mime_type:
+ vcard['PHOTO']['TYPE'] = avatar_mime_type
+ if account:
+ gajim.connections[account].send_vcard(vcard)
+ else:
+ for acc in gajim.connections:
+ gajim.connections[acc].send_vcard(vcard)
+
+ @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='')
+ def join_room(self, room_jid, nick, password, account):
+ if not account:
+ # get the first connected account
+ accounts = gajim.connections.keys()
+ for acct in accounts:
+ if gajim.account_is_connected(acct):
+ account = acct
+ break
+ if not account:
+ return
+ if not nick:
+ nick = ''
+ gajim.interface.instances[account]['join_gc'] = \
+ JoinGroupchatWindow(account, room_jid, nick)
+ else:
+ gajim.interface.join_gc_room(account, room_jid, nick, password)
diff --git a/src/roster_window.py b/src/roster_window.py
index 049bdb9e5..8f8bba315 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -65,5940 +65,5938 @@ from message_window import MessageWindowMgr
from common import dbus_support
if dbus_support.supported:
- import dbus
+ import dbus
from common.xmpp.protocol import NS_FILE
from common.pep import MOODS, ACTIVITIES
#(icon, name, type, jid, account, editable, second pixbuf)
(
- C_IMG, # image to show state (online, new message etc)
- C_NAME, # cellrenderer text that holds contact nickame
- C_TYPE, # account, group or contact?
- C_JID, # the jid of the row
- C_ACCOUNT, # cellrenderer text that holds account name
- C_MOOD_PIXBUF,
- C_ACTIVITY_PIXBUF,
- C_TUNE_PIXBUF,
- C_LOCATION_PIXBUF,
- C_AVATAR_PIXBUF, # avatar_pixbuf
- C_PADLOCK_PIXBUF, # use for account row only
+ C_IMG, # image to show state (online, new message etc)
+ C_NAME, # cellrenderer text that holds contact nickame
+ C_TYPE, # account, group or contact?
+ C_JID, # the jid of the row
+ C_ACCOUNT, # cellrenderer text that holds account name
+ C_MOOD_PIXBUF,
+ C_ACTIVITY_PIXBUF,
+ C_TUNE_PIXBUF,
+ C_LOCATION_PIXBUF,
+ C_AVATAR_PIXBUF, # avatar_pixbuf
+ C_PADLOCK_PIXBUF, # use for account row only
) = range(11)
class RosterWindow:
- """
- Class for main window of the GTK+ interface
- """
-
- def _get_account_iter(self, name, model=None):
- """
- Return the gtk.TreeIter of the given account or None if not found
-
- Keyword arguments:
- name -- the account name
- model -- the data model (default TreeFilterModel)
- """
- if not model:
- model = self.modelfilter
- if model is None:
- return
- account_iter = model.get_iter_root()
- if self.regroup:
- return account_iter
- while account_iter:
- account_name = model[account_iter][C_ACCOUNT]
- if account_name and name == account_name.decode('utf-8'):
- break
- account_iter = model.iter_next(account_iter)
- return account_iter
-
-
- def _get_group_iter(self, name, account, account_iter=None, model=None):
- """
- Return the gtk.TreeIter of the given group or None if not found
-
- Keyword arguments:
- name -- the group name
- account -- the account name
- account_iter -- the iter of the account the model (default None)
- model -- the data model (default TreeFilterModel)
- """
- if not model:
- model = self.modelfilter
- if not account_iter:
- account_iter = self._get_account_iter(account, model)
- group_iter = model.iter_children(account_iter)
- # C_NAME column contacts the pango escaped group name
- while group_iter:
- group_name = model[group_iter][C_JID].decode('utf-8')
- if name == group_name:
- break
- group_iter = model.iter_next(group_iter)
- return group_iter
-
-
- def _get_self_contact_iter(self, account, model=None):
- """
- Return the gtk.TreeIter of SelfContact or None if not found
-
- Keyword arguments:
- account -- the account of SelfContact
- model -- the data model (default TreeFilterModel)
- """
- if not model:
- model = self.modelfilter
- iterAcct = self._get_account_iter(account, model)
- iterC = model.iter_children(iterAcct)
-
- # There might be several SelfContacts in merged account view
- while iterC:
- if model[iterC][C_TYPE] != 'self_contact':
- break
- iter_account = model[iterC][C_ACCOUNT]
- if account == iter_account.decode('utf-8'):
- return iterC
- iterC = model.iter_next(iterC)
- return None
-
-
- def _get_contact_iter(self, jid, account, contact=None, model=None):
- """
- Return a list of gtk.TreeIter of the given contact
-
- Keyword arguments:
- jid -- the jid without resource
- account -- the account
- contact -- the contact (default None)
- model -- the data model (default TreeFilterModel)
- """
- if not model:
- model = self.modelfilter
- # when closing Gajim model can be none (async pbs?)
- if model is None:
- return []
-
- if jid == gajim.get_jid_from_account(account):
- contact_iter = self._get_self_contact_iter(account, model)
- if contact_iter:
- return [contact_iter]
- else:
- return []
-
- if not contact:
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if not contact:
- # We don't know this contact
- return []
-
- acct = self._get_account_iter(account, model)
- found = [] # the contact iters. One per group
- for group in contact.get_shown_groups():
- group_iter = self._get_group_iter(group, account, acct, model)
- contact_iter = model.iter_children(group_iter)
-
- while contact_iter:
- # Loop over all contacts in this group
- iter_jid = model[contact_iter][C_JID]
- if iter_jid and jid == iter_jid.decode('utf-8') and \
- account == model[contact_iter][C_ACCOUNT].decode('utf-8'):
- # only one iter per group
- found.append(contact_iter)
- contact_iter = None
- elif model.iter_has_child(contact_iter):
- # it's a big brother and has children
- contact_iter = model.iter_children(contact_iter)
- else:
- # try to find next contact:
- # other contact in this group or
- # brother contact
- next_contact_iter = model.iter_next(contact_iter)
- if next_contact_iter:
- contact_iter = next_contact_iter
- else:
- # It's the last one.
- # Go up if we are big brother
- parent_iter = model.iter_parent(contact_iter)
- if parent_iter and model[parent_iter][C_TYPE] == 'contact':
- contact_iter = model.iter_next(parent_iter)
- else:
- # we tested all
- # contacts in this group
- contact_iter = None
- return found
-
-
- def _iter_is_separator(self, model, titer):
- """
- Return True if the given iter is a separator
-
- Keyword arguments:
- model -- the data model
- iter -- the gtk.TreeIter to test
- """
- if model[titer][0] == 'SEPARATOR':
- return True
- return False
-
-
- def _iter_contact_rows(self, model=None):
- """
- Iterate over all contact rows in given model
-
- Keyword argument
- model -- the data model (default TreeFilterModel)
- """
- if not model:
- model = self.modelfilter
- account_iter = model.get_iter_root()
- while account_iter:
- group_iter = model.iter_children(account_iter)
- while group_iter:
- contact_iter = model.iter_children(group_iter)
- while contact_iter:
- yield model[contact_iter]
- contact_iter = model.iter_next(
- contact_iter)
- group_iter = model.iter_next(group_iter)
- account_iter = model.iter_next(account_iter)
+ """
+ Class for main window of the GTK+ interface
+ """
+
+ def _get_account_iter(self, name, model=None):
+ """
+ Return the gtk.TreeIter of the given account or None if not found
+
+ Keyword arguments:
+ name -- the account name
+ model -- the data model (default TreeFilterModel)
+ """
+ if not model:
+ model = self.modelfilter
+ if model is None:
+ return
+ account_iter = model.get_iter_root()
+ if self.regroup:
+ return account_iter
+ while account_iter:
+ account_name = model[account_iter][C_ACCOUNT]
+ if account_name and name == account_name.decode('utf-8'):
+ break
+ account_iter = model.iter_next(account_iter)
+ return account_iter
+
+
+ def _get_group_iter(self, name, account, account_iter=None, model=None):
+ """
+ Return the gtk.TreeIter of the given group or None if not found
+
+ Keyword arguments:
+ name -- the group name
+ account -- the account name
+ account_iter -- the iter of the account the model (default None)
+ model -- the data model (default TreeFilterModel)
+ """
+ if not model:
+ model = self.modelfilter
+ if not account_iter:
+ account_iter = self._get_account_iter(account, model)
+ group_iter = model.iter_children(account_iter)
+ # C_NAME column contacts the pango escaped group name
+ while group_iter:
+ group_name = model[group_iter][C_JID].decode('utf-8')
+ if name == group_name:
+ break
+ group_iter = model.iter_next(group_iter)
+ return group_iter
+
+
+ def _get_self_contact_iter(self, account, model=None):
+ """
+ Return the gtk.TreeIter of SelfContact or None if not found
+
+ Keyword arguments:
+ account -- the account of SelfContact
+ model -- the data model (default TreeFilterModel)
+ """
+ if not model:
+ model = self.modelfilter
+ iterAcct = self._get_account_iter(account, model)
+ iterC = model.iter_children(iterAcct)
+
+ # There might be several SelfContacts in merged account view
+ while iterC:
+ if model[iterC][C_TYPE] != 'self_contact':
+ break
+ iter_account = model[iterC][C_ACCOUNT]
+ if account == iter_account.decode('utf-8'):
+ return iterC
+ iterC = model.iter_next(iterC)
+ return None
+
+
+ def _get_contact_iter(self, jid, account, contact=None, model=None):
+ """
+ Return a list of gtk.TreeIter of the given contact
+
+ Keyword arguments:
+ jid -- the jid without resource
+ account -- the account
+ contact -- the contact (default None)
+ model -- the data model (default TreeFilterModel)
+ """
+ if not model:
+ model = self.modelfilter
+ # when closing Gajim model can be none (async pbs?)
+ if model is None:
+ return []
+
+ if jid == gajim.get_jid_from_account(account):
+ contact_iter = self._get_self_contact_iter(account, model)
+ if contact_iter:
+ return [contact_iter]
+ else:
+ return []
+
+ if not contact:
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ if not contact:
+ # We don't know this contact
+ return []
+
+ acct = self._get_account_iter(account, model)
+ found = [] # the contact iters. One per group
+ for group in contact.get_shown_groups():
+ group_iter = self._get_group_iter(group, account, acct, model)
+ contact_iter = model.iter_children(group_iter)
+
+ while contact_iter:
+ # Loop over all contacts in this group
+ iter_jid = model[contact_iter][C_JID]
+ if iter_jid and jid == iter_jid.decode('utf-8') and \
+ account == model[contact_iter][C_ACCOUNT].decode('utf-8'):
+ # only one iter per group
+ found.append(contact_iter)
+ contact_iter = None
+ elif model.iter_has_child(contact_iter):
+ # it's a big brother and has children
+ contact_iter = model.iter_children(contact_iter)
+ else:
+ # try to find next contact:
+ # other contact in this group or
+ # brother contact
+ next_contact_iter = model.iter_next(contact_iter)
+ if next_contact_iter:
+ contact_iter = next_contact_iter
+ else:
+ # It's the last one.
+ # Go up if we are big brother
+ parent_iter = model.iter_parent(contact_iter)
+ if parent_iter and model[parent_iter][C_TYPE] == 'contact':
+ contact_iter = model.iter_next(parent_iter)
+ else:
+ # we tested all
+ # contacts in this group
+ contact_iter = None
+ return found
+
+
+ def _iter_is_separator(self, model, titer):
+ """
+ Return True if the given iter is a separator
+
+ Keyword arguments:
+ model -- the data model
+ iter -- the gtk.TreeIter to test
+ """
+ if model[titer][0] == 'SEPARATOR':
+ return True
+ return False
+
+
+ def _iter_contact_rows(self, model=None):
+ """
+ Iterate over all contact rows in given model
+
+ Keyword argument
+ model -- the data model (default TreeFilterModel)
+ """
+ if not model:
+ model = self.modelfilter
+ account_iter = model.get_iter_root()
+ while account_iter:
+ group_iter = model.iter_children(account_iter)
+ while group_iter:
+ contact_iter = model.iter_children(group_iter)
+ while contact_iter:
+ yield model[contact_iter]
+ contact_iter = model.iter_next(
+ contact_iter)
+ group_iter = model.iter_next(group_iter)
+ account_iter = model.iter_next(account_iter)
#############################################################################
### Methods for adding and removing roster window items
#############################################################################
- def add_account(self, account):
- """
- Add account to roster and draw it. Do nothing if it is already in
- """
- if self._get_account_iter(account):
- # Will happen on reconnect or for merged accounts
- return
-
- if self.regroup:
- # Merged accounts view
- show = helpers.get_global_show()
- self.model.append(None, [
- gajim.interface.jabber_state_images['16'][show],
- _('Merged accounts'), 'account', '', 'all',
- None, None, None, None, None, None])
- else:
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- our_jid = gajim.get_jid_from_account(account)
-
- tls_pixbuf = None
- if gajim.account_is_securely_connected(account):
- # the only way to create a pixbuf from stock
- tls_pixbuf = self.window.render_icon(
- gtk.STOCK_DIALOG_AUTHENTICATION,
- gtk.ICON_SIZE_MENU)
-
- self.model.append(None, [
- gajim.interface.jabber_state_images['16'][show],
- gobject.markup_escape_text(account), 'account',
- our_jid, account, None, None, None, None, None,
- tls_pixbuf])
-
- self.draw_account(account)
-
-
- def add_account_contacts(self, account):
- """
- Add all contacts and groups of the given account to roster, draw them and
- account
- """
- self.starting = True
- jids = gajim.contacts.get_jid_list(account)
-
- self.tree.freeze_child_notify()
- for jid in jids:
- self.add_contact(jid, account)
-
- # Do not freeze the GUI when drawing the contacts
- if jids:
- # Overhead is big, only invoke when needed
- self._idle_draw_jids_of_account(jids, account)
-
- # Draw all known groups
- for group in gajim.groups[account]:
- self.draw_group(group, account)
- self.draw_account(account)
-
- self.tree.thaw_child_notify()
- self.starting = False
-
-
- def _add_entity(self, contact, account, groups=None,
- big_brother_contact=None, big_brother_account=None):
- """
- Add the given contact to roster data model
-
- Contact is added regardless if he is already in roster or not. Return
- list of newly added iters.
-
- Keyword arguments:
- contact -- the contact to add
- account -- the contacts account
- groups -- list of groups to add the contact to.
- (default groups in contact.get_shown_groups()).
- Parameter ignored when big_brother_contact is specified.
- big_brother_contact -- if specified contact is added as child
- big_brother_contact. (default None)
- """
- added_iters = []
- if big_brother_contact:
- # Add contact under big brother
-
- parent_iters = self._get_contact_iter(
- big_brother_contact.jid, big_brother_account,
- big_brother_contact, self.model)
- assert len(parent_iters) > 0, 'Big brother is not yet in roster!'
-
- # Do not confuse get_contact_iter: Sync groups of family members
- contact.groups = big_brother_contact.get_shown_groups()[:]
-
- for child_iter in parent_iters:
- it = self.model.append(child_iter, (None, contact.get_shown_name(),
- 'contact', contact.jid, account, None, None, None, None, None,
- None))
- added_iters.append(it)
- else:
- # We are a normal contact. Add us to our groups.
- if not groups:
- groups = contact.get_shown_groups()
- for group in groups:
- child_iterG = self._get_group_iter(group, account,
- model = self.model)
- if not child_iterG:
- # Group is not yet in roster, add it!
- child_iterA = self._get_account_iter(account, self.model)
- child_iterG = self.model.append(child_iterA,
- [gajim.interface.jabber_state_images['16']['closed'],
- gobject.markup_escape_text(group),
- 'group', group, account, None, None, None, None, None, None])
- self.draw_group(group, account)
-
- if contact.is_transport():
- typestr = 'agent'
- elif contact.is_groupchat():
- typestr = 'groupchat'
- else:
- typestr = 'contact'
-
- # we add some values here. see draw_contact
- # for more
- i_ = self.model.append(child_iterG, (None,
- contact.get_shown_name(), typestr,
- contact.jid, account, None, None, None,
- None, None, None))
- added_iters.append(i_)
-
- # Restore the group expand state
- if account + group in self.collapsed_rows:
- is_expanded = False
- else:
- is_expanded = True
- if group not in gajim.groups[account]:
- gajim.groups[account][group] = {'expand': is_expanded}
-
- assert len(added_iters), '%s has not been added to roster!' % contact.jid
- return added_iters
-
- def _remove_entity(self, contact, account, groups=None):
- """
- Remove the given contact from roster data model
-
- Empty groups after contact removal are removed too.
- Return False if contact still has children and deletion was
- not performed.
- Return True on success.
-
- Keyword arguments:
- contact -- the contact to add
- account -- the contacts account
- groups -- list of groups to remove the contact from.
- """
- iters = self._get_contact_iter(contact.jid, account, contact, self.model)
- assert iters, '%s shall be removed but is not in roster' % contact.jid
-
- parent_iter = self.model.iter_parent(iters[0])
- parent_type = self.model[parent_iter][C_TYPE]
-
- if groups:
- # Only remove from specified groups
- all_iters = iters[:]
- group_iters = [self._get_group_iter(group, account)
- for group in groups]
- iters = [titer for titer in all_iters
- if self.model.iter_parent(titer) in group_iters]
-
- iter_children = self.model.iter_children(iters[0])
-
- if iter_children:
- # We have children. We cannot be removed!
- return False
- else:
- # Remove us and empty groups from the model
- for i in iters:
- assert self.model[i][C_JID] == contact.jid and \
- self.model[i][C_ACCOUNT] == account, \
- "Invalidated iters of %s" % contact.jid
-
- parent_i = self.model.iter_parent(i)
-
- if parent_type == 'group' and \
- self.model.iter_n_children(parent_i) == 1:
- group = self.model[parent_i][C_JID].decode('utf-8')
- if group in gajim.groups[account]:
- del gajim.groups[account][group]
- self.model.remove(parent_i)
- else:
- self.model.remove(i)
- return True
-
- def _add_metacontact_family(self, family, account):
- """
- Add the give Metacontact family to roster data model
-
- Add Big Brother to his groups and all others under him.
- Return list of all added (contact, account) tuples with
- Big Brother as first element.
-
- Keyword arguments:
- family -- the family, see Contacts.get_metacontacts_family()
- """
-
- nearby_family, big_brother_jid, big_brother_account = \
- self._get_nearby_family_and_big_brother(family, account)
- big_brother_contact = gajim.contacts.get_first_contact_from_jid(
- big_brother_account, big_brother_jid)
-
- assert len(self._get_contact_iter(big_brother_jid,
- big_brother_account, big_brother_contact, self.model)) == 0, \
- 'Big brother %s already in roster\n Family: %s' \
- % (big_brother_jid, family)
- self._add_entity(big_brother_contact, big_brother_account)
-
- brothers = []
- # Filter family members
- for data in nearby_family:
- _account = data['account']
- _jid = data['jid']
- _contact = gajim.contacts.get_first_contact_from_jid(
- _account, _jid)
-
- if not _contact or _contact == big_brother_contact:
- # Corresponding account is not connected
- # or brother already added
- continue
-
- assert len(self._get_contact_iter(_jid, _account,
- _contact, self.model)) == 0, \
- "%s already in roster.\n Family: %s" % (_jid, nearby_family)
- self._add_entity(_contact, _account,
- big_brother_contact = big_brother_contact,
- big_brother_account = big_brother_account)
- brothers.append((_contact, _account))
-
- brothers.insert(0, (big_brother_contact, big_brother_account))
- return brothers
-
- def _remove_metacontact_family(self, family, account):
- """
- Remove the given Metacontact family from roster data model
-
- See Contacts.get_metacontacts_family() and
- RosterWindow._remove_entity()
- """
- nearby_family = self._get_nearby_family_and_big_brother(
- family, account)[0]
-
- # Family might has changed (actual big brother not on top).
- # Remove childs first then big brother
- family_in_roster = False
- for data in nearby_family:
- _account = data['account']
- _jid = data['jid']
- _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
-
- iters = self._get_contact_iter(_jid, _account, _contact, self.model)
- if not iters or not _contact:
- # Family might not be up to date.
- # Only try to remove what is actually in the roster
- continue
- assert iters, '%s shall be removed but is not in roster \
- \n Family: %s' % (_jid, family)
-
- family_in_roster = True
-
- parent_iter = self.model.iter_parent(iters[0])
- parent_type = self.model[parent_iter][C_TYPE]
-
- if parent_type != 'contact':
- # The contact on top
- old_big_account = _account
- old_big_contact = _contact
- old_big_jid = _jid
- continue
-
- ok = self._remove_entity(_contact, _account)
- assert ok, '%s was not removed' % _jid
- assert len(self._get_contact_iter(_jid, _account, _contact,
- self.model)) == 0, '%s is removed but still in roster' % _jid
-
- if not family_in_roster:
- return False
-
- assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \
- (nearby_family, family)
- iters = self._get_contact_iter(old_big_jid, old_big_account,
- old_big_contact, self.model)
- assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
- old_big_jid
- assert not self.model.iter_children(iters[0]),\
- 'Old Big Brother %s still has children' % old_big_jid
-
- ok = self._remove_entity(old_big_contact, old_big_account)
- assert ok, "Old Big Brother %s not removed" % old_big_jid
- assert len(self._get_contact_iter(old_big_jid, old_big_account,
- old_big_contact, self.model)) == 0,\
- 'Old Big Brother %s is removed but still in roster' % old_big_jid
-
- return True
-
-
- def _recalibrate_metacontact_family(self, family, account):
- """
- Regroup metacontact family if necessary
- """
-
- brothers = []
- nearby_family, big_brother_jid, big_brother_account = \
- self._get_nearby_family_and_big_brother(family, account)
- big_brother_contact = gajim.contacts.get_contact(big_brother_account,
- big_brother_jid)
- child_iters = self._get_contact_iter(big_brother_jid, big_brother_account,
- model=self.model)
- if child_iters:
- parent_iter = self.model.iter_parent(child_iters[0])
- parent_type = self.model[parent_iter][C_TYPE]
-
- # Check if the current BigBrother has even been before.
- if parent_type == 'contact':
- for data in nearby_family:
- # recalibrate after remove to keep highlight
- if data['jid'] in gajim.to_be_removed[data['account']]:
- return
-
- self._remove_metacontact_family(family, account)
- brothers = self._add_metacontact_family(family, account)
-
- for c, acc in brothers:
- self.draw_completely(c.jid, acc)
-
- # Check is small brothers are under the big brother
- for child in nearby_family:
- _jid = child['jid']
- _account = child['account']
- if _account == big_brother_account and _jid == big_brother_jid:
- continue
- child_iters = self._get_contact_iter(_jid, _account, model=self.model)
- if not child_iters:
- continue
- parent_iter = self.model.iter_parent(child_iters[0])
- parent_type = self.model[parent_iter][C_TYPE]
- if parent_type != 'contact':
- _contact = gajim.contacts.get_contact(_account, _jid)
- self._remove_entity(_contact, _account)
- self._add_entity(_contact, _account, groups=None,
- big_brother_contact=big_brother_contact,
- big_brother_account=big_brother_account)
-
- def _get_nearby_family_and_big_brother(self, family, account):
- return gajim.contacts.get_nearby_family_and_big_brother(family, account)
-
- def _add_self_contact(self, account):
- """
- Add account's SelfContact to roster and draw it and the account
-
- Return the SelfContact contact instance
- """
- jid = gajim.get_jid_from_account(account)
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
-
- assert len(self._get_contact_iter(jid, account, contact, self.model)) == \
- 0, 'Self contact %s already in roster' % jid
-
- child_iterA = self._get_account_iter(account, self.model)
- self.model.append(child_iterA, (None, gajim.nicks[account],
- 'self_contact', jid, account, None, None, None, None,
- None, None))
-
- self.draw_completely(jid, account)
- self.draw_account(account)
-
- return contact
-
- def redraw_metacontacts(self, account):
- for family in gajim.contacts.iter_metacontacts_families(account):
- self._recalibrate_metacontact_family(family, account)
-
- def add_contact(self, jid, account):
- """
- Add contact to roster and draw him
-
- Add contact to all its group and redraw the groups, the contact and the
- account. If it's a Metacontact, add and draw the whole family.
- Do nothing if the contact is already in roster.
-
- Return the added contact instance. If it is a Metacontact return
- Big Brother.
-
- Keyword arguments:
- jid -- the contact's jid or SelfJid to add SelfContact
- account -- the corresponding account.
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if len(self._get_contact_iter(jid, account, contact, self.model)):
- # If contact already in roster, do nothing
- return
-
- if jid == gajim.get_jid_from_account(account):
- show_self_contact = gajim.config.get('show_self_contact')
- if show_self_contact == 'never':
- return
- if (contact.resource != gajim.connections[account].server_resource and\
- show_self_contact == 'when_other_resource') or show_self_contact == \
- 'always':
- return self._add_self_contact(account)
- return
-
- is_observer = contact.is_observer()
- if is_observer:
- # if he has a tag, remove it
- gajim.contacts.remove_metacontact(account, jid)
-
- # Add contact to roster
- family = gajim.contacts.get_metacontacts_family(account, jid)
- contacts = []
- if family:
- # We have a family. So we are a metacontact.
- # Add all family members that we shall be grouped with
- if self.regroup:
- # remove existing family members to regroup them
- self._remove_metacontact_family(family, account)
- contacts = self._add_metacontact_family(family, account)
- else:
- # We are a normal contact
- contacts = [(contact, account),]
- self._add_entity(contact, account)
-
- # Draw the contact and its groups contact
- if not self.starting:
- for c, acc in contacts:
- self.draw_completely(c.jid, acc)
- for group in contact.get_shown_groups():
- self.draw_group(group, account)
- self._adjust_group_expand_collapse_state(group, account)
- self.draw_account(account)
-
- return contacts[0][0] # it's contact/big brother with highest priority
-
- def remove_contact(self, jid, account, force=False, backend=False):
- """
- Remove contact from roster
-
- Remove contact from all its group. Remove empty groups or redraw
- otherwise.
- Draw the account.
- If it's a Metacontact, remove the whole family.
- Do nothing if the contact is not in roster.
-
- Keyword arguments:
- jid -- the contact's jid or SelfJid to remove SelfContact
- account -- the corresponding account.
- force -- remove contact even it has pending evens (Default False)
- backend -- also remove contact instance (Default False)
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if not contact:
- return
-
- if not force and (self.contact_has_pending_roster_events(contact,
- account) or gajim.interface.msg_win_mgr.get_control(jid, account)):
- # Contact has pending events or window
- #TODO: or single message windows? Bur they are not listed for the
- # moment
- key = (jid, account)
- if not key in self.contacts_to_be_removed:
- self.contacts_to_be_removed[key] = {'backend': backend}
- # if more pending event, don't remove from roster
- if self.contact_has_pending_roster_events(contact, account):
- return False
-
- iters = self._get_contact_iter(jid, account, contact, self.model)
- if iters:
- # no more pending events
- # Remove contact from roster directly
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # We have a family. So we are a metacontact.
- self._remove_metacontact_family(family, account)
- else:
- self._remove_entity(contact, account)
-
- if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\
- or force):
- # If a window is still opened: don't remove contact instance
- # Remove contact before redrawing, otherwise the old
- # numbers will still be show
- gajim.contacts.remove_jid(account, jid, remove_meta=True)
- if iters:
- rest_of_family = [data for data in family
- if account != data['account'] or jid != data['jid']]
- if rest_of_family:
- # reshow the rest of the family
- brothers = self._add_metacontact_family(rest_of_family, account)
- for c, acc in brothers:
- self.draw_completely(c.jid, acc)
-
- if iters:
- # Draw all groups of the contact
- for group in contact.get_shown_groups():
- self.draw_group(group, account)
- self.draw_account(account)
-
- return True
-
- def rename_self_contact(self, old_jid, new_jid, account):
- """
- Rename the self_contact jid
-
- Keyword arguments:
- old_jid -- our old jid
- new_jid -- our new jid
- account -- the corresponding account.
- """
- gajim.contacts.change_contact_jid(old_jid, new_jid, account)
- self_iter = self._get_self_contact_iter(account, model=self.model)
- if not self_iter:
- return
- self.model[self_iter][C_JID] = new_jid
- self.draw_contact(new_jid, account)
-
- def add_groupchat(self, jid, account, status=''):
- """
- Add groupchat to roster and draw it. Return the added contact instance
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- # Do not show gc if we are disconnected and minimize it
- if gajim.account_is_connected(account):
- show = 'online'
- else:
- show = 'offline'
- status = ''
-
- if contact is None:
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
- if gc_control:
- # there is a window that we can minimize
- gajim.interface.minimized_controls[account][jid] = gc_control
- name = gc_control.name
- elif jid in gajim.interface.minimized_controls[account]:
- name = gajim.interface.minimized_controls[account][jid].name
- else:
- name = jid.split('@')[0]
- # New groupchat
- #GCMIN
- contact = gajim.contacts.create_contact(jid=jid, account=account, name=name,
- groups=[_('Groupchats')], show=show, status=status, sub='none')
- gajim.contacts.add_contact(account, contact)
- self.add_contact(jid, account)
- else:
- if jid not in gajim.interface.minimized_controls[account]:
- # there is a window that we can minimize
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
- account)
- gajim.interface.minimized_controls[account][jid] = gc_control
- contact.show = show
- contact.status = status
- self.adjust_and_draw_contact_context(jid, account)
-
- return contact
-
-
- def remove_groupchat(self, jid, account):
- """
- Remove groupchat from roster and redraw account and group
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if contact.is_groupchat():
- if jid in gajim.interface.minimized_controls[account]:
- del gajim.interface.minimized_controls[account][jid]
- self.remove_contact(jid, account, force=True, backend=True)
- return True
- else:
- return False
-
-
- # FIXME: This function is yet unused! Port to new API
- def add_transport(self, jid, account):
- """
- Add transport to roster and draw it. Return the added contact instance
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if contact is None:
- #TRANSP
- contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid,
- groups=[_('Transports')], show='offline', status='offline',
- sub='from')
- gajim.contacts.add_contact(account, contact)
- self.add_contact(jid, account)
- return contact
-
- def remove_transport(self, jid, account):
- """
- Remove transport from roster and redraw account and group
- """
- self.remove_contact(jid, account, force=True, backend=True)
- return True
-
- def rename_group(self, old_name, new_name, account):
- """
- Rename a roster group
- """
- if old_name == new_name:
- return
-
- # Groups may not change name from or to a special groups
- for g in helpers.special_groups:
- if g in (new_name, old_name):
- return
-
- # update all contacts in the given group
- if self.regroup:
- accounts = gajim.connections.keys()
- else:
- accounts = [account,]
-
- for acc in accounts:
- changed_contacts = []
- for jid in gajim.contacts.get_jid_list(acc):
- contact = gajim.contacts.get_first_contact_from_jid(acc, jid)
- if old_name not in contact.groups:
- continue
-
- self.remove_contact(jid, acc, force=True)
-
- contact.groups.remove(old_name)
- if new_name not in contact.groups:
- contact.groups.append(new_name)
-
- changed_contacts.append({'jid':jid, 'name':contact.name,
- 'groups':contact.groups})
-
- gajim.connections[acc].update_contacts(changed_contacts)
-
- for c in changed_contacts:
- self.add_contact(c['jid'], acc)
-
- self._adjust_group_expand_collapse_state(new_name, acc)
-
- self.draw_group(old_name, acc)
- self.draw_group(new_name, acc)
-
-
- def add_contact_to_groups(self, jid, account, groups, update=True):
- """
- Add contact to given groups and redraw them
-
- Contact on server is updated too. When the contact has a family,
- the action will be performed for all members.
-
- Keyword Arguments:
- jid -- the jid
- account -- the corresponding account
- groups -- list of Groups to add the contact to.
- update -- update contact on the server
- """
- self.remove_contact(jid, account, force=True)
- for contact in gajim.contacts.get_contacts(account, jid):
- for group in groups:
- if group not in contact.groups:
- # we might be dropped from meta to group
- contact.groups.append(group)
- if update:
- gajim.connections[account].update_contact(jid, contact.name,
- contact.groups)
-
- self.add_contact(jid, account)
-
- for group in groups:
- self._adjust_group_expand_collapse_state(group, account)
-
- def remove_contact_from_groups(self, jid, account, groups, update=True):
- """
- Remove contact from given groups and redraw them
-
- Contact on server is updated too. When the contact has a family,
- the action will be performed for all members.
-
- Keyword Arguments:
- jid -- the jid
- account -- the corresponding account
- groups -- list of Groups to remove the contact from
- update -- update contact on the server
- """
- self.remove_contact(jid, account, force=True)
- for contact in gajim.contacts.get_contacts(account, jid):
- for group in groups:
- if group in contact.groups:
- # Needed when we remove from "General" or "Observers"
- contact.groups.remove(group)
- if update:
- gajim.connections[account].update_contact(jid, contact.name,
- contact.groups)
- self.add_contact(jid, account)
-
- # Also redraw old groups
- for group in groups:
- self.draw_group(group, account)
-
- # FIXME: maybe move to gajim.py
- def remove_newly_added(self, jid, account):
- if jid in gajim.newly_added[account]:
- gajim.newly_added[account].remove(jid)
- self.draw_contact(jid, account)
-
- # FIXME: maybe move to gajim.py
- def remove_to_be_removed(self, jid, account):
- if account not in gajim.interface.instances:
- # Account has been deleted during the timeout that called us
- return
- if jid in gajim.newly_added[account]:
- return
- if jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].remove(jid)
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # Peform delayed recalibration
- self._recalibrate_metacontact_family(family, account)
- self.draw_contact(jid, account)
-
- # FIXME: integrate into add_contact()
- def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
- contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
- account=account, resource=resource, name=nick, keyID=keyID)
- gajim.contacts.add_contact(account, contact)
- self.add_contact(contact.jid, account)
- return contact
+ def add_account(self, account):
+ """
+ Add account to roster and draw it. Do nothing if it is already in
+ """
+ if self._get_account_iter(account):
+ # Will happen on reconnect or for merged accounts
+ return
+
+ if self.regroup:
+ # Merged accounts view
+ show = helpers.get_global_show()
+ self.model.append(None, [
+ gajim.interface.jabber_state_images['16'][show],
+ _('Merged accounts'), 'account', '', 'all',
+ None, None, None, None, None, None])
+ else:
+ show = gajim.SHOW_LIST[gajim.connections[account].connected]
+ our_jid = gajim.get_jid_from_account(account)
+
+ tls_pixbuf = None
+ if gajim.account_is_securely_connected(account):
+ # the only way to create a pixbuf from stock
+ tls_pixbuf = self.window.render_icon(
+ gtk.STOCK_DIALOG_AUTHENTICATION,
+ gtk.ICON_SIZE_MENU)
+
+ self.model.append(None, [
+ gajim.interface.jabber_state_images['16'][show],
+ gobject.markup_escape_text(account), 'account',
+ our_jid, account, None, None, None, None, None,
+ tls_pixbuf])
+
+ self.draw_account(account)
+
+
+ def add_account_contacts(self, account):
+ """
+ Add all contacts and groups of the given account to roster, draw them and
+ account
+ """
+ self.starting = True
+ jids = gajim.contacts.get_jid_list(account)
+
+ self.tree.freeze_child_notify()
+ for jid in jids:
+ self.add_contact(jid, account)
+
+ # Do not freeze the GUI when drawing the contacts
+ if jids:
+ # Overhead is big, only invoke when needed
+ self._idle_draw_jids_of_account(jids, account)
+
+ # Draw all known groups
+ for group in gajim.groups[account]:
+ self.draw_group(group, account)
+ self.draw_account(account)
+
+ self.tree.thaw_child_notify()
+ self.starting = False
+
+
+ def _add_entity(self, contact, account, groups=None,
+ big_brother_contact=None, big_brother_account=None):
+ """
+ Add the given contact to roster data model
+
+ Contact is added regardless if he is already in roster or not. Return
+ list of newly added iters.
+
+ Keyword arguments:
+ contact -- the contact to add
+ account -- the contacts account
+ groups -- list of groups to add the contact to.
+ (default groups in contact.get_shown_groups()).
+ Parameter ignored when big_brother_contact is specified.
+ big_brother_contact -- if specified contact is added as child
+ big_brother_contact. (default None)
+ """
+ added_iters = []
+ if big_brother_contact:
+ # Add contact under big brother
+
+ parent_iters = self._get_contact_iter(
+ big_brother_contact.jid, big_brother_account,
+ big_brother_contact, self.model)
+ assert len(parent_iters) > 0, 'Big brother is not yet in roster!'
+
+ # Do not confuse get_contact_iter: Sync groups of family members
+ contact.groups = big_brother_contact.get_shown_groups()[:]
+
+ for child_iter in parent_iters:
+ it = self.model.append(child_iter, (None, contact.get_shown_name(),
+ 'contact', contact.jid, account, None, None, None, None, None,
+ None))
+ added_iters.append(it)
+ else:
+ # We are a normal contact. Add us to our groups.
+ if not groups:
+ groups = contact.get_shown_groups()
+ for group in groups:
+ child_iterG = self._get_group_iter(group, account,
+ model = self.model)
+ if not child_iterG:
+ # Group is not yet in roster, add it!
+ child_iterA = self._get_account_iter(account, self.model)
+ child_iterG = self.model.append(child_iterA,
+ [gajim.interface.jabber_state_images['16']['closed'],
+ gobject.markup_escape_text(group),
+ 'group', group, account, None, None, None, None, None, None])
+ self.draw_group(group, account)
+
+ if contact.is_transport():
+ typestr = 'agent'
+ elif contact.is_groupchat():
+ typestr = 'groupchat'
+ else:
+ typestr = 'contact'
+
+ # we add some values here. see draw_contact
+ # for more
+ i_ = self.model.append(child_iterG, (None,
+ contact.get_shown_name(), typestr,
+ contact.jid, account, None, None, None,
+ None, None, None))
+ added_iters.append(i_)
+
+ # Restore the group expand state
+ if account + group in self.collapsed_rows:
+ is_expanded = False
+ else:
+ is_expanded = True
+ if group not in gajim.groups[account]:
+ gajim.groups[account][group] = {'expand': is_expanded}
+
+ assert len(added_iters), '%s has not been added to roster!' % contact.jid
+ return added_iters
+
+ def _remove_entity(self, contact, account, groups=None):
+ """
+ Remove the given contact from roster data model
+
+ Empty groups after contact removal are removed too.
+ Return False if contact still has children and deletion was
+ not performed.
+ Return True on success.
+
+ Keyword arguments:
+ contact -- the contact to add
+ account -- the contacts account
+ groups -- list of groups to remove the contact from.
+ """
+ iters = self._get_contact_iter(contact.jid, account, contact, self.model)
+ assert iters, '%s shall be removed but is not in roster' % contact.jid
+
+ parent_iter = self.model.iter_parent(iters[0])
+ parent_type = self.model[parent_iter][C_TYPE]
+
+ if groups:
+ # Only remove from specified groups
+ all_iters = iters[:]
+ group_iters = [self._get_group_iter(group, account)
+ for group in groups]
+ iters = [titer for titer in all_iters
+ if self.model.iter_parent(titer) in group_iters]
+
+ iter_children = self.model.iter_children(iters[0])
+
+ if iter_children:
+ # We have children. We cannot be removed!
+ return False
+ else:
+ # Remove us and empty groups from the model
+ for i in iters:
+ assert self.model[i][C_JID] == contact.jid and \
+ self.model[i][C_ACCOUNT] == account, \
+ "Invalidated iters of %s" % contact.jid
+
+ parent_i = self.model.iter_parent(i)
+
+ if parent_type == 'group' and \
+ self.model.iter_n_children(parent_i) == 1:
+ group = self.model[parent_i][C_JID].decode('utf-8')
+ if group in gajim.groups[account]:
+ del gajim.groups[account][group]
+ self.model.remove(parent_i)
+ else:
+ self.model.remove(i)
+ return True
+
+ def _add_metacontact_family(self, family, account):
+ """
+ Add the give Metacontact family to roster data model
+
+ Add Big Brother to his groups and all others under him.
+ Return list of all added (contact, account) tuples with
+ Big Brother as first element.
+
+ Keyword arguments:
+ family -- the family, see Contacts.get_metacontacts_family()
+ """
+
+ nearby_family, big_brother_jid, big_brother_account = \
+ self._get_nearby_family_and_big_brother(family, account)
+ big_brother_contact = gajim.contacts.get_first_contact_from_jid(
+ big_brother_account, big_brother_jid)
+
+ assert len(self._get_contact_iter(big_brother_jid,
+ big_brother_account, big_brother_contact, self.model)) == 0, \
+ 'Big brother %s already in roster\n Family: %s' \
+ % (big_brother_jid, family)
+ self._add_entity(big_brother_contact, big_brother_account)
+
+ brothers = []
+ # Filter family members
+ for data in nearby_family:
+ _account = data['account']
+ _jid = data['jid']
+ _contact = gajim.contacts.get_first_contact_from_jid(
+ _account, _jid)
+
+ if not _contact or _contact == big_brother_contact:
+ # Corresponding account is not connected
+ # or brother already added
+ continue
+
+ assert len(self._get_contact_iter(_jid, _account,
+ _contact, self.model)) == 0, \
+ "%s already in roster.\n Family: %s" % (_jid, nearby_family)
+ self._add_entity(_contact, _account,
+ big_brother_contact = big_brother_contact,
+ big_brother_account = big_brother_account)
+ brothers.append((_contact, _account))
+
+ brothers.insert(0, (big_brother_contact, big_brother_account))
+ return brothers
+
+ def _remove_metacontact_family(self, family, account):
+ """
+ Remove the given Metacontact family from roster data model
+
+ See Contacts.get_metacontacts_family() and
+ RosterWindow._remove_entity()
+ """
+ nearby_family = self._get_nearby_family_and_big_brother(
+ family, account)[0]
+
+ # Family might has changed (actual big brother not on top).
+ # Remove childs first then big brother
+ family_in_roster = False
+ for data in nearby_family:
+ _account = data['account']
+ _jid = data['jid']
+ _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
+
+ iters = self._get_contact_iter(_jid, _account, _contact, self.model)
+ if not iters or not _contact:
+ # Family might not be up to date.
+ # Only try to remove what is actually in the roster
+ continue
+ assert iters, '%s shall be removed but is not in roster \
+ \n Family: %s' % (_jid, family)
+
+ family_in_roster = True
+
+ parent_iter = self.model.iter_parent(iters[0])
+ parent_type = self.model[parent_iter][C_TYPE]
+
+ if parent_type != 'contact':
+ # The contact on top
+ old_big_account = _account
+ old_big_contact = _contact
+ old_big_jid = _jid
+ continue
+
+ ok = self._remove_entity(_contact, _account)
+ assert ok, '%s was not removed' % _jid
+ assert len(self._get_contact_iter(_jid, _account, _contact,
+ self.model)) == 0, '%s is removed but still in roster' % _jid
+
+ if not family_in_roster:
+ return False
+
+ assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \
+ (nearby_family, family)
+ iters = self._get_contact_iter(old_big_jid, old_big_account,
+ old_big_contact, self.model)
+ assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
+ old_big_jid
+ assert not self.model.iter_children(iters[0]),\
+ 'Old Big Brother %s still has children' % old_big_jid
+
+ ok = self._remove_entity(old_big_contact, old_big_account)
+ assert ok, "Old Big Brother %s not removed" % old_big_jid
+ assert len(self._get_contact_iter(old_big_jid, old_big_account,
+ old_big_contact, self.model)) == 0,\
+ 'Old Big Brother %s is removed but still in roster' % old_big_jid
+
+ return True
+
+
+ def _recalibrate_metacontact_family(self, family, account):
+ """
+ Regroup metacontact family if necessary
+ """
+
+ brothers = []
+ nearby_family, big_brother_jid, big_brother_account = \
+ self._get_nearby_family_and_big_brother(family, account)
+ big_brother_contact = gajim.contacts.get_contact(big_brother_account,
+ big_brother_jid)
+ child_iters = self._get_contact_iter(big_brother_jid, big_brother_account,
+ model=self.model)
+ if child_iters:
+ parent_iter = self.model.iter_parent(child_iters[0])
+ parent_type = self.model[parent_iter][C_TYPE]
+
+ # Check if the current BigBrother has even been before.
+ if parent_type == 'contact':
+ for data in nearby_family:
+ # recalibrate after remove to keep highlight
+ if data['jid'] in gajim.to_be_removed[data['account']]:
+ return
+
+ self._remove_metacontact_family(family, account)
+ brothers = self._add_metacontact_family(family, account)
+
+ for c, acc in brothers:
+ self.draw_completely(c.jid, acc)
+
+ # Check is small brothers are under the big brother
+ for child in nearby_family:
+ _jid = child['jid']
+ _account = child['account']
+ if _account == big_brother_account and _jid == big_brother_jid:
+ continue
+ child_iters = self._get_contact_iter(_jid, _account, model=self.model)
+ if not child_iters:
+ continue
+ parent_iter = self.model.iter_parent(child_iters[0])
+ parent_type = self.model[parent_iter][C_TYPE]
+ if parent_type != 'contact':
+ _contact = gajim.contacts.get_contact(_account, _jid)
+ self._remove_entity(_contact, _account)
+ self._add_entity(_contact, _account, groups=None,
+ big_brother_contact=big_brother_contact,
+ big_brother_account=big_brother_account)
+
+ def _get_nearby_family_and_big_brother(self, family, account):
+ return gajim.contacts.get_nearby_family_and_big_brother(family, account)
+
+ def _add_self_contact(self, account):
+ """
+ Add account's SelfContact to roster and draw it and the account
+
+ Return the SelfContact contact instance
+ """
+ jid = gajim.get_jid_from_account(account)
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+
+ assert len(self._get_contact_iter(jid, account, contact, self.model)) == \
+ 0, 'Self contact %s already in roster' % jid
+
+ child_iterA = self._get_account_iter(account, self.model)
+ self.model.append(child_iterA, (None, gajim.nicks[account],
+ 'self_contact', jid, account, None, None, None, None,
+ None, None))
+
+ self.draw_completely(jid, account)
+ self.draw_account(account)
+
+ return contact
+
+ def redraw_metacontacts(self, account):
+ for family in gajim.contacts.iter_metacontacts_families(account):
+ self._recalibrate_metacontact_family(family, account)
+
+ def add_contact(self, jid, account):
+ """
+ Add contact to roster and draw him
+
+ Add contact to all its group and redraw the groups, the contact and the
+ account. If it's a Metacontact, add and draw the whole family.
+ Do nothing if the contact is already in roster.
+
+ Return the added contact instance. If it is a Metacontact return
+ Big Brother.
+
+ Keyword arguments:
+ jid -- the contact's jid or SelfJid to add SelfContact
+ account -- the corresponding account.
+ """
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ if len(self._get_contact_iter(jid, account, contact, self.model)):
+ # If contact already in roster, do nothing
+ return
+
+ if jid == gajim.get_jid_from_account(account):
+ show_self_contact = gajim.config.get('show_self_contact')
+ if show_self_contact == 'never':
+ return
+ if (contact.resource != gajim.connections[account].server_resource and\
+ show_self_contact == 'when_other_resource') or show_self_contact == \
+ 'always':
+ return self._add_self_contact(account)
+ return
+
+ is_observer = contact.is_observer()
+ if is_observer:
+ # if he has a tag, remove it
+ gajim.contacts.remove_metacontact(account, jid)
+
+ # Add contact to roster
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ contacts = []
+ if family:
+ # We have a family. So we are a metacontact.
+ # Add all family members that we shall be grouped with
+ if self.regroup:
+ # remove existing family members to regroup them
+ self._remove_metacontact_family(family, account)
+ contacts = self._add_metacontact_family(family, account)
+ else:
+ # We are a normal contact
+ contacts = [(contact, account),]
+ self._add_entity(contact, account)
+
+ # Draw the contact and its groups contact
+ if not self.starting:
+ for c, acc in contacts:
+ self.draw_completely(c.jid, acc)
+ for group in contact.get_shown_groups():
+ self.draw_group(group, account)
+ self._adjust_group_expand_collapse_state(group, account)
+ self.draw_account(account)
+
+ return contacts[0][0] # it's contact/big brother with highest priority
+
+ def remove_contact(self, jid, account, force=False, backend=False):
+ """
+ Remove contact from roster
+
+ Remove contact from all its group. Remove empty groups or redraw
+ otherwise.
+ Draw the account.
+ If it's a Metacontact, remove the whole family.
+ Do nothing if the contact is not in roster.
+
+ Keyword arguments:
+ jid -- the contact's jid or SelfJid to remove SelfContact
+ account -- the corresponding account.
+ force -- remove contact even it has pending evens (Default False)
+ backend -- also remove contact instance (Default False)
+ """
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ if not contact:
+ return
+
+ if not force and (self.contact_has_pending_roster_events(contact,
+ account) or gajim.interface.msg_win_mgr.get_control(jid, account)):
+ # Contact has pending events or window
+ #TODO: or single message windows? Bur they are not listed for the
+ # moment
+ key = (jid, account)
+ if not key in self.contacts_to_be_removed:
+ self.contacts_to_be_removed[key] = {'backend': backend}
+ # if more pending event, don't remove from roster
+ if self.contact_has_pending_roster_events(contact, account):
+ return False
+
+ iters = self._get_contact_iter(jid, account, contact, self.model)
+ if iters:
+ # no more pending events
+ # Remove contact from roster directly
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ # We have a family. So we are a metacontact.
+ self._remove_metacontact_family(family, account)
+ else:
+ self._remove_entity(contact, account)
+
+ if backend and (not gajim.interface.msg_win_mgr.get_control(jid, account)\
+ or force):
+ # If a window is still opened: don't remove contact instance
+ # Remove contact before redrawing, otherwise the old
+ # numbers will still be show
+ gajim.contacts.remove_jid(account, jid, remove_meta=True)
+ if iters:
+ rest_of_family = [data for data in family
+ if account != data['account'] or jid != data['jid']]
+ if rest_of_family:
+ # reshow the rest of the family
+ brothers = self._add_metacontact_family(rest_of_family, account)
+ for c, acc in brothers:
+ self.draw_completely(c.jid, acc)
+
+ if iters:
+ # Draw all groups of the contact
+ for group in contact.get_shown_groups():
+ self.draw_group(group, account)
+ self.draw_account(account)
+
+ return True
+
+ def rename_self_contact(self, old_jid, new_jid, account):
+ """
+ Rename the self_contact jid
+
+ Keyword arguments:
+ old_jid -- our old jid
+ new_jid -- our new jid
+ account -- the corresponding account.
+ """
+ gajim.contacts.change_contact_jid(old_jid, new_jid, account)
+ self_iter = self._get_self_contact_iter(account, model=self.model)
+ if not self_iter:
+ return
+ self.model[self_iter][C_JID] = new_jid
+ self.draw_contact(new_jid, account)
+
+ def add_groupchat(self, jid, account, status=''):
+ """
+ Add groupchat to roster and draw it. Return the added contact instance
+ """
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ # Do not show gc if we are disconnected and minimize it
+ if gajim.account_is_connected(account):
+ show = 'online'
+ else:
+ show = 'offline'
+ status = ''
+
+ if contact is None:
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
+ if gc_control:
+ # there is a window that we can minimize
+ gajim.interface.minimized_controls[account][jid] = gc_control
+ name = gc_control.name
+ elif jid in gajim.interface.minimized_controls[account]:
+ name = gajim.interface.minimized_controls[account][jid].name
+ else:
+ name = jid.split('@')[0]
+ # New groupchat
+ #GCMIN
+ contact = gajim.contacts.create_contact(jid=jid, account=account, name=name,
+ groups=[_('Groupchats')], show=show, status=status, sub='none')
+ gajim.contacts.add_contact(account, contact)
+ self.add_contact(jid, account)
+ else:
+ if jid not in gajim.interface.minimized_controls[account]:
+ # there is a window that we can minimize
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
+ account)
+ gajim.interface.minimized_controls[account][jid] = gc_control
+ contact.show = show
+ contact.status = status
+ self.adjust_and_draw_contact_context(jid, account)
+
+ return contact
+
+
+ def remove_groupchat(self, jid, account):
+ """
+ Remove groupchat from roster and redraw account and group
+ """
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ if contact.is_groupchat():
+ if jid in gajim.interface.minimized_controls[account]:
+ del gajim.interface.minimized_controls[account][jid]
+ self.remove_contact(jid, account, force=True, backend=True)
+ return True
+ else:
+ return False
+
+
+ # FIXME: This function is yet unused! Port to new API
+ def add_transport(self, jid, account):
+ """
+ Add transport to roster and draw it. Return the added contact instance
+ """
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ if contact is None:
+ #TRANSP
+ contact = gajim.contacts.create_contact(jid=jid, account=account, name=jid,
+ groups=[_('Transports')], show='offline', status='offline',
+ sub='from')
+ gajim.contacts.add_contact(account, contact)
+ self.add_contact(jid, account)
+ return contact
+
+ def remove_transport(self, jid, account):
+ """
+ Remove transport from roster and redraw account and group
+ """
+ self.remove_contact(jid, account, force=True, backend=True)
+ return True
+
+ def rename_group(self, old_name, new_name, account):
+ """
+ Rename a roster group
+ """
+ if old_name == new_name:
+ return
+
+ # Groups may not change name from or to a special groups
+ for g in helpers.special_groups:
+ if g in (new_name, old_name):
+ return
+
+ # update all contacts in the given group
+ if self.regroup:
+ accounts = gajim.connections.keys()
+ else:
+ accounts = [account,]
+
+ for acc in accounts:
+ changed_contacts = []
+ for jid in gajim.contacts.get_jid_list(acc):
+ contact = gajim.contacts.get_first_contact_from_jid(acc, jid)
+ if old_name not in contact.groups:
+ continue
+
+ self.remove_contact(jid, acc, force=True)
+
+ contact.groups.remove(old_name)
+ if new_name not in contact.groups:
+ contact.groups.append(new_name)
+
+ changed_contacts.append({'jid':jid, 'name':contact.name,
+ 'groups':contact.groups})
+
+ gajim.connections[acc].update_contacts(changed_contacts)
+
+ for c in changed_contacts:
+ self.add_contact(c['jid'], acc)
+
+ self._adjust_group_expand_collapse_state(new_name, acc)
+
+ self.draw_group(old_name, acc)
+ self.draw_group(new_name, acc)
+
+
+ def add_contact_to_groups(self, jid, account, groups, update=True):
+ """
+ Add contact to given groups and redraw them
+
+ Contact on server is updated too. When the contact has a family,
+ the action will be performed for all members.
+
+ Keyword Arguments:
+ jid -- the jid
+ account -- the corresponding account
+ groups -- list of Groups to add the contact to.
+ update -- update contact on the server
+ """
+ self.remove_contact(jid, account, force=True)
+ for contact in gajim.contacts.get_contacts(account, jid):
+ for group in groups:
+ if group not in contact.groups:
+ # we might be dropped from meta to group
+ contact.groups.append(group)
+ if update:
+ gajim.connections[account].update_contact(jid, contact.name,
+ contact.groups)
+
+ self.add_contact(jid, account)
+
+ for group in groups:
+ self._adjust_group_expand_collapse_state(group, account)
+
+ def remove_contact_from_groups(self, jid, account, groups, update=True):
+ """
+ Remove contact from given groups and redraw them
+
+ Contact on server is updated too. When the contact has a family,
+ the action will be performed for all members.
+
+ Keyword Arguments:
+ jid -- the jid
+ account -- the corresponding account
+ groups -- list of Groups to remove the contact from
+ update -- update contact on the server
+ """
+ self.remove_contact(jid, account, force=True)
+ for contact in gajim.contacts.get_contacts(account, jid):
+ for group in groups:
+ if group in contact.groups:
+ # Needed when we remove from "General" or "Observers"
+ contact.groups.remove(group)
+ if update:
+ gajim.connections[account].update_contact(jid, contact.name,
+ contact.groups)
+ self.add_contact(jid, account)
+
+ # Also redraw old groups
+ for group in groups:
+ self.draw_group(group, account)
+
+ # FIXME: maybe move to gajim.py
+ def remove_newly_added(self, jid, account):
+ if jid in gajim.newly_added[account]:
+ gajim.newly_added[account].remove(jid)
+ self.draw_contact(jid, account)
+
+ # FIXME: maybe move to gajim.py
+ def remove_to_be_removed(self, jid, account):
+ if account not in gajim.interface.instances:
+ # Account has been deleted during the timeout that called us
+ return
+ if jid in gajim.newly_added[account]:
+ return
+ if jid in gajim.to_be_removed[account]:
+ gajim.to_be_removed[account].remove(jid)
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ # Peform delayed recalibration
+ self._recalibrate_metacontact_family(family, account)
+ self.draw_contact(jid, account)
+
+ # FIXME: integrate into add_contact()
+ def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
+ keyID = ''
+ attached_keys = gajim.config.get_per('accounts', account,
+ 'attached_gpg_keys').split()
+ if jid in attached_keys:
+ keyID = attached_keys[attached_keys.index(jid) + 1]
+ contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
+ account=account, resource=resource, name=nick, keyID=keyID)
+ gajim.contacts.add_contact(account, contact)
+ self.add_contact(contact.jid, account)
+ return contact
################################################################################
### Methods for adding and removing roster window items
################################################################################
- def draw_account(self, account):
- child_iter = self._get_account_iter(account, self.model)
- if not child_iter:
- assert False, 'Account iter of %s could not be found.' % account
- return
-
- num_of_accounts = gajim.get_number_of_connected_accounts()
- num_of_secured = gajim.get_number_of_securely_connected_accounts()
-
- if gajim.account_is_securely_connected(account) and not self.regroup or \
- self.regroup and num_of_secured and num_of_secured == num_of_accounts:
- tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
- gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
- self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf
- else:
- self.model[child_iter][C_PADLOCK_PIXBUF] = None
-
- if self.regroup:
- account_name = _('Merged accounts')
- accounts = []
- else:
- account_name = account
- accounts = [account]
-
- if account in self.collapsed_rows and \
- self.model.iter_has_child(child_iter):
- account_name = '[%s]' % account_name
-
- if (gajim.account_is_connected(account) or (self.regroup and \
- gajim.get_number_of_connected_accounts())) and gajim.config.get(
- 'show_contacts_number'):
- nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
- accounts = accounts)
- account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
-
- self.model[child_iter][C_NAME] = account_name
-
- pep = gajim.connections[account].pep
- if gajim.config.get('show_mood_in_roster') and 'mood' in pep:
- self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon()
- else:
- self.model[child_iter][C_MOOD_PIXBUF] = None
-
- if gajim.config.get('show_activity_in_roster') and 'activity' in pep:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon()
- else:
- self.model[child_iter][C_ACTIVITY_PIXBUF] = None
-
- if gajim.config.get('show_tunes_in_roster') and 'tune' in pep:
- self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon()
- else:
- self.model[child_iter][C_TUNE_PIXBUF] = None
-
- if gajim.config.get('show_location_in_roster') and 'location' in pep:
- self.model[child_iter][C_LOCATION_PIXBUF] = pep['location'].asPixbufIcon()
- else:
- self.model[child_iter][C_LOCATION_PIXBUF] = None
- return False
-
- def draw_group(self, group, account):
- child_iter = self._get_group_iter(group, account, model=self.model)
- if not child_iter:
- # Eg. We redraw groups after we removed a entitiy
- # and its empty groups
- return
- if self.regroup:
- accounts = []
- else:
- accounts = [account]
- text = gobject.markup_escape_text(group)
- if helpers.group_is_blocked(account, group):
- text = '<span strikethrough="true">%s</span>' % text
- if gajim.config.get('show_contacts_number'):
- nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
- accounts = accounts, groups = [group])
- text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
-
- self.model[child_iter][C_NAME] = text
- return False
-
- def draw_parent_contact(self, jid, account):
- child_iters = self._get_contact_iter(jid, account, model=self.model)
- if not child_iters:
- return False
- parent_iter = self.model.iter_parent(child_iters[0])
- if self.model[parent_iter][C_TYPE] != 'contact':
- # parent is not a contact
- return
- parent_jid = self.model[parent_iter][C_JID].decode('utf-8')
- parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8')
- self.draw_contact(parent_jid, parent_account)
- return False
-
- def draw_contact(self, jid, account, selected=False, focus=False):
- """
- Draw the correct state image, name BUT not avatar
- """
- # focus is about if the roster window has toplevel-focus or not
- # FIXME: We really need a custom cell_renderer
-
- contact_instances = gajim.contacts.get_contacts(account, jid)
- contact = gajim.contacts.get_highest_prio_contact_from_contacts(
- contact_instances)
-
- child_iters = self._get_contact_iter(jid, account, contact, self.model)
- if not child_iters:
- return False
-
- name = gobject.markup_escape_text(contact.get_shown_name())
-
- # gets number of unread gc marked messages
- if jid in gajim.interface.minimized_controls[account] and \
- gajim.interface.minimized_controls[account][jid]:
- nb_unread = len(gajim.events.get_events(account, jid,
- ['printed_marked_gc_msg']))
- nb_unread += gajim.interface.minimized_controls \
- [account][jid].get_nb_unread_pm()
-
- if nb_unread == 1:
- name = '%s *' % name
- elif nb_unread > 1:
- name = '%s [%s]' % (name, str(nb_unread))
-
- # Strike name if blocked
- strike = False
- if helpers.jid_is_blocked(account, jid):
- strike = True
- else:
- for group in contact.get_shown_groups():
- if helpers.group_is_blocked(account, group):
- strike = True
- break
- if strike:
- name = '<span strikethrough="true">%s</span>' % name
-
- # Show resource counter
- nb_connected_contact = 0
- for c in contact_instances:
- if c.show not in ('error', 'offline'):
- nb_connected_contact += 1
- if nb_connected_contact > 1:
- # switch back to default writing direction
- name += i18n.paragraph_direction_mark(unicode(name))
- name += u' (%d)' % nb_connected_contact
-
- # show (account_name) if there are 2 contact with same jid
- # in merged mode
- if self.regroup:
- add_acct = False
- # look through all contacts of all accounts
- for account_ in gajim.connections:
- # useless to add account name
- if account_ == account:
- continue
- for jid_ in gajim.contacts.get_jid_list(account_):
- contact_ = gajim.contacts.get_first_contact_from_jid(
- account_, jid_)
- if contact_.get_shown_name() == contact.get_shown_name() and \
- (jid_, account_) != (jid, account):
- add_acct = True
- break
- if add_acct:
- # No need to continue in other account
- # if we already found one
- break
- if add_acct:
- name += ' (' + account + ')'
-
- # add status msg, if not empty, under contact name in
- # the treeview
- if contact.status and gajim.config.get('show_status_msgs_in_roster'):
- status = contact.status.strip()
- if status != '':
- status = helpers.reduce_chars_newlines(status,
- max_lines = 1)
- # escape markup entities and make them small
- # italic and fg color color is calcuted to be
- # always readable
- color = gtkgui_helpers._get_fade_color(self.tree, selected, focus)
- colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue)
- name += '\n<span size="small" style="italic" ' \
- 'foreground="%s">%s</span>' % (
- colorstring,
- gobject.markup_escape_text(status))
-
- icon_name = helpers.get_icon_name_to_show(contact, account)
- # look if another resource has awaiting events
- for c in contact_instances:
- c_icon_name = helpers.get_icon_name_to_show(c, account)
- if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
- icon_name = c_icon_name
- break
-
- # Check for events of collapsed (hidden) brothers
- family = gajim.contacts.get_metacontacts_family(account, jid)
- is_big_brother = False
- have_visible_children = False
- if family:
- bb_jid, bb_account = \
- self._get_nearby_family_and_big_brother(family, account)[1:]
- is_big_brother = (jid, account) == (bb_jid, bb_account)
- iters = self._get_contact_iter(jid, account)
- have_visible_children = iters \
- and self.modelfilter.iter_has_child(iters[0])
-
- if have_visible_children:
- # We are the big brother and have a visible family
- for child_iter in child_iters:
- child_path = self.model.get_path(child_iter)
- path = self.modelfilter.convert_child_path_to_path(child_path)
-
- if not path:
- continue
-
- if not self.tree.row_expanded(path) and icon_name != 'event':
- iterC = self.model.iter_children(child_iter)
- while iterC:
- # a child has awaiting messages?
- jidC = self.model[iterC][C_JID].decode('utf-8')
- accountC = self.model[iterC][C_ACCOUNT].decode('utf-8')
- if len(gajim.events.get_events(accountC, jidC)):
- icon_name = 'event'
- break
- iterC = self.model.iter_next(iterC)
-
- if self.tree.row_expanded(path):
- state_images = self.get_appropriate_state_images(
- jid, size = 'opened',
- icon_name = icon_name)
- else:
- state_images = self.get_appropriate_state_images(
- jid, size = 'closed',
- icon_name = icon_name)
-
- # Expand/collapse icon might differ per iter
- # (group)
- img = state_images[icon_name]
- self.model[child_iter][C_IMG] = img
- self.model[child_iter][C_NAME] = name
- else:
- # A normal contact or little brother
- state_images = self.get_appropriate_state_images(jid,
- icon_name = icon_name)
-
- # All iters have the same icon (no expand/collapse)
- img = state_images[icon_name]
- for child_iter in child_iters:
- self.model[child_iter][C_IMG] = img
- self.model[child_iter][C_NAME] = name
-
- # We are a little brother
- if family and not is_big_brother and not self.starting:
- self.draw_parent_contact(jid, account)
-
- for group in contact.get_shown_groups():
- # We need to make sure that _visible_func is called for
- # our groups otherwise we might not be shown
- iterG = self._get_group_iter(group, account, model=self.model)
- if iterG:
- # it's not self contact
- self.model[iterG][C_JID] = self.model[iterG][C_JID]
-
- return False
-
- def _is_pep_shown_in_roster(self, pep_type):
- if pep_type == 'mood':
- return gajim.config.get('show_mood_in_roster')
- elif pep_type == 'activity':
- return gajim.config.get('show_activity_in_roster')
- elif pep_type == 'tune':
- return gajim.config.get('show_tunes_in_roster')
- elif pep_type == 'location':
- return gajim.config.get('show_location_in_roster')
- else:
- return False
-
- def draw_all_pep_types(self, jid, account):
- for pep_type in self._pep_type_to_model_column:
- self.draw_pep(jid, account, pep_type)
-
- def draw_pep(self, jid, account, pep_type):
- if pep_type not in self._pep_type_to_model_column:
- return
- if not self._is_pep_shown_in_roster(pep_type):
- return
-
- model_column = self._pep_type_to_model_column[pep_type]
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters:
- return
- jid = self.model[iters[0]][C_JID].decode('utf-8')
- contact = gajim.contacts.get_contact(account, jid)
- if pep_type in contact.pep:
- pixbuf = contact.pep[pep_type].asPixbufIcon()
- else:
- pixbuf = None
- for child_iter in iters:
- self.model[child_iter][model_column] = pixbuf
-
- def draw_avatar(self, jid, account):
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_avatars_in_roster'):
- return
- jid = self.model[iters[0]][C_JID].decode('utf-8')
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
- if pixbuf in (None, 'ask'):
- scaled_pixbuf = None
- else:
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
- for child_iter in iters:
- self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf
- return False
-
- def draw_completely(self, jid, account):
- self.draw_contact(jid, account)
- self.draw_all_pep_types(jid, account)
- self.draw_avatar(jid, account)
-
- def adjust_and_draw_contact_context(self, jid, account):
- """
- Draw contact, account and groups of given jid Show contact if it has
- pending events
- """
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if not contact:
- # idle draw or just removed SelfContact
- return
-
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # There might be a new big brother
- self._recalibrate_metacontact_family(family, account)
- self.draw_contact(jid, account)
- self.draw_account(account)
-
- for group in contact.get_shown_groups():
- self.draw_group(group, account)
- self._adjust_group_expand_collapse_state(group, account)
-
- def _idle_draw_jids_of_account(self, jids, account):
- """
- Draw given contacts and their avatars in a lazy fashion
-
- Keyword arguments:
- jids -- a list of jids to draw
- account -- the corresponding account
- """
- def _draw_all_contacts(jids, account):
- for jid in jids:
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # For metacontacts over several accounts:
- # When we connect a new account existing brothers
- # must be redrawn (got removed and readded)
- for data in family:
- self.draw_completely(data['jid'], data['account'])
- else:
- self.draw_completely(jid, account)
- yield True
- yield False
-
- task = _draw_all_contacts(jids, account)
- gobject.idle_add(task.next)
-
- def setup_and_draw_roster(self):
- """
- Create new empty model and draw roster
- """
- self.modelfilter = None
- # (icon, name, type, jid, account, editable, mood_pixbuf,
- # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
- # padlock_pixbuf)
- self.model = gtk.TreeStore(gtk.Image, str, str, str, str,
- gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
- gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf)
-
- self.model.set_sort_func(1, self._compareIters)
- self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
- self.modelfilter = self.model.filter_new()
- self.modelfilter.set_visible_func(self._visible_func)
- self.modelfilter.connect('row-has-child-toggled',
- self.on_modelfilter_row_has_child_toggled)
- self.tree.set_model(self.modelfilter)
-
- for acct in gajim.connections:
- self.add_account(acct)
- self.add_account_contacts(acct)
- # Recalculate column width for ellipsizing
- self.tree.columns_autosize()
-
-
- def select_contact(self, jid, account):
- """
- Select contact in roster. If contact is hidden but has events, show him
- """
- # Refiltering SHOULD NOT be needed:
- # When a contact gets a new event he will be redrawn and his
- # icon changes, so _visible_func WILL be called on him anyway
- iters = self._get_contact_iter(jid, account)
- if not iters:
- # Not visible in roster
- return
- path = self.modelfilter.get_path(iters[0])
- if self.dragging or not gajim.config.get('scroll_roster_to_last_message'):
- # do not change selection while DND'ing
- return
- # Expand his parent, so this path is visible, don't expand it.
- self.tree.expand_to_path(path[:-1])
- self.tree.scroll_to_cell(path)
- self.tree.set_cursor(path)
-
-
- def _adjust_account_expand_collapse_state(self, account):
- """
- Expand/collapse account row based on self.collapsed_rows
- """
- iterA = self._get_account_iter(account)
- if not iterA:
- # thank you modelfilter
- return
- path = self.modelfilter.get_path(iterA)
- if account in self.collapsed_rows:
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- return False
-
-
- def _adjust_group_expand_collapse_state(self, group, account):
- """
- Expand/collapse group row based on self.collapsed_rows
- """
- iterG = self._get_group_iter(group, account)
- if not iterG:
- # Group not visible
- return
- path = self.modelfilter.get_path(iterG)
- if account + group in self.collapsed_rows:
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- return False
+ def draw_account(self, account):
+ child_iter = self._get_account_iter(account, self.model)
+ if not child_iter:
+ assert False, 'Account iter of %s could not be found.' % account
+ return
+
+ num_of_accounts = gajim.get_number_of_connected_accounts()
+ num_of_secured = gajim.get_number_of_securely_connected_accounts()
+
+ if gajim.account_is_securely_connected(account) and not self.regroup or \
+ self.regroup and num_of_secured and num_of_secured == num_of_accounts:
+ tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
+ gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
+ self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf
+ else:
+ self.model[child_iter][C_PADLOCK_PIXBUF] = None
+
+ if self.regroup:
+ account_name = _('Merged accounts')
+ accounts = []
+ else:
+ account_name = account
+ accounts = [account]
+
+ if account in self.collapsed_rows and \
+ self.model.iter_has_child(child_iter):
+ account_name = '[%s]' % account_name
+
+ if (gajim.account_is_connected(account) or (self.regroup and \
+ gajim.get_number_of_connected_accounts())) and gajim.config.get(
+ 'show_contacts_number'):
+ nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
+ accounts = accounts)
+ account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
+
+ self.model[child_iter][C_NAME] = account_name
+
+ pep = gajim.connections[account].pep
+ if gajim.config.get('show_mood_in_roster') and 'mood' in pep:
+ self.model[child_iter][C_MOOD_PIXBUF] = pep['mood'].asPixbufIcon()
+ else:
+ self.model[child_iter][C_MOOD_PIXBUF] = None
+
+ if gajim.config.get('show_activity_in_roster') and 'activity' in pep:
+ self.model[child_iter][C_ACTIVITY_PIXBUF] = pep['activity'].asPixbufIcon()
+ else:
+ self.model[child_iter][C_ACTIVITY_PIXBUF] = None
+
+ if gajim.config.get('show_tunes_in_roster') and 'tune' in pep:
+ self.model[child_iter][C_TUNE_PIXBUF] = pep['tune'].asPixbufIcon()
+ else:
+ self.model[child_iter][C_TUNE_PIXBUF] = None
+
+ if gajim.config.get('show_location_in_roster') and 'location' in pep:
+ self.model[child_iter][C_LOCATION_PIXBUF] = pep['location'].asPixbufIcon()
+ else:
+ self.model[child_iter][C_LOCATION_PIXBUF] = None
+ return False
+
+ def draw_group(self, group, account):
+ child_iter = self._get_group_iter(group, account, model=self.model)
+ if not child_iter:
+ # Eg. We redraw groups after we removed a entitiy
+ # and its empty groups
+ return
+ if self.regroup:
+ accounts = []
+ else:
+ accounts = [account]
+ text = gobject.markup_escape_text(group)
+ if helpers.group_is_blocked(account, group):
+ text = '<span strikethrough="true">%s</span>' % text
+ if gajim.config.get('show_contacts_number'):
+ nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
+ accounts = accounts, groups = [group])
+ text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
+
+ self.model[child_iter][C_NAME] = text
+ return False
+
+ def draw_parent_contact(self, jid, account):
+ child_iters = self._get_contact_iter(jid, account, model=self.model)
+ if not child_iters:
+ return False
+ parent_iter = self.model.iter_parent(child_iters[0])
+ if self.model[parent_iter][C_TYPE] != 'contact':
+ # parent is not a contact
+ return
+ parent_jid = self.model[parent_iter][C_JID].decode('utf-8')
+ parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8')
+ self.draw_contact(parent_jid, parent_account)
+ return False
+
+ def draw_contact(self, jid, account, selected=False, focus=False):
+ """
+ Draw the correct state image, name BUT not avatar
+ """
+ # focus is about if the roster window has toplevel-focus or not
+ # FIXME: We really need a custom cell_renderer
+
+ contact_instances = gajim.contacts.get_contacts(account, jid)
+ contact = gajim.contacts.get_highest_prio_contact_from_contacts(
+ contact_instances)
+
+ child_iters = self._get_contact_iter(jid, account, contact, self.model)
+ if not child_iters:
+ return False
+
+ name = gobject.markup_escape_text(contact.get_shown_name())
+
+ # gets number of unread gc marked messages
+ if jid in gajim.interface.minimized_controls[account] and \
+ gajim.interface.minimized_controls[account][jid]:
+ nb_unread = len(gajim.events.get_events(account, jid,
+ ['printed_marked_gc_msg']))
+ nb_unread += gajim.interface.minimized_controls \
+ [account][jid].get_nb_unread_pm()
+
+ if nb_unread == 1:
+ name = '%s *' % name
+ elif nb_unread > 1:
+ name = '%s [%s]' % (name, str(nb_unread))
+
+ # Strike name if blocked
+ strike = False
+ if helpers.jid_is_blocked(account, jid):
+ strike = True
+ else:
+ for group in contact.get_shown_groups():
+ if helpers.group_is_blocked(account, group):
+ strike = True
+ break
+ if strike:
+ name = '<span strikethrough="true">%s</span>' % name
+
+ # Show resource counter
+ nb_connected_contact = 0
+ for c in contact_instances:
+ if c.show not in ('error', 'offline'):
+ nb_connected_contact += 1
+ if nb_connected_contact > 1:
+ # switch back to default writing direction
+ name += i18n.paragraph_direction_mark(unicode(name))
+ name += u' (%d)' % nb_connected_contact
+
+ # show (account_name) if there are 2 contact with same jid
+ # in merged mode
+ if self.regroup:
+ add_acct = False
+ # look through all contacts of all accounts
+ for account_ in gajim.connections:
+ # useless to add account name
+ if account_ == account:
+ continue
+ for jid_ in gajim.contacts.get_jid_list(account_):
+ contact_ = gajim.contacts.get_first_contact_from_jid(
+ account_, jid_)
+ if contact_.get_shown_name() == contact.get_shown_name() and \
+ (jid_, account_) != (jid, account):
+ add_acct = True
+ break
+ if add_acct:
+ # No need to continue in other account
+ # if we already found one
+ break
+ if add_acct:
+ name += ' (' + account + ')'
+
+ # add status msg, if not empty, under contact name in
+ # the treeview
+ if contact.status and gajim.config.get('show_status_msgs_in_roster'):
+ status = contact.status.strip()
+ if status != '':
+ status = helpers.reduce_chars_newlines(status,
+ max_lines = 1)
+ # escape markup entities and make them small
+ # italic and fg color color is calcuted to be
+ # always readable
+ color = gtkgui_helpers._get_fade_color(self.tree, selected, focus)
+ colorstring = '#%04x%04x%04x' % (color.red, color.green, color.blue)
+ name += '\n<span size="small" style="italic" ' \
+ 'foreground="%s">%s</span>' % (
+ colorstring,
+ gobject.markup_escape_text(status))
+
+ icon_name = helpers.get_icon_name_to_show(contact, account)
+ # look if another resource has awaiting events
+ for c in contact_instances:
+ c_icon_name = helpers.get_icon_name_to_show(c, account)
+ if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
+ icon_name = c_icon_name
+ break
+
+ # Check for events of collapsed (hidden) brothers
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ is_big_brother = False
+ have_visible_children = False
+ if family:
+ bb_jid, bb_account = \
+ self._get_nearby_family_and_big_brother(family, account)[1:]
+ is_big_brother = (jid, account) == (bb_jid, bb_account)
+ iters = self._get_contact_iter(jid, account)
+ have_visible_children = iters \
+ and self.modelfilter.iter_has_child(iters[0])
+
+ if have_visible_children:
+ # We are the big brother and have a visible family
+ for child_iter in child_iters:
+ child_path = self.model.get_path(child_iter)
+ path = self.modelfilter.convert_child_path_to_path(child_path)
+
+ if not path:
+ continue
+
+ if not self.tree.row_expanded(path) and icon_name != 'event':
+ iterC = self.model.iter_children(child_iter)
+ while iterC:
+ # a child has awaiting messages?
+ jidC = self.model[iterC][C_JID].decode('utf-8')
+ accountC = self.model[iterC][C_ACCOUNT].decode('utf-8')
+ if len(gajim.events.get_events(accountC, jidC)):
+ icon_name = 'event'
+ break
+ iterC = self.model.iter_next(iterC)
+
+ if self.tree.row_expanded(path):
+ state_images = self.get_appropriate_state_images(
+ jid, size = 'opened',
+ icon_name = icon_name)
+ else:
+ state_images = self.get_appropriate_state_images(
+ jid, size = 'closed',
+ icon_name = icon_name)
+
+ # Expand/collapse icon might differ per iter
+ # (group)
+ img = state_images[icon_name]
+ self.model[child_iter][C_IMG] = img
+ self.model[child_iter][C_NAME] = name
+ else:
+ # A normal contact or little brother
+ state_images = self.get_appropriate_state_images(jid,
+ icon_name = icon_name)
+
+ # All iters have the same icon (no expand/collapse)
+ img = state_images[icon_name]
+ for child_iter in child_iters:
+ self.model[child_iter][C_IMG] = img
+ self.model[child_iter][C_NAME] = name
+
+ # We are a little brother
+ if family and not is_big_brother and not self.starting:
+ self.draw_parent_contact(jid, account)
+
+ for group in contact.get_shown_groups():
+ # We need to make sure that _visible_func is called for
+ # our groups otherwise we might not be shown
+ iterG = self._get_group_iter(group, account, model=self.model)
+ if iterG:
+ # it's not self contact
+ self.model[iterG][C_JID] = self.model[iterG][C_JID]
+
+ return False
+
+ def _is_pep_shown_in_roster(self, pep_type):
+ if pep_type == 'mood':
+ return gajim.config.get('show_mood_in_roster')
+ elif pep_type == 'activity':
+ return gajim.config.get('show_activity_in_roster')
+ elif pep_type == 'tune':
+ return gajim.config.get('show_tunes_in_roster')
+ elif pep_type == 'location':
+ return gajim.config.get('show_location_in_roster')
+ else:
+ return False
+
+ def draw_all_pep_types(self, jid, account):
+ for pep_type in self._pep_type_to_model_column:
+ self.draw_pep(jid, account, pep_type)
+
+ def draw_pep(self, jid, account, pep_type):
+ if pep_type not in self._pep_type_to_model_column:
+ return
+ if not self._is_pep_shown_in_roster(pep_type):
+ return
+
+ model_column = self._pep_type_to_model_column[pep_type]
+ iters = self._get_contact_iter(jid, account, model=self.model)
+ if not iters:
+ return
+ jid = self.model[iters[0]][C_JID].decode('utf-8')
+ contact = gajim.contacts.get_contact(account, jid)
+ if pep_type in contact.pep:
+ pixbuf = contact.pep[pep_type].asPixbufIcon()
+ else:
+ pixbuf = None
+ for child_iter in iters:
+ self.model[child_iter][model_column] = pixbuf
+
+ def draw_avatar(self, jid, account):
+ iters = self._get_contact_iter(jid, account, model=self.model)
+ if not iters or not gajim.config.get('show_avatars_in_roster'):
+ return
+ jid = self.model[iters[0]][C_JID].decode('utf-8')
+ pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
+ if pixbuf in (None, 'ask'):
+ scaled_pixbuf = None
+ else:
+ scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
+ for child_iter in iters:
+ self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf
+ return False
+
+ def draw_completely(self, jid, account):
+ self.draw_contact(jid, account)
+ self.draw_all_pep_types(jid, account)
+ self.draw_avatar(jid, account)
+
+ def adjust_and_draw_contact_context(self, jid, account):
+ """
+ Draw contact, account and groups of given jid Show contact if it has
+ pending events
+ """
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ if not contact:
+ # idle draw or just removed SelfContact
+ return
+
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ # There might be a new big brother
+ self._recalibrate_metacontact_family(family, account)
+ self.draw_contact(jid, account)
+ self.draw_account(account)
+
+ for group in contact.get_shown_groups():
+ self.draw_group(group, account)
+ self._adjust_group_expand_collapse_state(group, account)
+
+ def _idle_draw_jids_of_account(self, jids, account):
+ """
+ Draw given contacts and their avatars in a lazy fashion
+
+ Keyword arguments:
+ jids -- a list of jids to draw
+ account -- the corresponding account
+ """
+ def _draw_all_contacts(jids, account):
+ for jid in jids:
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ # For metacontacts over several accounts:
+ # When we connect a new account existing brothers
+ # must be redrawn (got removed and readded)
+ for data in family:
+ self.draw_completely(data['jid'], data['account'])
+ else:
+ self.draw_completely(jid, account)
+ yield True
+ yield False
+
+ task = _draw_all_contacts(jids, account)
+ gobject.idle_add(task.next)
+
+ def setup_and_draw_roster(self):
+ """
+ Create new empty model and draw roster
+ """
+ self.modelfilter = None
+ # (icon, name, type, jid, account, editable, mood_pixbuf,
+ # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
+ # padlock_pixbuf)
+ self.model = gtk.TreeStore(gtk.Image, str, str, str, str,
+ gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
+ gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf)
+
+ self.model.set_sort_func(1, self._compareIters)
+ self.model.set_sort_column_id(1, gtk.SORT_ASCENDING)
+ self.modelfilter = self.model.filter_new()
+ self.modelfilter.set_visible_func(self._visible_func)
+ self.modelfilter.connect('row-has-child-toggled',
+ self.on_modelfilter_row_has_child_toggled)
+ self.tree.set_model(self.modelfilter)
+
+ for acct in gajim.connections:
+ self.add_account(acct)
+ self.add_account_contacts(acct)
+ # Recalculate column width for ellipsizing
+ self.tree.columns_autosize()
+
+
+ def select_contact(self, jid, account):
+ """
+ Select contact in roster. If contact is hidden but has events, show him
+ """
+ # Refiltering SHOULD NOT be needed:
+ # When a contact gets a new event he will be redrawn and his
+ # icon changes, so _visible_func WILL be called on him anyway
+ iters = self._get_contact_iter(jid, account)
+ if not iters:
+ # Not visible in roster
+ return
+ path = self.modelfilter.get_path(iters[0])
+ if self.dragging or not gajim.config.get('scroll_roster_to_last_message'):
+ # do not change selection while DND'ing
+ return
+ # Expand his parent, so this path is visible, don't expand it.
+ self.tree.expand_to_path(path[:-1])
+ self.tree.scroll_to_cell(path)
+ self.tree.set_cursor(path)
+
+
+ def _adjust_account_expand_collapse_state(self, account):
+ """
+ Expand/collapse account row based on self.collapsed_rows
+ """
+ iterA = self._get_account_iter(account)
+ if not iterA:
+ # thank you modelfilter
+ return
+ path = self.modelfilter.get_path(iterA)
+ if account in self.collapsed_rows:
+ self.tree.collapse_row(path)
+ else:
+ self.tree.expand_row(path, False)
+ return False
+
+
+ def _adjust_group_expand_collapse_state(self, group, account):
+ """
+ Expand/collapse group row based on self.collapsed_rows
+ """
+ iterG = self._get_group_iter(group, account)
+ if not iterG:
+ # Group not visible
+ return
+ path = self.modelfilter.get_path(iterG)
+ if account + group in self.collapsed_rows:
+ self.tree.collapse_row(path)
+ else:
+ self.tree.expand_row(path, False)
+ return False
##############################################################################
### Roster and Modelfilter handling
##############################################################################
- def _search_roster_func(self, model, column, key, titer):
- key = key.decode('utf-8').lower()
- name = model[titer][C_NAME].decode('utf-8').lower()
- return not (key in name)
-
- def refilter_shown_roster_items(self):
- self.filtering = True
- self.modelfilter.refilter()
- self.filtering = False
-
- def contact_has_pending_roster_events(self, contact, account):
- """
- Return True if the contact or one if it resources has pending events
- """
- # jid has pending events
- if gajim.events.get_nb_roster_events(account, contact.jid) > 0:
- return True
- # check events of all resources
- for contact_ in gajim.contacts.get_contacts(account, contact.jid):
- if contact_.resource and gajim.events.get_nb_roster_events(account,
- contact_.get_full_jid()) > 0:
- return True
- return False
-
- def contact_is_visible(self, contact, account):
- if self.contact_has_pending_roster_events(contact, account):
- return True
-
- if contact.show in ('offline', 'error'):
- if contact.jid in gajim.to_be_removed[account]:
- return True
- return False
- if gajim.config.get('show_only_chat_and_online') and contact.show in (
- 'away', 'xa', 'busy'):
- return False
- return True
-
- def _visible_func(self, model, titer):
- """
- Determine whether iter should be visible in the treeview
- """
- type_ = model[titer][C_TYPE]
- if not type_:
- return False
- if type_ == 'account':
- # Always show account
- return True
-
- account = model[titer][C_ACCOUNT]
- if not account:
- return False
-
- account = account.decode('utf-8')
- jid = model[titer][C_JID]
- if not jid:
- return False
- jid = jid.decode('utf-8')
- if type_ == 'group':
- group = jid
- if group == _('Transports'):
- if self.regroup:
- accounts = gajim.contacts.get_accounts()
- else:
- accounts = [account]
- for _acc in accounts:
- for contact in gajim.contacts.iter_contacts(_acc):
- if group in contact.get_shown_groups() and \
- self.contact_has_pending_roster_events(contact, _acc):
- return True
- return gajim.config.get('show_transports_group') and \
- (gajim.account_is_connected(account) or \
- gajim.config.get('showoffline'))
- if gajim.config.get('showoffline'):
- return True
-
-
- if self.regroup:
- # C_ACCOUNT for groups depends on the order
- # accounts were connected
- # Check all accounts for online group contacts
- accounts = gajim.contacts.get_accounts()
- else:
- accounts = [account]
- for _acc in accounts:
- for contact in gajim.contacts.iter_contacts(_acc):
- # Is this contact in this group ? (last part of if check if it's
- # self contact)
- if group in contact.get_shown_groups():
- if self.contact_is_visible(contact, _acc):
- return True
- return False
- if type_ == 'contact':
- if gajim.config.get('showoffline'):
- return True
- bb_jid = None
- bb_account = None
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- self._get_nearby_family_and_big_brother(family, account)
- if (bb_jid, bb_account) == (jid, account):
- # Show the big brother if a child has pending events
- for data in nearby_family:
- jid = data['jid']
- account = data['account']
- contact = gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- if contact and self.contact_is_visible(contact, account):
- return True
- return False
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- return self.contact_is_visible(contact, account)
- if type_ == 'agent':
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- return self.contact_has_pending_roster_events(contact, account) or \
- (gajim.config.get('show_transports_group') and \
- (gajim.account_is_connected(account) or \
- gajim.config.get('showoffline')))
- return True
-
- def _compareIters(self, model, iter1, iter2, data=None):
- """
- Compare two iters to sort them
- """
- name1 = model[iter1][C_NAME]
- name2 = model[iter2][C_NAME]
- if not name1 or not name2:
- return 0
- name1 = name1.decode('utf-8')
- name2 = name2.decode('utf-8')
- type1 = model[iter1][C_TYPE]
- type2 = model[iter2][C_TYPE]
- if type1 == 'self_contact':
- return -1
- if type2 == 'self_contact':
- return 1
- if type1 == 'group':
- name1 = model[iter1][C_JID]
- name2 = model[iter2][C_JID]
- if name1 == _('Transports'):
- return 1
- if name2 == _('Transports'):
- return -1
- if name1 == _('Not in Roster'):
- return 1
- if name2 == _('Not in Roster'):
- return -1
- if name1 == _('Groupchats'):
- return 1
- if name2 == _('Groupchats'):
- return -1
- account1 = model[iter1][C_ACCOUNT]
- account2 = model[iter2][C_ACCOUNT]
- if not account1 or not account2:
- return 0
- account1 = account1.decode('utf-8')
- account2 = account2.decode('utf-8')
- if type1 == 'account':
- return locale.strcoll(account1, account2)
- jid1 = model[iter1][C_JID].decode('utf-8')
- jid2 = model[iter2][C_JID].decode('utf-8')
- if type1 == 'contact':
- lcontact1 = gajim.contacts.get_contacts(account1, jid1)
- contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1)
- if not contact1:
- return 0
- name1 = contact1.get_shown_name()
- if type2 == 'contact':
- lcontact2 = gajim.contacts.get_contacts(account2, jid2)
- contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2)
- if not contact2:
- return 0
- name2 = contact2.get_shown_name()
- # We first compare by show if sort_by_show_in_roster is True or if it's a
- # child contact
- if type1 == 'contact' and type2 == 'contact' and \
- gajim.config.get('sort_by_show_in_roster'):
- cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
- 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
- s = self.get_show(lcontact1)
- show1 = cshow.get(s, 9)
- s = self.get_show(lcontact2)
- show2 = cshow.get(s, 9)
- removing1 = False
- removing2 = False
- if show1 == 6 and jid1 in gajim.to_be_removed[account1]:
- removing1 = True
- if show2 == 6 and jid2 in gajim.to_be_removed[account2]:
- removing2 = True
- if removing1 and not removing2:
- return 1
- if removing2 and not removing1:
- return -1
- sub1 = contact1.sub
- sub2 = contact2.sub
- # none and from goes after
- if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']:
- return -1
- if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']:
- return 1
- if show1 < show2:
- return -1
- elif show1 > show2:
- return 1
- # We compare names
- cmp_result = locale.strcoll(name1.lower(), name2.lower())
- if cmp_result < 0:
- return -1
- if cmp_result > 0:
- return 1
- if type1 == 'contact' and type2 == 'contact':
- # We compare account names
- cmp_result = locale.strcoll(account1.lower(), account2.lower())
- if cmp_result < 0:
- return -1
- if cmp_result > 0:
- return 1
- # We compare jids
- cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
- if cmp_result < 0:
- return -1
- if cmp_result > 0:
- return 1
- return 0
+ def _search_roster_func(self, model, column, key, titer):
+ key = key.decode('utf-8').lower()
+ name = model[titer][C_NAME].decode('utf-8').lower()
+ return not (key in name)
+
+ def refilter_shown_roster_items(self):
+ self.filtering = True
+ self.modelfilter.refilter()
+ self.filtering = False
+
+ def contact_has_pending_roster_events(self, contact, account):
+ """
+ Return True if the contact or one if it resources has pending events
+ """
+ # jid has pending events
+ if gajim.events.get_nb_roster_events(account, contact.jid) > 0:
+ return True
+ # check events of all resources
+ for contact_ in gajim.contacts.get_contacts(account, contact.jid):
+ if contact_.resource and gajim.events.get_nb_roster_events(account,
+ contact_.get_full_jid()) > 0:
+ return True
+ return False
+
+ def contact_is_visible(self, contact, account):
+ if self.contact_has_pending_roster_events(contact, account):
+ return True
+
+ if contact.show in ('offline', 'error'):
+ if contact.jid in gajim.to_be_removed[account]:
+ return True
+ return False
+ if gajim.config.get('show_only_chat_and_online') and contact.show in (
+ 'away', 'xa', 'busy'):
+ return False
+ return True
+
+ def _visible_func(self, model, titer):
+ """
+ Determine whether iter should be visible in the treeview
+ """
+ type_ = model[titer][C_TYPE]
+ if not type_:
+ return False
+ if type_ == 'account':
+ # Always show account
+ return True
+
+ account = model[titer][C_ACCOUNT]
+ if not account:
+ return False
+
+ account = account.decode('utf-8')
+ jid = model[titer][C_JID]
+ if not jid:
+ return False
+ jid = jid.decode('utf-8')
+ if type_ == 'group':
+ group = jid
+ if group == _('Transports'):
+ if self.regroup:
+ accounts = gajim.contacts.get_accounts()
+ else:
+ accounts = [account]
+ for _acc in accounts:
+ for contact in gajim.contacts.iter_contacts(_acc):
+ if group in contact.get_shown_groups() and \
+ self.contact_has_pending_roster_events(contact, _acc):
+ return True
+ return gajim.config.get('show_transports_group') and \
+ (gajim.account_is_connected(account) or \
+ gajim.config.get('showoffline'))
+ if gajim.config.get('showoffline'):
+ return True
+
+
+ if self.regroup:
+ # C_ACCOUNT for groups depends on the order
+ # accounts were connected
+ # Check all accounts for online group contacts
+ accounts = gajim.contacts.get_accounts()
+ else:
+ accounts = [account]
+ for _acc in accounts:
+ for contact in gajim.contacts.iter_contacts(_acc):
+ # Is this contact in this group ? (last part of if check if it's
+ # self contact)
+ if group in contact.get_shown_groups():
+ if self.contact_is_visible(contact, _acc):
+ return True
+ return False
+ if type_ == 'contact':
+ if gajim.config.get('showoffline'):
+ return True
+ bb_jid = None
+ bb_account = None
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ nearby_family, bb_jid, bb_account = \
+ self._get_nearby_family_and_big_brother(family, account)
+ if (bb_jid, bb_account) == (jid, account):
+ # Show the big brother if a child has pending events
+ for data in nearby_family:
+ jid = data['jid']
+ account = data['account']
+ contact = gajim.contacts.get_contact_with_highest_priority(
+ account, jid)
+ if contact and self.contact_is_visible(contact, account):
+ return True
+ return False
+ else:
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ return self.contact_is_visible(contact, account)
+ if type_ == 'agent':
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ return self.contact_has_pending_roster_events(contact, account) or \
+ (gajim.config.get('show_transports_group') and \
+ (gajim.account_is_connected(account) or \
+ gajim.config.get('showoffline')))
+ return True
+
+ def _compareIters(self, model, iter1, iter2, data=None):
+ """
+ Compare two iters to sort them
+ """
+ name1 = model[iter1][C_NAME]
+ name2 = model[iter2][C_NAME]
+ if not name1 or not name2:
+ return 0
+ name1 = name1.decode('utf-8')
+ name2 = name2.decode('utf-8')
+ type1 = model[iter1][C_TYPE]
+ type2 = model[iter2][C_TYPE]
+ if type1 == 'self_contact':
+ return -1
+ if type2 == 'self_contact':
+ return 1
+ if type1 == 'group':
+ name1 = model[iter1][C_JID]
+ name2 = model[iter2][C_JID]
+ if name1 == _('Transports'):
+ return 1
+ if name2 == _('Transports'):
+ return -1
+ if name1 == _('Not in Roster'):
+ return 1
+ if name2 == _('Not in Roster'):
+ return -1
+ if name1 == _('Groupchats'):
+ return 1
+ if name2 == _('Groupchats'):
+ return -1
+ account1 = model[iter1][C_ACCOUNT]
+ account2 = model[iter2][C_ACCOUNT]
+ if not account1 or not account2:
+ return 0
+ account1 = account1.decode('utf-8')
+ account2 = account2.decode('utf-8')
+ if type1 == 'account':
+ return locale.strcoll(account1, account2)
+ jid1 = model[iter1][C_JID].decode('utf-8')
+ jid2 = model[iter2][C_JID].decode('utf-8')
+ if type1 == 'contact':
+ lcontact1 = gajim.contacts.get_contacts(account1, jid1)
+ contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1)
+ if not contact1:
+ return 0
+ name1 = contact1.get_shown_name()
+ if type2 == 'contact':
+ lcontact2 = gajim.contacts.get_contacts(account2, jid2)
+ contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2)
+ if not contact2:
+ return 0
+ name2 = contact2.get_shown_name()
+ # We first compare by show if sort_by_show_in_roster is True or if it's a
+ # child contact
+ if type1 == 'contact' and type2 == 'contact' and \
+ gajim.config.get('sort_by_show_in_roster'):
+ cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
+ 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
+ s = self.get_show(lcontact1)
+ show1 = cshow.get(s, 9)
+ s = self.get_show(lcontact2)
+ show2 = cshow.get(s, 9)
+ removing1 = False
+ removing2 = False
+ if show1 == 6 and jid1 in gajim.to_be_removed[account1]:
+ removing1 = True
+ if show2 == 6 and jid2 in gajim.to_be_removed[account2]:
+ removing2 = True
+ if removing1 and not removing2:
+ return 1
+ if removing2 and not removing1:
+ return -1
+ sub1 = contact1.sub
+ sub2 = contact2.sub
+ # none and from goes after
+ if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']:
+ return -1
+ if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']:
+ return 1
+ if show1 < show2:
+ return -1
+ elif show1 > show2:
+ return 1
+ # We compare names
+ cmp_result = locale.strcoll(name1.lower(), name2.lower())
+ if cmp_result < 0:
+ return -1
+ if cmp_result > 0:
+ return 1
+ if type1 == 'contact' and type2 == 'contact':
+ # We compare account names
+ cmp_result = locale.strcoll(account1.lower(), account2.lower())
+ if cmp_result < 0:
+ return -1
+ if cmp_result > 0:
+ return 1
+ # We compare jids
+ cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
+ if cmp_result < 0:
+ return -1
+ if cmp_result > 0:
+ return 1
+ return 0
################################################################################
### FIXME: Methods that don't belong to roster window...
-### ... atleast not in there current form
+### ... atleast not in there current form
################################################################################
- def fire_up_unread_messages_events(self, account):
- """
- Read from db the unread messages, and fire them up, and if we find very
- old unread messages, delete them from unread table
- """
- results = gajim.logger.get_unread_msgs()
- for result in results:
- jid = result[4]
- shown = result[5]
- if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
- shown:
- # We have this jid in our contacts list
- # XXX unread messages should probably have their session saved with
- # them
- session = gajim.connections[account].make_new_session(jid)
-
- tim = time.localtime(float(result[2]))
- session.roster_message(jid, result[1], tim, msg_type='chat',
- msg_id=result[0])
- gajim.logger.set_shown_unread_msgs(result[0])
-
- elif (time.time() - result[2]) > 2592000:
- # ok, here we see that we have a message in unread messages table
- # that is older than a month. It is probably from someone not in our
- # roster for accounts we usually launch, so we will delete this id
- # from unread message tables.
- gajim.logger.set_read_messages([result[0]])
-
- def fill_contacts_and_groups_dicts(self, array, account):
- """
- Fill gajim.contacts and gajim.groups
- """
- # FIXME: This function needs to be splitted
- # Most of the logic SHOULD NOT be done at GUI level
- if account not in gajim.contacts.get_accounts():
- gajim.contacts.add_account(account)
- if account not in gajim.groups:
- gajim.groups[account] = {}
- if gajim.config.get('show_self_contact') == 'always':
- self_jid = gajim.get_jid_from_account(account)
- if gajim.connections[account].server_resource:
- self_jid += '/' + gajim.connections[account].server_resource
- array[self_jid] = {'name': gajim.nicks[account],
- 'groups': ['self_contact'], 'subscription': 'both', 'ask': 'none'}
- # .keys() is needed
- for jid in array.keys():
- # Remove the contact in roster. It might has changed
- self.remove_contact(jid, account, force=True)
- # Remove old Contact instances
- gajim.contacts.remove_jid(account, jid, remove_meta=False)
- jids = jid.split('/')
- # get jid
- ji = jids[0]
- # get resource
- resource = ''
- if len(jids) > 1:
- resource = '/'.join(jids[1:])
- # get name
- name = array[jid]['name'] or ''
- show = 'offline' # show is offline by default
- status = '' # no status message by default
-
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
-
- if gajim.jid_is_transport(jid):
- array[jid]['groups'] = [_('Transports')]
- #TRANSP - potential
- contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name,
- groups=array[jid]['groups'], show=show, status=status,
- sub=array[jid]['subscription'], ask=array[jid]['ask'],
- resource=resource, keyID=keyID)
- gajim.contacts.add_contact(account, contact1)
-
- if gajim.config.get('ask_avatars_on_startup'):
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji)
- if pixbuf == 'ask':
- transport = gajim.get_transport_name_from_jid(contact1.jid)
- if not transport or gajim.jid_is_transport(contact1.jid):
- jid_with_resource = contact1.jid
- if contact1.resource:
- jid_with_resource += '/' + contact1.resource
- gajim.connections[account].request_vcard(jid_with_resource)
- else:
- host = gajim.get_server_from_jid(contact1.jid)
- if host not in gajim.transport_avatar[account]:
- gajim.transport_avatar[account][host] = [contact1.jid]
- else:
- gajim.transport_avatar[account][host].append(contact1.jid)
-
- # If we already have chat windows opened, update them with new contact
- # instance
- chat_control = gajim.interface.msg_win_mgr.get_control(ji, account)
- if chat_control:
- chat_control.contact = contact1
-
- def connected_rooms(self, account):
- if account in gajim.gc_connected[account].values():
- return True
- return False
-
- def on_event_removed(self, event_list):
- """
- Remove contacts on last events removed
-
- Only performed if removal was requested before but the contact still had
- pending events
- """
- contact_list = ((event.jid.split('/')[0], event.account) for event in \
- event_list)
-
- for jid, account in contact_list:
- self.draw_contact(jid, account)
- # Remove contacts in roster if removal was requested
- key = (jid, account)
- if key in self.contacts_to_be_removed.keys():
- backend = self.contacts_to_be_removed[key]['backend']
- del self.contacts_to_be_removed[key]
- # Remove contact will delay removal if there are more events pending
- self.remove_contact(jid, account, backend=backend)
- self.show_title()
-
- def open_event(self, account, jid, event):
- """
- If an event was handled, return True, else return False
- """
- data = event.parameters
- ft = gajim.interface.instances['file_transfers']
- event = gajim.events.get_first_event(account, jid, event.type_)
- if event.type_ == 'normal':
- dialogs.SingleMessageWindow(account, jid,
- action='receive', from_whom=jid, subject=data[1], message=data[0],
- resource=data[5], session=data[8], form_node=data[9])
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'file-request':
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- ft.show_file_request(account, contact, data)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ in ('file-request-error', 'file-send-error'):
- ft.show_send_error(data)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ in ('file-error', 'file-stopped'):
- msg_err = ''
- if data['error'] == -1:
- msg_err = _('Remote contact stopped transfer')
- elif data['error'] == -6:
- msg_err = _('Error opening file')
- ft.show_stopped(jid, data, error_msg=msg_err)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'file-completed':
- ft.show_completed(jid, data)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'gc-invitation':
- dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
- data[1])
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'subscription_request':
- dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'unsubscribed':
- gajim.interface.show_unsubscribed_dialog(account, data)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'jingle-incoming':
- peerjid, sid, content_types = data
- dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
- gajim.events.remove_events(account, jid, event)
- return True
- return False
+ def fire_up_unread_messages_events(self, account):
+ """
+ Read from db the unread messages, and fire them up, and if we find very
+ old unread messages, delete them from unread table
+ """
+ results = gajim.logger.get_unread_msgs()
+ for result in results:
+ jid = result[4]
+ shown = result[5]
+ if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
+ shown:
+ # We have this jid in our contacts list
+ # XXX unread messages should probably have their session saved with
+ # them
+ session = gajim.connections[account].make_new_session(jid)
+
+ tim = time.localtime(float(result[2]))
+ session.roster_message(jid, result[1], tim, msg_type='chat',
+ msg_id=result[0])
+ gajim.logger.set_shown_unread_msgs(result[0])
+
+ elif (time.time() - result[2]) > 2592000:
+ # ok, here we see that we have a message in unread messages table
+ # that is older than a month. It is probably from someone not in our
+ # roster for accounts we usually launch, so we will delete this id
+ # from unread message tables.
+ gajim.logger.set_read_messages([result[0]])
+
+ def fill_contacts_and_groups_dicts(self, array, account):
+ """
+ Fill gajim.contacts and gajim.groups
+ """
+ # FIXME: This function needs to be splitted
+ # Most of the logic SHOULD NOT be done at GUI level
+ if account not in gajim.contacts.get_accounts():
+ gajim.contacts.add_account(account)
+ if account not in gajim.groups:
+ gajim.groups[account] = {}
+ if gajim.config.get('show_self_contact') == 'always':
+ self_jid = gajim.get_jid_from_account(account)
+ if gajim.connections[account].server_resource:
+ self_jid += '/' + gajim.connections[account].server_resource
+ array[self_jid] = {'name': gajim.nicks[account],
+ 'groups': ['self_contact'], 'subscription': 'both', 'ask': 'none'}
+ # .keys() is needed
+ for jid in array.keys():
+ # Remove the contact in roster. It might has changed
+ self.remove_contact(jid, account, force=True)
+ # Remove old Contact instances
+ gajim.contacts.remove_jid(account, jid, remove_meta=False)
+ jids = jid.split('/')
+ # get jid
+ ji = jids[0]
+ # get resource
+ resource = ''
+ if len(jids) > 1:
+ resource = '/'.join(jids[1:])
+ # get name
+ name = array[jid]['name'] or ''
+ show = 'offline' # show is offline by default
+ status = '' # no status message by default
+
+ keyID = ''
+ attached_keys = gajim.config.get_per('accounts', account,
+ 'attached_gpg_keys').split()
+ if jid in attached_keys:
+ keyID = attached_keys[attached_keys.index(jid) + 1]
+
+ if gajim.jid_is_transport(jid):
+ array[jid]['groups'] = [_('Transports')]
+ #TRANSP - potential
+ contact1 = gajim.contacts.create_contact(jid=ji, account=account, name=name,
+ groups=array[jid]['groups'], show=show, status=status,
+ sub=array[jid]['subscription'], ask=array[jid]['ask'],
+ resource=resource, keyID=keyID)
+ gajim.contacts.add_contact(account, contact1)
+
+ if gajim.config.get('ask_avatars_on_startup'):
+ pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji)
+ if pixbuf == 'ask':
+ transport = gajim.get_transport_name_from_jid(contact1.jid)
+ if not transport or gajim.jid_is_transport(contact1.jid):
+ jid_with_resource = contact1.jid
+ if contact1.resource:
+ jid_with_resource += '/' + contact1.resource
+ gajim.connections[account].request_vcard(jid_with_resource)
+ else:
+ host = gajim.get_server_from_jid(contact1.jid)
+ if host not in gajim.transport_avatar[account]:
+ gajim.transport_avatar[account][host] = [contact1.jid]
+ else:
+ gajim.transport_avatar[account][host].append(contact1.jid)
+
+ # If we already have chat windows opened, update them with new contact
+ # instance
+ chat_control = gajim.interface.msg_win_mgr.get_control(ji, account)
+ if chat_control:
+ chat_control.contact = contact1
+
+ def connected_rooms(self, account):
+ if account in gajim.gc_connected[account].values():
+ return True
+ return False
+
+ def on_event_removed(self, event_list):
+ """
+ Remove contacts on last events removed
+
+ Only performed if removal was requested before but the contact still had
+ pending events
+ """
+ contact_list = ((event.jid.split('/')[0], event.account) for event in \
+ event_list)
+
+ for jid, account in contact_list:
+ self.draw_contact(jid, account)
+ # Remove contacts in roster if removal was requested
+ key = (jid, account)
+ if key in self.contacts_to_be_removed.keys():
+ backend = self.contacts_to_be_removed[key]['backend']
+ del self.contacts_to_be_removed[key]
+ # Remove contact will delay removal if there are more events pending
+ self.remove_contact(jid, account, backend=backend)
+ self.show_title()
+
+ def open_event(self, account, jid, event):
+ """
+ If an event was handled, return True, else return False
+ """
+ data = event.parameters
+ ft = gajim.interface.instances['file_transfers']
+ event = gajim.events.get_first_event(account, jid, event.type_)
+ if event.type_ == 'normal':
+ dialogs.SingleMessageWindow(account, jid,
+ action='receive', from_whom=jid, subject=data[1], message=data[0],
+ resource=data[5], session=data[8], form_node=data[9])
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ == 'file-request':
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ ft.show_file_request(account, contact, data)
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ in ('file-request-error', 'file-send-error'):
+ ft.show_send_error(data)
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ in ('file-error', 'file-stopped'):
+ msg_err = ''
+ if data['error'] == -1:
+ msg_err = _('Remote contact stopped transfer')
+ elif data['error'] == -6:
+ msg_err = _('Error opening file')
+ ft.show_stopped(jid, data, error_msg=msg_err)
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ == 'file-completed':
+ ft.show_completed(jid, data)
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ == 'gc-invitation':
+ dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
+ data[1])
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ == 'subscription_request':
+ dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ == 'unsubscribed':
+ gajim.interface.show_unsubscribed_dialog(account, data)
+ gajim.events.remove_events(account, jid, event)
+ return True
+ elif event.type_ == 'jingle-incoming':
+ peerjid, sid, content_types = data
+ dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
+ gajim.events.remove_events(account, jid, event)
+ return True
+ return False
################################################################################
### This and that... random.
################################################################################
- def show_roster_vbox(self, active):
- if active:
- self.xml.get_object('roster_vbox2').show()
- else:
- self.xml.get_object('roster_vbox2').hide()
-
-
- def show_tooltip(self, contact):
- pointer = self.tree.get_pointer()
- props = self.tree.get_path_at_pos(pointer[0], pointer[1])
- # check if the current pointer is at the same path
- # as it was before setting the timeout
- if props and self.tooltip.id == props[0]:
- # bounding rectangle of coordinates for the cell within the treeview
- rect = self.tree.get_cell_area(props[0], props[1])
-
- # position of the treeview on the screen
- position = self.tree.window.get_origin()
- self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y)
- else:
- self.tooltip.hide_tooltip()
-
-
- def authorize(self, widget, jid, account):
- """
- Authorize a contact (by re-sending auth menuitem)
- """
- gajim.connections[account].send_authorization(jid)
- dialogs.InformationDialog(_('Authorization has been sent'),
- _('Now "%s" will know your status.') %jid)
-
- def req_sub(self, widget, jid, txt, account, groups=[], nickname=None,
- auto_auth=False):
- """
- Request subscription to a contact
- """
- gajim.connections[account].request_subscription(jid, txt, nickname,
- groups, auto_auth, gajim.nicks[account])
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if not contact:
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
- contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname,
- groups=groups, show='requested', status='', ask='none',
- sub='subscribe', keyID=keyID)
- gajim.contacts.add_contact(account, contact)
- else:
- if not _('Not in Roster') in contact.get_shown_groups():
- dialogs.InformationDialog(_('Subscription request has been sent'),
- _('If "%s" accepts this request you will know his or her status.'
- ) % jid)
- return
- self.remove_contact(contact.jid, account, force=True)
- contact.groups = groups
- if nickname:
- contact.name = nickname
- self.add_contact(jid, account)
-
- def revoke_auth(self, widget, jid, account):
- """
- Revoke a contact's authorization
- """
- gajim.connections[account].refuse_authorization(jid)
- dialogs.InformationDialog(_('Authorization has been removed'),
- _('Now "%s" will always see you as offline.') %jid)
-
- def set_state(self, account, state):
- child_iterA = self._get_account_iter(account, self.model)
- if child_iterA:
- self.model[child_iterA][0] = \
- gajim.interface.jabber_state_images['16'][state]
- if gajim.interface.systray_enabled:
- gajim.interface.systray.change_status(state)
-
- def set_connecting_state(self, account):
- self.set_state(account, 'connecting')
-
- def send_status(self, account, status, txt, auto=False, to=None):
- child_iterA = self._get_account_iter(account, self.model)
- if status != 'offline':
- if to is None:
- if status == gajim.connections[account].get_status() and \
- txt == gajim.connections[account].status:
- return
- gajim.config.set_per('accounts', account, 'last_status', status)
- gajim.config.set_per('accounts', account, 'last_status_msg',
- helpers.to_one_line(txt))
- if gajim.connections[account].connected < 2:
- self.set_connecting_state(account)
-
- keyid = gajim.config.get_per('accounts', account, 'keyid')
- if keyid and not gajim.connections[account].gpg:
- dialogs.WarningDialog(_('GPG is not usable'),
- _('You will be connected to %s without OpenPGP.') % account)
-
- self.send_status_continue(account, status, txt, auto, to)
-
- def send_pep(self, account, pep_dict):
- connection = gajim.connections[account]
-
- if 'activity' in pep_dict:
- activity = pep_dict['activity']
- subactivity = pep_dict.get('subactivity', None)
- activity_text = pep_dict.get('activity_text', None)
- connection.send_activity(activity, subactivity, activity_text)
- else:
- connection.retract_activity()
-
- if '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:
- if status == 'online' and gajim.interface.sleeper.getState() != \
- common.sleepy.STATE_UNKNOWN:
- gajim.sleeper_state[account] = 'online'
- elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \
- status == 'offline':
- gajim.sleeper_state[account] = 'off'
-
- if to:
- gajim.connections[account].send_custom_status(status, txt, to)
- else:
- if status in ('invisible', 'offline'):
- self.delete_pep(gajim.get_jid_from_account(account), account)
- was_invisible = gajim.connections[account].connected == \
- gajim.SHOW_LIST.index('invisible')
- gajim.connections[account].change_status(status, txt, auto)
-
- if account in gajim.interface.status_sent_to_users:
- gajim.interface.status_sent_to_users[account] = {}
- if account in gajim.interface.status_sent_to_groups:
- gajim.interface.status_sent_to_groups[account] = {}
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + \
- gajim.interface.minimized_controls[account].values():
- if gc_control.account == account:
- if gajim.gc_connected[account][gc_control.room_jid]:
- gajim.connections[account].send_gc_status(gc_control.nick,
- gc_control.room_jid, status, txt)
- else:
- # for some reason, we are not connected to the room even if
- # tab is opened, send initial join_gc()
- gajim.connections[account].join_gc(gc_control.nick,
- gc_control.room_jid, None)
- if was_invisible and status != 'offline':
- # We come back from invisible, join bookmarks
- gajim.interface.auto_join_bookmarks(account)
-
-
- def chg_contact_status(self, contact, show, status, account):
- """
- When a contact changes his or her status
- """
- contact_instances = gajim.contacts.get_contacts(account, contact.jid)
- contact.show = show
- contact.status = status
- # name is to show in conversation window
- name = contact.get_shown_name()
- fjid = contact.get_full_jid()
-
- # The contact has several resources
- if len(contact_instances) > 1:
- if contact.resource != '':
- name += '/' + contact.resource
-
- # Remove resource when going offline
- if show in ('offline', 'error') and \
- not self.contact_has_pending_roster_events(contact, account):
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
- if ctrl:
- ctrl.update_ui()
- ctrl.parent_win.redraw_tab(ctrl)
- # keep the contact around, since it's
- # already attached to the control
- else:
- gajim.contacts.remove_contact(account, contact)
-
- elif contact.jid == gajim.get_jid_from_account(account) and \
- show in ('offline', 'error'):
- if gajim.config.get('show_self_contact') != 'never':
- # SelfContact went offline. Remove him when last pending
- # message was read
- self.remove_contact(contact.jid, account, backend=True)
-
- uf_show = helpers.get_uf_show(show)
-
- # print status in chat window and update status/GPG image
- ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
- if ctrl and ctrl.type_id != message_control.TYPE_GC:
- ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
- account, contact.jid)
- ctrl.update_status_display(name, uf_show, status)
-
- if contact.resource:
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
- if ctrl:
- ctrl.update_status_display(name, uf_show, status)
-
- # Delete pep if needed
- keep_pep = any(c.show not in ('error', 'offline') for c in
- contact_instances)
- if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
- and not contact.is_groupchat():
- self.delete_pep(contact.jid, account)
-
- # Redraw everything and select the sender
- self.adjust_and_draw_contact_context(contact.jid, account)
-
-
- def on_status_changed(self, account, show):
- """
- The core tells us that our status has changed
- """
- if account not in gajim.contacts.get_accounts():
- return
- child_iterA = self._get_account_iter(account, self.model)
- if gajim.config.get('show_self_contact') == 'always':
- self_resource = gajim.connections[account].server_resource
- self_contact = gajim.contacts.get_contact(account,
- gajim.get_jid_from_account(account), resource=self_resource)
- if self_contact:
- status = gajim.connections[account].status
- self.chg_contact_status(self_contact, show, status, account)
- self.set_account_status_icon(account)
- if show == 'offline':
- if self.quit_on_next_offline > -1:
- # we want to quit, we are waiting for all accounts to be offline
- self.quit_on_next_offline -= 1
- if self.quit_on_next_offline < 1:
- # all accounts offline, quit
- self.quit_gtkgui_interface()
- else:
- # No need to redraw contacts if we're quitting
- if child_iterA:
- self.model[child_iterA][C_AVATAR_PIXBUF] = None
- if account in gajim.con_types:
- gajim.con_types[account] = None
- for jid in gajim.contacts.get_jid_list(account):
- lcontact = gajim.contacts.get_contacts(account, jid)
- ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
- for contact in [c for c in lcontact if ((c.show != 'offline' or \
- c.is_transport()) and not ctrl)]:
- self.chg_contact_status(contact, 'offline', '', account)
- self.actions_menu_needs_rebuild = True
- self.update_status_combobox()
-
- def get_status_message(self, show, on_response, show_pep=True,
- always_ask=False):
- """
- Get the status message by:
-
- 1/ looking in default status message
- 2/ asking to user if needed depending on ask_on(ff)line_status and
- always_ask
- show_pep can be False to hide pep things from status message or True
- """
- empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '',
- 'mood': '', 'mood_text': ''}
- if show in gajim.config.get_per('defaultstatusmsg'):
- if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
- msg = gajim.config.get_per('defaultstatusmsg', show, 'message')
- msg = helpers.from_one_line(msg)
- on_response(msg, empty_pep)
- return
- if not always_ask and ((show == 'online' and not gajim.config.get(
- 'ask_online_status')) or (show in ('offline', 'invisible') and not \
- gajim.config.get('ask_offline_status'))):
- on_response('', empty_pep)
- return
-
- dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep)
- dlg.dialog.present() # show it on current workspace
-
- def change_status(self, widget, account, status):
- def change(account, status):
- def on_response(message, pep_dict):
- if message is None:
- # user pressed Cancel to change status message dialog
- return
- self.send_status(account, status, message)
- self.send_pep(account, pep_dict)
- self.get_status_message(status, on_response)
-
- if status == 'invisible' and self.connected_rooms(account):
- dialogs.ConfirmationDialog(
- _('You are participating in one or more group chats'),
- _('Changing your status to invisible will result in disconnection '
- 'from those group chats. Are you sure you want to go invisible?'),
- on_response_ok = (change, account, status))
- else:
- change(account, status)
-
- def update_status_combobox(self):
- # table to change index in connection.connected to index in combobox
- table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
- 'xa':3, 'dnd':4, 'invisible':5}
-
- # we check if there are more options in the combobox that it should
- # if yes, we remove the first ones
- while len(self.status_combobox.get_model()) > len(table)+2:
- self.status_combobox.remove_text(0)
-
- show = helpers.get_global_show()
- # temporarily block signal in order not to send status that we show
- # in the combobox
- self.combobox_callback_active = False
- if helpers.statuses_unified():
- self.status_combobox.set_active(table[show])
- else:
- uf_show = helpers.get_uf_show(show)
- liststore = self.status_combobox.get_model()
- liststore.prepend(['SEPARATOR', None, '', True])
- status_combobox_text = uf_show + ' (' + _("desync'ed") +')'
- liststore.prepend([status_combobox_text,
- gajim.interface.jabber_state_images['16'][show], show, False])
- self.status_combobox.set_active(0)
- gajim.interface._change_awn_icon_status(show)
- self.combobox_callback_active = True
- if gajim.interface.systray_enabled:
- gajim.interface.systray.change_status(show)
-
- def get_show(self, lcontact):
- prio = lcontact[0].priority
- show = lcontact[0].show
- for u in lcontact:
- if u.priority > prio:
- prio = u.priority
- show = u.show
- return show
-
- def on_message_window_delete(self, win_mgr, msg_win):
- if gajim.config.get('one_message_window') == 'always_with_roster':
- self.show_roster_vbox(True)
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('roster_width'),
- gajim.config.get('roster_height'))
-
- def close_all_from_dict(self, dic):
- """
- Close all the windows in the given dictionary
- """
- for w in dic.values():
- if isinstance(w, dict):
- self.close_all_from_dict(w)
- else:
- w.window.destroy()
-
- def close_all(self, account, force=False):
- """
- Close all the windows from an account. If force is True, do not ask
- confirmation before closing chat/gc windows
- """
- if account in gajim.interface.instances:
- self.close_all_from_dict(gajim.interface.instances[account])
- for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account):
- ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON,
- force = force)
-
- def on_roster_window_delete_event(self, widget, event):
- """
- Main window X button was clicked
- """
- if gajim.interface.systray_enabled and not gajim.config.get(
- 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event':
- self.tooltip.hide_tooltip()
- self.window.hide()
- elif gajim.config.get('quit_on_roster_x_button'):
- self.on_quit_request()
- else:
- def on_ok(checked):
- if checked:
- gajim.config.set('quit_on_roster_x_button', True)
- self.on_quit_request()
- dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'),
- _('Are you sure you want to quit Gajim?'),
- _('Always close Gajim'), on_response_ok=on_ok)
- return True # do NOT destroy the window
-
- def prepare_quit(self):
- msgwin_width_adjust = 0
-
- # in case show_roster_on_start is False and roster is never shown
- # window.window is None
- if self.window.window is not None:
- x, y = self.window.window.get_root_origin()
- gajim.config.set('roster_x-position', x)
- gajim.config.set('roster_y-position', y)
- width, height = self.window.get_size()
- # For the width use the size of the vbox containing the tree and
- # status combo, this will cancel out any hpaned width
- width = self.xml.get_object('roster_vbox2').allocation.width
- gajim.config.set('roster_width', width)
- gajim.config.set('roster_height', height)
- if not self.xml.get_object('roster_vbox2').get_property('visible'):
- # The roster vbox is hidden, so the message window is larger
- # then we want to save (i.e. the window will grow every startup)
- # so adjust.
- msgwin_width_adjust = -1 * width
- gajim.config.set('show_roster_on_startup',
- self.window.get_property('visible'))
- gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust)
-
- gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows))
- gajim.interface.save_config()
- for account in gajim.connections:
- gajim.connections[account].quit(True)
- self.close_all(account)
- if gajim.interface.systray_enabled:
- gajim.interface.hide_systray()
-
- def quit_gtkgui_interface(self):
- """
- When we quit the gtk interface - exit gtk
- """
- self.prepare_quit()
- gtk.main_quit()
-
- def on_quit_request(self, widget=None):
- """
- User wants to quit. Check if he should be warned about messages pending.
- Terminate all sessions and send offline to all connected account. We do
- NOT really quit gajim here
- """
- accounts = gajim.connections.keys()
- get_msg = False
- for acct in accounts:
- if gajim.connections[acct].connected:
- get_msg = True
- break
-
- def on_continue3(message, pep_dict):
- self.quit_on_next_offline = 0
- accounts_to_disconnect = []
- for acct in accounts:
- if gajim.connections[acct].connected:
- self.quit_on_next_offline += 1
- accounts_to_disconnect.append(acct)
-
- for acct in accounts_to_disconnect:
- self.send_status(acct, 'offline', message)
- self.send_pep(acct, pep_dict)
-
- if not self.quit_on_next_offline:
- self.quit_gtkgui_interface()
-
- def on_continue2(message, pep_dict):
- # check if there is an active file transfer
- from common.protocol.bytestream import (is_transfer_active)
- files_props = gajim.interface.instances['file_transfers'].files_props
- transfer_active = False
- for x in files_props:
- for y in files_props[x]:
- if is_transfer_active(files_props[x][y]):
- transfer_active = True
- break
-
- if transfer_active:
- dialogs.ConfirmationDialog(_('You have running file transfers'),
- _('If you quit now, the file(s) being transfered will be '
- 'stopped. Do you still want to quit?'),
- on_response_ok=(on_continue3, message, pep_dict))
- return
- on_continue3(message, pep_dict)
-
- def on_continue(message, pep_dict):
- if message is None:
- # user pressed Cancel to change status message dialog
- return
- # check if we have unread messages
- unread = gajim.events.get_nb_events()
- if not gajim.config.get('notify_on_all_muc_messages'):
- unread_not_to_notify = gajim.events.get_nb_events(
- ['printed_gc_msg'])
- unread -= unread_not_to_notify
-
- # check if we have recent messages
- recent = False
- for win in gajim.interface.msg_win_mgr.windows():
- for ctrl in win.controls():
- fjid = ctrl.get_full_jid()
- if fjid in gajim.last_message_time[ctrl.account]:
- if time.time() - gajim.last_message_time[ctrl.account][fjid] \
- < 2:
- recent = True
- break
- if recent:
- break
-
- if unread or recent:
- dialogs.ConfirmationDialog(_('You have unread messages'),
- _('Messages will only be available for reading them later if you'
- ' have history enabled and contact is in your roster.'),
- on_response_ok=(on_continue2, message, pep_dict))
- return
- on_continue2(message, pep_dict)
-
- if get_msg:
- self.get_status_message('offline', on_continue, show_pep=False)
- else:
- on_continue('', None)
+ def show_roster_vbox(self, active):
+ if active:
+ self.xml.get_object('roster_vbox2').show()
+ else:
+ self.xml.get_object('roster_vbox2').hide()
+
+
+ def show_tooltip(self, contact):
+ pointer = self.tree.get_pointer()
+ props = self.tree.get_path_at_pos(pointer[0], pointer[1])
+ # check if the current pointer is at the same path
+ # as it was before setting the timeout
+ if props and self.tooltip.id == props[0]:
+ # bounding rectangle of coordinates for the cell within the treeview
+ rect = self.tree.get_cell_area(props[0], props[1])
+
+ # position of the treeview on the screen
+ position = self.tree.window.get_origin()
+ self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y)
+ else:
+ self.tooltip.hide_tooltip()
+
+
+ def authorize(self, widget, jid, account):
+ """
+ Authorize a contact (by re-sending auth menuitem)
+ """
+ gajim.connections[account].send_authorization(jid)
+ dialogs.InformationDialog(_('Authorization has been sent'),
+ _('Now "%s" will know your status.') %jid)
+
+ def req_sub(self, widget, jid, txt, account, groups=[], nickname=None,
+ auto_auth=False):
+ """
+ Request subscription to a contact
+ """
+ gajim.connections[account].request_subscription(jid, txt, nickname,
+ groups, auto_auth, gajim.nicks[account])
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ if not contact:
+ keyID = ''
+ attached_keys = gajim.config.get_per('accounts', account,
+ 'attached_gpg_keys').split()
+ if jid in attached_keys:
+ keyID = attached_keys[attached_keys.index(jid) + 1]
+ contact = gajim.contacts.create_contact(jid=jid, account=account, name=nickname,
+ groups=groups, show='requested', status='', ask='none',
+ sub='subscribe', keyID=keyID)
+ gajim.contacts.add_contact(account, contact)
+ else:
+ if not _('Not in Roster') in contact.get_shown_groups():
+ dialogs.InformationDialog(_('Subscription request has been sent'),
+ _('If "%s" accepts this request you will know his or her status.'
+ ) % jid)
+ return
+ self.remove_contact(contact.jid, account, force=True)
+ contact.groups = groups
+ if nickname:
+ contact.name = nickname
+ self.add_contact(jid, account)
+
+ def revoke_auth(self, widget, jid, account):
+ """
+ Revoke a contact's authorization
+ """
+ gajim.connections[account].refuse_authorization(jid)
+ dialogs.InformationDialog(_('Authorization has been removed'),
+ _('Now "%s" will always see you as offline.') %jid)
+
+ def set_state(self, account, state):
+ child_iterA = self._get_account_iter(account, self.model)
+ if child_iterA:
+ self.model[child_iterA][0] = \
+ gajim.interface.jabber_state_images['16'][state]
+ if gajim.interface.systray_enabled:
+ gajim.interface.systray.change_status(state)
+
+ def set_connecting_state(self, account):
+ self.set_state(account, 'connecting')
+
+ def send_status(self, account, status, txt, auto=False, to=None):
+ child_iterA = self._get_account_iter(account, self.model)
+ if status != 'offline':
+ if to is None:
+ if status == gajim.connections[account].get_status() and \
+ txt == gajim.connections[account].status:
+ return
+ gajim.config.set_per('accounts', account, 'last_status', status)
+ gajim.config.set_per('accounts', account, 'last_status_msg',
+ helpers.to_one_line(txt))
+ if gajim.connections[account].connected < 2:
+ self.set_connecting_state(account)
+
+ keyid = gajim.config.get_per('accounts', account, 'keyid')
+ if keyid and not gajim.connections[account].gpg:
+ dialogs.WarningDialog(_('GPG is not usable'),
+ _('You will be connected to %s without OpenPGP.') % account)
+
+ self.send_status_continue(account, status, txt, auto, to)
+
+ def send_pep(self, account, pep_dict):
+ connection = gajim.connections[account]
+
+ if 'activity' in pep_dict:
+ activity = pep_dict['activity']
+ subactivity = pep_dict.get('subactivity', None)
+ activity_text = pep_dict.get('activity_text', None)
+ connection.send_activity(activity, subactivity, activity_text)
+ else:
+ connection.retract_activity()
+
+ if '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:
+ if status == 'online' and gajim.interface.sleeper.getState() != \
+ common.sleepy.STATE_UNKNOWN:
+ gajim.sleeper_state[account] = 'online'
+ elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \
+ status == 'offline':
+ gajim.sleeper_state[account] = 'off'
+
+ if to:
+ gajim.connections[account].send_custom_status(status, txt, to)
+ else:
+ if status in ('invisible', 'offline'):
+ self.delete_pep(gajim.get_jid_from_account(account), account)
+ was_invisible = gajim.connections[account].connected == \
+ gajim.SHOW_LIST.index('invisible')
+ gajim.connections[account].change_status(status, txt, auto)
+
+ if account in gajim.interface.status_sent_to_users:
+ gajim.interface.status_sent_to_users[account] = {}
+ if account in gajim.interface.status_sent_to_groups:
+ gajim.interface.status_sent_to_groups[account] = {}
+ for gc_control in gajim.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC) + \
+ gajim.interface.minimized_controls[account].values():
+ if gc_control.account == account:
+ if gajim.gc_connected[account][gc_control.room_jid]:
+ gajim.connections[account].send_gc_status(gc_control.nick,
+ gc_control.room_jid, status, txt)
+ else:
+ # for some reason, we are not connected to the room even if
+ # tab is opened, send initial join_gc()
+ gajim.connections[account].join_gc(gc_control.nick,
+ gc_control.room_jid, None)
+ if was_invisible and status != 'offline':
+ # We come back from invisible, join bookmarks
+ gajim.interface.auto_join_bookmarks(account)
+
+
+ def chg_contact_status(self, contact, show, status, account):
+ """
+ When a contact changes his or her status
+ """
+ contact_instances = gajim.contacts.get_contacts(account, contact.jid)
+ contact.show = show
+ contact.status = status
+ # name is to show in conversation window
+ name = contact.get_shown_name()
+ fjid = contact.get_full_jid()
+
+ # The contact has several resources
+ if len(contact_instances) > 1:
+ if contact.resource != '':
+ name += '/' + contact.resource
+
+ # Remove resource when going offline
+ if show in ('offline', 'error') and \
+ not self.contact_has_pending_roster_events(contact, account):
+ ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
+ if ctrl:
+ ctrl.update_ui()
+ ctrl.parent_win.redraw_tab(ctrl)
+ # keep the contact around, since it's
+ # already attached to the control
+ else:
+ gajim.contacts.remove_contact(account, contact)
+
+ elif contact.jid == gajim.get_jid_from_account(account) and \
+ show in ('offline', 'error'):
+ if gajim.config.get('show_self_contact') != 'never':
+ # SelfContact went offline. Remove him when last pending
+ # message was read
+ self.remove_contact(contact.jid, account, backend=True)
+
+ uf_show = helpers.get_uf_show(show)
+
+ # print status in chat window and update status/GPG image
+ ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
+ if ctrl and ctrl.type_id != message_control.TYPE_GC:
+ ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
+ account, contact.jid)
+ ctrl.update_status_display(name, uf_show, status)
+
+ if contact.resource:
+ ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
+ if ctrl:
+ ctrl.update_status_display(name, uf_show, status)
+
+ # Delete pep if needed
+ keep_pep = any(c.show not in ('error', 'offline') for c in
+ contact_instances)
+ if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
+ and not contact.is_groupchat():
+ self.delete_pep(contact.jid, account)
+
+ # Redraw everything and select the sender
+ self.adjust_and_draw_contact_context(contact.jid, account)
+
+
+ def on_status_changed(self, account, show):
+ """
+ The core tells us that our status has changed
+ """
+ if account not in gajim.contacts.get_accounts():
+ return
+ child_iterA = self._get_account_iter(account, self.model)
+ if gajim.config.get('show_self_contact') == 'always':
+ self_resource = gajim.connections[account].server_resource
+ self_contact = gajim.contacts.get_contact(account,
+ gajim.get_jid_from_account(account), resource=self_resource)
+ if self_contact:
+ status = gajim.connections[account].status
+ self.chg_contact_status(self_contact, show, status, account)
+ self.set_account_status_icon(account)
+ if show == 'offline':
+ if self.quit_on_next_offline > -1:
+ # we want to quit, we are waiting for all accounts to be offline
+ self.quit_on_next_offline -= 1
+ if self.quit_on_next_offline < 1:
+ # all accounts offline, quit
+ self.quit_gtkgui_interface()
+ else:
+ # No need to redraw contacts if we're quitting
+ if child_iterA:
+ self.model[child_iterA][C_AVATAR_PIXBUF] = None
+ if account in gajim.con_types:
+ gajim.con_types[account] = None
+ for jid in gajim.contacts.get_jid_list(account):
+ lcontact = gajim.contacts.get_contacts(account, jid)
+ ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
+ for contact in [c for c in lcontact if ((c.show != 'offline' or \
+ c.is_transport()) and not ctrl)]:
+ self.chg_contact_status(contact, 'offline', '', account)
+ self.actions_menu_needs_rebuild = True
+ self.update_status_combobox()
+
+ def get_status_message(self, show, on_response, show_pep=True,
+ always_ask=False):
+ """
+ Get the status message by:
+
+ 1/ looking in default status message
+ 2/ asking to user if needed depending on ask_on(ff)line_status and
+ always_ask
+ show_pep can be False to hide pep things from status message or True
+ """
+ empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '',
+ 'mood': '', 'mood_text': ''}
+ if show in gajim.config.get_per('defaultstatusmsg'):
+ if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
+ msg = gajim.config.get_per('defaultstatusmsg', show, 'message')
+ msg = helpers.from_one_line(msg)
+ on_response(msg, empty_pep)
+ return
+ if not always_ask and ((show == 'online' and not gajim.config.get(
+ 'ask_online_status')) or (show in ('offline', 'invisible') and not \
+ gajim.config.get('ask_offline_status'))):
+ on_response('', empty_pep)
+ return
+
+ dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep)
+ dlg.dialog.present() # show it on current workspace
+
+ def change_status(self, widget, account, status):
+ def change(account, status):
+ def on_response(message, pep_dict):
+ if message is None:
+ # user pressed Cancel to change status message dialog
+ return
+ self.send_status(account, status, message)
+ self.send_pep(account, pep_dict)
+ self.get_status_message(status, on_response)
+
+ if status == 'invisible' and self.connected_rooms(account):
+ dialogs.ConfirmationDialog(
+ _('You are participating in one or more group chats'),
+ _('Changing your status to invisible will result in disconnection '
+ 'from those group chats. Are you sure you want to go invisible?'),
+ on_response_ok = (change, account, status))
+ else:
+ change(account, status)
+
+ def update_status_combobox(self):
+ # table to change index in connection.connected to index in combobox
+ table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
+ 'xa':3, 'dnd':4, 'invisible':5}
+
+ # we check if there are more options in the combobox that it should
+ # if yes, we remove the first ones
+ while len(self.status_combobox.get_model()) > len(table)+2:
+ self.status_combobox.remove_text(0)
+
+ show = helpers.get_global_show()
+ # temporarily block signal in order not to send status that we show
+ # in the combobox
+ self.combobox_callback_active = False
+ if helpers.statuses_unified():
+ self.status_combobox.set_active(table[show])
+ else:
+ uf_show = helpers.get_uf_show(show)
+ liststore = self.status_combobox.get_model()
+ liststore.prepend(['SEPARATOR', None, '', True])
+ status_combobox_text = uf_show + ' (' + _("desync'ed") +')'
+ liststore.prepend([status_combobox_text,
+ gajim.interface.jabber_state_images['16'][show], show, False])
+ self.status_combobox.set_active(0)
+ gajim.interface._change_awn_icon_status(show)
+ self.combobox_callback_active = True
+ if gajim.interface.systray_enabled:
+ gajim.interface.systray.change_status(show)
+
+ def get_show(self, lcontact):
+ prio = lcontact[0].priority
+ show = lcontact[0].show
+ for u in lcontact:
+ if u.priority > prio:
+ prio = u.priority
+ show = u.show
+ return show
+
+ def on_message_window_delete(self, win_mgr, msg_win):
+ if gajim.config.get('one_message_window') == 'always_with_roster':
+ self.show_roster_vbox(True)
+ gtkgui_helpers.resize_window(self.window,
+ gajim.config.get('roster_width'),
+ gajim.config.get('roster_height'))
+
+ def close_all_from_dict(self, dic):
+ """
+ Close all the windows in the given dictionary
+ """
+ for w in dic.values():
+ if isinstance(w, dict):
+ self.close_all_from_dict(w)
+ else:
+ w.window.destroy()
+
+ def close_all(self, account, force=False):
+ """
+ Close all the windows from an account. If force is True, do not ask
+ confirmation before closing chat/gc windows
+ """
+ if account in gajim.interface.instances:
+ self.close_all_from_dict(gajim.interface.instances[account])
+ for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account):
+ ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON,
+ force = force)
+
+ def on_roster_window_delete_event(self, widget, event):
+ """
+ Main window X button was clicked
+ """
+ if gajim.interface.systray_enabled and not gajim.config.get(
+ 'quit_on_roster_x_button') and gajim.config.get('trayicon') != 'on_event':
+ self.tooltip.hide_tooltip()
+ self.window.hide()
+ elif gajim.config.get('quit_on_roster_x_button'):
+ self.on_quit_request()
+ else:
+ def on_ok(checked):
+ if checked:
+ gajim.config.set('quit_on_roster_x_button', True)
+ self.on_quit_request()
+ dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'),
+ _('Are you sure you want to quit Gajim?'),
+ _('Always close Gajim'), on_response_ok=on_ok)
+ return True # do NOT destroy the window
+
+ def prepare_quit(self):
+ msgwin_width_adjust = 0
+
+ # in case show_roster_on_start is False and roster is never shown
+ # window.window is None
+ if self.window.window is not None:
+ x, y = self.window.window.get_root_origin()
+ gajim.config.set('roster_x-position', x)
+ gajim.config.set('roster_y-position', y)
+ width, height = self.window.get_size()
+ # For the width use the size of the vbox containing the tree and
+ # status combo, this will cancel out any hpaned width
+ width = self.xml.get_object('roster_vbox2').allocation.width
+ gajim.config.set('roster_width', width)
+ gajim.config.set('roster_height', height)
+ if not self.xml.get_object('roster_vbox2').get_property('visible'):
+ # The roster vbox is hidden, so the message window is larger
+ # then we want to save (i.e. the window will grow every startup)
+ # so adjust.
+ msgwin_width_adjust = -1 * width
+ gajim.config.set('show_roster_on_startup',
+ self.window.get_property('visible'))
+ gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust)
+
+ gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows))
+ gajim.interface.save_config()
+ for account in gajim.connections:
+ gajim.connections[account].quit(True)
+ self.close_all(account)
+ if gajim.interface.systray_enabled:
+ gajim.interface.hide_systray()
+
+ def quit_gtkgui_interface(self):
+ """
+ When we quit the gtk interface - exit gtk
+ """
+ self.prepare_quit()
+ gtk.main_quit()
+
+ def on_quit_request(self, widget=None):
+ """
+ User wants to quit. Check if he should be warned about messages pending.
+ Terminate all sessions and send offline to all connected account. We do
+ NOT really quit gajim here
+ """
+ accounts = gajim.connections.keys()
+ get_msg = False
+ for acct in accounts:
+ if gajim.connections[acct].connected:
+ get_msg = True
+ break
+
+ def on_continue3(message, pep_dict):
+ self.quit_on_next_offline = 0
+ accounts_to_disconnect = []
+ for acct in accounts:
+ if gajim.connections[acct].connected:
+ self.quit_on_next_offline += 1
+ accounts_to_disconnect.append(acct)
+
+ for acct in accounts_to_disconnect:
+ self.send_status(acct, 'offline', message)
+ self.send_pep(acct, pep_dict)
+
+ if not self.quit_on_next_offline:
+ self.quit_gtkgui_interface()
+
+ def on_continue2(message, pep_dict):
+ # check if there is an active file transfer
+ from common.protocol.bytestream import (is_transfer_active)
+ files_props = gajim.interface.instances['file_transfers'].files_props
+ transfer_active = False
+ for x in files_props:
+ for y in files_props[x]:
+ if is_transfer_active(files_props[x][y]):
+ transfer_active = True
+ break
+
+ if transfer_active:
+ dialogs.ConfirmationDialog(_('You have running file transfers'),
+ _('If you quit now, the file(s) being transfered will be '
+ 'stopped. Do you still want to quit?'),
+ on_response_ok=(on_continue3, message, pep_dict))
+ return
+ on_continue3(message, pep_dict)
+
+ def on_continue(message, pep_dict):
+ if message is None:
+ # user pressed Cancel to change status message dialog
+ return
+ # check if we have unread messages
+ unread = gajim.events.get_nb_events()
+ if not gajim.config.get('notify_on_all_muc_messages'):
+ unread_not_to_notify = gajim.events.get_nb_events(
+ ['printed_gc_msg'])
+ unread -= unread_not_to_notify
+
+ # check if we have recent messages
+ recent = False
+ for win in gajim.interface.msg_win_mgr.windows():
+ for ctrl in win.controls():
+ fjid = ctrl.get_full_jid()
+ if fjid in gajim.last_message_time[ctrl.account]:
+ if time.time() - gajim.last_message_time[ctrl.account][fjid] \
+ < 2:
+ recent = True
+ break
+ if recent:
+ break
+
+ if unread or recent:
+ dialogs.ConfirmationDialog(_('You have unread messages'),
+ _('Messages will only be available for reading them later if you'
+ ' have history enabled and contact is in your roster.'),
+ on_response_ok=(on_continue2, message, pep_dict))
+ return
+ on_continue2(message, pep_dict)
+
+ if get_msg:
+ self.get_status_message('offline', on_continue, show_pep=False)
+ else:
+ on_continue('', None)
################################################################################
### Menu and GUI callbacks
### FIXME: order callbacks in itself...
################################################################################
- def on_actions_menuitem_activate(self, widget):
- self.make_menu()
-
- def on_edit_menuitem_activate(self, widget):
- """
- Need to call make_menu to build profile, avatar item
- """
- self.make_menu()
-
- def on_bookmark_menuitem_activate(self, widget, account, bookmark):
- gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'],
- bookmark['password'])
-
- def on_send_server_message_menuitem_activate(self, widget, account):
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/online'
- dialogs.SingleMessageWindow(account, server, 'send')
-
- def on_xml_console_menuitem_activate(self, widget, account):
- if 'xml_console' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['xml_console'].window.present()
- else:
- gajim.interface.instances[account]['xml_console'] = \
- dialogs.XMLConsoleWindow(account)
-
- def on_privacy_lists_menuitem_activate(self, widget, account):
- if 'privacy_lists' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['privacy_lists'].window.present()
- else:
- gajim.interface.instances[account]['privacy_lists'] = \
- dialogs.PrivacyListsWindow(account)
-
- def on_set_motd_menuitem_activate(self, widget, account):
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/motd'
- dialogs.SingleMessageWindow(account, server, 'send')
-
- def on_update_motd_menuitem_activate(self, widget, account):
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/motd/update'
- dialogs.SingleMessageWindow(account, server, 'send')
-
- def on_delete_motd_menuitem_activate(self, widget, account):
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/motd/delete'
- gajim.connections[account].send_motd(server)
-
- def on_history_manager_menuitem_activate(self, widget):
- if os.name == 'nt':
- if os.path.exists('history_manager.exe'): # user is running stable
- helpers.exec_command('history_manager.exe')
- else: # user is running svn
- helpers.exec_command('%s history_manager.py' % sys.executable)
- else: # Unix user
- helpers.exec_command('%s history_manager.py' % sys.executable)
-
- def on_info(self, widget, contact, account):
- """
- Call vcard_information_window class to display contact's information
- """
- if gajim.connections[account].is_zeroconf:
- self.on_info_zeroconf(widget, contact, account)
- return
-
- info = gajim.interface.instances[account]['infos']
- if contact.jid in info:
- info[contact.jid].window.present()
- else:
- info[contact.jid] = vcard.VcardWindow(contact, account)
-
- def on_info_zeroconf(self, widget, contact, account):
- info = gajim.interface.instances[account]['infos']
- if contact.jid in info:
- info[contact.jid].window.present()
- else:
- contact = gajim.contacts.get_first_contact_from_jid(account,
- contact.jid)
- if contact.show in ('offline', 'error'):
- # don't show info on offline contacts
- return
- info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
-
- def on_roster_treeview_leave_notify_event(self, widget, event):
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id == props[0]:
- self.tooltip.hide_tooltip()
-
- def on_roster_treeview_motion_notify_event(self, widget, event):
- model = widget.get_model()
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- if self.tooltip.timeout > 0:
- if not props or self.tooltip.id != props[0]:
- self.tooltip.hide_tooltip()
- if props:
- row = props[0]
- titer = None
- try:
- titer = model.get_iter(row)
- except Exception:
- self.tooltip.hide_tooltip()
- return
- if model[titer][C_TYPE] in ('contact', 'self_contact'):
- # we're on a contact entry in the roster
- account = model[titer][C_ACCOUNT].decode('utf-8')
- jid = model[titer][C_JID].decode('utf-8')
- if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
- self.tooltip.id = row
- contacts = gajim.contacts.get_contacts(account, jid)
- connected_contacts = []
- for c in contacts:
- if c.show not in ('offline', 'error'):
- connected_contacts.append(c)
- if not connected_contacts:
- # no connected contacts, show the ofline one
- connected_contacts = contacts
- self.tooltip.account = account
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, connected_contacts)
- elif model[titer][C_TYPE] == 'groupchat':
- account = model[titer][C_ACCOUNT].decode('utf-8')
- jid = model[titer][C_JID].decode('utf-8')
- if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
- self.tooltip.id = row
- contact = gajim.contacts.get_contacts(account, jid)
- self.tooltip.account = account
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, contact)
- elif model[titer][C_TYPE] == 'account':
- # we're on an account entry in the roster
- account = model[titer][C_ACCOUNT].decode('utf-8')
- if account == 'all':
- if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
- self.tooltip.id = row
- self.tooltip.account = None
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, [])
- return
- jid = gajim.get_jid_from_account(account)
- contacts = []
- connection = gajim.connections[account]
- # get our current contact info
-
- nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
- accounts = [account])
- account_name = account
- if gajim.account_is_connected(account):
- account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
- contact = gajim.contacts.create_self_contact(jid=jid, account=account,
- name=account_name, show=connection.get_status(),
- status=connection.status, resource=connection.server_resource,
- priority=connection.priority)
- if gajim.connections[account].gpg:
- contact.keyID = gajim.config.get_per('accounts', connection.name,
- 'keyid')
- contacts.append(contact)
- # if we're online ...
- if connection.connection:
- roster = connection.connection.getRoster()
- # in threadless connection when no roster stanza is sent,
- # 'roster' is None
- if roster and roster.getItem(jid):
- resources = roster.getResources(jid)
- # ...get the contact info for our other online resources
- for resource in resources:
- # Check if we already have this resource
- found = False
- for contact_ in contacts:
- if contact_.resource == resource:
- found = True
- break
- if found:
- continue
- show = roster.getShow(jid+'/'+resource)
- if not show:
- show = 'online'
- contact = gajim.contacts.create_self_contact(jid=jid,
- account=account, show=show, status=roster.getStatus(jid + '/' + resource),
- priority=roster.getPriority(jid + '/' + resource),
- resource=resource)
- contacts.append(contact)
- if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
- self.tooltip.id = row
- self.tooltip.account = None
- self.tooltip.timeout = gobject.timeout_add(500,
- self.show_tooltip, contacts)
-
- def on_agent_logging(self, widget, jid, state, account):
- """
- When an agent is requested to log in or off
- """
- gajim.connections[account].send_agent_status(jid, state)
-
- def on_edit_agent(self, widget, contact, account):
- """
- When we want to modify the agent registration
- """
- gajim.connections[account].request_register_agent_info(contact.jid)
-
- def on_remove_agent(self, widget, list_):
- """
- When an agent is requested to be removed. list_ is a list of (contact,
- account) tuple
- """
- for (contact, account) in list_:
- if gajim.config.get_per('accounts', account, 'hostname') == \
- contact.jid:
- # We remove the server contact
- # remove it from treeview
- gajim.connections[account].unsubscribe(contact.jid)
- self.remove_contact(contact.jid, account, backend=True)
- return
-
- def remove(list_):
- for (contact, account) in list_:
- full_jid = contact.get_full_jid()
- gajim.connections[account].unsubscribe_agent(full_jid)
- # remove transport from treeview
- self.remove_contact(contact.jid, account, backend=True)
-
- # Check if there are unread events from some contacts
- has_unread_events = False
- for (contact, account) in list_:
- for jid in gajim.events.get_events(account):
- if jid.endswith(contact.jid):
- has_unread_events = True
- break
- if has_unread_events:
- dialogs.ErrorDialog(_('You have unread messages'),
- _('You must read them before removing this transport.'))
- return
- if len(list_) == 1:
- pritext = _('Transport "%s" will be removed') % list_[0][0].jid
- sectext = _('You will no longer be able to send and receive messages '
- 'from contacts using this transport.')
- else:
- pritext = _('Transports will be removed')
- jids = ''
- for (contact, account) in list_:
- jids += '\n ' + contact.get_shown_name() + ','
- jids = jids[:-1] + '.'
- sectext = _('You will no longer be able to send and receive messages '
- 'to contacts from these transports: %s') % jids
- dialogs.ConfirmationDialog(pritext, sectext,
- on_response_ok = (remove, list_))
-
- def on_block(self, widget, list_, group=None):
- """
- When clicked on the 'block' button in context menu. list_ is a list of
- (contact, account)
- """
- def on_continue(msg, pep_dict):
- if msg is None:
- # user pressed Cancel to change status message dialog
- return
- accounts = []
- if group is None:
- for (contact, account) in list_:
- if account not in accounts:
- if not gajim.connections[account].privacy_rules_supported:
- continue
- accounts.append(account)
- self.send_status(account, 'offline', msg, to=contact.jid)
- new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny',
- 'value' : contact.jid, 'child': [u'message', u'iq',
- u'presence-out']}
- gajim.connections[account].blocked_list.append(new_rule)
- # needed for draw_contact:
- gajim.connections[account].blocked_contacts.append(
- contact.jid)
- self.draw_contact(contact.jid, account)
- else:
- for (contact, account) in list_:
- if account not in accounts:
- if not gajim.connections[account].privacy_rules_supported:
- continue
- accounts.append(account)
- # needed for draw_group:
- gajim.connections[account].blocked_groups.append(group)
- self.draw_group(group, account)
- self.send_status(account, 'offline', msg, to=contact.jid)
- self.draw_contact(contact.jid, account)
- new_rule = {'order': u'1', 'type': u'group', 'action': u'deny',
- 'value' : group, 'child': [u'message', u'iq', u'presence-out']}
- gajim.connections[account].blocked_list.append(new_rule)
- for account in accounts:
- connection = gajim.connections[account]
- connection.set_privacy_list('block', connection.blocked_list)
- if len(connection.blocked_list) == 1:
- connection.set_active_list('block')
- connection.set_default_list('block')
- connection.get_privacy_list('block')
-
- def _block_it(is_checked=None):
- if is_checked is not None: # dialog has been shown
- if is_checked: # user does not want to be asked again
- gajim.config.set('confirm_block', 'no')
- else:
- gajim.config.set('confirm_block', 'yes')
- self.get_status_message('offline', on_continue, show_pep=False)
-
- confirm_block = gajim.config.get('confirm_block')
- if confirm_block == 'no':
- _block_it()
- return
- pritext = _('You are about to block a contact. Are you sure you want'
- ' to continue?')
- sectext = _('This contact will see you offline and you will not receive '
- 'messages he will send you.')
- dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('Do _not ask me again'), on_response_ok=_block_it)
-
- def on_unblock(self, widget, list_, group=None):
- """
- When clicked on the 'unblock' button in context menu.
- """
- accounts = []
- if group is None:
- for (contact, account) in list_:
- if account not in accounts:
- if gajim.connections[account].privacy_rules_supported:
- accounts.append(account)
- gajim.connections[account].new_blocked_list = []
- gajim.connections[account].to_unblock = []
- gajim.connections[account].to_unblock.append(contact.jid)
- else:
- gajim.connections[account].to_unblock.append(contact.jid)
- # needed for draw_contact:
- if contact.jid in gajim.connections[account].blocked_contacts:
- gajim.connections[account].blocked_contacts.remove(contact.jid)
- self.draw_contact(contact.jid, account)
- for account in accounts:
- for rule in gajim.connections[account].blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'jid' \
- or rule['value'] not in gajim.connections[account].to_unblock:
- gajim.connections[account].new_blocked_list.append(rule)
- else:
- for (contact, account) in list_:
- if account not in accounts:
- if gajim.connections[account].privacy_rules_supported:
- accounts.append(account)
- # needed for draw_group:
- if group in gajim.connections[account].blocked_groups:
- gajim.connections[account].blocked_groups.remove(group)
- self.draw_group(group, account)
- gajim.connections[account].new_blocked_list = []
- for rule in gajim.connections[account].blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'group' \
- or rule['value'] != group:
- gajim.connections[account].new_blocked_list.append(rule)
- self.draw_contact(contact.jid, account)
- for account in accounts:
- gajim.connections[account].set_privacy_list('block',
- gajim.connections[account].new_blocked_list)
- gajim.connections[account].get_privacy_list('block')
- if len(gajim.connections[account].new_blocked_list) == 0:
- gajim.connections[account].blocked_list = []
- gajim.connections[account].blocked_contacts = []
- gajim.connections[account].blocked_groups = []
- gajim.connections[account].set_default_list('')
- gajim.connections[account].set_active_list('')
- gajim.connections[account].del_privacy_list('block')
- if 'blocked_contacts' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['blocked_contacts'].\
- privacy_list_received([])
- for (contact, account) in list_:
- if not self.regroup:
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- else: # accounts merged
- show = helpers.get_global_show()
- if show == 'invisible':
- # Don't send our presence if we're invisible
- continue
- if account not in accounts:
- accounts.append(account)
- if gajim.connections[account].privacy_rules_supported:
- self.send_status(account, show,
- gajim.connections[account].status, to=contact.jid)
- else:
- self.send_status(account, show,
- gajim.connections[account].status, to=contact.jid)
-
- def on_rename(self, widget, row_type, jid, account):
- # this function is called either by F2 or by Rename menuitem
- if 'rename' in gajim.interface.instances:
- gajim.interface.instances['rename'].dialog.present()
- return
-
- # account is offline, don't allow to rename
- if gajim.connections[account].connected < 2:
- return
- if row_type in ('contact', 'agent'):
- # it's jid
- title = _('Rename Contact')
- message = _('Enter a new nickname for contact %s') % jid
- old_text = gajim.contacts.get_contact_with_highest_priority(account,
- jid).name
- elif row_type == 'group':
- if jid in helpers.special_groups + (_('General'),):
- return
- old_text = jid
- title = _('Rename Group')
- message = _('Enter a new name for group %s') % \
- gobject.markup_escape_text(jid)
-
- def on_renamed(new_text, account, row_type, jid, old_text):
- if 'rename' in gajim.interface.instances:
- del gajim.interface.instances['rename']
- if row_type in ('contact', 'agent'):
- if old_text == new_text:
- return
- for contact in gajim.contacts.get_contacts(account, jid):
- contact.name = new_text
- gajim.connections[account].update_contact(jid, new_text, \
- contact.groups)
- self.draw_contact(jid, account)
- # Update opened chats
- for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account):
- ctrl.update_ui()
- win = gajim.interface.msg_win_mgr.get_window(jid, account)
- win.redraw_tab(ctrl)
- win.show_title()
- elif row_type == 'group':
- # in C_JID column, we hold the group name (which is not escaped)
- self.rename_group(old_text, new_text, account)
-
- def on_canceled():
- if 'rename' in gajim.interface.instances:
- del gajim.interface.instances['rename']
-
- gajim.interface.instances['rename'] = dialogs.InputDialog(title, message,
- old_text, False, (on_renamed, account, row_type, jid, old_text),
- on_canceled)
-
- def on_remove_group_item_activated(self, widget, group, account):
- def on_ok(checked):
- for contact in gajim.contacts.get_contacts_from_group(account, group):
- if not checked:
- self.remove_contact_from_groups(contact.jid,account, [group])
- else:
- gajim.connections[account].unsubscribe(contact.jid)
- self.remove_contact(contact.jid, account, backend=True)
-
- dialogs.ConfirmationDialogCheck(_('Remove Group'),
- _('Do you want to remove group %s from the roster?') % group,
- _('Also remove all contacts in this group from your roster'),
- on_response_ok=on_ok)
-
- def on_assign_pgp_key(self, widget, contact, account):
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- keys = {}
- keyID = _('None')
- for i in xrange(len(attached_keys)/2):
- keys[attached_keys[2*i]] = attached_keys[2*i+1]
- if attached_keys[2*i] == contact.jid:
- keyID = attached_keys[2*i+1]
- public_keys = gajim.connections[account].ask_gpg_keys()
- public_keys[_('None')] = _('None')
-
- def on_key_selected(keyID):
- if keyID is None:
- return
- if keyID[0] == _('None'):
- if contact.jid in keys:
- del keys[contact.jid]
- keyID = ''
- else:
- keyID = keyID[0]
- keys[contact.jid] = keyID
-
- ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
- if ctrl:
- ctrl.update_ui()
-
- keys_str = ''
- for jid in keys:
- keys_str += jid + ' ' + keys[jid] + ' '
- gajim.config.set_per('accounts', account, 'attached_gpg_keys',
- keys_str)
- for u in gajim.contacts.get_contacts(account, contact.jid):
- u.keyID = helpers.prepare_and_validate_gpg_keyID(account,
- contact.jid, keyID)
-
- dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'),
- _('Select a key to apply to the contact'), public_keys,
- on_key_selected, selected=keyID)
-
- def on_set_custom_avatar_activate(self, widget, contact, account):
- def on_ok(widget, path_to_file):
- filesize = os.path.getsize(path_to_file) # in bytes
- invalid_file = False
- msg = ''
- if os.path.isfile(path_to_file):
- stat = os.stat(path_to_file)
- if stat[6] == 0:
- invalid_file = True
- msg = _('File is empty')
- else:
- invalid_file = True
- msg = _('File does not exist')
- if invalid_file:
- dialogs.ErrorDialog(_('Could not load image'), msg)
- return
- try:
- pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
- if filesize > 16384: # 16 kb
- # get the image at 'tooltip size'
- # and hope that user did not specify in ACE crazy size
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip')
- except gobject.GError, msg: # unknown format
- # msg should be string, not object instance
- msg = str(msg)
- dialogs.ErrorDialog(_('Could not load image'), msg)
- return
- gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True)
- dlg.destroy()
- self.update_avatar_in_gui(contact.jid, account)
-
- def on_clear(widget):
- dlg.destroy()
- # Delete file:
- gajim.interface.remove_avatar_files(contact.jid, local=True)
- self.update_avatar_in_gui(contact.jid, account)
-
- dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok,
- on_response_clear=on_clear)
-
- def on_edit_groups(self, widget, list_):
- dialogs.EditGroupsDialog(list_)
-
- def on_history(self, widget, contact, account):
- """
- When history menuitem is activated: call log window
- """
- if 'logs' in gajim.interface.instances:
- gajim.interface.instances['logs'].window.present()
- gajim.interface.instances['logs'].open_history(contact.jid, account)
- else:
- gajim.interface.instances['logs'] = history_window.\
- HistoryWindow(contact.jid, account)
-
- def on_disconnect(self, widget, jid, account):
- """
- When disconnect menuitem is activated: disconect from room
- """
- if jid in gajim.interface.minimized_controls[account]:
- ctrl = gajim.interface.minimized_controls[account][jid]
- ctrl.shutdown()
- ctrl.got_disconnected()
- self.remove_groupchat(jid, account)
-
- def on_reconnect(self, widget, jid, account):
- """
- When disconnect menuitem is activated: disconect from room
- """
- if jid in gajim.interface.minimized_controls[account]:
- ctrl = gajim.interface.minimized_controls[account][jid]
- gajim.interface.join_gc_room(account, jid, ctrl.nick,
- gajim.gc_passwords.get(jid, ''))
-
- def on_send_single_message_menuitem_activate(self, widget, account,
- contact=None):
- if contact is None:
- dialogs.SingleMessageWindow(account, action='send')
- elif isinstance(contact, list):
- dialogs.SingleMessageWindow(account, contact, 'send')
- else:
- jid = contact.jid
- if contact.jid == gajim.get_jid_from_account(account):
- jid += '/' + contact.resource
- dialogs.SingleMessageWindow(account, jid, 'send')
-
- def on_send_file_menuitem_activate(self, widget, contact, account,
- resource=None):
- gajim.interface.instances['file_transfers'].show_file_send_request(
- account, contact)
-
- def on_add_special_notification_menuitem_activate(self, widget, jid):
- dialogs.AddSpecialNotificationDialog(jid)
-
- def on_invite_to_new_room(self, widget, list_, resource=None):
- """
- Resource parameter MUST NOT be used if more than one contact in list
- """
- account_list = []
- jid_list = []
- for (contact, account) in list_:
- if contact.jid not in jid_list:
- if resource: # we MUST have one contact only in list_
- fjid = contact.jid + '/' + resource
- jid_list.append(fjid)
- else:
- jid_list.append(contact.jid)
- if account not in account_list:
- account_list.append(account)
- # transform None in 'jabber'
- type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber'
- for account in account_list:
- if gajim.connections[account].muc_jid[type_]:
- # create the room on this muc server
- if 'join_gc' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['join_gc'].window.destroy()
- try:
- gajim.interface.instances[account]['join_gc'] = \
- dialogs.JoinGroupchatWindow(account,
- gajim.connections[account].muc_jid[type_],
- automatic = {'invities': jid_list})
- except GajimGeneralException:
- continue
- break
-
- def on_invite_to_room(self, widget, list_, room_jid, room_account,
- resource=None):
- """
- Resource parameter MUST NOT be used if more than one contact in list
- """
- for e in list_:
- contact = e[0]
- contact_jid = contact.jid
- if resource: # we MUST have one contact only in list_
- contact_jid += '/' + resource
- gajim.connections[room_account].send_invite(room_jid, contact_jid)
-
- def on_all_groupchat_maximized(self, widget, group_list):
- for (contact, account) in group_list:
- self.on_groupchat_maximized(widget, contact.jid, account)
-
- def on_groupchat_maximized(self, widget, jid, account):
- """
- When a groupchat is maximized
- """
- if not jid in gajim.interface.minimized_controls[account]:
- # Already opened?
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
- if gc_control:
- mw = gajim.interface.msg_win_mgr.get_window(jid, account)
- mw.set_active_tab(gc_control)
- mw.window.window.focus(gtk.get_current_event_time())
- return
- ctrl = gajim.interface.minimized_controls[account][jid]
- mw = gajim.interface.msg_win_mgr.get_window(jid, account)
- if not mw:
- mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact,
- ctrl.account, ctrl.type_id)
- ctrl.parent_win = mw
- mw.new_tab(ctrl)
- mw.set_active_tab(ctrl)
- mw.window.window.focus(gtk.get_current_event_time())
- self.remove_groupchat(jid, account)
-
- def on_edit_account(self, widget, account):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].window.present()
- else:
- gajim.interface.instances['accounts'] = config.AccountsWindow()
- gajim.interface.instances['accounts'].select_account(account)
-
- def on_zeroconf_properties(self, widget, account):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].window.present()
- else:
- gajim.interface.instances['accounts'] = config.AccountsWindow()
- gajim.interface.instances['accounts'].select_account(account)
-
- def on_open_gmail_inbox(self, widget, account):
- url = gajim.connections[account].gmail_url
- if url:
- helpers.launch_browser_mailer('url', url)
-
- def on_change_status_message_activate(self, widget, account):
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- def on_response(message, pep_dict):
- if message is None: # None is if user pressed Cancel
- return
- self.send_status(account, show, message)
- self.send_pep(account, pep_dict)
- dialogs.ChangeStatusMessageDialog(on_response, show)
-
- def on_add_to_roster(self, widget, contact, account):
- dialogs.AddNewContactWindow(account, contact.jid, contact.name)
-
-
- def on_roster_treeview_scroll_event(self, widget, event):
- self.tooltip.hide_tooltip()
-
- def on_roster_treeview_key_press_event(self, widget, event):
- """
- When a key is pressed in the treeviews
- """
- self.tooltip.hide_tooltip()
- if event.keyval == gtk.keysyms.Escape:
- self.tree.get_selection().unselect_all()
- elif event.keyval == gtk.keysyms.F2:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- type_ = model[path][C_TYPE]
- if type_ in ('contact', 'group', 'agent'):
- jid = model[path][C_JID].decode('utf-8')
- account = model[path][C_ACCOUNT].decode('utf-8')
- self.on_rename(widget, type_, jid, account)
-
- elif event.keyval == gtk.keysyms.Delete:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if not len(list_of_paths):
- return
- type_ = model[list_of_paths[0]][C_TYPE]
- account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8')
- if type_ in ('account', 'group', 'self_contact') or \
- account == gajim.ZEROCONF_ACC_NAME:
- return
- list_ = []
- for path in list_of_paths:
- if model[path][C_TYPE] != type_:
- return
- jid = model[path][C_JID].decode('utf-8')
- account = model[path][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- list_.append((contact, account))
- if type_ == 'contact':
- self.on_req_usub(widget, list_)
- elif type_ == 'agent':
- self.on_remove_agent(widget, list_)
-
- def on_roster_treeview_button_release_event(self, widget, event):
- try:
- path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
- except TypeError:
- return False
-
- if event.button == 1: # Left click
- if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
- not event.state & gtk.gdk.CONTROL_MASK:
- # Check if button has been pressed on the same row
- if self.clicked_path == path:
- self.on_row_activated(widget, path)
- self.clicked_path = None
-
- def on_roster_treeview_button_press_event(self, widget, event):
- # hide tooltip, no matter the button is pressed
- self.tooltip.hide_tooltip()
- try:
- pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
- path, x = pos[0], pos[2]
- except TypeError:
- self.tree.get_selection().unselect_all()
- return False
-
- if event.button == 3: # Right click
- try:
- model, list_of_paths = self.tree.get_selection().get_selected_rows()
- except TypeError:
- list_of_paths = []
- if path not in list_of_paths:
- self.tree.get_selection().unselect_all()
- self.tree.get_selection().select_path(path)
- return self.show_treeview_menu(event)
-
- elif event.button == 2: # Middle click
- try:
- model, list_of_paths = self.tree.get_selection().get_selected_rows()
- except TypeError:
- list_of_paths = []
- if list_of_paths != [path]:
- self.tree.get_selection().unselect_all()
- self.tree.get_selection().select_path(path)
- type_ = model[path][C_TYPE]
- if type_ in ('agent', 'contact', 'self_contact', 'groupchat'):
- self.on_row_activated(widget, path)
- elif type_ == 'account':
- account = model[path][C_ACCOUNT].decode('utf-8')
- if account != 'all':
- show = gajim.connections[account].connected
- if show > 1: # We are connected
- self.on_change_status_message_activate(widget, account)
- return True
- show = helpers.get_global_show()
- if show == 'offline':
- return True
- def on_response(message, pep_dict):
- if message is None:
- return True
- for acct in gajim.connections:
- if not gajim.config.get_per('accounts', acct,
- 'sync_with_global_status'):
- continue
- current_show = gajim.SHOW_LIST[gajim.connections[acct].\
- connected]
- self.send_status(acct, current_show, message)
- self.send_pep(acct, pep_dict)
- dialogs.ChangeStatusMessageDialog(on_response, show)
- return True
-
- elif event.button == 1: # Left click
- model = self.modelfilter
- type_ = model[path][C_TYPE]
- # x_min is the x start position of status icon column
- if gajim.config.get('avatar_position_in_roster') == 'left':
- x_min = gajim.config.get('roster_avatar_width')
- else:
- x_min = 0
- if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
- not event.state & gtk.gdk.CONTROL_MASK:
- # Don't handle double click if we press icon of a metacontact
- titer = model.get_iter(path)
- if x > x_min and x < x_min + 27 and type_ == 'contact' and \
- model.iter_has_child(titer):
- if (self.tree.row_expanded(path)):
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- return
- # We just save on which row we press button, and open chat window on
- # button release to be able to do DND without opening chat window
- self.clicked_path = path
- return
- else:
- if type_ == 'group' and x < 27:
- # first cell in 1st column (the arrow SINGLE clicked)
- if (self.tree.row_expanded(path)):
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
-
- elif type_ == 'contact' and x > x_min and x < x_min + 27:
- if (self.tree.row_expanded(path)):
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
-
- def on_req_usub(self, widget, list_):
- """
- Remove a contact. list_ is a list of (contact, account) tuples
- """
- def on_ok(is_checked, list_):
- remove_auth = True
- if len(list_) == 1:
- contact = list_[0][0]
- if contact.sub != 'to' and is_checked:
- remove_auth = False
- for (contact, account) in list_:
- if _('Not in Roster') not in contact.get_shown_groups():
- gajim.connections[account].unsubscribe(contact.jid, remove_auth)
- self.remove_contact(contact.jid, account, backend=True)
- if not remove_auth and contact.sub == 'both':
- contact.name = ''
- contact.groups = []
- contact.sub = 'from'
- # we can't see him, but have to set it manually in contact
- contact.show = 'offline'
- gajim.contacts.add_contact(account, contact)
- self.add_contact(contact.jid, account)
- def on_ok2(list_):
- on_ok(False, list_)
-
- if len(list_) == 1:
- contact = list_[0][0]
- pritext = _('Contact "%s" will be removed from your roster') % \
- contact.get_shown_name()
- sectext = _('You are about to remove "%(name)s" (%(jid)s) from your '
- 'roster.\n') % {'name': contact.get_shown_name(),
- 'jid': contact.jid}
- if contact.sub == 'to':
- dialogs.ConfirmationDialog(pritext, sectext + \
- _('By removing this contact you also remove authorization '
- 'resulting in him or her always seeing you as offline.'),
- on_response_ok = (on_ok2, list_))
- elif _('Not in Roster') in contact.get_shown_groups():
- # Contact is not in roster
- dialogs.ConfirmationDialog(pritext, sectext + \
- _('Do you want to continue?'), on_response_ok = (on_ok2, list_))
- else:
- dialogs.ConfirmationDialogCheck(pritext, sectext + \
- _('By removing this contact you also by default remove '
- 'authorization resulting in him or her always seeing you as '
- 'offline.'),
- _('I want this contact to know my status after removal'),
- on_response_ok = (on_ok, list_))
- else:
- # several contact to remove at the same time
- pritext = _('Contacts will be removed from your roster')
- jids = ''
- for (contact, account) in list_:
- jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\
- ','
- sectext = _('By removing these contacts:%s\nyou also remove '
- 'authorization resulting in them always seeing you as offline.') % \
- jids
- dialogs.ConfirmationDialog(pritext, sectext,
- on_response_ok = (on_ok2, list_))
-
- def on_send_custom_status(self, widget, contact_list, show, group=None):
- """
- Send custom status
- """
- # contact_list has only one element except if group != None
- def on_response(message, pep_dict):
- if message is None: # None if user pressed Cancel
- return
- account_list = []
- for (contact, account) in contact_list:
- if account not in account_list:
- account_list.append(account)
- # 1. update status_sent_to_[groups|users] list
- if group:
- for account in account_list:
- if account not in gajim.interface.status_sent_to_groups:
- gajim.interface.status_sent_to_groups[account] = {}
- gajim.interface.status_sent_to_groups[account][group] = show
- else:
- for (contact, account) in contact_list:
- if account not in gajim.interface.status_sent_to_users:
- gajim.interface.status_sent_to_users[account] = {}
- gajim.interface.status_sent_to_users[account][contact.jid] = show
-
- # 2. update privacy lists if main status is invisible
- for account in account_list:
- if gajim.SHOW_LIST[gajim.connections[account].connected] == \
- 'invisible':
- gajim.connections[account].set_invisible_rule()
-
- # 3. send directed presence
- for (contact, account) in contact_list:
- our_jid = gajim.get_jid_from_account(account)
- jid = contact.jid
- if jid == our_jid:
- jid += '/' + contact.resource
- self.send_status(account, show, message, to=jid)
-
- def send_it(is_checked=None):
- if is_checked is not None: # dialog has been shown
- if is_checked: # user does not want to be asked again
- gajim.config.set('confirm_custom_status', 'no')
- else:
- gajim.config.set('confirm_custom_status', 'yes')
- self.get_status_message(show, on_response, show_pep=False,
- always_ask=True)
-
- confirm_custom_status = gajim.config.get('confirm_custom_status')
- if confirm_custom_status == 'no':
- send_it()
- return
- pritext = _('You are about to send a custom status. Are you sure you want'
- ' to continue?')
- sectext = _('This contact will temporarily see you as %(status)s, '
- 'but only until you change your status. Then he or she will see your '
- 'global status.') % {'status': show}
- dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('Do _not ask me again'), on_response_ok=send_it)
-
- def on_status_combobox_changed(self, widget):
- """
- When we change our status via the combobox
- """
- model = self.status_combobox.get_model()
- active = self.status_combobox.get_active()
- if active == -1: # no active item
- return
- if not self.combobox_callback_active:
- self.previous_status_combobox_active = active
- return
- accounts = gajim.connections.keys()
- if len(accounts) == 0:
- dialogs.ErrorDialog(_('No account available'),
- _('You must create an account before you can chat with other contacts.'))
- self.update_status_combobox()
- return
- status = model[active][2].decode('utf-8')
- statuses_unified = helpers.statuses_unified() # status "desync'ed" or not
- if (active == 7 and statuses_unified) or (active == 9 and \
- not statuses_unified):
- # 'Change status message' selected:
- # do not change show, just show change status dialog
- status = model[self.previous_status_combobox_active][2].decode('utf-8')
- def on_response(message, pep_dict):
- if message is not None: # None if user pressed Cancel
- for account in accounts:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- current_show = gajim.SHOW_LIST[
- gajim.connections[account].connected]
- self.send_status(account, current_show, message)
- self.send_pep(account, pep_dict)
- self.combobox_callback_active = False
- self.status_combobox.set_active(
- self.previous_status_combobox_active)
- self.combobox_callback_active = True
- dialogs.ChangeStatusMessageDialog(on_response, status)
- return
- # we are about to change show, so save this new show so in case
- # after user chooses "Change status message" menuitem
- # we can return to this show
- self.previous_status_combobox_active = active
- connected_accounts = gajim.get_number_of_connected_accounts()
-
- def on_continue(message, pep_dict):
- if message is None:
- # user pressed Cancel to change status message dialog
- self.update_status_combobox()
- return
- global_sync_accounts = []
- for acct in accounts:
- if gajim.config.get_per('accounts', acct,
- 'sync_with_global_status'):
- global_sync_accounts.append(acct)
- global_sync_connected_accounts = \
- gajim.get_number_of_connected_accounts(global_sync_accounts)
- for account in accounts:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- # we are connected (so we wanna change show and status)
- # or no account is connected and we want to connect with new show
- # and status
-
- if not global_sync_connected_accounts > 0 or \
- gajim.connections[account].connected > 0:
- self.send_status(account, status, message)
- self.send_pep(account, pep_dict)
- self.update_status_combobox()
-
- if status == 'invisible':
- bug_user = False
- for account in accounts:
- if connected_accounts < 1 or gajim.account_is_connected(account):
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- # We're going to change our status to invisible
- if self.connected_rooms(account):
- bug_user = True
- break
- if bug_user:
- def on_ok():
- self.get_status_message(status, on_continue, show_pep=False)
-
- def on_cancel():
- self.update_status_combobox()
-
- dialogs.ConfirmationDialog(
- _('You are participating in one or more group chats'),
- _('Changing your status to invisible will result in '
- 'disconnection from those group chats. Are you sure you want to '
- 'go invisible?'), on_reponse_ok=on_ok,
- on_response_cancel=on_cancel)
- return
-
- self.get_status_message(status, on_continue)
-
- def on_preferences_menuitem_activate(self, widget):
- if 'preferences' in gajim.interface.instances:
- gajim.interface.instances['preferences'].window.present()
- else:
- gajim.interface.instances['preferences'] = config.PreferencesWindow()
-
- def on_publish_tune_toggled(self, widget, account):
- active = widget.get_active()
- gajim.config.set_per('accounts', account, 'publish_tune', active)
- if active:
- gajim.interface.enable_music_listener()
- else:
- gajim.connections[account].retract_tune()
- # disable music listener only if no other account uses it
- for acc in gajim.connections:
- if gajim.config.get_per('accounts', acc, 'publish_tune'):
- break
- else:
- gajim.interface.disable_music_listener()
-
- helpers.update_optional_features(account)
-
- def on_publish_location_toggled(self, widget, account):
- active = widget.get_active()
- gajim.config.set_per('accounts', account, 'publish_location', active)
- if active:
- location_listener.enable()
- else:
- gajim.connections[account].retract_location()
- # disable music listener only if no other account uses it
- for acc in gajim.connections:
- if gajim.config.get_per('accounts', acc, 'publish_location'):
- break
- else:
- location_listener.disable()
-
- helpers.update_optional_features(account)
-
- def on_pep_services_menuitem_activate(self, widget, account):
- if 'pep_services' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['pep_services'].window.present()
- else:
- gajim.interface.instances[account]['pep_services'] = \
- config.ManagePEPServicesWindow(account)
-
- def on_add_new_contact(self, widget, account):
- dialogs.AddNewContactWindow(account)
-
- def on_join_gc_activate(self, widget, account):
- """
- When the join gc menuitem is clicked, show the join gc window
- """
- invisible_show = gajim.SHOW_LIST.index('invisible')
- if gajim.connections[account].connected == invisible_show:
- dialogs.ErrorDialog(_('You cannot join a group chat while you are '
- 'invisible'))
- return
- if 'join_gc' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['join_gc'].window.present()
- else:
- try:
- gajim.interface.instances[account]['join_gc'] = \
- dialogs.JoinGroupchatWindow(account)
- except GajimGeneralException:
- pass
-
- def on_new_chat_menuitem_activate(self, widget, account):
- dialogs.NewChatDialog(account)
-
- def on_contents_menuitem_activate(self, widget):
- helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
-
- def on_faq_menuitem_activate(self, widget):
- helpers.launch_browser_mailer('url',
- 'http://trac.gajim.org/wiki/GajimFaq')
-
- def on_features_menuitem_activate(self, widget):
- features_window.FeaturesWindow()
-
- def on_about_menuitem_activate(self, widget):
- dialogs.AboutDialog()
-
- def on_accounts_menuitem_activate(self, widget):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].window.present()
- else:
- gajim.interface.instances['accounts'] = config.AccountsWindow()
-
- def on_file_transfers_menuitem_activate(self, widget):
- if gajim.interface.instances['file_transfers'].window.get_property(
- 'visible'):
- gajim.interface.instances['file_transfers'].window.present()
- else:
- gajim.interface.instances['file_transfers'].window.show_all()
-
- def on_history_menuitem_activate(self, widget):
- if 'logs' in gajim.interface.instances:
- gajim.interface.instances['logs'].window.present()
- else:
- gajim.interface.instances['logs'] = history_window.\
- HistoryWindow()
-
- def on_show_transports_menuitem_activate(self, widget):
- gajim.config.set('show_transports_group', widget.get_active())
- self.refilter_shown_roster_items()
-
- def on_manage_bookmarks_menuitem_activate(self, widget):
- config.ManageBookmarksWindow()
-
- def on_profile_avatar_menuitem_activate(self, widget, account):
- gajim.interface.edit_own_details(account)
-
- def on_execute_command(self, widget, contact, account, resource=None):
- """
- Execute command. Full JID needed; if it is other contact, resource is
- necessary. Widget is unnecessary, only to be able to make this a callback
- """
- jid = contact.jid
- if resource is not None:
- jid = jid + u'/' + resource
- adhoc_commands.CommandWindow(account, jid)
-
- def on_roster_window_focus_in_event(self, widget, event):
- # roster received focus, so if we had urgency REMOVE IT
- # NOTE: we do not have to read the message to remove urgency
- # so this functions does that
- gtkgui_helpers.set_unset_urgency_hint(widget, False)
-
- # if a contact row is selected, update colors (eg. for status msg)
- # because gtk engines may differ in bg when window is selected
- # or not
- if len(self._last_selected_contact):
- for (jid, account) in self._last_selected_contact:
- self.draw_contact(jid, account, selected=True, focus=True)
-
- def on_roster_window_focus_out_event(self, widget, event):
- # if a contact row is selected, update colors (eg. for status msg)
- # because gtk engines may differ in bg when window is selected
- # or not
- if len(self._last_selected_contact):
- for (jid, account) in self._last_selected_contact:
- self.draw_contact(jid, account, selected=True, focus=False)
-
- def on_roster_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- if gajim.interface.msg_win_mgr.mode == \
- MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \
- gajim.interface.msg_win_mgr.one_window_opened():
- # let message window close the tab
- return
- list_of_paths = self.tree.get_selection().get_selected_rows()[1]
- if not len(list_of_paths) and gajim.interface.systray_enabled and \
- not gajim.config.get('quit_on_roster_x_button'):
- self.tooltip.hide_tooltip()
- self.window.hide()
- elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.i:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- for path in list_of_paths:
- type_ = model[path][C_TYPE]
- if type_ in ('contact', 'agent'):
- jid = model[path][C_JID].decode('utf-8')
- account = model[path][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- self.on_info(widget, contact, account)
- elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.h:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- type_ = model[path][C_TYPE]
- if type_ in ('contact', 'agent'):
- jid = model[path][C_JID].decode('utf-8')
- account = model[path][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- self.on_history(widget, contact, account)
-
- def on_roster_window_popup_menu(self, widget):
- event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
- self.show_treeview_menu(event)
-
- def on_row_activated(self, widget, path):
- """
- When an iter is activated (double-click or single click if gnome is set
- this way)
- """
- model = self.modelfilter
- account = model[path][C_ACCOUNT].decode('utf-8')
- type_ = model[path][C_TYPE]
- if type_ in ('group', 'account'):
- if self.tree.row_expanded(path):
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- return
- jid = model[path][C_JID].decode('utf-8')
- resource = None
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- titer = model.get_iter(path)
- if contact.is_groupchat():
- first_ev = gajim.events.get_first_event(account, jid)
- if first_ev and self.open_event(account, jid, first_ev):
- # We are invited to a GC
- # open event cares about connecting to it
- self.remove_groupchat(jid, account)
- else:
- self.on_groupchat_maximized(None, jid, account)
- return
-
- # else
- first_ev = gajim.events.get_first_event(account, jid)
- if not first_ev:
- # look in other resources
- for c in gajim.contacts.get_contacts(account, jid):
- fjid = c.get_full_jid()
- first_ev = gajim.events.get_first_event(account, fjid)
- if first_ev:
- resource = c.resource
- break
- if not first_ev and model.iter_has_child(titer):
- child_iter = model.iter_children(titer)
- while not first_ev and child_iter:
- child_jid = model[child_iter][C_JID].decode('utf-8')
- first_ev = gajim.events.get_first_event(account, child_jid)
- if first_ev:
- jid = child_jid
- else:
- child_iter = model.iter_next(child_iter)
- session = None
- if first_ev:
- if first_ev.type_ in ('chat', 'normal'):
- session = first_ev.parameters[8]
- fjid = jid
- if resource:
- fjid += '/' + resource
- if self.open_event(account, fjid, first_ev):
- return
- # else
- contact = gajim.contacts.get_contact(account, jid, resource)
- if not contact or isinstance(contact, list):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if jid == gajim.get_jid_from_account(account):
- resource = contact.resource
-
- gajim.interface.on_open_chat_window(None, contact, account, \
- resource=resource, session=session)
-
- def on_roster_treeview_row_activated(self, widget, path, col=0):
- """
- When an iter is double clicked: open the first event window
- """
- if not gajim.single_click:
- self.on_row_activated(widget, path)
-
- def on_roster_treeview_row_expanded(self, widget, titer, path):
- """
- When a row is expanded change the icon of the arrow
- """
- self._toggeling_row = True
- model = widget.get_model()
- child_model = model.get_model()
- child_iter = model.convert_iter_to_child_iter(titer)
-
- if self.regroup: # merged accounts
- accounts = gajim.connections.keys()
- else:
- accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
-
- type_ = model[titer][C_TYPE]
- if type_ == 'group':
- group = model[titer][C_JID].decode('utf-8')
- child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[
- '16']['opened']
- for account in accounts:
- if group in gajim.groups[account]: # This account has this group
- gajim.groups[account][group]['expand'] = True
- if account + group in self.collapsed_rows:
- self.collapsed_rows.remove(account + group)
- for contact in gajim.contacts.iter_contacts(account):
- jid = contact.jid
- if group in contact.groups and gajim.contacts.is_big_brother(
- account, jid, accounts) and account + group + jid \
- not in self.collapsed_rows:
- titers = self._get_contact_iter(jid, account)
- for titer in titers:
- path = model.get_path(titer)
- self.tree.expand_row(path, False)
- elif type_ == 'account':
- account = accounts[0] # There is only one cause we don't use merge
- if account in self.collapsed_rows:
- self.collapsed_rows.remove(account)
- self.draw_account(account)
- # When we expand, groups are collapsed. Restore expand state
- for group in gajim.groups[account]:
- if gajim.groups[account][group]['expand']:
- titer = self._get_group_iter(group, account)
- if titer:
- path = model.get_path(titer)
- self.tree.expand_row(path, False)
- elif type_ == 'contact':
- # Metacontact got toggled, update icon
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_contact(account, jid)
- for group in contact.groups:
- if account + group + jid in self.collapsed_rows:
- self.collapsed_rows.remove(account + group + jid)
- family = gajim.contacts.get_metacontacts_family(account, jid)
- nearby_family = \
- self._get_nearby_family_and_big_brother(family, account)[0]
- # Redraw all brothers to show pending events
- for data in nearby_family:
- self.draw_contact(data['jid'], data['account'])
-
- self._toggeling_row = False
-
- def on_roster_treeview_row_collapsed(self, widget, titer, path):
- """
- When a row is collapsed change the icon of the arrow
- """
- self._toggeling_row = True
- model = widget.get_model()
- child_model = model.get_model()
- child_iter = model.convert_iter_to_child_iter(titer)
-
- if self.regroup: # merged accounts
- accounts = gajim.connections.keys()
- else:
- accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
-
- type_ = model[titer][C_TYPE]
- if type_ == 'group':
- child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[
- '16']['closed']
- group = model[titer][C_JID].decode('utf-8')
- for account in accounts:
- if group in gajim.groups[account]: # This account has this group
- gajim.groups[account][group]['expand'] = False
- if account + group not in self.collapsed_rows:
- self.collapsed_rows.append(account + group)
- elif type_ == 'account':
- account = accounts[0] # There is only one cause we don't use merge
- if account not in self.collapsed_rows:
- self.collapsed_rows.append(account)
- self.draw_account(account)
- elif type_ == 'contact':
- # Metacontact got toggled, update icon
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_contact(account, jid)
- for group in contact.groups:
- if account + group + jid not in self.collapsed_rows:
- self.collapsed_rows.append(account + group + jid)
- family = gajim.contacts.get_metacontacts_family(account, jid)
- nearby_family = \
- self._get_nearby_family_and_big_brother(family, account)[0]
- # Redraw all brothers to show pending events
- for data in nearby_family:
- self.draw_contact(data['jid'], data['account'])
-
- self._toggeling_row = False
-
- def on_modelfilter_row_has_child_toggled(self, model, path, titer):
- """
- Called when a row has gotten the first or lost its last child row
-
- Expand Parent if necessary.
- """
- if self._toggeling_row:
- # Signal is emitted when we write to our model
- return
-
- type_ = model[titer][C_TYPE]
- account = model[titer][C_ACCOUNT]
- if not account:
- return
-
- account = account.decode('utf-8')
-
- if type_ == 'contact':
- child_iter = model.convert_iter_to_child_iter(titer)
- if self.model.iter_has_child(child_iter):
- # we are a bigbrother metacontact
- # redraw us to show/hide expand icon
- if self.filtering:
- # Prevent endless loops
- jid = model[titer][C_JID].decode('utf-8')
- gobject.idle_add(self.draw_contact, jid, account)
- elif type_ == 'group':
- group = model[titer][C_JID].decode('utf-8')
- self._adjust_group_expand_collapse_state(group, account)
- elif type_ == 'account':
- self._adjust_account_expand_collapse_state(account)
+ def on_actions_menuitem_activate(self, widget):
+ self.make_menu()
+
+ def on_edit_menuitem_activate(self, widget):
+ """
+ Need to call make_menu to build profile, avatar item
+ """
+ self.make_menu()
+
+ def on_bookmark_menuitem_activate(self, widget, account, bookmark):
+ gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'],
+ bookmark['password'])
+
+ def on_send_server_message_menuitem_activate(self, widget, account):
+ server = gajim.config.get_per('accounts', account, 'hostname')
+ server += '/announce/online'
+ dialogs.SingleMessageWindow(account, server, 'send')
+
+ def on_xml_console_menuitem_activate(self, widget, account):
+ if 'xml_console' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['xml_console'].window.present()
+ else:
+ gajim.interface.instances[account]['xml_console'] = \
+ dialogs.XMLConsoleWindow(account)
+
+ def on_privacy_lists_menuitem_activate(self, widget, account):
+ if 'privacy_lists' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['privacy_lists'].window.present()
+ else:
+ gajim.interface.instances[account]['privacy_lists'] = \
+ dialogs.PrivacyListsWindow(account)
+
+ def on_set_motd_menuitem_activate(self, widget, account):
+ server = gajim.config.get_per('accounts', account, 'hostname')
+ server += '/announce/motd'
+ dialogs.SingleMessageWindow(account, server, 'send')
+
+ def on_update_motd_menuitem_activate(self, widget, account):
+ server = gajim.config.get_per('accounts', account, 'hostname')
+ server += '/announce/motd/update'
+ dialogs.SingleMessageWindow(account, server, 'send')
+
+ def on_delete_motd_menuitem_activate(self, widget, account):
+ server = gajim.config.get_per('accounts', account, 'hostname')
+ server += '/announce/motd/delete'
+ gajim.connections[account].send_motd(server)
+
+ def on_history_manager_menuitem_activate(self, widget):
+ if os.name == 'nt':
+ if os.path.exists('history_manager.exe'): # user is running stable
+ helpers.exec_command('history_manager.exe')
+ else: # user is running svn
+ helpers.exec_command('%s history_manager.py' % sys.executable)
+ else: # Unix user
+ helpers.exec_command('%s history_manager.py' % sys.executable)
+
+ def on_info(self, widget, contact, account):
+ """
+ Call vcard_information_window class to display contact's information
+ """
+ if gajim.connections[account].is_zeroconf:
+ self.on_info_zeroconf(widget, contact, account)
+ return
+
+ info = gajim.interface.instances[account]['infos']
+ if contact.jid in info:
+ info[contact.jid].window.present()
+ else:
+ info[contact.jid] = vcard.VcardWindow(contact, account)
+
+ def on_info_zeroconf(self, widget, contact, account):
+ info = gajim.interface.instances[account]['infos']
+ if contact.jid in info:
+ info[contact.jid].window.present()
+ else:
+ contact = gajim.contacts.get_first_contact_from_jid(account,
+ contact.jid)
+ if contact.show in ('offline', 'error'):
+ # don't show info on offline contacts
+ return
+ info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
+
+ def on_roster_treeview_leave_notify_event(self, widget, event):
+ props = widget.get_path_at_pos(int(event.x), int(event.y))
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id == props[0]:
+ self.tooltip.hide_tooltip()
+
+ def on_roster_treeview_motion_notify_event(self, widget, event):
+ model = widget.get_model()
+ props = widget.get_path_at_pos(int(event.x), int(event.y))
+ if self.tooltip.timeout > 0:
+ if not props or self.tooltip.id != props[0]:
+ self.tooltip.hide_tooltip()
+ if props:
+ row = props[0]
+ titer = None
+ try:
+ titer = model.get_iter(row)
+ except Exception:
+ self.tooltip.hide_tooltip()
+ return
+ if model[titer][C_TYPE] in ('contact', 'self_contact'):
+ # we're on a contact entry in the roster
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ jid = model[titer][C_JID].decode('utf-8')
+ if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
+ self.tooltip.id = row
+ contacts = gajim.contacts.get_contacts(account, jid)
+ connected_contacts = []
+ for c in contacts:
+ if c.show not in ('offline', 'error'):
+ connected_contacts.append(c)
+ if not connected_contacts:
+ # no connected contacts, show the ofline one
+ connected_contacts = contacts
+ self.tooltip.account = account
+ self.tooltip.timeout = gobject.timeout_add(500,
+ self.show_tooltip, connected_contacts)
+ elif model[titer][C_TYPE] == 'groupchat':
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ jid = model[titer][C_JID].decode('utf-8')
+ if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
+ self.tooltip.id = row
+ contact = gajim.contacts.get_contacts(account, jid)
+ self.tooltip.account = account
+ self.tooltip.timeout = gobject.timeout_add(500,
+ self.show_tooltip, contact)
+ elif model[titer][C_TYPE] == 'account':
+ # we're on an account entry in the roster
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ if account == 'all':
+ if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
+ self.tooltip.id = row
+ self.tooltip.account = None
+ self.tooltip.timeout = gobject.timeout_add(500,
+ self.show_tooltip, [])
+ return
+ jid = gajim.get_jid_from_account(account)
+ contacts = []
+ connection = gajim.connections[account]
+ # get our current contact info
+
+ nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
+ accounts = [account])
+ account_name = account
+ if gajim.account_is_connected(account):
+ account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
+ contact = gajim.contacts.create_self_contact(jid=jid, account=account,
+ name=account_name, show=connection.get_status(),
+ status=connection.status, resource=connection.server_resource,
+ priority=connection.priority)
+ if gajim.connections[account].gpg:
+ contact.keyID = gajim.config.get_per('accounts', connection.name,
+ 'keyid')
+ contacts.append(contact)
+ # if we're online ...
+ if connection.connection:
+ roster = connection.connection.getRoster()
+ # in threadless connection when no roster stanza is sent,
+ # 'roster' is None
+ if roster and roster.getItem(jid):
+ resources = roster.getResources(jid)
+ # ...get the contact info for our other online resources
+ for resource in resources:
+ # Check if we already have this resource
+ found = False
+ for contact_ in contacts:
+ if contact_.resource == resource:
+ found = True
+ break
+ if found:
+ continue
+ show = roster.getShow(jid+'/'+resource)
+ if not show:
+ show = 'online'
+ contact = gajim.contacts.create_self_contact(jid=jid,
+ account=account, show=show, status=roster.getStatus(jid + '/' + resource),
+ priority=roster.getPriority(jid + '/' + resource),
+ resource=resource)
+ contacts.append(contact)
+ if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
+ self.tooltip.id = row
+ self.tooltip.account = None
+ self.tooltip.timeout = gobject.timeout_add(500,
+ self.show_tooltip, contacts)
+
+ def on_agent_logging(self, widget, jid, state, account):
+ """
+ When an agent is requested to log in or off
+ """
+ gajim.connections[account].send_agent_status(jid, state)
+
+ def on_edit_agent(self, widget, contact, account):
+ """
+ When we want to modify the agent registration
+ """
+ gajim.connections[account].request_register_agent_info(contact.jid)
+
+ def on_remove_agent(self, widget, list_):
+ """
+ When an agent is requested to be removed. list_ is a list of (contact,
+ account) tuple
+ """
+ for (contact, account) in list_:
+ if gajim.config.get_per('accounts', account, 'hostname') == \
+ contact.jid:
+ # We remove the server contact
+ # remove it from treeview
+ gajim.connections[account].unsubscribe(contact.jid)
+ self.remove_contact(contact.jid, account, backend=True)
+ return
+
+ def remove(list_):
+ for (contact, account) in list_:
+ full_jid = contact.get_full_jid()
+ gajim.connections[account].unsubscribe_agent(full_jid)
+ # remove transport from treeview
+ self.remove_contact(contact.jid, account, backend=True)
+
+ # Check if there are unread events from some contacts
+ has_unread_events = False
+ for (contact, account) in list_:
+ for jid in gajim.events.get_events(account):
+ if jid.endswith(contact.jid):
+ has_unread_events = True
+ break
+ if has_unread_events:
+ dialogs.ErrorDialog(_('You have unread messages'),
+ _('You must read them before removing this transport.'))
+ return
+ if len(list_) == 1:
+ pritext = _('Transport "%s" will be removed') % list_[0][0].jid
+ sectext = _('You will no longer be able to send and receive messages '
+ 'from contacts using this transport.')
+ else:
+ pritext = _('Transports will be removed')
+ jids = ''
+ for (contact, account) in list_:
+ jids += '\n ' + contact.get_shown_name() + ','
+ jids = jids[:-1] + '.'
+ sectext = _('You will no longer be able to send and receive messages '
+ 'to contacts from these transports: %s') % jids
+ dialogs.ConfirmationDialog(pritext, sectext,
+ on_response_ok = (remove, list_))
+
+ def on_block(self, widget, list_, group=None):
+ """
+ When clicked on the 'block' button in context menu. list_ is a list of
+ (contact, account)
+ """
+ def on_continue(msg, pep_dict):
+ if msg is None:
+ # user pressed Cancel to change status message dialog
+ return
+ accounts = []
+ if group is None:
+ for (contact, account) in list_:
+ if account not in accounts:
+ if not gajim.connections[account].privacy_rules_supported:
+ continue
+ accounts.append(account)
+ self.send_status(account, 'offline', msg, to=contact.jid)
+ new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny',
+ 'value' : contact.jid, 'child': [u'message', u'iq',
+ u'presence-out']}
+ gajim.connections[account].blocked_list.append(new_rule)
+ # needed for draw_contact:
+ gajim.connections[account].blocked_contacts.append(
+ contact.jid)
+ self.draw_contact(contact.jid, account)
+ else:
+ for (contact, account) in list_:
+ if account not in accounts:
+ if not gajim.connections[account].privacy_rules_supported:
+ continue
+ accounts.append(account)
+ # needed for draw_group:
+ gajim.connections[account].blocked_groups.append(group)
+ self.draw_group(group, account)
+ self.send_status(account, 'offline', msg, to=contact.jid)
+ self.draw_contact(contact.jid, account)
+ new_rule = {'order': u'1', 'type': u'group', 'action': u'deny',
+ 'value' : group, 'child': [u'message', u'iq', u'presence-out']}
+ gajim.connections[account].blocked_list.append(new_rule)
+ for account in accounts:
+ connection = gajim.connections[account]
+ connection.set_privacy_list('block', connection.blocked_list)
+ if len(connection.blocked_list) == 1:
+ connection.set_active_list('block')
+ connection.set_default_list('block')
+ connection.get_privacy_list('block')
+
+ def _block_it(is_checked=None):
+ if is_checked is not None: # dialog has been shown
+ if is_checked: # user does not want to be asked again
+ gajim.config.set('confirm_block', 'no')
+ else:
+ gajim.config.set('confirm_block', 'yes')
+ self.get_status_message('offline', on_continue, show_pep=False)
+
+ confirm_block = gajim.config.get('confirm_block')
+ if confirm_block == 'no':
+ _block_it()
+ return
+ pritext = _('You are about to block a contact. Are you sure you want'
+ ' to continue?')
+ sectext = _('This contact will see you offline and you will not receive '
+ 'messages he will send you.')
+ dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
+ _('Do _not ask me again'), on_response_ok=_block_it)
+
+ def on_unblock(self, widget, list_, group=None):
+ """
+ When clicked on the 'unblock' button in context menu.
+ """
+ accounts = []
+ if group is None:
+ for (contact, account) in list_:
+ if account not in accounts:
+ if gajim.connections[account].privacy_rules_supported:
+ accounts.append(account)
+ gajim.connections[account].new_blocked_list = []
+ gajim.connections[account].to_unblock = []
+ gajim.connections[account].to_unblock.append(contact.jid)
+ else:
+ gajim.connections[account].to_unblock.append(contact.jid)
+ # needed for draw_contact:
+ if contact.jid in gajim.connections[account].blocked_contacts:
+ gajim.connections[account].blocked_contacts.remove(contact.jid)
+ self.draw_contact(contact.jid, account)
+ for account in accounts:
+ for rule in gajim.connections[account].blocked_list:
+ if rule['action'] != 'deny' or rule['type'] != 'jid' \
+ or rule['value'] not in gajim.connections[account].to_unblock:
+ gajim.connections[account].new_blocked_list.append(rule)
+ else:
+ for (contact, account) in list_:
+ if account not in accounts:
+ if gajim.connections[account].privacy_rules_supported:
+ accounts.append(account)
+ # needed for draw_group:
+ if group in gajim.connections[account].blocked_groups:
+ gajim.connections[account].blocked_groups.remove(group)
+ self.draw_group(group, account)
+ gajim.connections[account].new_blocked_list = []
+ for rule in gajim.connections[account].blocked_list:
+ if rule['action'] != 'deny' or rule['type'] != 'group' \
+ or rule['value'] != group:
+ gajim.connections[account].new_blocked_list.append(rule)
+ self.draw_contact(contact.jid, account)
+ for account in accounts:
+ gajim.connections[account].set_privacy_list('block',
+ gajim.connections[account].new_blocked_list)
+ gajim.connections[account].get_privacy_list('block')
+ if len(gajim.connections[account].new_blocked_list) == 0:
+ gajim.connections[account].blocked_list = []
+ gajim.connections[account].blocked_contacts = []
+ gajim.connections[account].blocked_groups = []
+ gajim.connections[account].set_default_list('')
+ gajim.connections[account].set_active_list('')
+ gajim.connections[account].del_privacy_list('block')
+ if 'blocked_contacts' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['blocked_contacts'].\
+ privacy_list_received([])
+ for (contact, account) in list_:
+ if not self.regroup:
+ show = gajim.SHOW_LIST[gajim.connections[account].connected]
+ else: # accounts merged
+ show = helpers.get_global_show()
+ if show == 'invisible':
+ # Don't send our presence if we're invisible
+ continue
+ if account not in accounts:
+ accounts.append(account)
+ if gajim.connections[account].privacy_rules_supported:
+ self.send_status(account, show,
+ gajim.connections[account].status, to=contact.jid)
+ else:
+ self.send_status(account, show,
+ gajim.connections[account].status, to=contact.jid)
+
+ def on_rename(self, widget, row_type, jid, account):
+ # this function is called either by F2 or by Rename menuitem
+ if 'rename' in gajim.interface.instances:
+ gajim.interface.instances['rename'].dialog.present()
+ return
+
+ # account is offline, don't allow to rename
+ if gajim.connections[account].connected < 2:
+ return
+ if row_type in ('contact', 'agent'):
+ # it's jid
+ title = _('Rename Contact')
+ message = _('Enter a new nickname for contact %s') % jid
+ old_text = gajim.contacts.get_contact_with_highest_priority(account,
+ jid).name
+ elif row_type == 'group':
+ if jid in helpers.special_groups + (_('General'),):
+ return
+ old_text = jid
+ title = _('Rename Group')
+ message = _('Enter a new name for group %s') % \
+ gobject.markup_escape_text(jid)
+
+ def on_renamed(new_text, account, row_type, jid, old_text):
+ if 'rename' in gajim.interface.instances:
+ del gajim.interface.instances['rename']
+ if row_type in ('contact', 'agent'):
+ if old_text == new_text:
+ return
+ for contact in gajim.contacts.get_contacts(account, jid):
+ contact.name = new_text
+ gajim.connections[account].update_contact(jid, new_text, \
+ contact.groups)
+ self.draw_contact(jid, account)
+ # Update opened chats
+ for ctrl in gajim.interface.msg_win_mgr.get_controls(jid, account):
+ ctrl.update_ui()
+ win = gajim.interface.msg_win_mgr.get_window(jid, account)
+ win.redraw_tab(ctrl)
+ win.show_title()
+ elif row_type == 'group':
+ # in C_JID column, we hold the group name (which is not escaped)
+ self.rename_group(old_text, new_text, account)
+
+ def on_canceled():
+ if 'rename' in gajim.interface.instances:
+ del gajim.interface.instances['rename']
+
+ gajim.interface.instances['rename'] = dialogs.InputDialog(title, message,
+ old_text, False, (on_renamed, account, row_type, jid, old_text),
+ on_canceled)
+
+ def on_remove_group_item_activated(self, widget, group, account):
+ def on_ok(checked):
+ for contact in gajim.contacts.get_contacts_from_group(account, group):
+ if not checked:
+ self.remove_contact_from_groups(contact.jid,account, [group])
+ else:
+ gajim.connections[account].unsubscribe(contact.jid)
+ self.remove_contact(contact.jid, account, backend=True)
+
+ dialogs.ConfirmationDialogCheck(_('Remove Group'),
+ _('Do you want to remove group %s from the roster?') % group,
+ _('Also remove all contacts in this group from your roster'),
+ on_response_ok=on_ok)
+
+ def on_assign_pgp_key(self, widget, contact, account):
+ attached_keys = gajim.config.get_per('accounts', account,
+ 'attached_gpg_keys').split()
+ keys = {}
+ keyID = _('None')
+ for i in xrange(len(attached_keys)/2):
+ keys[attached_keys[2*i]] = attached_keys[2*i+1]
+ if attached_keys[2*i] == contact.jid:
+ keyID = attached_keys[2*i+1]
+ public_keys = gajim.connections[account].ask_gpg_keys()
+ public_keys[_('None')] = _('None')
+
+ def on_key_selected(keyID):
+ if keyID is None:
+ return
+ if keyID[0] == _('None'):
+ if contact.jid in keys:
+ del keys[contact.jid]
+ keyID = ''
+ else:
+ keyID = keyID[0]
+ keys[contact.jid] = keyID
+
+ ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
+ if ctrl:
+ ctrl.update_ui()
+
+ keys_str = ''
+ for jid in keys:
+ keys_str += jid + ' ' + keys[jid] + ' '
+ gajim.config.set_per('accounts', account, 'attached_gpg_keys',
+ keys_str)
+ for u in gajim.contacts.get_contacts(account, contact.jid):
+ u.keyID = helpers.prepare_and_validate_gpg_keyID(account,
+ contact.jid, keyID)
+
+ dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'),
+ _('Select a key to apply to the contact'), public_keys,
+ on_key_selected, selected=keyID)
+
+ def on_set_custom_avatar_activate(self, widget, contact, account):
+ def on_ok(widget, path_to_file):
+ filesize = os.path.getsize(path_to_file) # in bytes
+ invalid_file = False
+ msg = ''
+ if os.path.isfile(path_to_file):
+ stat = os.stat(path_to_file)
+ if stat[6] == 0:
+ invalid_file = True
+ msg = _('File is empty')
+ else:
+ invalid_file = True
+ msg = _('File does not exist')
+ if invalid_file:
+ dialogs.ErrorDialog(_('Could not load image'), msg)
+ return
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
+ if filesize > 16384: # 16 kb
+ # get the image at 'tooltip size'
+ # and hope that user did not specify in ACE crazy size
+ pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip')
+ except gobject.GError, msg: # unknown format
+ # msg should be string, not object instance
+ msg = str(msg)
+ dialogs.ErrorDialog(_('Could not load image'), msg)
+ return
+ gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True)
+ dlg.destroy()
+ self.update_avatar_in_gui(contact.jid, account)
+
+ def on_clear(widget):
+ dlg.destroy()
+ # Delete file:
+ gajim.interface.remove_avatar_files(contact.jid, local=True)
+ self.update_avatar_in_gui(contact.jid, account)
+
+ dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok,
+ on_response_clear=on_clear)
+
+ def on_edit_groups(self, widget, list_):
+ dialogs.EditGroupsDialog(list_)
+
+ def on_history(self, widget, contact, account):
+ """
+ When history menuitem is activated: call log window
+ """
+ if 'logs' in gajim.interface.instances:
+ gajim.interface.instances['logs'].window.present()
+ gajim.interface.instances['logs'].open_history(contact.jid, account)
+ else:
+ gajim.interface.instances['logs'] = history_window.\
+ HistoryWindow(contact.jid, account)
+
+ def on_disconnect(self, widget, jid, account):
+ """
+ When disconnect menuitem is activated: disconect from room
+ """
+ if jid in gajim.interface.minimized_controls[account]:
+ ctrl = gajim.interface.minimized_controls[account][jid]
+ ctrl.shutdown()
+ ctrl.got_disconnected()
+ self.remove_groupchat(jid, account)
+
+ def on_reconnect(self, widget, jid, account):
+ """
+ When disconnect menuitem is activated: disconect from room
+ """
+ if jid in gajim.interface.minimized_controls[account]:
+ ctrl = gajim.interface.minimized_controls[account][jid]
+ gajim.interface.join_gc_room(account, jid, ctrl.nick,
+ gajim.gc_passwords.get(jid, ''))
+
+ def on_send_single_message_menuitem_activate(self, widget, account,
+ contact=None):
+ if contact is None:
+ dialogs.SingleMessageWindow(account, action='send')
+ elif isinstance(contact, list):
+ dialogs.SingleMessageWindow(account, contact, 'send')
+ else:
+ jid = contact.jid
+ if contact.jid == gajim.get_jid_from_account(account):
+ jid += '/' + contact.resource
+ dialogs.SingleMessageWindow(account, jid, 'send')
+
+ def on_send_file_menuitem_activate(self, widget, contact, account,
+ resource=None):
+ gajim.interface.instances['file_transfers'].show_file_send_request(
+ account, contact)
+
+ def on_add_special_notification_menuitem_activate(self, widget, jid):
+ dialogs.AddSpecialNotificationDialog(jid)
+
+ def on_invite_to_new_room(self, widget, list_, resource=None):
+ """
+ Resource parameter MUST NOT be used if more than one contact in list
+ """
+ account_list = []
+ jid_list = []
+ for (contact, account) in list_:
+ if contact.jid not in jid_list:
+ if resource: # we MUST have one contact only in list_
+ fjid = contact.jid + '/' + resource
+ jid_list.append(fjid)
+ else:
+ jid_list.append(contact.jid)
+ if account not in account_list:
+ account_list.append(account)
+ # transform None in 'jabber'
+ type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber'
+ for account in account_list:
+ if gajim.connections[account].muc_jid[type_]:
+ # create the room on this muc server
+ if 'join_gc' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['join_gc'].window.destroy()
+ try:
+ gajim.interface.instances[account]['join_gc'] = \
+ dialogs.JoinGroupchatWindow(account,
+ gajim.connections[account].muc_jid[type_],
+ automatic = {'invities': jid_list})
+ except GajimGeneralException:
+ continue
+ break
+
+ def on_invite_to_room(self, widget, list_, room_jid, room_account,
+ resource=None):
+ """
+ Resource parameter MUST NOT be used if more than one contact in list
+ """
+ for e in list_:
+ contact = e[0]
+ contact_jid = contact.jid
+ if resource: # we MUST have one contact only in list_
+ contact_jid += '/' + resource
+ gajim.connections[room_account].send_invite(room_jid, contact_jid)
+
+ def on_all_groupchat_maximized(self, widget, group_list):
+ for (contact, account) in group_list:
+ self.on_groupchat_maximized(widget, contact.jid, account)
+
+ def on_groupchat_maximized(self, widget, jid, account):
+ """
+ When a groupchat is maximized
+ """
+ if not jid in gajim.interface.minimized_controls[account]:
+ # Already opened?
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid, account)
+ if gc_control:
+ mw = gajim.interface.msg_win_mgr.get_window(jid, account)
+ mw.set_active_tab(gc_control)
+ mw.window.window.focus(gtk.get_current_event_time())
+ return
+ ctrl = gajim.interface.minimized_controls[account][jid]
+ mw = gajim.interface.msg_win_mgr.get_window(jid, account)
+ if not mw:
+ mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact,
+ ctrl.account, ctrl.type_id)
+ ctrl.parent_win = mw
+ mw.new_tab(ctrl)
+ mw.set_active_tab(ctrl)
+ mw.window.window.focus(gtk.get_current_event_time())
+ self.remove_groupchat(jid, account)
+
+ def on_edit_account(self, widget, account):
+ if 'accounts' in gajim.interface.instances:
+ gajim.interface.instances['accounts'].window.present()
+ else:
+ gajim.interface.instances['accounts'] = config.AccountsWindow()
+ gajim.interface.instances['accounts'].select_account(account)
+
+ def on_zeroconf_properties(self, widget, account):
+ if 'accounts' in gajim.interface.instances:
+ gajim.interface.instances['accounts'].window.present()
+ else:
+ gajim.interface.instances['accounts'] = config.AccountsWindow()
+ gajim.interface.instances['accounts'].select_account(account)
+
+ def on_open_gmail_inbox(self, widget, account):
+ url = gajim.connections[account].gmail_url
+ if url:
+ helpers.launch_browser_mailer('url', url)
+
+ def on_change_status_message_activate(self, widget, account):
+ show = gajim.SHOW_LIST[gajim.connections[account].connected]
+ def on_response(message, pep_dict):
+ if message is None: # None is if user pressed Cancel
+ return
+ self.send_status(account, show, message)
+ self.send_pep(account, pep_dict)
+ dialogs.ChangeStatusMessageDialog(on_response, show)
+
+ def on_add_to_roster(self, widget, contact, account):
+ dialogs.AddNewContactWindow(account, contact.jid, contact.name)
+
+
+ def on_roster_treeview_scroll_event(self, widget, event):
+ self.tooltip.hide_tooltip()
+
+ def on_roster_treeview_key_press_event(self, widget, event):
+ """
+ When a key is pressed in the treeviews
+ """
+ self.tooltip.hide_tooltip()
+ if event.keyval == gtk.keysyms.Escape:
+ self.tree.get_selection().unselect_all()
+ elif event.keyval == gtk.keysyms.F2:
+ treeselection = self.tree.get_selection()
+ model, list_of_paths = treeselection.get_selected_rows()
+ if len(list_of_paths) != 1:
+ return
+ path = list_of_paths[0]
+ type_ = model[path][C_TYPE]
+ if type_ in ('contact', 'group', 'agent'):
+ jid = model[path][C_JID].decode('utf-8')
+ account = model[path][C_ACCOUNT].decode('utf-8')
+ self.on_rename(widget, type_, jid, account)
+
+ elif event.keyval == gtk.keysyms.Delete:
+ treeselection = self.tree.get_selection()
+ model, list_of_paths = treeselection.get_selected_rows()
+ if not len(list_of_paths):
+ return
+ type_ = model[list_of_paths[0]][C_TYPE]
+ account = model[list_of_paths[0]][C_ACCOUNT].decode('utf-8')
+ if type_ in ('account', 'group', 'self_contact') or \
+ account == gajim.ZEROCONF_ACC_NAME:
+ return
+ list_ = []
+ for path in list_of_paths:
+ if model[path][C_TYPE] != type_:
+ return
+ jid = model[path][C_JID].decode('utf-8')
+ account = model[path][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ list_.append((contact, account))
+ if type_ == 'contact':
+ self.on_req_usub(widget, list_)
+ elif type_ == 'agent':
+ self.on_remove_agent(widget, list_)
+
+ def on_roster_treeview_button_release_event(self, widget, event):
+ try:
+ path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
+ except TypeError:
+ return False
+
+ if event.button == 1: # Left click
+ if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
+ not event.state & gtk.gdk.CONTROL_MASK:
+ # Check if button has been pressed on the same row
+ if self.clicked_path == path:
+ self.on_row_activated(widget, path)
+ self.clicked_path = None
+
+ def on_roster_treeview_button_press_event(self, widget, event):
+ # hide tooltip, no matter the button is pressed
+ self.tooltip.hide_tooltip()
+ try:
+ pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
+ path, x = pos[0], pos[2]
+ except TypeError:
+ self.tree.get_selection().unselect_all()
+ return False
+
+ if event.button == 3: # Right click
+ try:
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ except TypeError:
+ list_of_paths = []
+ if path not in list_of_paths:
+ self.tree.get_selection().unselect_all()
+ self.tree.get_selection().select_path(path)
+ return self.show_treeview_menu(event)
+
+ elif event.button == 2: # Middle click
+ try:
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ except TypeError:
+ list_of_paths = []
+ if list_of_paths != [path]:
+ self.tree.get_selection().unselect_all()
+ self.tree.get_selection().select_path(path)
+ type_ = model[path][C_TYPE]
+ if type_ in ('agent', 'contact', 'self_contact', 'groupchat'):
+ self.on_row_activated(widget, path)
+ elif type_ == 'account':
+ account = model[path][C_ACCOUNT].decode('utf-8')
+ if account != 'all':
+ show = gajim.connections[account].connected
+ if show > 1: # We are connected
+ self.on_change_status_message_activate(widget, account)
+ return True
+ show = helpers.get_global_show()
+ if show == 'offline':
+ return True
+ def on_response(message, pep_dict):
+ if message is None:
+ return True
+ for acct in gajim.connections:
+ if not gajim.config.get_per('accounts', acct,
+ 'sync_with_global_status'):
+ continue
+ current_show = gajim.SHOW_LIST[gajim.connections[acct].\
+ connected]
+ self.send_status(acct, current_show, message)
+ self.send_pep(acct, pep_dict)
+ dialogs.ChangeStatusMessageDialog(on_response, show)
+ return True
+
+ elif event.button == 1: # Left click
+ model = self.modelfilter
+ type_ = model[path][C_TYPE]
+ # x_min is the x start position of status icon column
+ if gajim.config.get('avatar_position_in_roster') == 'left':
+ x_min = gajim.config.get('roster_avatar_width')
+ else:
+ x_min = 0
+ if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \
+ not event.state & gtk.gdk.CONTROL_MASK:
+ # Don't handle double click if we press icon of a metacontact
+ titer = model.get_iter(path)
+ if x > x_min and x < x_min + 27 and type_ == 'contact' and \
+ model.iter_has_child(titer):
+ if (self.tree.row_expanded(path)):
+ self.tree.collapse_row(path)
+ else:
+ self.tree.expand_row(path, False)
+ return
+ # We just save on which row we press button, and open chat window on
+ # button release to be able to do DND without opening chat window
+ self.clicked_path = path
+ return
+ else:
+ if type_ == 'group' and x < 27:
+ # first cell in 1st column (the arrow SINGLE clicked)
+ if (self.tree.row_expanded(path)):
+ self.tree.collapse_row(path)
+ else:
+ self.tree.expand_row(path, False)
+
+ elif type_ == 'contact' and x > x_min and x < x_min + 27:
+ if (self.tree.row_expanded(path)):
+ self.tree.collapse_row(path)
+ else:
+ self.tree.expand_row(path, False)
+
+ def on_req_usub(self, widget, list_):
+ """
+ Remove a contact. list_ is a list of (contact, account) tuples
+ """
+ def on_ok(is_checked, list_):
+ remove_auth = True
+ if len(list_) == 1:
+ contact = list_[0][0]
+ if contact.sub != 'to' and is_checked:
+ remove_auth = False
+ for (contact, account) in list_:
+ if _('Not in Roster') not in contact.get_shown_groups():
+ gajim.connections[account].unsubscribe(contact.jid, remove_auth)
+ self.remove_contact(contact.jid, account, backend=True)
+ if not remove_auth and contact.sub == 'both':
+ contact.name = ''
+ contact.groups = []
+ contact.sub = 'from'
+ # we can't see him, but have to set it manually in contact
+ contact.show = 'offline'
+ gajim.contacts.add_contact(account, contact)
+ self.add_contact(contact.jid, account)
+ def on_ok2(list_):
+ on_ok(False, list_)
+
+ if len(list_) == 1:
+ contact = list_[0][0]
+ pritext = _('Contact "%s" will be removed from your roster') % \
+ contact.get_shown_name()
+ sectext = _('You are about to remove "%(name)s" (%(jid)s) from your '
+ 'roster.\n') % {'name': contact.get_shown_name(),
+ 'jid': contact.jid}
+ if contact.sub == 'to':
+ dialogs.ConfirmationDialog(pritext, sectext + \
+ _('By removing this contact you also remove authorization '
+ 'resulting in him or her always seeing you as offline.'),
+ on_response_ok = (on_ok2, list_))
+ elif _('Not in Roster') in contact.get_shown_groups():
+ # Contact is not in roster
+ dialogs.ConfirmationDialog(pritext, sectext + \
+ _('Do you want to continue?'), on_response_ok = (on_ok2, list_))
+ else:
+ dialogs.ConfirmationDialogCheck(pritext, sectext + \
+ _('By removing this contact you also by default remove '
+ 'authorization resulting in him or her always seeing you as '
+ 'offline.'),
+ _('I want this contact to know my status after removal'),
+ on_response_ok = (on_ok, list_))
+ else:
+ # several contact to remove at the same time
+ pritext = _('Contacts will be removed from your roster')
+ jids = ''
+ for (contact, account) in list_:
+ jids += '\n ' + contact.get_shown_name() + ' (%s)' % contact.jid +\
+ ','
+ sectext = _('By removing these contacts:%s\nyou also remove '
+ 'authorization resulting in them always seeing you as offline.') % \
+ jids
+ dialogs.ConfirmationDialog(pritext, sectext,
+ on_response_ok = (on_ok2, list_))
+
+ def on_send_custom_status(self, widget, contact_list, show, group=None):
+ """
+ Send custom status
+ """
+ # contact_list has only one element except if group != None
+ def on_response(message, pep_dict):
+ if message is None: # None if user pressed Cancel
+ return
+ account_list = []
+ for (contact, account) in contact_list:
+ if account not in account_list:
+ account_list.append(account)
+ # 1. update status_sent_to_[groups|users] list
+ if group:
+ for account in account_list:
+ if account not in gajim.interface.status_sent_to_groups:
+ gajim.interface.status_sent_to_groups[account] = {}
+ gajim.interface.status_sent_to_groups[account][group] = show
+ else:
+ for (contact, account) in contact_list:
+ if account not in gajim.interface.status_sent_to_users:
+ gajim.interface.status_sent_to_users[account] = {}
+ gajim.interface.status_sent_to_users[account][contact.jid] = show
+
+ # 2. update privacy lists if main status is invisible
+ for account in account_list:
+ if gajim.SHOW_LIST[gajim.connections[account].connected] == \
+ 'invisible':
+ gajim.connections[account].set_invisible_rule()
+
+ # 3. send directed presence
+ for (contact, account) in contact_list:
+ our_jid = gajim.get_jid_from_account(account)
+ jid = contact.jid
+ if jid == our_jid:
+ jid += '/' + contact.resource
+ self.send_status(account, show, message, to=jid)
+
+ def send_it(is_checked=None):
+ if is_checked is not None: # dialog has been shown
+ if is_checked: # user does not want to be asked again
+ gajim.config.set('confirm_custom_status', 'no')
+ else:
+ gajim.config.set('confirm_custom_status', 'yes')
+ self.get_status_message(show, on_response, show_pep=False,
+ always_ask=True)
+
+ confirm_custom_status = gajim.config.get('confirm_custom_status')
+ if confirm_custom_status == 'no':
+ send_it()
+ return
+ pritext = _('You are about to send a custom status. Are you sure you want'
+ ' to continue?')
+ sectext = _('This contact will temporarily see you as %(status)s, '
+ 'but only until you change your status. Then he or she will see your '
+ 'global status.') % {'status': show}
+ dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
+ _('Do _not ask me again'), on_response_ok=send_it)
+
+ def on_status_combobox_changed(self, widget):
+ """
+ When we change our status via the combobox
+ """
+ model = self.status_combobox.get_model()
+ active = self.status_combobox.get_active()
+ if active == -1: # no active item
+ return
+ if not self.combobox_callback_active:
+ self.previous_status_combobox_active = active
+ return
+ accounts = gajim.connections.keys()
+ if len(accounts) == 0:
+ dialogs.ErrorDialog(_('No account available'),
+ _('You must create an account before you can chat with other contacts.'))
+ self.update_status_combobox()
+ return
+ status = model[active][2].decode('utf-8')
+ statuses_unified = helpers.statuses_unified() # status "desync'ed" or not
+ if (active == 7 and statuses_unified) or (active == 9 and \
+ not statuses_unified):
+ # 'Change status message' selected:
+ # do not change show, just show change status dialog
+ status = model[self.previous_status_combobox_active][2].decode('utf-8')
+ def on_response(message, pep_dict):
+ if message is not None: # None if user pressed Cancel
+ for account in accounts:
+ if not gajim.config.get_per('accounts', account,
+ 'sync_with_global_status'):
+ continue
+ current_show = gajim.SHOW_LIST[
+ gajim.connections[account].connected]
+ self.send_status(account, current_show, message)
+ self.send_pep(account, pep_dict)
+ self.combobox_callback_active = False
+ self.status_combobox.set_active(
+ self.previous_status_combobox_active)
+ self.combobox_callback_active = True
+ dialogs.ChangeStatusMessageDialog(on_response, status)
+ return
+ # we are about to change show, so save this new show so in case
+ # after user chooses "Change status message" menuitem
+ # we can return to this show
+ self.previous_status_combobox_active = active
+ connected_accounts = gajim.get_number_of_connected_accounts()
+
+ def on_continue(message, pep_dict):
+ if message is None:
+ # user pressed Cancel to change status message dialog
+ self.update_status_combobox()
+ return
+ global_sync_accounts = []
+ for acct in accounts:
+ if gajim.config.get_per('accounts', acct,
+ 'sync_with_global_status'):
+ global_sync_accounts.append(acct)
+ global_sync_connected_accounts = \
+ gajim.get_number_of_connected_accounts(global_sync_accounts)
+ for account in accounts:
+ if not gajim.config.get_per('accounts', account,
+ 'sync_with_global_status'):
+ continue
+ # we are connected (so we wanna change show and status)
+ # or no account is connected and we want to connect with new show
+ # and status
+
+ if not global_sync_connected_accounts > 0 or \
+ gajim.connections[account].connected > 0:
+ self.send_status(account, status, message)
+ self.send_pep(account, pep_dict)
+ self.update_status_combobox()
+
+ if status == 'invisible':
+ bug_user = False
+ for account in accounts:
+ if connected_accounts < 1 or gajim.account_is_connected(account):
+ if not gajim.config.get_per('accounts', account,
+ 'sync_with_global_status'):
+ continue
+ # We're going to change our status to invisible
+ if self.connected_rooms(account):
+ bug_user = True
+ break
+ if bug_user:
+ def on_ok():
+ self.get_status_message(status, on_continue, show_pep=False)
+
+ def on_cancel():
+ self.update_status_combobox()
+
+ dialogs.ConfirmationDialog(
+ _('You are participating in one or more group chats'),
+ _('Changing your status to invisible will result in '
+ 'disconnection from those group chats. Are you sure you want to '
+ 'go invisible?'), on_reponse_ok=on_ok,
+ on_response_cancel=on_cancel)
+ return
+
+ self.get_status_message(status, on_continue)
+
+ def on_preferences_menuitem_activate(self, widget):
+ if 'preferences' in gajim.interface.instances:
+ gajim.interface.instances['preferences'].window.present()
+ else:
+ gajim.interface.instances['preferences'] = config.PreferencesWindow()
+
+ def on_publish_tune_toggled(self, widget, account):
+ active = widget.get_active()
+ gajim.config.set_per('accounts', account, 'publish_tune', active)
+ if active:
+ gajim.interface.enable_music_listener()
+ else:
+ gajim.connections[account].retract_tune()
+ # disable music listener only if no other account uses it
+ for acc in gajim.connections:
+ if gajim.config.get_per('accounts', acc, 'publish_tune'):
+ break
+ else:
+ gajim.interface.disable_music_listener()
+
+ helpers.update_optional_features(account)
+
+ def on_publish_location_toggled(self, widget, account):
+ active = widget.get_active()
+ gajim.config.set_per('accounts', account, 'publish_location', active)
+ if active:
+ location_listener.enable()
+ else:
+ gajim.connections[account].retract_location()
+ # disable music listener only if no other account uses it
+ for acc in gajim.connections:
+ if gajim.config.get_per('accounts', acc, 'publish_location'):
+ break
+ else:
+ location_listener.disable()
+
+ helpers.update_optional_features(account)
+
+ def on_pep_services_menuitem_activate(self, widget, account):
+ if 'pep_services' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['pep_services'].window.present()
+ else:
+ gajim.interface.instances[account]['pep_services'] = \
+ config.ManagePEPServicesWindow(account)
+
+ def on_add_new_contact(self, widget, account):
+ dialogs.AddNewContactWindow(account)
+
+ def on_join_gc_activate(self, widget, account):
+ """
+ When the join gc menuitem is clicked, show the join gc window
+ """
+ invisible_show = gajim.SHOW_LIST.index('invisible')
+ if gajim.connections[account].connected == invisible_show:
+ dialogs.ErrorDialog(_('You cannot join a group chat while you are '
+ 'invisible'))
+ return
+ if 'join_gc' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['join_gc'].window.present()
+ else:
+ try:
+ gajim.interface.instances[account]['join_gc'] = \
+ dialogs.JoinGroupchatWindow(account)
+ except GajimGeneralException:
+ pass
+
+ def on_new_chat_menuitem_activate(self, widget, account):
+ dialogs.NewChatDialog(account)
+
+ def on_contents_menuitem_activate(self, widget):
+ helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki')
+
+ def on_faq_menuitem_activate(self, widget):
+ helpers.launch_browser_mailer('url',
+ 'http://trac.gajim.org/wiki/GajimFaq')
+
+ def on_features_menuitem_activate(self, widget):
+ features_window.FeaturesWindow()
+
+ def on_about_menuitem_activate(self, widget):
+ dialogs.AboutDialog()
+
+ def on_accounts_menuitem_activate(self, widget):
+ if 'accounts' in gajim.interface.instances:
+ gajim.interface.instances['accounts'].window.present()
+ else:
+ gajim.interface.instances['accounts'] = config.AccountsWindow()
+
+ def on_file_transfers_menuitem_activate(self, widget):
+ if gajim.interface.instances['file_transfers'].window.get_property(
+ 'visible'):
+ gajim.interface.instances['file_transfers'].window.present()
+ else:
+ gajim.interface.instances['file_transfers'].window.show_all()
+
+ def on_history_menuitem_activate(self, widget):
+ if 'logs' in gajim.interface.instances:
+ gajim.interface.instances['logs'].window.present()
+ else:
+ gajim.interface.instances['logs'] = history_window.\
+ HistoryWindow()
+
+ def on_show_transports_menuitem_activate(self, widget):
+ gajim.config.set('show_transports_group', widget.get_active())
+ self.refilter_shown_roster_items()
+
+ def on_manage_bookmarks_menuitem_activate(self, widget):
+ config.ManageBookmarksWindow()
+
+ def on_profile_avatar_menuitem_activate(self, widget, account):
+ gajim.interface.edit_own_details(account)
+
+ def on_execute_command(self, widget, contact, account, resource=None):
+ """
+ Execute command. Full JID needed; if it is other contact, resource is
+ necessary. Widget is unnecessary, only to be able to make this a callback
+ """
+ jid = contact.jid
+ if resource is not None:
+ jid = jid + u'/' + resource
+ adhoc_commands.CommandWindow(account, jid)
+
+ def on_roster_window_focus_in_event(self, widget, event):
+ # roster received focus, so if we had urgency REMOVE IT
+ # NOTE: we do not have to read the message to remove urgency
+ # so this functions does that
+ gtkgui_helpers.set_unset_urgency_hint(widget, False)
+
+ # if a contact row is selected, update colors (eg. for status msg)
+ # because gtk engines may differ in bg when window is selected
+ # or not
+ if len(self._last_selected_contact):
+ for (jid, account) in self._last_selected_contact:
+ self.draw_contact(jid, account, selected=True, focus=True)
+
+ def on_roster_window_focus_out_event(self, widget, event):
+ # if a contact row is selected, update colors (eg. for status msg)
+ # because gtk engines may differ in bg when window is selected
+ # or not
+ if len(self._last_selected_contact):
+ for (jid, account) in self._last_selected_contact:
+ self.draw_contact(jid, account, selected=True, focus=False)
+
+ def on_roster_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ if gajim.interface.msg_win_mgr.mode == \
+ MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \
+ gajim.interface.msg_win_mgr.one_window_opened():
+ # let message window close the tab
+ return
+ list_of_paths = self.tree.get_selection().get_selected_rows()[1]
+ if not len(list_of_paths) and gajim.interface.systray_enabled and \
+ not gajim.config.get('quit_on_roster_x_button'):
+ self.tooltip.hide_tooltip()
+ self.window.hide()
+ elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.i:
+ treeselection = self.tree.get_selection()
+ model, list_of_paths = treeselection.get_selected_rows()
+ for path in list_of_paths:
+ type_ = model[path][C_TYPE]
+ if type_ in ('contact', 'agent'):
+ jid = model[path][C_JID].decode('utf-8')
+ account = model[path][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ self.on_info(widget, contact, account)
+ elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.h:
+ treeselection = self.tree.get_selection()
+ model, list_of_paths = treeselection.get_selected_rows()
+ if len(list_of_paths) != 1:
+ return
+ path = list_of_paths[0]
+ type_ = model[path][C_TYPE]
+ if type_ in ('contact', 'agent'):
+ jid = model[path][C_JID].decode('utf-8')
+ account = model[path][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+ self.on_history(widget, contact, account)
+
+ def on_roster_window_popup_menu(self, widget):
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
+ self.show_treeview_menu(event)
+
+ def on_row_activated(self, widget, path):
+ """
+ When an iter is activated (double-click or single click if gnome is set
+ this way)
+ """
+ model = self.modelfilter
+ account = model[path][C_ACCOUNT].decode('utf-8')
+ type_ = model[path][C_TYPE]
+ if type_ in ('group', 'account'):
+ if self.tree.row_expanded(path):
+ self.tree.collapse_row(path)
+ else:
+ self.tree.expand_row(path, False)
+ return
+ jid = model[path][C_JID].decode('utf-8')
+ resource = None
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ titer = model.get_iter(path)
+ if contact.is_groupchat():
+ first_ev = gajim.events.get_first_event(account, jid)
+ if first_ev and self.open_event(account, jid, first_ev):
+ # We are invited to a GC
+ # open event cares about connecting to it
+ self.remove_groupchat(jid, account)
+ else:
+ self.on_groupchat_maximized(None, jid, account)
+ return
+
+ # else
+ first_ev = gajim.events.get_first_event(account, jid)
+ if not first_ev:
+ # look in other resources
+ for c in gajim.contacts.get_contacts(account, jid):
+ fjid = c.get_full_jid()
+ first_ev = gajim.events.get_first_event(account, fjid)
+ if first_ev:
+ resource = c.resource
+ break
+ if not first_ev and model.iter_has_child(titer):
+ child_iter = model.iter_children(titer)
+ while not first_ev and child_iter:
+ child_jid = model[child_iter][C_JID].decode('utf-8')
+ first_ev = gajim.events.get_first_event(account, child_jid)
+ if first_ev:
+ jid = child_jid
+ else:
+ child_iter = model.iter_next(child_iter)
+ session = None
+ if first_ev:
+ if first_ev.type_ in ('chat', 'normal'):
+ session = first_ev.parameters[8]
+ fjid = jid
+ if resource:
+ fjid += '/' + resource
+ if self.open_event(account, fjid, first_ev):
+ return
+ # else
+ contact = gajim.contacts.get_contact(account, jid, resource)
+ if not contact or isinstance(contact, list):
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ if jid == gajim.get_jid_from_account(account):
+ resource = contact.resource
+
+ gajim.interface.on_open_chat_window(None, contact, account, \
+ resource=resource, session=session)
+
+ def on_roster_treeview_row_activated(self, widget, path, col=0):
+ """
+ When an iter is double clicked: open the first event window
+ """
+ if not gajim.single_click:
+ self.on_row_activated(widget, path)
+
+ def on_roster_treeview_row_expanded(self, widget, titer, path):
+ """
+ When a row is expanded change the icon of the arrow
+ """
+ self._toggeling_row = True
+ model = widget.get_model()
+ child_model = model.get_model()
+ child_iter = model.convert_iter_to_child_iter(titer)
+
+ if self.regroup: # merged accounts
+ accounts = gajim.connections.keys()
+ else:
+ accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
+
+ type_ = model[titer][C_TYPE]
+ if type_ == 'group':
+ group = model[titer][C_JID].decode('utf-8')
+ child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[
+ '16']['opened']
+ for account in accounts:
+ if group in gajim.groups[account]: # This account has this group
+ gajim.groups[account][group]['expand'] = True
+ if account + group in self.collapsed_rows:
+ self.collapsed_rows.remove(account + group)
+ for contact in gajim.contacts.iter_contacts(account):
+ jid = contact.jid
+ if group in contact.groups and gajim.contacts.is_big_brother(
+ account, jid, accounts) and account + group + jid \
+ not in self.collapsed_rows:
+ titers = self._get_contact_iter(jid, account)
+ for titer in titers:
+ path = model.get_path(titer)
+ self.tree.expand_row(path, False)
+ elif type_ == 'account':
+ account = accounts[0] # There is only one cause we don't use merge
+ if account in self.collapsed_rows:
+ self.collapsed_rows.remove(account)
+ self.draw_account(account)
+ # When we expand, groups are collapsed. Restore expand state
+ for group in gajim.groups[account]:
+ if gajim.groups[account][group]['expand']:
+ titer = self._get_group_iter(group, account)
+ if titer:
+ path = model.get_path(titer)
+ self.tree.expand_row(path, False)
+ elif type_ == 'contact':
+ # Metacontact got toggled, update icon
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_contact(account, jid)
+ for group in contact.groups:
+ if account + group + jid in self.collapsed_rows:
+ self.collapsed_rows.remove(account + group + jid)
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ nearby_family = \
+ self._get_nearby_family_and_big_brother(family, account)[0]
+ # Redraw all brothers to show pending events
+ for data in nearby_family:
+ self.draw_contact(data['jid'], data['account'])
+
+ self._toggeling_row = False
+
+ def on_roster_treeview_row_collapsed(self, widget, titer, path):
+ """
+ When a row is collapsed change the icon of the arrow
+ """
+ self._toggeling_row = True
+ model = widget.get_model()
+ child_model = model.get_model()
+ child_iter = model.convert_iter_to_child_iter(titer)
+
+ if self.regroup: # merged accounts
+ accounts = gajim.connections.keys()
+ else:
+ accounts = [model[titer][C_ACCOUNT].decode('utf-8')]
+
+ type_ = model[titer][C_TYPE]
+ if type_ == 'group':
+ child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images[
+ '16']['closed']
+ group = model[titer][C_JID].decode('utf-8')
+ for account in accounts:
+ if group in gajim.groups[account]: # This account has this group
+ gajim.groups[account][group]['expand'] = False
+ if account + group not in self.collapsed_rows:
+ self.collapsed_rows.append(account + group)
+ elif type_ == 'account':
+ account = accounts[0] # There is only one cause we don't use merge
+ if account not in self.collapsed_rows:
+ self.collapsed_rows.append(account)
+ self.draw_account(account)
+ elif type_ == 'contact':
+ # Metacontact got toggled, update icon
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_contact(account, jid)
+ for group in contact.groups:
+ if account + group + jid not in self.collapsed_rows:
+ self.collapsed_rows.append(account + group + jid)
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ nearby_family = \
+ self._get_nearby_family_and_big_brother(family, account)[0]
+ # Redraw all brothers to show pending events
+ for data in nearby_family:
+ self.draw_contact(data['jid'], data['account'])
+
+ self._toggeling_row = False
+
+ def on_modelfilter_row_has_child_toggled(self, model, path, titer):
+ """
+ Called when a row has gotten the first or lost its last child row
+
+ Expand Parent if necessary.
+ """
+ if self._toggeling_row:
+ # Signal is emitted when we write to our model
+ return
+
+ type_ = model[titer][C_TYPE]
+ account = model[titer][C_ACCOUNT]
+ if not account:
+ return
+
+ account = account.decode('utf-8')
+
+ if type_ == 'contact':
+ child_iter = model.convert_iter_to_child_iter(titer)
+ if self.model.iter_has_child(child_iter):
+ # we are a bigbrother metacontact
+ # redraw us to show/hide expand icon
+ if self.filtering:
+ # Prevent endless loops
+ jid = model[titer][C_JID].decode('utf-8')
+ gobject.idle_add(self.draw_contact, jid, account)
+ elif type_ == 'group':
+ group = model[titer][C_JID].decode('utf-8')
+ self._adjust_group_expand_collapse_state(group, account)
+ elif type_ == 'account':
+ self._adjust_account_expand_collapse_state(account)
# Selection can change when the model is filtered
# Only write to the model when filtering is finished!
#
# FIXME: When we are filtering our custom colors are somehow lost
#
-# def on_treeview_selection_changed(self, selection):
-# '''Called when selection in TreeView has changed.
+# def on_treeview_selection_changed(self, selection):
+# '''Called when selection in TreeView has changed.
#
-# Redraw unselected rows to make status message readable
-# on all possible backgrounds.
-# '''
-# model, list_of_paths = selection.get_selected_rows()
-# if len(self._last_selected_contact):
-# # update unselected rows
-# for (jid, account) in self._last_selected_contact:
-# gobject.idle_add(self.draw_contact, jid, account)
-# self._last_selected_contact = []
-# if len(list_of_paths) == 0:
-# return
-# for path in list_of_paths:
-# row = model[path]
-# if row[C_TYPE] != 'contact':
-# self._last_selected_contact = []
-# return
-# jid = row[C_JID].decode('utf-8')
-# account = row[C_ACCOUNT].decode('utf-8')
-# self._last_selected_contact.append((jid, account))
-# gobject.idle_add(self.draw_contact, jid, account, True)
-
- def on_service_disco_menuitem_activate(self, widget, account):
- server_jid = gajim.config.get_per('accounts', account, 'hostname')
- if server_jid in gajim.interface.instances[account]['disco']:
- gajim.interface.instances[account]['disco'][server_jid].\
- window.present()
- else:
- try:
- # Object will add itself to the window dict
- disco.ServiceDiscoveryWindow(account, address_entry=True)
- except GajimGeneralException:
- pass
-
- def on_show_offline_contacts_menuitem_activate(self, widget):
- """
- When show offline option is changed: redraw the treeview
- """
- gajim.config.set('showoffline', not gajim.config.get('showoffline'))
- self.refilter_shown_roster_items()
- w = self.xml.get_object('show_only_active_contacts_menuitem')
- if gajim.config.get('showoffline'):
- # We need to filter twice to show groups with no contacts inside
- # in the correct expand state
- self.refilter_shown_roster_items()
- w.set_sensitive(False)
- else:
- w.set_sensitive(True)
-
- def on_show_only_active_contacts_menuitem_activate(self, widget):
- """
- When show only active contact option is changed: redraw the treeview
- """
- gajim.config.set('show_only_chat_and_online', not gajim.config.get(
- 'show_only_chat_and_online'))
- self.refilter_shown_roster_items()
- w = self.xml.get_object('show_offline_contacts_menuitem')
- if gajim.config.get('show_only_chat_and_online'):
- # We need to filter twice to show groups with no contacts inside
- # in the correct expand state
- self.refilter_shown_roster_items()
- w.set_sensitive(False)
- else:
- w.set_sensitive(True)
-
- def on_view_menu_activate(self, widget):
- # Hide the show roster menu if we are not in the right windowing mode.
- if self.hpaned.get_child2() is not None:
- self.xml.get_object('show_roster_menuitem').show()
- else:
- self.xml.get_object('show_roster_menuitem').hide()
-
- def on_show_roster_menuitem_toggled(self, widget):
- # when num controls is 0 this menuitem is hidden, but still need to
- # disable keybinding
- if self.hpaned.get_child2() is not None:
- self.show_roster_vbox(widget.get_active())
+# Redraw unselected rows to make status message readable
+# on all possible backgrounds.
+# '''
+# model, list_of_paths = selection.get_selected_rows()
+# if len(self._last_selected_contact):
+# # update unselected rows
+# for (jid, account) in self._last_selected_contact:
+# gobject.idle_add(self.draw_contact, jid, account)
+# self._last_selected_contact = []
+# if len(list_of_paths) == 0:
+# return
+# for path in list_of_paths:
+# row = model[path]
+# if row[C_TYPE] != 'contact':
+# self._last_selected_contact = []
+# return
+# jid = row[C_JID].decode('utf-8')
+# account = row[C_ACCOUNT].decode('utf-8')
+# self._last_selected_contact.append((jid, account))
+# gobject.idle_add(self.draw_contact, jid, account, True)
+
+ def on_service_disco_menuitem_activate(self, widget, account):
+ server_jid = gajim.config.get_per('accounts', account, 'hostname')
+ if server_jid in gajim.interface.instances[account]['disco']:
+ gajim.interface.instances[account]['disco'][server_jid].\
+ window.present()
+ else:
+ try:
+ # Object will add itself to the window dict
+ disco.ServiceDiscoveryWindow(account, address_entry=True)
+ except GajimGeneralException:
+ pass
+
+ def on_show_offline_contacts_menuitem_activate(self, widget):
+ """
+ When show offline option is changed: redraw the treeview
+ """
+ gajim.config.set('showoffline', not gajim.config.get('showoffline'))
+ self.refilter_shown_roster_items()
+ w = self.xml.get_object('show_only_active_contacts_menuitem')
+ if gajim.config.get('showoffline'):
+ # We need to filter twice to show groups with no contacts inside
+ # in the correct expand state
+ self.refilter_shown_roster_items()
+ w.set_sensitive(False)
+ else:
+ w.set_sensitive(True)
+
+ def on_show_only_active_contacts_menuitem_activate(self, widget):
+ """
+ When show only active contact option is changed: redraw the treeview
+ """
+ gajim.config.set('show_only_chat_and_online', not gajim.config.get(
+ 'show_only_chat_and_online'))
+ self.refilter_shown_roster_items()
+ w = self.xml.get_object('show_offline_contacts_menuitem')
+ if gajim.config.get('show_only_chat_and_online'):
+ # We need to filter twice to show groups with no contacts inside
+ # in the correct expand state
+ self.refilter_shown_roster_items()
+ w.set_sensitive(False)
+ else:
+ w.set_sensitive(True)
+
+ def on_view_menu_activate(self, widget):
+ # Hide the show roster menu if we are not in the right windowing mode.
+ if self.hpaned.get_child2() is not None:
+ self.xml.get_object('show_roster_menuitem').show()
+ else:
+ self.xml.get_object('show_roster_menuitem').hide()
+
+ def on_show_roster_menuitem_toggled(self, widget):
+ # when num controls is 0 this menuitem is hidden, but still need to
+ # disable keybinding
+ if self.hpaned.get_child2() is not None:
+ self.show_roster_vbox(widget.get_active())
################################################################################
### Drag and Drop handling
################################################################################
- def drag_data_get_data(self, treeview, context, selection, target_id, etime):
- model, list_of_paths = self.tree.get_selection().get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- data = ''
- if len(path) >= 3:
- data = model[path][C_JID]
- selection.set(selection.target, 8, data)
-
- def drag_begin(self, treeview, context):
- self.dragging = True
-
- def drag_end(self, treeview, context):
- self.dragging = False
-
- def on_drop_rosterx(self, widget, account_source, c_source, account_dest,
- c_dest, was_big_brother, context, etime):
- gajim.connections[account_dest].send_contacts([c_source], c_dest.jid)
-
- def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
- c_dest, was_big_brother, context, etime):
-
- if not gajim.connections[account_source].private_storage_supported or not\
- gajim.connections[account_dest].private_storage_supported:
- dialogs.WarningDialog(_('Metacontacts storage not supported by your '
- 'server'),
- _('Your server does not support storing metacontacts information. '
- 'So those information will not be saved on next reconnection.'))
-
- def merge_contacts(is_checked=None):
- contacts = 0
- if is_checked is not None: # dialog has been shown
- if is_checked: # user does not want to be asked again
- gajim.config.set('confirm_metacontacts', 'no')
- else:
- gajim.config.set('confirm_metacontacts', 'yes')
-
- # We might have dropped on a metacontact.
- # Remove it and readd later with updated family info
- dest_family = gajim.contacts.get_metacontacts_family(account_dest,
- c_dest.jid)
- if dest_family:
- self._remove_metacontact_family(dest_family, account_dest)
- source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid)
- if dest_family == source_family:
- n = contacts = len(dest_family)
- for tag in source_family:
- if tag['jid'] == c_source.jid:
- tag['order'] = contacts
- continue
- if 'order' in tag:
- n -= 1
- tag['order'] = n
- else:
- self._remove_entity(c_dest, account_dest)
-
- old_family = gajim.contacts.get_metacontacts_family(account_source,
- c_source.jid)
- old_groups = c_source.groups
-
- # Remove old source contact(s)
- if was_big_brother:
- # We have got little brothers. Readd them all
- self._remove_metacontact_family(old_family, account_source)
- else:
- # We are only a litle brother. Simply remove us from our big brother
- if self._get_contact_iter(c_source.jid, account_source):
- # When we have been in the group before.
- # Do not try to remove us again
- self._remove_entity(c_source, account_source)
-
- own_data = {}
- own_data['jid'] = c_source.jid
- own_data['account'] = account_source
- # Don't touch the rest of the family
- old_family = [own_data]
-
- # Apply new tag and update contact
- for data in old_family:
- if account_source != data['account'] and not self.regroup:
- continue
-
- _account = data['account']
- _jid = data['jid']
- _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
- if not _contact:
- # One of the metacontacts may be not connected.
- continue
-
- _contact.groups = c_dest.groups[:]
- gajim.contacts.add_metacontact(account_dest, c_dest.jid,
- _account, _contact.jid, contacts)
- gajim.connections[account_source].update_contact(_contact.jid,
- _contact.name, _contact.groups)
-
- # Re-add all and update GUI
- new_family = gajim.contacts.get_metacontacts_family(account_source,
- c_source.jid)
- brothers = self._add_metacontact_family(new_family, account_source)
-
- for c, acc in brothers:
- self.draw_completely(c.jid, acc)
-
- old_groups.extend(c_dest.groups)
- for g in old_groups:
- self.draw_group(g, account_source)
-
- self.draw_account(account_source)
- context.finish(True, True, etime)
-
- confirm_metacontacts = gajim.config.get('confirm_metacontacts')
- if confirm_metacontacts == 'no':
- merge_contacts()
- return
- pritext = _('You are about to create a metacontact. Are you sure you want'
- ' to continue?')
- sectext = _('Metacontacts are a way to regroup several contacts in one '
- 'line. Generally it is used when the same person has several Jabber '
- 'accounts or transport accounts.')
- dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('Do _not ask me again'), on_response_ok=merge_contacts)
- if not confirm_metacontacts: # First time we see this window
- dlg.checkbutton.set_active(True)
-
-
- def on_drop_in_group(self, widget, account, c_source, grp_dest,
- is_big_brother, context, etime, grp_source = None):
- if is_big_brother:
- # add whole metacontact to new group
- self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
- # remove afterwards so the contact is not moved to General in the
- # meantime
- if grp_dest != grp_source:
- self.remove_contact_from_groups(c_source.jid, account, [grp_source])
- else:
- # Normal contact or little brother
- family = gajim.contacts.get_metacontacts_family(account,
- c_source.jid)
- if family:
- # Little brother
- # Remove whole family. Remove us from the family.
- # Then re-add other family members.
- self._remove_metacontact_family(family, account)
- gajim.contacts.remove_metacontact(account, c_source.jid)
- for data in family:
- if account != data['account'] and not self.regroup:
- continue
- if data['jid'] == c_source.jid and\
- data['account'] == account:
- continue
- self.add_contact(data['jid'], data['account'])
- break
-
- self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
-
- else:
- # Normal contact
- self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
- # remove afterwards so the contact is not moved to General in the
- # meantime
- if grp_dest != grp_source:
- self.remove_contact_from_groups(c_source.jid, account,
- [grp_source])
-
- if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY):
- context.finish(True, True, etime)
-
-
- def drag_drop(self, treeview, context, x, y, timestamp):
- target_list = treeview.drag_dest_get_target_list()
- target = treeview.drag_dest_find_target(context, target_list)
- treeview.drag_get_data(context, target)
- context.finish(False, True)
- return True
-
- def drag_data_received_data(self, treeview, context, x, y, selection, info,
- etime):
- treeview.stop_emission('drag_data_received')
- drop_info = treeview.get_dest_row_at_pos(x, y)
- if not drop_info:
- return
- if not selection.data:
- return # prevents tb when several entrys are dragged
- model = treeview.get_model()
- data = selection.data
- path_dest, position = drop_info
-
- if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \
- and path_dest[1] == 0: # dropped before the first group
- return
- if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
- # dropped before a group: we drop it in the previous group every time
- path_dest = (path_dest[0], path_dest[1]-1)
- # destination: the row something got dropped on
- iter_dest = model.get_iter(path_dest)
- type_dest = model[iter_dest][C_TYPE].decode('utf-8')
- jid_dest = model[iter_dest][C_JID].decode('utf-8')
- account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8')
-
- # drop on account row in merged mode, we cannot know the desired account
- if account_dest == 'all':
- return
- # nothing can be done, if destination account is offline
- if gajim.connections[account_dest].connected < 2:
- return
-
- # A file got dropped on the roster
- if info == self.TARGET_TYPE_URI_LIST:
- if len(path_dest) < 3:
- return
- if type_dest != 'contact':
- return
- c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
- jid_dest)
- if not c_dest.supports(NS_FILE):
- return
- uri = data.strip()
- uri_splitted = uri.split() # we may have more than one file dropped
- try:
- # This is always the last element in windows
- uri_splitted.remove('\0')
- except ValueError:
- pass
- nb_uri = len(uri_splitted)
- # Check the URIs
- bad_uris = []
- for a_uri in uri_splitted:
- path = helpers.get_file_path_from_dnd_dropped_uri(a_uri)
- if not os.path.isfile(path):
- bad_uris.append(a_uri)
- if len(bad_uris):
- dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
- return
- def _on_send_files(account, jid, uris):
- c = gajim.contacts.get_contact_with_highest_priority(account, jid)
- for uri in uris:
- path = helpers.get_file_path_from_dnd_dropped_uri(uri)
- if os.path.isfile(path): # is it file?
- gajim.interface.instances['file_transfers'].send_file(
- account, c, path)
- # Popup dialog to confirm sending
- prim_text = 'Send file?'
- sec_text = i18n.ngettext('Do you want to send this file to %s:',
- 'Do you want to send these files to %s:', nb_uri) %\
- c_dest.get_shown_name()
- for uri in uri_splitted:
- path = helpers.get_file_path_from_dnd_dropped_uri(uri)
- sec_text += '\n' + os.path.basename(path)
- dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
- on_response_ok = (_on_send_files, account_dest, jid_dest,
- uri_splitted))
- dialog.popup()
- return
-
- # a roster entry was dragged and dropped somewhere in the roster
-
- # source: the row that was dragged
- path_source = treeview.get_selection().get_selected_rows()[1][0]
- iter_source = model.get_iter(path_source)
- type_source = model[iter_source][C_TYPE]
- account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
-
- # Only normal contacts can be dragged
- if type_source != 'contact':
- return
- if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
- return
-
- # A contact was dropped
- if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
- # drop on zeroconf account, adding not possible
- return
- if type_dest == 'self_contact':
- # drop on self contact row
- return
- if type_dest == 'account' and account_source == account_dest:
- # drop on the account it was dragged from
- return
- if type_dest == 'groupchat':
- # drop on a minimized groupchat
- # TODO: Invite to groupchat
- return
-
- # Get valid source group, jid and contact
- it = iter_source
- while model[it][C_TYPE] == 'contact':
- it = model.iter_parent(it)
- grp_source = model[it][C_JID].decode('utf-8')
- if grp_source in helpers.special_groups and \
- grp_source not in ('Not in Roster', 'Observers'):
- # a transport or a minimized groupchat was dragged
- # we can add it to other accounts but not move it to another group,
- # see below
- return
- jid_source = data.decode('utf-8')
- c_source = gajim.contacts.get_contact_with_highest_priority(
- account_source, jid_source)
-
- # Get destination group
- grp_dest = None
- if type_dest == 'group':
- grp_dest = model[iter_dest][C_JID].decode('utf-8')
- elif type_dest in ('contact', 'agent'):
- it = iter_dest
- while model[it][C_TYPE] != 'group':
- it = model.iter_parent(it)
- grp_dest = model[it][C_JID].decode('utf-8')
- if grp_dest in helpers.special_groups:
- return
-
- if jid_source == jid_dest:
- if grp_source == grp_dest and account_source == account_dest:
- # Drop on self
- return
-
- # contact drop somewhere in or on a foreign account
- if (type_dest == 'account' or not self.regroup) and \
- account_source != account_dest:
- # add to account in specified group
- dialogs.AddNewContactWindow(account=account_dest, jid=jid_source,
- user_nick=c_source.name, group=grp_dest)
- return
-
- # we may not add contacts from special_groups
- if grp_source in helpers.special_groups :
- return
-
- # Is the contact we drag a meta contact?
- accounts = (self.regroup and gajim.contacts.get_accounts()) or \
- account_source
- is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source,
- accounts)
-
- drop_in_middle_of_meta = False
- if type_dest == 'contact':
- if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4:
- drop_in_middle_of_meta = True
- if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or \
- self.modelfilter.iter_has_child(iter_dest)):
- drop_in_middle_of_meta = True
- # Contact drop on group row or between two contacts that are
- # not metacontacts
- if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE,
- gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta:
- self.on_drop_in_group(None, account_source, c_source, grp_dest,
- is_big_brother, context, etime, grp_source)
- return
-
- # Contact drop on another contact, make meta contacts
- if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \
- position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta:
- c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
- jid_dest)
- if not c_dest:
- # c_dest is None if jid_dest doesn't belong to account
- return
- menu = gtk.Menu()
- item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(),
- c_dest.get_shown_name()))
- item.connect('activate', self.on_drop_rosterx, account_source,
- c_source, account_dest, c_dest, is_big_brother, context, etime)
- menu.append(item)
-
- item = gtk.MenuItem(_('Make %s and %s metacontacts') % (
- c_source.get_shown_name(), c_dest.get_shown_name()))
- item.connect('activate', self.on_drop_in_contact, account_source,
- c_source, account_dest, c_dest, is_big_brother, context, etime)
-
- menu.append(item)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, 1, etime)
+ def drag_data_get_data(self, treeview, context, selection, target_id, etime):
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ if len(list_of_paths) != 1:
+ return
+ path = list_of_paths[0]
+ data = ''
+ if len(path) >= 3:
+ data = model[path][C_JID]
+ selection.set(selection.target, 8, data)
+
+ def drag_begin(self, treeview, context):
+ self.dragging = True
+
+ def drag_end(self, treeview, context):
+ self.dragging = False
+
+ def on_drop_rosterx(self, widget, account_source, c_source, account_dest,
+ c_dest, was_big_brother, context, etime):
+ gajim.connections[account_dest].send_contacts([c_source], c_dest.jid)
+
+ def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
+ c_dest, was_big_brother, context, etime):
+
+ if not gajim.connections[account_source].private_storage_supported or not\
+ gajim.connections[account_dest].private_storage_supported:
+ dialogs.WarningDialog(_('Metacontacts storage not supported by your '
+ 'server'),
+ _('Your server does not support storing metacontacts information. '
+ 'So those information will not be saved on next reconnection.'))
+
+ def merge_contacts(is_checked=None):
+ contacts = 0
+ if is_checked is not None: # dialog has been shown
+ if is_checked: # user does not want to be asked again
+ gajim.config.set('confirm_metacontacts', 'no')
+ else:
+ gajim.config.set('confirm_metacontacts', 'yes')
+
+ # We might have dropped on a metacontact.
+ # Remove it and readd later with updated family info
+ dest_family = gajim.contacts.get_metacontacts_family(account_dest,
+ c_dest.jid)
+ if dest_family:
+ self._remove_metacontact_family(dest_family, account_dest)
+ source_family = gajim.contacts.get_metacontacts_family(account_source, c_source.jid)
+ if dest_family == source_family:
+ n = contacts = len(dest_family)
+ for tag in source_family:
+ if tag['jid'] == c_source.jid:
+ tag['order'] = contacts
+ continue
+ if 'order' in tag:
+ n -= 1
+ tag['order'] = n
+ else:
+ self._remove_entity(c_dest, account_dest)
+
+ old_family = gajim.contacts.get_metacontacts_family(account_source,
+ c_source.jid)
+ old_groups = c_source.groups
+
+ # Remove old source contact(s)
+ if was_big_brother:
+ # We have got little brothers. Readd them all
+ self._remove_metacontact_family(old_family, account_source)
+ else:
+ # We are only a litle brother. Simply remove us from our big brother
+ if self._get_contact_iter(c_source.jid, account_source):
+ # When we have been in the group before.
+ # Do not try to remove us again
+ self._remove_entity(c_source, account_source)
+
+ own_data = {}
+ own_data['jid'] = c_source.jid
+ own_data['account'] = account_source
+ # Don't touch the rest of the family
+ old_family = [own_data]
+
+ # Apply new tag and update contact
+ for data in old_family:
+ if account_source != data['account'] and not self.regroup:
+ continue
+
+ _account = data['account']
+ _jid = data['jid']
+ _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
+ if not _contact:
+ # One of the metacontacts may be not connected.
+ continue
+
+ _contact.groups = c_dest.groups[:]
+ gajim.contacts.add_metacontact(account_dest, c_dest.jid,
+ _account, _contact.jid, contacts)
+ gajim.connections[account_source].update_contact(_contact.jid,
+ _contact.name, _contact.groups)
+
+ # Re-add all and update GUI
+ new_family = gajim.contacts.get_metacontacts_family(account_source,
+ c_source.jid)
+ brothers = self._add_metacontact_family(new_family, account_source)
+
+ for c, acc in brothers:
+ self.draw_completely(c.jid, acc)
+
+ old_groups.extend(c_dest.groups)
+ for g in old_groups:
+ self.draw_group(g, account_source)
+
+ self.draw_account(account_source)
+ context.finish(True, True, etime)
+
+ confirm_metacontacts = gajim.config.get('confirm_metacontacts')
+ if confirm_metacontacts == 'no':
+ merge_contacts()
+ return
+ pritext = _('You are about to create a metacontact. Are you sure you want'
+ ' to continue?')
+ sectext = _('Metacontacts are a way to regroup several contacts in one '
+ 'line. Generally it is used when the same person has several Jabber '
+ 'accounts or transport accounts.')
+ dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
+ _('Do _not ask me again'), on_response_ok=merge_contacts)
+ if not confirm_metacontacts: # First time we see this window
+ dlg.checkbutton.set_active(True)
+
+
+ def on_drop_in_group(self, widget, account, c_source, grp_dest,
+ is_big_brother, context, etime, grp_source = None):
+ if is_big_brother:
+ # add whole metacontact to new group
+ self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
+ # remove afterwards so the contact is not moved to General in the
+ # meantime
+ if grp_dest != grp_source:
+ self.remove_contact_from_groups(c_source.jid, account, [grp_source])
+ else:
+ # Normal contact or little brother
+ family = gajim.contacts.get_metacontacts_family(account,
+ c_source.jid)
+ if family:
+ # Little brother
+ # Remove whole family. Remove us from the family.
+ # Then re-add other family members.
+ self._remove_metacontact_family(family, account)
+ gajim.contacts.remove_metacontact(account, c_source.jid)
+ for data in family:
+ if account != data['account'] and not self.regroup:
+ continue
+ if data['jid'] == c_source.jid and\
+ data['account'] == account:
+ continue
+ self.add_contact(data['jid'], data['account'])
+ break
+
+ self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
+
+ else:
+ # Normal contact
+ self.add_contact_to_groups(c_source.jid, account, [grp_dest,])
+ # remove afterwards so the contact is not moved to General in the
+ # meantime
+ if grp_dest != grp_source:
+ self.remove_contact_from_groups(c_source.jid, account,
+ [grp_source])
+
+ if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY):
+ context.finish(True, True, etime)
+
+
+ def drag_drop(self, treeview, context, x, y, timestamp):
+ target_list = treeview.drag_dest_get_target_list()
+ target = treeview.drag_dest_find_target(context, target_list)
+ treeview.drag_get_data(context, target)
+ context.finish(False, True)
+ return True
+
+ def drag_data_received_data(self, treeview, context, x, y, selection, info,
+ etime):
+ treeview.stop_emission('drag_data_received')
+ drop_info = treeview.get_dest_row_at_pos(x, y)
+ if not drop_info:
+ return
+ if not selection.data:
+ return # prevents tb when several entrys are dragged
+ model = treeview.get_model()
+ data = selection.data
+ path_dest, position = drop_info
+
+ if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \
+ and path_dest[1] == 0: # dropped before the first group
+ return
+ if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
+ # dropped before a group: we drop it in the previous group every time
+ path_dest = (path_dest[0], path_dest[1]-1)
+ # destination: the row something got dropped on
+ iter_dest = model.get_iter(path_dest)
+ type_dest = model[iter_dest][C_TYPE].decode('utf-8')
+ jid_dest = model[iter_dest][C_JID].decode('utf-8')
+ account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8')
+
+ # drop on account row in merged mode, we cannot know the desired account
+ if account_dest == 'all':
+ return
+ # nothing can be done, if destination account is offline
+ if gajim.connections[account_dest].connected < 2:
+ return
+
+ # A file got dropped on the roster
+ if info == self.TARGET_TYPE_URI_LIST:
+ if len(path_dest) < 3:
+ return
+ if type_dest != 'contact':
+ return
+ c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
+ jid_dest)
+ if not c_dest.supports(NS_FILE):
+ return
+ uri = data.strip()
+ uri_splitted = uri.split() # we may have more than one file dropped
+ try:
+ # This is always the last element in windows
+ uri_splitted.remove('\0')
+ except ValueError:
+ pass
+ nb_uri = len(uri_splitted)
+ # Check the URIs
+ bad_uris = []
+ for a_uri in uri_splitted:
+ path = helpers.get_file_path_from_dnd_dropped_uri(a_uri)
+ if not os.path.isfile(path):
+ bad_uris.append(a_uri)
+ if len(bad_uris):
+ dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
+ return
+ def _on_send_files(account, jid, uris):
+ c = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ for uri in uris:
+ path = helpers.get_file_path_from_dnd_dropped_uri(uri)
+ if os.path.isfile(path): # is it file?
+ gajim.interface.instances['file_transfers'].send_file(
+ account, c, path)
+ # Popup dialog to confirm sending
+ prim_text = 'Send file?'
+ sec_text = i18n.ngettext('Do you want to send this file to %s:',
+ 'Do you want to send these files to %s:', nb_uri) %\
+ c_dest.get_shown_name()
+ for uri in uri_splitted:
+ path = helpers.get_file_path_from_dnd_dropped_uri(uri)
+ sec_text += '\n' + os.path.basename(path)
+ dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
+ on_response_ok = (_on_send_files, account_dest, jid_dest,
+ uri_splitted))
+ dialog.popup()
+ return
+
+ # a roster entry was dragged and dropped somewhere in the roster
+
+ # source: the row that was dragged
+ path_source = treeview.get_selection().get_selected_rows()[1][0]
+ iter_source = model.get_iter(path_source)
+ type_source = model[iter_source][C_TYPE]
+ account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
+
+ # Only normal contacts can be dragged
+ if type_source != 'contact':
+ return
+ if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
+ return
+
+ # A contact was dropped
+ if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
+ # drop on zeroconf account, adding not possible
+ return
+ if type_dest == 'self_contact':
+ # drop on self contact row
+ return
+ if type_dest == 'account' and account_source == account_dest:
+ # drop on the account it was dragged from
+ return
+ if type_dest == 'groupchat':
+ # drop on a minimized groupchat
+ # TODO: Invite to groupchat
+ return
+
+ # Get valid source group, jid and contact
+ it = iter_source
+ while model[it][C_TYPE] == 'contact':
+ it = model.iter_parent(it)
+ grp_source = model[it][C_JID].decode('utf-8')
+ if grp_source in helpers.special_groups and \
+ grp_source not in ('Not in Roster', 'Observers'):
+ # a transport or a minimized groupchat was dragged
+ # we can add it to other accounts but not move it to another group,
+ # see below
+ return
+ jid_source = data.decode('utf-8')
+ c_source = gajim.contacts.get_contact_with_highest_priority(
+ account_source, jid_source)
+
+ # Get destination group
+ grp_dest = None
+ if type_dest == 'group':
+ grp_dest = model[iter_dest][C_JID].decode('utf-8')
+ elif type_dest in ('contact', 'agent'):
+ it = iter_dest
+ while model[it][C_TYPE] != 'group':
+ it = model.iter_parent(it)
+ grp_dest = model[it][C_JID].decode('utf-8')
+ if grp_dest in helpers.special_groups:
+ return
+
+ if jid_source == jid_dest:
+ if grp_source == grp_dest and account_source == account_dest:
+ # Drop on self
+ return
+
+ # contact drop somewhere in or on a foreign account
+ if (type_dest == 'account' or not self.regroup) and \
+ account_source != account_dest:
+ # add to account in specified group
+ dialogs.AddNewContactWindow(account=account_dest, jid=jid_source,
+ user_nick=c_source.name, group=grp_dest)
+ return
+
+ # we may not add contacts from special_groups
+ if grp_source in helpers.special_groups :
+ return
+
+ # Is the contact we drag a meta contact?
+ accounts = (self.regroup and gajim.contacts.get_accounts()) or \
+ account_source
+ is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source,
+ accounts)
+
+ drop_in_middle_of_meta = False
+ if type_dest == 'contact':
+ if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 4:
+ drop_in_middle_of_meta = True
+ if position == gtk.TREE_VIEW_DROP_AFTER and (len(path_dest) == 4 or \
+ self.modelfilter.iter_has_child(iter_dest)):
+ drop_in_middle_of_meta = True
+ # Contact drop on group row or between two contacts that are
+ # not metacontacts
+ if (type_dest == 'group' or position in (gtk.TREE_VIEW_DROP_BEFORE,
+ gtk.TREE_VIEW_DROP_AFTER)) and not drop_in_middle_of_meta:
+ self.on_drop_in_group(None, account_source, c_source, grp_dest,
+ is_big_brother, context, etime, grp_source)
+ return
+
+ # Contact drop on another contact, make meta contacts
+ if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \
+ position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or drop_in_middle_of_meta:
+ c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
+ jid_dest)
+ if not c_dest:
+ # c_dest is None if jid_dest doesn't belong to account
+ return
+ menu = gtk.Menu()
+ item = gtk.MenuItem(_('Send %s to %s') % (c_source.get_shown_name(),
+ c_dest.get_shown_name()))
+ item.connect('activate', self.on_drop_rosterx, account_source,
+ c_source, account_dest, c_dest, is_big_brother, context, etime)
+ menu.append(item)
+
+ item = gtk.MenuItem(_('Make %s and %s metacontacts') % (
+ c_source.get_shown_name(), c_dest.get_shown_name()))
+ item.connect('activate', self.on_drop_in_contact, account_source,
+ c_source, account_dest, c_dest, is_big_brother, context, etime)
+
+ menu.append(item)
+
+ menu.attach_to_widget(self.tree, None)
+ menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ menu.show_all()
+ menu.popup(None, None, None, 1, etime)
################################################################################
### Everything about images and icons....
### Cleanup assigned to Jim++ :-)
################################################################################
- def get_appropriate_state_images(self, jid, size='16', icon_name='online'):
- """
- Check jid and return the appropriate state images dict for the demanded
- size. icon_name is taken into account when jid is from transport:
- transport iconset doesn't contain all icons, so we fall back to jabber
- one
- """
- transport = gajim.get_transport_name_from_jid(jid)
- if transport and size in self.transports_state_images:
- if transport not in self.transports_state_images[size]:
- # we don't have iconset for this transport loaded yet. Let's do it
- self.make_transport_state_images(transport)
- if transport in self.transports_state_images[size] and \
- icon_name in self.transports_state_images[size][transport]:
- return self.transports_state_images[size][transport]
- return gajim.interface.jabber_state_images[size]
-
- def make_transport_state_images(self, transport):
- """
- Initialize opened and closed 'transport' iconset dict
- """
- if gajim.config.get('use_transports_iconsets'):
- folder = os.path.join(helpers.get_transport_path(transport),
- '16x16')
- pixo, pixc = gtkgui_helpers.load_icons_meta()
- self.transports_state_images['opened'][transport] = \
- gtkgui_helpers.load_iconset(folder, pixo, transport=True)
- self.transports_state_images['closed'][transport] = \
- gtkgui_helpers.load_iconset(folder, pixc, transport=True)
- folder = os.path.join(helpers.get_transport_path(transport), '32x32')
- self.transports_state_images['32'][transport] = \
- gtkgui_helpers.load_iconset(folder, transport=True)
- folder = os.path.join(helpers.get_transport_path(transport), '16x16')
- self.transports_state_images['16'][transport] = \
- gtkgui_helpers.load_iconset(folder, transport=True)
-
- def update_jabber_state_images(self):
- # Update the roster
- self.setup_and_draw_roster()
- # Update the status combobox
- model = self.status_combobox.get_model()
- titer = model.get_iter_root()
- while titer:
- if model[titer][2] != '':
- # If it's not change status message iter
- # eg. if it has show parameter not ''
- model[titer][1] = gajim.interface.jabber_state_images['16'][model[
- titer][2]]
- titer = model.iter_next(titer)
- # Update the systray
- if gajim.interface.systray_enabled:
- gajim.interface.systray.set_img()
-
- for win in gajim.interface.msg_win_mgr.windows():
- for ctrl in win.controls():
- ctrl.update_ui()
- win.redraw_tab(ctrl)
-
- self.update_status_combobox()
-
- def set_account_status_icon(self, account):
- status = gajim.connections[account].connected
- child_iterA = self._get_account_iter(account, self.model)
- if not child_iterA:
- return
- if not self.regroup:
- show = gajim.SHOW_LIST[status]
- else: # accounts merged
- show = helpers.get_global_show()
- self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[
- '16'][show]
+ def get_appropriate_state_images(self, jid, size='16', icon_name='online'):
+ """
+ Check jid and return the appropriate state images dict for the demanded
+ size. icon_name is taken into account when jid is from transport:
+ transport iconset doesn't contain all icons, so we fall back to jabber
+ one
+ """
+ transport = gajim.get_transport_name_from_jid(jid)
+ if transport and size in self.transports_state_images:
+ if transport not in self.transports_state_images[size]:
+ # we don't have iconset for this transport loaded yet. Let's do it
+ self.make_transport_state_images(transport)
+ if transport in self.transports_state_images[size] and \
+ icon_name in self.transports_state_images[size][transport]:
+ return self.transports_state_images[size][transport]
+ return gajim.interface.jabber_state_images[size]
+
+ def make_transport_state_images(self, transport):
+ """
+ Initialize opened and closed 'transport' iconset dict
+ """
+ if gajim.config.get('use_transports_iconsets'):
+ folder = os.path.join(helpers.get_transport_path(transport),
+ '16x16')
+ pixo, pixc = gtkgui_helpers.load_icons_meta()
+ self.transports_state_images['opened'][transport] = \
+ gtkgui_helpers.load_iconset(folder, pixo, transport=True)
+ self.transports_state_images['closed'][transport] = \
+ gtkgui_helpers.load_iconset(folder, pixc, transport=True)
+ folder = os.path.join(helpers.get_transport_path(transport), '32x32')
+ self.transports_state_images['32'][transport] = \
+ gtkgui_helpers.load_iconset(folder, transport=True)
+ folder = os.path.join(helpers.get_transport_path(transport), '16x16')
+ self.transports_state_images['16'][transport] = \
+ gtkgui_helpers.load_iconset(folder, transport=True)
+
+ def update_jabber_state_images(self):
+ # Update the roster
+ self.setup_and_draw_roster()
+ # Update the status combobox
+ model = self.status_combobox.get_model()
+ titer = model.get_iter_root()
+ while titer:
+ if model[titer][2] != '':
+ # If it's not change status message iter
+ # eg. if it has show parameter not ''
+ model[titer][1] = gajim.interface.jabber_state_images['16'][model[
+ titer][2]]
+ titer = model.iter_next(titer)
+ # Update the systray
+ if gajim.interface.systray_enabled:
+ gajim.interface.systray.set_img()
+
+ for win in gajim.interface.msg_win_mgr.windows():
+ for ctrl in win.controls():
+ ctrl.update_ui()
+ win.redraw_tab(ctrl)
+
+ self.update_status_combobox()
+
+ def set_account_status_icon(self, account):
+ status = gajim.connections[account].connected
+ child_iterA = self._get_account_iter(account, self.model)
+ if not child_iterA:
+ return
+ if not self.regroup:
+ show = gajim.SHOW_LIST[status]
+ else: # accounts merged
+ show = helpers.get_global_show()
+ self.model[child_iterA][C_IMG] = gajim.interface.jabber_state_images[
+ '16'][show]
################################################################################
### Style and theme related methods
################################################################################
- def show_title(self):
- change_title_allowed = gajim.config.get('change_roster_title')
- if not change_title_allowed:
- return
-
- if gajim.config.get('one_message_window') == 'always_with_roster':
- # always_with_roster mode defers to the MessageWindow
- if not gajim.interface.msg_win_mgr.one_window_opened():
- # No MessageWindow to defer to
- self.window.set_title('Gajim')
- return
-
- nb_unread = 0
- start = ''
- for account in gajim.connections:
- # Count events in roster title only if we don't auto open them
- if not helpers.allow_popup_window(account):
- nb_unread += gajim.events.get_nb_events(['chat', 'normal',
- 'file-request', 'file-error', 'file-completed',
- 'file-request-error', 'file-send-error', 'file-stopped',
- 'printed_chat'], account)
- if nb_unread > 1:
- start = '[' + str(nb_unread) + '] '
- elif nb_unread == 1:
- start = '* '
-
- self.window.set_title(start + 'Gajim')
-
- gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
-
- def _change_style(self, model, path, titer, option):
- if option is None or model[titer][C_TYPE] == option:
- # We changed style for this type of row
- model[titer][C_NAME] = model[titer][C_NAME]
-
- def change_roster_style(self, option):
- self.model.foreach(self._change_style, option)
- for win in gajim.interface.msg_win_mgr.windows():
- win.repaint_themed_widgets()
-
- def repaint_themed_widgets(self):
- """
- Notify windows that contain themed widgets to repaint them
- """
- for win in gajim.interface.msg_win_mgr.windows():
- win.repaint_themed_widgets()
- for account in gajim.connections:
- for addr in gajim.interface.instances[account]['disco']:
- gajim.interface.instances[account]['disco'][addr].paint_banner()
- for ctrl in gajim.interface.minimized_controls[account].values():
- ctrl.repaint_themed_widgets()
-
- def update_avatar_in_gui(self, jid, account):
- # Update roster
- self.draw_avatar(jid, account)
- # Update chat window
-
- ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
- if ctrl:
- ctrl.show_avatar()
-
- def on_roster_treeview_style_set(self, treeview, style):
- """
- When style (theme) changes, redraw all contacts
- """
- for contact in self._iter_contact_rows():
- self.draw_contact(contact[C_JID].decode('utf-8'),
- contact[C_ACCOUNT].decode('utf-8'))
-
- def set_renderer_color(self, renderer, style, set_background=True):
- """
- Set style for treeview cell, using PRELIGHT system color
- """
- if set_background:
- bgcolor = self.tree.style.bg[style]
- renderer.set_property('cell-background-gdk', bgcolor)
- else:
- fgcolor = self.tree.style.fg[style]
- renderer.set_property('foreground-gdk', fgcolor)
-
- def _iconCellDataFunc(self, column, renderer, model, titer, data=None):
- """
- When a row is added, set properties for icon renderer
- """
- type_ = model[titer][C_TYPE]
- if type_ == 'account':
- self._set_account_row_background_color(renderer)
- renderer.set_property('xalign', 0)
- elif type_ == 'group':
- self._set_group_row_background_color(renderer)
- renderer.set_property('xalign', 0.2)
- elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
- # This can append when at the moment we add the row
- return
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- self._set_contact_row_background_color(renderer, jid, account)
- parent_iter = model.iter_parent(titer)
- if model[parent_iter][C_TYPE] == 'contact':
- renderer.set_property('xalign', 1)
- else:
- renderer.set_property('xalign', 0.4)
- renderer.set_property('width', 26)
-
- def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
- """
- When a row is added, set properties for name renderer
- """
- theme = gajim.config.get('roster_theme')
- type_ = model[titer][C_TYPE]
- if type_ == 'account':
- color = gajim.config.get_per('themes', theme, 'accounttextcolor')
- if color:
- renderer.set_property('foreground', color)
- else:
- self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
- renderer.set_property('xpad', 0)
- renderer.set_property('width', 3)
- self._set_account_row_background_color(renderer)
- elif type_ == 'group':
- color = gajim.config.get_per('themes', theme, 'grouptextcolor')
- if color:
- renderer.set_property('foreground', color)
- else:
- self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
- renderer.set_property('xpad', 4)
- self._set_group_row_background_color(renderer)
- elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
- # This can append when at the moment we add the row
- return
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- color = None
- if type_ == 'groupchat':
- ctrl = gajim.interface.minimized_controls[account].get(jid, None)
- if ctrl and ctrl.attention_flag:
- color = gajim.config.get_per('themes', theme,
- 'state_muc_directed_msg_color')
- renderer.set_property('foreground', 'red')
- if not color:
- color = gajim.config.get_per('themes', theme, 'contacttextcolor')
- if color:
- renderer.set_property('foreground', color)
- else:
- renderer.set_property('foreground', None)
- self._set_contact_row_background_color(renderer, jid, account)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
- parent_iter = model.iter_parent(titer)
- if model[parent_iter][C_TYPE] == 'contact':
- renderer.set_property('xpad', 16)
- else:
- renderer.set_property('xpad', 8)
-
- def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
- data=None):
- """
- When a row is added, draw the respective pep icon
- """
- theme = gajim.config.get('roster_theme')
- type_ = model[titer][C_TYPE]
-
- # allocate space for the icon only if needed
- if not model[titer][data]:
- renderer.set_property('visible', False)
- else:
- renderer.set_property('visible', True)
-
- if type_ == 'account':
- self._set_account_row_background_color(renderer)
- renderer.set_property('xalign', 1)
- elif type_:
- if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
- # This can append at the moment we add the row
- return
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- self._set_contact_row_background_color(renderer, jid, account)
-
- def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, data
- = None):
- """
- When a row is added, set properties for avatar renderer
- """
- type_ = model[titer][C_TYPE]
- if type_ in ('group', 'account'):
- renderer.set_property('visible', False)
- return
-
- # allocate space for the icon only if needed
- if model[titer][C_AVATAR_PIXBUF] or \
- gajim.config.get('avatar_position_in_roster') == 'left':
- renderer.set_property('visible', True)
- if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
- # This can append at the moment we add the row
- return
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- self._set_contact_row_background_color(renderer, jid, account)
- else:
- renderer.set_property('visible', False)
-
- if gajim.config.get('avatar_position_in_roster') == 'left':
- renderer.set_property('width', gajim.config.get('roster_avatar_width'))
- renderer.set_property('xalign', 0.5)
- else:
- renderer.set_property('xalign', 1) # align pixbuf to the right
-
- def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, data=None):
- """
- When a row is added, set properties for padlock renderer
- """
- type_ = model[titer][C_TYPE]
- # allocate space for the icon only if needed
- if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]:
- renderer.set_property('visible', True)
- self._set_account_row_background_color(renderer)
- renderer.set_property('xalign', 1) # align pixbuf to the right
- else:
- renderer.set_property('visible', False)
-
- def _set_account_row_background_color(self, renderer):
- theme = gajim.config.get('roster_theme')
- color = gajim.config.get_per('themes', theme, 'accountbgcolor')
- if color:
- renderer.set_property('cell-background', color)
- else:
- self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
-
- def _set_contact_row_background_color(self, renderer, jid, account):
- theme = gajim.config.get('roster_theme')
- if jid in gajim.newly_added[account]:
- renderer.set_property('cell-background', gajim.config.get(
- 'just_connected_bg_color'))
- elif jid in gajim.to_be_removed[account]:
- renderer.set_property('cell-background', gajim.config.get(
- 'just_disconnected_bg_color'))
- else:
- color = gajim.config.get_per('themes', theme, 'contactbgcolor')
- renderer.set_property('cell-background', color if color else None)
-
- def _set_group_row_background_color(self, renderer):
- theme = gajim.config.get('roster_theme')
- color = gajim.config.get_per('themes', theme, 'groupbgcolor')
- if color:
- renderer.set_property('cell-background', color)
- else:
- self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
+ def show_title(self):
+ change_title_allowed = gajim.config.get('change_roster_title')
+ if not change_title_allowed:
+ return
+
+ if gajim.config.get('one_message_window') == 'always_with_roster':
+ # always_with_roster mode defers to the MessageWindow
+ if not gajim.interface.msg_win_mgr.one_window_opened():
+ # No MessageWindow to defer to
+ self.window.set_title('Gajim')
+ return
+
+ nb_unread = 0
+ start = ''
+ for account in gajim.connections:
+ # Count events in roster title only if we don't auto open them
+ if not helpers.allow_popup_window(account):
+ nb_unread += gajim.events.get_nb_events(['chat', 'normal',
+ 'file-request', 'file-error', 'file-completed',
+ 'file-request-error', 'file-send-error', 'file-stopped',
+ 'printed_chat'], account)
+ if nb_unread > 1:
+ start = '[' + str(nb_unread) + '] '
+ elif nb_unread == 1:
+ start = '* '
+
+ self.window.set_title(start + 'Gajim')
+
+ gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
+
+ def _change_style(self, model, path, titer, option):
+ if option is None or model[titer][C_TYPE] == option:
+ # We changed style for this type of row
+ model[titer][C_NAME] = model[titer][C_NAME]
+
+ def change_roster_style(self, option):
+ self.model.foreach(self._change_style, option)
+ for win in gajim.interface.msg_win_mgr.windows():
+ win.repaint_themed_widgets()
+
+ def repaint_themed_widgets(self):
+ """
+ Notify windows that contain themed widgets to repaint them
+ """
+ for win in gajim.interface.msg_win_mgr.windows():
+ win.repaint_themed_widgets()
+ for account in gajim.connections:
+ for addr in gajim.interface.instances[account]['disco']:
+ gajim.interface.instances[account]['disco'][addr].paint_banner()
+ for ctrl in gajim.interface.minimized_controls[account].values():
+ ctrl.repaint_themed_widgets()
+
+ def update_avatar_in_gui(self, jid, account):
+ # Update roster
+ self.draw_avatar(jid, account)
+ # Update chat window
+
+ ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
+ if ctrl:
+ ctrl.show_avatar()
+
+ def on_roster_treeview_style_set(self, treeview, style):
+ """
+ When style (theme) changes, redraw all contacts
+ """
+ for contact in self._iter_contact_rows():
+ self.draw_contact(contact[C_JID].decode('utf-8'),
+ contact[C_ACCOUNT].decode('utf-8'))
+
+ def set_renderer_color(self, renderer, style, set_background=True):
+ """
+ Set style for treeview cell, using PRELIGHT system color
+ """
+ if set_background:
+ bgcolor = self.tree.style.bg[style]
+ renderer.set_property('cell-background-gdk', bgcolor)
+ else:
+ fgcolor = self.tree.style.fg[style]
+ renderer.set_property('foreground-gdk', fgcolor)
+
+ def _iconCellDataFunc(self, column, renderer, model, titer, data=None):
+ """
+ When a row is added, set properties for icon renderer
+ """
+ type_ = model[titer][C_TYPE]
+ if type_ == 'account':
+ self._set_account_row_background_color(renderer)
+ renderer.set_property('xalign', 0)
+ elif type_ == 'group':
+ self._set_group_row_background_color(renderer)
+ renderer.set_property('xalign', 0.2)
+ elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
+ if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
+ # This can append when at the moment we add the row
+ return
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ self._set_contact_row_background_color(renderer, jid, account)
+ parent_iter = model.iter_parent(titer)
+ if model[parent_iter][C_TYPE] == 'contact':
+ renderer.set_property('xalign', 1)
+ else:
+ renderer.set_property('xalign', 0.4)
+ renderer.set_property('width', 26)
+
+ def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
+ """
+ When a row is added, set properties for name renderer
+ """
+ theme = gajim.config.get('roster_theme')
+ type_ = model[titer][C_TYPE]
+ if type_ == 'account':
+ color = gajim.config.get_per('themes', theme, 'accounttextcolor')
+ if color:
+ renderer.set_property('foreground', color)
+ else:
+ self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False)
+ renderer.set_property('font',
+ gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
+ renderer.set_property('xpad', 0)
+ renderer.set_property('width', 3)
+ self._set_account_row_background_color(renderer)
+ elif type_ == 'group':
+ color = gajim.config.get_per('themes', theme, 'grouptextcolor')
+ if color:
+ renderer.set_property('foreground', color)
+ else:
+ self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False)
+ renderer.set_property('font',
+ gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
+ renderer.set_property('xpad', 4)
+ self._set_group_row_background_color(renderer)
+ elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
+ if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
+ # This can append when at the moment we add the row
+ return
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ color = None
+ if type_ == 'groupchat':
+ ctrl = gajim.interface.minimized_controls[account].get(jid, None)
+ if ctrl and ctrl.attention_flag:
+ color = gajim.config.get_per('themes', theme,
+ 'state_muc_directed_msg_color')
+ renderer.set_property('foreground', 'red')
+ if not color:
+ color = gajim.config.get_per('themes', theme, 'contacttextcolor')
+ if color:
+ renderer.set_property('foreground', color)
+ else:
+ renderer.set_property('foreground', None)
+ self._set_contact_row_background_color(renderer, jid, account)
+ renderer.set_property('font',
+ gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
+ parent_iter = model.iter_parent(titer)
+ if model[parent_iter][C_TYPE] == 'contact':
+ renderer.set_property('xpad', 16)
+ else:
+ renderer.set_property('xpad', 8)
+
+ def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
+ data=None):
+ """
+ When a row is added, draw the respective pep icon
+ """
+ theme = gajim.config.get('roster_theme')
+ type_ = model[titer][C_TYPE]
+
+ # allocate space for the icon only if needed
+ if not model[titer][data]:
+ renderer.set_property('visible', False)
+ else:
+ renderer.set_property('visible', True)
+
+ if type_ == 'account':
+ self._set_account_row_background_color(renderer)
+ renderer.set_property('xalign', 1)
+ elif type_:
+ if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
+ # This can append at the moment we add the row
+ return
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ self._set_contact_row_background_color(renderer, jid, account)
+
+ def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer, data
+ = None):
+ """
+ When a row is added, set properties for avatar renderer
+ """
+ type_ = model[titer][C_TYPE]
+ if type_ in ('group', 'account'):
+ renderer.set_property('visible', False)
+ return
+
+ # allocate space for the icon only if needed
+ if model[titer][C_AVATAR_PIXBUF] or \
+ gajim.config.get('avatar_position_in_roster') == 'left':
+ renderer.set_property('visible', True)
+ if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534
+ if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
+ # This can append at the moment we add the row
+ return
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ self._set_contact_row_background_color(renderer, jid, account)
+ else:
+ renderer.set_property('visible', False)
+
+ if gajim.config.get('avatar_position_in_roster') == 'left':
+ renderer.set_property('width', gajim.config.get('roster_avatar_width'))
+ renderer.set_property('xalign', 0.5)
+ else:
+ renderer.set_property('xalign', 1) # align pixbuf to the right
+
+ def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, data=None):
+ """
+ When a row is added, set properties for padlock renderer
+ """
+ type_ = model[titer][C_TYPE]
+ # allocate space for the icon only if needed
+ if type_ == 'account' and model[titer][C_PADLOCK_PIXBUF]:
+ renderer.set_property('visible', True)
+ self._set_account_row_background_color(renderer)
+ renderer.set_property('xalign', 1) # align pixbuf to the right
+ else:
+ renderer.set_property('visible', False)
+
+ def _set_account_row_background_color(self, renderer):
+ theme = gajim.config.get('roster_theme')
+ color = gajim.config.get_per('themes', theme, 'accountbgcolor')
+ if color:
+ renderer.set_property('cell-background', color)
+ else:
+ self.set_renderer_color(renderer, gtk.STATE_ACTIVE)
+
+ def _set_contact_row_background_color(self, renderer, jid, account):
+ theme = gajim.config.get('roster_theme')
+ if jid in gajim.newly_added[account]:
+ renderer.set_property('cell-background', gajim.config.get(
+ 'just_connected_bg_color'))
+ elif jid in gajim.to_be_removed[account]:
+ renderer.set_property('cell-background', gajim.config.get(
+ 'just_disconnected_bg_color'))
+ else:
+ color = gajim.config.get_per('themes', theme, 'contactbgcolor')
+ renderer.set_property('cell-background', color if color else None)
+
+ def _set_group_row_background_color(self, renderer):
+ theme = gajim.config.get('roster_theme')
+ color = gajim.config.get_per('themes', theme, 'groupbgcolor')
+ if color:
+ renderer.set_property('cell-background', color)
+ else:
+ self.set_renderer_color(renderer, gtk.STATE_PRELIGHT)
################################################################################
### Everything about building menus
### FIXME: We really need to make it simpler! 1465 lines are a few to much....
################################################################################
- def make_menu(self, force=False):
- """
- Create the main window's menus
- """
- if not force and not self.actions_menu_needs_rebuild:
- return
- new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
- single_message_menuitem = self.xml.get_object(
- 'send_single_message_menuitem')
- join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
- muc_icon = gtkgui_helpers.load_icon('muc_active')
- if muc_icon:
- join_gc_menuitem.set_image(muc_icon)
- add_new_contact_menuitem = self.xml.get_object('add_new_contact_menuitem')
- service_disco_menuitem = self.xml.get_object('service_disco_menuitem')
- advanced_menuitem = self.xml.get_object('advanced_menuitem')
- profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem')
-
- # destroy old advanced menus
- for m in self.advanced_menus:
- m.destroy()
-
- # make it sensitive. it is insensitive only if no accounts are *available*
- advanced_menuitem.set_sensitive(True)
-
- if self.add_new_contact_handler_id:
- add_new_contact_menuitem.handler_disconnect(
- self.add_new_contact_handler_id)
- self.add_new_contact_handler_id = None
-
- if self.service_disco_handler_id:
- service_disco_menuitem.handler_disconnect(
- self.service_disco_handler_id)
- self.service_disco_handler_id = None
-
- if self.new_chat_menuitem_handler_id:
- new_chat_menuitem.handler_disconnect(
- self.new_chat_menuitem_handler_id)
- self.new_chat_menuitem_handler_id = None
-
- if self.single_message_menuitem_handler_id:
- single_message_menuitem.handler_disconnect(
- self.single_message_menuitem_handler_id)
- self.single_message_menuitem_handler_id = None
-
- if self.profile_avatar_menuitem_handler_id:
- profile_avatar_menuitem.handler_disconnect(
- self.profile_avatar_menuitem_handler_id)
- self.profile_avatar_menuitem_handler_id = None
-
- # remove the existing submenus
- add_new_contact_menuitem.remove_submenu()
- service_disco_menuitem.remove_submenu()
- join_gc_menuitem.remove_submenu()
- single_message_menuitem.remove_submenu()
- new_chat_menuitem.remove_submenu()
- advanced_menuitem.remove_submenu()
- profile_avatar_menuitem.remove_submenu()
-
- # remove the existing accelerator
- if self.have_new_chat_accel:
- ag = gtk.accel_groups_from_object(self.window)[0]
- new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n,
- gtk.gdk.CONTROL_MASK)
- self.have_new_chat_accel = False
-
- gc_sub_menu = gtk.Menu() # gc is always a submenu
- join_gc_menuitem.set_submenu(gc_sub_menu)
-
- connected_accounts = gajim.get_number_of_connected_accounts()
-
- connected_accounts_with_private_storage = 0
-
- # items that get shown whether an account is zeroconf or not
- accounts_list = sorted(gajim.contacts.get_accounts())
- if connected_accounts > 1: # 2 or more accounts? make submenus
- new_chat_sub_menu = gtk.Menu()
-
- for account in accounts_list:
- if gajim.connections[account].connected <= 1:
- # if offline or connecting
- continue
-
- # new chat
- new_chat_item = gtk.MenuItem(_('using account %s') % account,
- False)
- new_chat_sub_menu.append(new_chat_item)
- new_chat_item.connect('activate',
- self.on_new_chat_menuitem_activate, account)
-
- new_chat_menuitem.set_submenu(new_chat_sub_menu)
- new_chat_sub_menu.show_all()
-
- elif connected_accounts == 1: # user has only one account
- for account in gajim.connections:
- if gajim.account_is_connected(account): # THE connected account
- # new chat
- if not self.new_chat_menuitem_handler_id:
- self.new_chat_menuitem_handler_id = new_chat_menuitem.\
- connect('activate', self.on_new_chat_menuitem_activate,
- account)
-
- break
-
- # menu items that don't apply to zeroconf connections
- if connected_accounts == 1 or (connected_accounts == 2 and \
- gajim.zeroconf_is_connected()):
- # only one 'real' (non-zeroconf) account is connected, don't need submenus
-
- for account in accounts_list:
- if gajim.account_is_connected(account) and \
- not gajim.config.get_per('accounts', account, 'is_zeroconf'):
- # gc
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
- self.add_bookmarks_list(gc_sub_menu, account)
- gc_sub_menu.show_all()
- # add
- if not self.add_new_contact_handler_id:
- self.add_new_contact_handler_id =\
- add_new_contact_menuitem.connect(
- 'activate', self.on_add_new_contact, account)
- # disco
- if not self.service_disco_handler_id:
- self.service_disco_handler_id = service_disco_menuitem.\
- connect('activate',
- self.on_service_disco_menuitem_activate, account)
-
- # single message
- if not self.single_message_menuitem_handler_id:
- self.single_message_menuitem_handler_id = \
- single_message_menuitem.connect('activate', \
- self.on_send_single_message_menuitem_activate, account)
-
- # new chat accel
- if not self.have_new_chat_accel:
- ag = gtk.accel_groups_from_object(self.window)[0]
- new_chat_menuitem.add_accelerator('activate', ag,
- gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
- self.have_new_chat_accel = True
-
- break # No other account connected
- else:
- # 2 or more 'real' accounts are connected, make submenus
- single_message_sub_menu = gtk.Menu()
- add_sub_menu = gtk.Menu()
- disco_sub_menu = gtk.Menu()
-
- for account in accounts_list:
- if gajim.connections[account].connected <= 1 or \
- gajim.config.get_per('accounts', account, 'is_zeroconf'):
- # skip account if it's offline or connecting or is zeroconf
- continue
-
- # single message
- single_message_item = gtk.MenuItem(_('using account %s') % account,
- False)
- single_message_sub_menu.append(single_message_item)
- single_message_item.connect('activate',
- self.on_send_single_message_menuitem_activate, account)
-
- # join gc
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
- gc_item = gtk.MenuItem(_('using account %s') % account, False)
- gc_sub_menu.append(gc_item)
- gc_menuitem_menu = gtk.Menu()
- self.add_bookmarks_list(gc_menuitem_menu, account)
- gc_item.set_submenu(gc_menuitem_menu)
-
- # add
- add_item = gtk.MenuItem(_('to %s account') % account, False)
- add_sub_menu.append(add_item)
- add_item.connect('activate', self.on_add_new_contact, account)
-
- # disco
- disco_item = gtk.MenuItem(_('using %s account') % account, False)
- disco_sub_menu.append(disco_item)
- disco_item.connect('activate',
- self.on_service_disco_menuitem_activate, account)
-
- single_message_menuitem.set_submenu(single_message_sub_menu)
- single_message_sub_menu.show_all()
- gc_sub_menu.show_all()
- add_new_contact_menuitem.set_submenu(add_sub_menu)
- add_sub_menu.show_all()
- service_disco_menuitem.set_submenu(disco_sub_menu)
- disco_sub_menu.show_all()
-
- if connected_accounts == 0:
- # no connected accounts, make the menuitems insensitive
- for item in (new_chat_menuitem, join_gc_menuitem,\
- add_new_contact_menuitem, service_disco_menuitem,\
- single_message_menuitem):
- item.set_sensitive(False)
- else: # we have one or more connected accounts
- for item in (new_chat_menuitem, join_gc_menuitem,
- add_new_contact_menuitem, service_disco_menuitem,
- single_message_menuitem):
- item.set_sensitive(True)
- # disable some fields if only local account is there
- if connected_accounts == 1:
- for account in gajim.connections:
- if gajim.account_is_connected(account) and \
- gajim.connections[account].is_zeroconf:
- for item in (join_gc_menuitem, add_new_contact_menuitem,
- service_disco_menuitem, single_message_menuitem):
- item.set_sensitive(False)
-
- # Manage GC bookmarks
- newitem = gtk.SeparatorMenuItem() # separator
- gc_sub_menu.append(newitem)
-
- newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
- img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
- gtk.ICON_SIZE_MENU)
- newitem.set_image(img)
- newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
- gc_sub_menu.append(newitem)
- gc_sub_menu.show_all()
- if connected_accounts_with_private_storage == 0:
- newitem.set_sensitive(False)
-
- connected_accounts_with_vcard = []
- for account in gajim.connections:
- if gajim.account_is_connected(account) and \
- gajim.connections[account].vcard_supported:
- connected_accounts_with_vcard.append(account)
- if len(connected_accounts_with_vcard) > 1:
- # 2 or more accounts? make submenus
- profile_avatar_sub_menu = gtk.Menu()
- for account in connected_accounts_with_vcard:
- # profile, avatar
- profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
- False)
- profile_avatar_sub_menu.append(profile_avatar_item)
- profile_avatar_item.connect('activate',
- self.on_profile_avatar_menuitem_activate, account)
- profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
- profile_avatar_sub_menu.show_all()
- elif len(connected_accounts_with_vcard) == 1: # user has only one account
- account = connected_accounts_with_vcard[0]
- # profile, avatar
- if not self.profile_avatar_menuitem_handler_id:
- self.profile_avatar_menuitem_handler_id = \
- profile_avatar_menuitem.connect('activate',
- self.on_profile_avatar_menuitem_activate, account)
-
- if len(connected_accounts_with_vcard) == 0:
- profile_avatar_menuitem.set_sensitive(False)
- else:
- profile_avatar_menuitem.set_sensitive(True)
-
- # Advanced Actions
- if len(gajim.connections) == 0: # user has no accounts
- advanced_menuitem.set_sensitive(False)
- elif len(gajim.connections) == 1: # we have one acccount
- account = gajim.connections.keys()[0]
- advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
- account)
- self.advanced_menus.append(advanced_menuitem_menu)
-
- self.add_history_manager_menuitem(advanced_menuitem_menu)
-
- advanced_menuitem.set_submenu(advanced_menuitem_menu)
- advanced_menuitem_menu.show_all()
- else: # user has *more* than one account : build advanced submenus
- advanced_sub_menu = gtk.Menu()
- accounts = [] # Put accounts in a list to sort them
- for account in gajim.connections:
- accounts.append(account)
- accounts.sort()
- for account in accounts:
- advanced_item = gtk.MenuItem(_('for account %s') % account, False)
- advanced_sub_menu.append(advanced_item)
- advanced_menuitem_menu = \
- self.get_and_connect_advanced_menuitem_menu(account)
- self.advanced_menus.append(advanced_menuitem_menu)
- advanced_item.set_submenu(advanced_menuitem_menu)
-
- self.add_history_manager_menuitem(advanced_sub_menu)
-
- advanced_menuitem.set_submenu(advanced_sub_menu)
- advanced_sub_menu.show_all()
-
- self.actions_menu_needs_rebuild = False
-
- def build_account_menu(self, account):
- # we have to create our own set of icons for the menu
- # using self.jabber_status_images is poopoo
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- state_images = gtkgui_helpers.load_iconset(path)
-
- if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
- xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui')
- account_context_menu = xml.get_object('account_context_menu')
-
- status_menuitem = xml.get_object('status_menuitem')
- start_chat_menuitem = xml.get_object('start_chat_menuitem')
- join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem')
- muc_icon = gtkgui_helpers.load_icon('muc_active')
- if muc_icon:
- join_group_chat_menuitem.set_image(muc_icon)
- open_gmail_inbox_menuitem = xml.get_object('open_gmail_inbox_menuitem')
- add_contact_menuitem = xml.get_object('add_contact_menuitem')
- service_discovery_menuitem = xml.get_object(
- 'service_discovery_menuitem')
- execute_command_menuitem = xml.get_object('execute_command_menuitem')
- edit_account_menuitem = xml.get_object('edit_account_menuitem')
- sub_menu = gtk.Menu()
- status_menuitem.set_submenu(sub_menu)
-
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic=True)
- item = gtk.ImageMenuItem(uf_show)
- icon = state_images[show]
- item.set_image(icon)
- sub_menu.append(item)
- con = gajim.connections[account]
- if show == 'invisible' and con.connected > 1 and \
- not con.privacy_rules_supported:
- item.set_sensitive(False)
- else:
- item.connect('activate', self.change_status, account, show)
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
-
- item = gtk.ImageMenuItem(_('_Change Status Message'))
- gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate,
- account)
- if gajim.connections[account].connected < 2:
- item.set_sensitive(False)
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
-
- uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
- item = gtk.ImageMenuItem(uf_show)
- icon = state_images['offline']
- item.set_image(icon)
- sub_menu.append(item)
- item.connect('activate', self.change_status, account, 'offline')
-
- pep_menuitem = xml.get_object('pep_menuitem')
- if gajim.connections[account].pep_supported:
- pep_submenu = gtk.Menu()
- pep_menuitem.set_submenu(pep_submenu)
- def add_item(label, opt_name, func):
- item = gtk.CheckMenuItem(label)
- pep_submenu.append(item)
- if not dbus_support.supported:
- item.set_sensitive(False)
- else:
- activ = gajim.config.get_per('accounts', account, opt_name)
- item.set_active(activ)
- item.connect('toggled', func, account)
-
- add_item(_('Publish Tune'), 'publish_tune',
- self.on_publish_tune_toggled)
- add_item(_('Publish Location'), 'publish_location',
- self.on_publish_location_toggled)
-
- pep_config = gtk.ImageMenuItem(_('Configure Services...'))
- item = gtk.SeparatorMenuItem()
- pep_submenu.append(item)
- pep_config.set_sensitive(True)
- pep_submenu.append(pep_config)
- pep_config.connect('activate',
- self.on_pep_services_menuitem_activate, account)
- img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
- gtk.ICON_SIZE_MENU)
- pep_config.set_image(img)
-
- else:
- pep_menuitem.set_sensitive(False)
-
- if not gajim.connections[account].gmail_url:
- open_gmail_inbox_menuitem.set_no_show_all(True)
- open_gmail_inbox_menuitem.hide()
- else:
- open_gmail_inbox_menuitem.connect('activate',
- self.on_open_gmail_inbox, account)
-
- edit_account_menuitem.connect('activate', self.on_edit_account,
- account)
- add_contact_menuitem.connect('activate', self.on_add_new_contact,
- account)
- service_discovery_menuitem.connect('activate',
- self.on_service_disco_menuitem_activate, account)
- hostname = gajim.config.get_per('accounts', account, 'hostname')
- contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact
- execute_command_menuitem.connect('activate',
- self.on_execute_command, contact, account)
-
- start_chat_menuitem.connect('activate',
- self.on_new_chat_menuitem_activate, account)
-
- gc_sub_menu = gtk.Menu() # gc is always a submenu
- join_group_chat_menuitem.set_submenu(gc_sub_menu)
- self.add_bookmarks_list(gc_sub_menu, account)
-
- # make some items insensitive if account is offline
- if gajim.connections[account].connected < 2:
- for widget in (add_contact_menuitem, service_discovery_menuitem,
- join_group_chat_menuitem, execute_command_menuitem, pep_menuitem,
- start_chat_menuitem):
- widget.set_sensitive(False)
- else:
- xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui')
- account_context_menu = xml.get_object('zeroconf_context_menu')
-
- status_menuitem = xml.get_object('status_menuitem')
- zeroconf_properties_menuitem = xml.get_object(
- 'zeroconf_properties_menuitem')
- sub_menu = gtk.Menu()
- status_menuitem.set_submenu(sub_menu)
-
- for show in ('online', 'away', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic=True)
- item = gtk.ImageMenuItem(uf_show)
- icon = state_images[show]
- item.set_image(icon)
- sub_menu.append(item)
- item.connect('activate', self.change_status, account, show)
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
-
- item = gtk.ImageMenuItem(_('_Change Status Message'))
- gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate,
- account)
- if gajim.connections[account].connected < 2:
- item.set_sensitive(False)
-
- uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
- item = gtk.ImageMenuItem(uf_show)
- icon = state_images['offline']
- item.set_image(icon)
- sub_menu.append(item)
- item.connect('activate', self.change_status, account, 'offline')
-
- zeroconf_properties_menuitem.connect('activate',
- self.on_zeroconf_properties, account)
-
- return account_context_menu
-
- def make_account_menu(self, event, titer):
- """
- Make account's popup menu
- """
- model = self.modelfilter
- account = model[titer][C_ACCOUNT].decode('utf-8')
-
- if account != 'all': # not in merged mode
- menu = self.build_account_menu(account)
- else:
- menu = gtk.Menu()
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- accounts = [] # Put accounts in a list to sort them
- for account in gajim.connections:
- accounts.append(account)
- accounts.sort()
- for account in accounts:
- state_images = gtkgui_helpers.load_iconset(path)
- item = gtk.ImageMenuItem(account)
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- icon = state_images[show]
- item.set_image(icon)
- account_menu = self.build_account_menu(account)
- item.set_submenu(account_menu)
- menu.append(item)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, event_button, event.time)
-
- def make_group_menu(self, event, titer):
- """
- Make group's popup menu
- """
- model = self.modelfilter
- path = model.get_path(titer)
- group = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
-
- list_ = [] # list of (jid, account) tuples
- list_online = [] # list of (jid, account) tuples
-
- group = model[titer][C_JID]
- for jid in gajim.contacts.get_jid_list(account):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if group in contact.get_shown_groups():
- if contact.show not in ('offline', 'error'):
- list_online.append((contact, account))
- list_.append((contact, account))
- menu = gtk.Menu()
-
- # Make special context menu if group is Groupchats
- if group == _('Groupchats'):
- maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All'))
- icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU)
- maximize_menuitem.set_image(icon)
- maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\
- list_)
- menu.append(maximize_menuitem)
- else:
- # Send Group Message
- send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage'))
- icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
- send_group_message_item.set_image(icon)
-
- send_group_message_submenu = gtk.Menu()
- send_group_message_item.set_submenu(send_group_message_submenu)
- menu.append(send_group_message_item)
-
- group_message_to_all_item = gtk.MenuItem(_('To all users'))
- send_group_message_submenu.append(group_message_to_all_item)
-
- group_message_to_all_online_item = gtk.MenuItem(
- _('To all online users'))
- send_group_message_submenu.append(group_message_to_all_online_item)
-
- group_message_to_all_online_item.connect('activate',
- self.on_send_single_message_menuitem_activate, account, list_online)
- group_message_to_all_item.connect('activate',
- self.on_send_single_message_menuitem_activate, account, list_)
-
- # Invite to
- invite_menuitem = gtk.ImageMenuItem(_('In_vite to'))
- muc_icon = gtkgui_helpers.load_icon('muc_active')
- if muc_icon:
- invite_menuitem.set_image(muc_icon)
-
- gui_menu_builder.build_invite_submenu(invite_menuitem, list_online)
- menu.append(invite_menuitem)
-
- # Send Custom Status
- send_custom_status_menuitem = gtk.ImageMenuItem(
- _('Send Cus_tom Status'))
- # add a special img for this menuitem
- if helpers.group_is_blocked(account, group):
- send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
- 'offline'))
- send_custom_status_menuitem.set_sensitive(False)
- else:
- icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
- gtk.ICON_SIZE_MENU)
- send_custom_status_menuitem.set_image(icon)
- status_menuitems = gtk.Menu()
- send_custom_status_menuitem.set_submenu(status_menuitems)
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
- # icon MUST be different instance for every item
- state_images = gtkgui_helpers.load_iconset(path)
- status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
- status_menuitem.connect('activate', self.on_send_custom_status,
- list_, s, group)
- icon = state_images[s]
- status_menuitem.set_image(icon)
- status_menuitems.append(status_menuitem)
- menu.append(send_custom_status_menuitem)
-
- # there is no singlemessage and custom status for zeroconf
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- send_custom_status_menuitem.set_sensitive(False)
- send_group_message_item.set_sensitive(False)
-
- if not group in helpers.special_groups:
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- # Rename
- rename_item = gtk.ImageMenuItem(_('Re_name'))
- # add a special img for rename menuitem
- gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input')
- menu.append(rename_item)
- rename_item.connect('activate', self.on_rename, 'group', group,
- account)
-
- # Block group
- is_blocked = False
- if self.regroup:
- for g_account in gajim.connections:
- if helpers.group_is_blocked(g_account, group):
- is_blocked = True
- else:
- if helpers.group_is_blocked(account, group):
- is_blocked = True
-
- if is_blocked and gajim.connections[account].privacy_rules_supported:
- unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
- icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
- unblock_menuitem.set_image(icon)
- unblock_menuitem.connect('activate', self.on_unblock, list_, group)
- menu.append(unblock_menuitem)
- else:
- block_menuitem = gtk.ImageMenuItem(_('_Block'))
- icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
- block_menuitem.set_image(icon)
- block_menuitem.connect('activate', self.on_block, list_, group)
- menu.append(block_menuitem)
- if not gajim.connections[account].privacy_rules_supported:
- block_menuitem.set_sensitive(False)
-
- # Remove group
- remove_item = gtk.ImageMenuItem(_('_Remove'))
- icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
- remove_item.set_image(icon)
- menu.append(remove_item)
- remove_item.connect('activate', self.on_remove_group_item_activated,
- group, account)
-
- # unsensitive if account is not connected
- if gajim.connections[account].connected < 2:
- rename_item.set_sensitive(False)
-
- # General group cannot be changed
- if group == _('General'):
- rename_item.set_sensitive(False)
- block_menuitem.set_sensitive(False)
- remove_item.set_sensitive(False)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, event_button, event.time)
-
- def make_contact_menu(self, event, titer):
- """
- Make contact's popup menu
- """
- model = self.modelfilter
- jid = model[titer][C_JID].decode('utf-8')
- tree_path = model.get_path(titer)
- account = model[titer][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- menu = gui_menu_builder.get_contact_menu(contact, account)
- event_button = gtkgui_helpers.get_possible_button_event(event)
- menu.attach_to_widget(self.tree, None)
- menu.popup(None, None, None, event_button, event.time)
-
- def make_multiple_contact_menu(self, event, iters):
- """
- Make group's popup menu
- """
- model = self.modelfilter
- list_ = [] # list of (jid, account) tuples
- one_account_offline = False
- is_blocked = True
- privacy_rules_supported = True
- for titer in iters:
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- if gajim.connections[account].connected < 2:
- one_account_offline = True
- if not gajim.connections[account].privacy_rules_supported:
- privacy_rules_supported = False
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if helpers.jid_is_blocked(account, jid):
- is_blocked = False
- list_.append((contact, account))
-
- menu = gtk.Menu()
- account = None
- for (contact, current_account) in list_:
- # check that we use the same account for every sender
- if account is not None and account != current_account:
- account = None
- break
- account = current_account
- if account is not None:
- send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage'))
- icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
- send_group_message_item.set_image(icon)
- menu.append(send_group_message_item)
- send_group_message_item.connect('activate',
- self.on_send_single_message_menuitem_activate, account, list_)
-
- # Invite to Groupchat
- invite_item = gtk.ImageMenuItem(_('In_vite to'))
- muc_icon = gtkgui_helpers.load_icon('muc_active')
- if muc_icon:
- invite_item.set_image(muc_icon)
-
- gui_menu_builder.build_invite_submenu(invite_item, list_)
- menu.append(invite_item)
-
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- # Manage Transport submenu
- item = gtk.ImageMenuItem(_('_Manage Contacts'))
- icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- manage_contacts_submenu = gtk.Menu()
- item.set_submenu(manage_contacts_submenu)
- menu.append(item)
-
- # Edit Groups
- edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups'))
- icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
- edit_groups_item.set_image(icon)
- manage_contacts_submenu.append(edit_groups_item)
- edit_groups_item.connect('activate', self.on_edit_groups, list_)
-
- item = gtk.SeparatorMenuItem() # separator
- manage_contacts_submenu.append(item)
-
- # Block
- if is_blocked and privacy_rules_supported:
- unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
- icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
- unblock_menuitem.set_image(icon)
- unblock_menuitem.connect('activate', self.on_unblock, list_)
- manage_contacts_submenu.append(unblock_menuitem)
- else:
- block_menuitem = gtk.ImageMenuItem(_('_Block'))
- icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
- block_menuitem.set_image(icon)
- block_menuitem.connect('activate', self.on_block, list_)
- manage_contacts_submenu.append(block_menuitem)
-
- if not privacy_rules_supported:
- block_menuitem.set_sensitive(False)
-
- # Remove
- remove_item = gtk.ImageMenuItem(_('_Remove'))
- icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
- remove_item.set_image(icon)
- manage_contacts_submenu.append(remove_item)
- remove_item.connect('activate', self.on_req_usub, list_)
- # unsensitive remove if one account is not connected
- if one_account_offline:
- remove_item.set_sensitive(False)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, event_button, event.time)
-
- def make_transport_menu(self, event, titer):
- """
- Make transport's popup menu
- """
- model = self.modelfilter
- jid = model[titer][C_JID].decode('utf-8')
- path = model.get_path(titer)
- account = model[titer][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- menu = gtk.Menu()
-
- # Send single message
- item = gtk.ImageMenuItem(_('Send Single Message'))
- icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- item.connect('activate',
- self.on_send_single_message_menuitem_activate, account, contact)
- menu.append(item)
-
- blocked = False
- if helpers.jid_is_blocked(account, jid):
- blocked = True
-
- # Send Custom Status
- send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status'))
- # add a special img for this menuitem
- if blocked:
- send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
- 'offline'))
- send_custom_status_menuitem.set_sensitive(False)
- else:
- if account in gajim.interface.status_sent_to_users and \
- jid in gajim.interface.status_sent_to_users[account]:
- send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
- gajim.interface.status_sent_to_users[account][jid]))
- else:
- icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
- gtk.ICON_SIZE_MENU)
- send_custom_status_menuitem.set_image(icon)
- status_menuitems = gtk.Menu()
- send_custom_status_menuitem.set_submenu(status_menuitems)
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
- # icon MUST be different instance for every item
- state_images = gtkgui_helpers.load_iconset(path)
- status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
- status_menuitem.connect('activate', self.on_send_custom_status,
- [(contact, account)], s)
- icon = state_images[s]
- status_menuitem.set_image(icon)
- status_menuitems.append(status_menuitem)
- menu.append(send_custom_status_menuitem)
-
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- # Execute Command
- item = gtk.ImageMenuItem(_('Execute Command...'))
- icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- menu.append(item)
- item.connect('activate', self.on_execute_command, contact, account,
- contact.resource)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- # Manage Transport submenu
- item = gtk.ImageMenuItem(_('_Manage Transport'))
- icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- manage_transport_submenu = gtk.Menu()
- item.set_submenu(manage_transport_submenu)
- menu.append(item)
-
- # Modify Transport
- item = gtk.ImageMenuItem(_('_Modify Transport'))
- icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- manage_transport_submenu.append(item)
- item.connect('activate', self.on_edit_agent, contact, account)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- # Rename
- item = gtk.ImageMenuItem(_('_Rename'))
- # add a special img for rename menuitem
- gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
- manage_transport_submenu.append(item)
- item.connect('activate', self.on_rename, 'agent', jid, account)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- item = gtk.SeparatorMenuItem() # separator
- manage_transport_submenu.append(item)
-
- # Block
- if blocked:
- item = gtk.ImageMenuItem(_('_Unblock'))
- item.connect('activate', self.on_unblock, [(contact, account)])
- else:
- item = gtk.ImageMenuItem(_('_Block'))
- item.connect('activate', self.on_block, [(contact, account)])
-
- icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- manage_transport_submenu.append(item)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- # Remove
- item = gtk.ImageMenuItem(_('_Remove'))
- icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- manage_transport_submenu.append(item)
- item.connect('activate', self.on_remove_agent, [(contact, account)])
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- # Information
- information_menuitem = gtk.ImageMenuItem(_('_Information'))
- icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU)
- information_menuitem.set_image(icon)
- menu.append(information_menuitem)
- information_menuitem.connect('activate', self.on_info, contact, account)
-
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, event_button, event.time)
-
- def make_groupchat_menu(self, event, titer):
- model = self.modelfilter
-
- jid = model[titer][C_JID].decode('utf-8')
- account = model[titer][C_ACCOUNT].decode('utf-8')
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- menu = gtk.Menu()
-
- if jid in gajim.interface.minimized_controls[account]:
- maximize_menuitem = gtk.ImageMenuItem(_('_Maximize'))
- icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU)
- maximize_menuitem.set_image(icon)
- maximize_menuitem.connect('activate', self.on_groupchat_maximized, \
- jid, account)
- menu.append(maximize_menuitem)
-
- if not gajim.gc_connected[account].get(jid, False):
- connect_menuitem = gtk.ImageMenuItem(_('_Reconnect'))
- connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \
- gtk.ICON_SIZE_MENU)
- connect_menuitem.set_image(connect_icon)
- connect_menuitem.connect('activate', self.on_reconnect, jid, account)
- menu.append(connect_menuitem)
- disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect'))
- disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \
- gtk.ICON_SIZE_MENU)
- disconnect_menuitem.set_image(disconnect_icon)
- disconnect_menuitem.connect('activate', self.on_disconnect, jid, account)
- menu.append(disconnect_menuitem)
-
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- history_menuitem = gtk.ImageMenuItem(_('_History'))
- history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \
- gtk.ICON_SIZE_MENU)
- history_menuitem.set_image(history_icon)
- history_menuitem .connect('activate', self.on_history, \
- contact, account)
- menu.append(history_menuitem)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, event_button, event.time)
-
- def get_and_connect_advanced_menuitem_menu(self, account):
- """
- Add FOR ACCOUNT options
- """
- xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui')
- advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu')
-
- xml_console_menuitem = xml.get_object('xml_console_menuitem')
- privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem')
- administrator_menuitem = xml.get_object('administrator_menuitem')
- send_server_message_menuitem = xml.get_object(
- 'send_server_message_menuitem')
- set_motd_menuitem = xml.get_object('set_motd_menuitem')
- update_motd_menuitem = xml.get_object('update_motd_menuitem')
- delete_motd_menuitem = xml.get_object('delete_motd_menuitem')
-
- xml_console_menuitem.connect('activate',
- self.on_xml_console_menuitem_activate, account)
-
- if gajim.connections[account] and gajim.connections[account].\
- privacy_rules_supported:
- privacy_lists_menuitem.connect('activate',
- self.on_privacy_lists_menuitem_activate, account)
- else:
- privacy_lists_menuitem.set_sensitive(False)
-
- if gajim.connections[account].is_zeroconf:
- administrator_menuitem.set_sensitive(False)
- send_server_message_menuitem.set_sensitive(False)
- set_motd_menuitem.set_sensitive(False)
- update_motd_menuitem.set_sensitive(False)
- delete_motd_menuitem.set_sensitive(False)
- else:
- send_server_message_menuitem.connect('activate',
- self.on_send_server_message_menuitem_activate, account)
-
- set_motd_menuitem.connect('activate',
- self.on_set_motd_menuitem_activate, account)
-
- update_motd_menuitem.connect('activate',
- self.on_update_motd_menuitem_activate, account)
-
- delete_motd_menuitem.connect('activate',
- self.on_delete_motd_menuitem_activate, account)
-
- advanced_menuitem_menu.show_all()
-
- return advanced_menuitem_menu
-
- def add_history_manager_menuitem(self, menu):
- """
- Add a seperator and History Manager menuitem BELOW for account menuitems
- """
- item = gtk.SeparatorMenuItem() # separator
- menu.append(item)
-
- # History manager
- item = gtk.ImageMenuItem(_('History Manager'))
- icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL,
- gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- menu.append(item)
- item.connect('activate', self.on_history_manager_menuitem_activate)
-
- def add_bookmarks_list(self, gc_sub_menu, account):
- """
- Show join new group chat item and bookmarks list for an account
- """
- item = gtk.ImageMenuItem(_('_Join New Group Chat'))
- icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
- item.set_image(icon)
- item.connect('activate', self.on_join_gc_activate, account)
-
- gc_sub_menu.append(item)
-
- # User has at least one bookmark.
- if gajim.connections[account].bookmarks:
- item = gtk.SeparatorMenuItem()
- gc_sub_menu.append(item)
-
- for bookmark in gajim.connections[account].bookmarks:
- # Do not use underline.
- item = gtk.MenuItem(bookmark['name'], False)
- item.connect('activate', self.on_bookmark_menuitem_activate,
- account, bookmark)
- gc_sub_menu.append(item)
-
- def set_actions_menu_needs_rebuild(self):
- self.actions_menu_needs_rebuild = True
-
- def show_appropriate_context_menu(self, event, iters):
- # iters must be all of the same type
- model = self.modelfilter
- type_ = model[iters[0]][C_TYPE]
- for titer in iters[1:]:
- if model[titer][C_TYPE] != type_:
- return
- if type_ == 'group' and len(iters) == 1:
- self.make_group_menu(event, iters[0])
- if type_ == 'groupchat' and len(iters) == 1:
- self.make_groupchat_menu(event, iters[0])
- elif type_ == 'agent' and len(iters) == 1:
- self.make_transport_menu(event, iters[0])
- elif type_ in ('contact', 'self_contact') and len(iters) == 1:
- self.make_contact_menu(event, iters[0])
- elif type_ == 'contact':
- self.make_multiple_contact_menu(event, iters)
- elif type_ == 'account' and len(iters) == 1:
- self.make_account_menu(event, iters[0])
-
- def show_treeview_menu(self, event):
- try:
- model, list_of_paths = self.tree.get_selection().get_selected_rows()
- except TypeError:
- self.tree.get_selection().unselect_all()
- return
- if not len(list_of_paths):
- # no row is selected
- return
- if len(list_of_paths) > 1:
- iters = []
- for path in list_of_paths:
- iters.append(model.get_iter(path))
- else:
- path = list_of_paths[0]
- iters = [model.get_iter(path)]
- self.show_appropriate_context_menu(event, iters)
-
- return True
-
- def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier):
- """
- Bring up the conference join dialog, when CTRL+J accelerator is being
- activated
- """
- # find a connected account:
- for account in gajim.connections:
- if gajim.account_is_connected(account):
- break
- self.on_join_gc_activate(None, account)
- return True
+ def make_menu(self, force=False):
+ """
+ Create the main window's menus
+ """
+ if not force and not self.actions_menu_needs_rebuild:
+ return
+ new_chat_menuitem = self.xml.get_object('new_chat_menuitem')
+ single_message_menuitem = self.xml.get_object(
+ 'send_single_message_menuitem')
+ join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
+ muc_icon = gtkgui_helpers.load_icon('muc_active')
+ if muc_icon:
+ join_gc_menuitem.set_image(muc_icon)
+ add_new_contact_menuitem = self.xml.get_object('add_new_contact_menuitem')
+ service_disco_menuitem = self.xml.get_object('service_disco_menuitem')
+ advanced_menuitem = self.xml.get_object('advanced_menuitem')
+ profile_avatar_menuitem = self.xml.get_object('profile_avatar_menuitem')
+
+ # destroy old advanced menus
+ for m in self.advanced_menus:
+ m.destroy()
+
+ # make it sensitive. it is insensitive only if no accounts are *available*
+ advanced_menuitem.set_sensitive(True)
+
+ if self.add_new_contact_handler_id:
+ add_new_contact_menuitem.handler_disconnect(
+ self.add_new_contact_handler_id)
+ self.add_new_contact_handler_id = None
+
+ if self.service_disco_handler_id:
+ service_disco_menuitem.handler_disconnect(
+ self.service_disco_handler_id)
+ self.service_disco_handler_id = None
+
+ if self.new_chat_menuitem_handler_id:
+ new_chat_menuitem.handler_disconnect(
+ self.new_chat_menuitem_handler_id)
+ self.new_chat_menuitem_handler_id = None
+
+ if self.single_message_menuitem_handler_id:
+ single_message_menuitem.handler_disconnect(
+ self.single_message_menuitem_handler_id)
+ self.single_message_menuitem_handler_id = None
+
+ if self.profile_avatar_menuitem_handler_id:
+ profile_avatar_menuitem.handler_disconnect(
+ self.profile_avatar_menuitem_handler_id)
+ self.profile_avatar_menuitem_handler_id = None
+
+ # remove the existing submenus
+ add_new_contact_menuitem.remove_submenu()
+ service_disco_menuitem.remove_submenu()
+ join_gc_menuitem.remove_submenu()
+ single_message_menuitem.remove_submenu()
+ new_chat_menuitem.remove_submenu()
+ advanced_menuitem.remove_submenu()
+ profile_avatar_menuitem.remove_submenu()
+
+ # remove the existing accelerator
+ if self.have_new_chat_accel:
+ ag = gtk.accel_groups_from_object(self.window)[0]
+ new_chat_menuitem.remove_accelerator(ag, gtk.keysyms.n,
+ gtk.gdk.CONTROL_MASK)
+ self.have_new_chat_accel = False
+
+ gc_sub_menu = gtk.Menu() # gc is always a submenu
+ join_gc_menuitem.set_submenu(gc_sub_menu)
+
+ connected_accounts = gajim.get_number_of_connected_accounts()
+
+ connected_accounts_with_private_storage = 0
+
+ # items that get shown whether an account is zeroconf or not
+ accounts_list = sorted(gajim.contacts.get_accounts())
+ if connected_accounts > 1: # 2 or more accounts? make submenus
+ new_chat_sub_menu = gtk.Menu()
+
+ for account in accounts_list:
+ if gajim.connections[account].connected <= 1:
+ # if offline or connecting
+ continue
+
+ # new chat
+ new_chat_item = gtk.MenuItem(_('using account %s') % account,
+ False)
+ new_chat_sub_menu.append(new_chat_item)
+ new_chat_item.connect('activate',
+ self.on_new_chat_menuitem_activate, account)
+
+ new_chat_menuitem.set_submenu(new_chat_sub_menu)
+ new_chat_sub_menu.show_all()
+
+ elif connected_accounts == 1: # user has only one account
+ for account in gajim.connections:
+ if gajim.account_is_connected(account): # THE connected account
+ # new chat
+ if not self.new_chat_menuitem_handler_id:
+ self.new_chat_menuitem_handler_id = new_chat_menuitem.\
+ connect('activate', self.on_new_chat_menuitem_activate,
+ account)
+
+ break
+
+ # menu items that don't apply to zeroconf connections
+ if connected_accounts == 1 or (connected_accounts == 2 and \
+ gajim.zeroconf_is_connected()):
+ # only one 'real' (non-zeroconf) account is connected, don't need submenus
+
+ for account in accounts_list:
+ if gajim.account_is_connected(account) and \
+ not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ # gc
+ if gajim.connections[account].private_storage_supported:
+ connected_accounts_with_private_storage += 1
+ self.add_bookmarks_list(gc_sub_menu, account)
+ gc_sub_menu.show_all()
+ # add
+ if not self.add_new_contact_handler_id:
+ self.add_new_contact_handler_id =\
+ add_new_contact_menuitem.connect(
+ 'activate', self.on_add_new_contact, account)
+ # disco
+ if not self.service_disco_handler_id:
+ self.service_disco_handler_id = service_disco_menuitem.\
+ connect('activate',
+ self.on_service_disco_menuitem_activate, account)
+
+ # single message
+ if not self.single_message_menuitem_handler_id:
+ self.single_message_menuitem_handler_id = \
+ single_message_menuitem.connect('activate', \
+ self.on_send_single_message_menuitem_activate, account)
+
+ # new chat accel
+ if not self.have_new_chat_accel:
+ ag = gtk.accel_groups_from_object(self.window)[0]
+ new_chat_menuitem.add_accelerator('activate', ag,
+ gtk.keysyms.n, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
+ self.have_new_chat_accel = True
+
+ break # No other account connected
+ else:
+ # 2 or more 'real' accounts are connected, make submenus
+ single_message_sub_menu = gtk.Menu()
+ add_sub_menu = gtk.Menu()
+ disco_sub_menu = gtk.Menu()
+
+ for account in accounts_list:
+ if gajim.connections[account].connected <= 1 or \
+ gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ # skip account if it's offline or connecting or is zeroconf
+ continue
+
+ # single message
+ single_message_item = gtk.MenuItem(_('using account %s') % account,
+ False)
+ single_message_sub_menu.append(single_message_item)
+ single_message_item.connect('activate',
+ self.on_send_single_message_menuitem_activate, account)
+
+ # join gc
+ if gajim.connections[account].private_storage_supported:
+ connected_accounts_with_private_storage += 1
+ gc_item = gtk.MenuItem(_('using account %s') % account, False)
+ gc_sub_menu.append(gc_item)
+ gc_menuitem_menu = gtk.Menu()
+ self.add_bookmarks_list(gc_menuitem_menu, account)
+ gc_item.set_submenu(gc_menuitem_menu)
+
+ # add
+ add_item = gtk.MenuItem(_('to %s account') % account, False)
+ add_sub_menu.append(add_item)
+ add_item.connect('activate', self.on_add_new_contact, account)
+
+ # disco
+ disco_item = gtk.MenuItem(_('using %s account') % account, False)
+ disco_sub_menu.append(disco_item)
+ disco_item.connect('activate',
+ self.on_service_disco_menuitem_activate, account)
+
+ single_message_menuitem.set_submenu(single_message_sub_menu)
+ single_message_sub_menu.show_all()
+ gc_sub_menu.show_all()
+ add_new_contact_menuitem.set_submenu(add_sub_menu)
+ add_sub_menu.show_all()
+ service_disco_menuitem.set_submenu(disco_sub_menu)
+ disco_sub_menu.show_all()
+
+ if connected_accounts == 0:
+ # no connected accounts, make the menuitems insensitive
+ for item in (new_chat_menuitem, join_gc_menuitem,\
+ add_new_contact_menuitem, service_disco_menuitem,\
+ single_message_menuitem):
+ item.set_sensitive(False)
+ else: # we have one or more connected accounts
+ for item in (new_chat_menuitem, join_gc_menuitem,
+ add_new_contact_menuitem, service_disco_menuitem,
+ single_message_menuitem):
+ item.set_sensitive(True)
+ # disable some fields if only local account is there
+ if connected_accounts == 1:
+ for account in gajim.connections:
+ if gajim.account_is_connected(account) and \
+ gajim.connections[account].is_zeroconf:
+ for item in (join_gc_menuitem, add_new_contact_menuitem,
+ service_disco_menuitem, single_message_menuitem):
+ item.set_sensitive(False)
+
+ # Manage GC bookmarks
+ newitem = gtk.SeparatorMenuItem() # separator
+ gc_sub_menu.append(newitem)
+
+ newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
+ img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
+ gtk.ICON_SIZE_MENU)
+ newitem.set_image(img)
+ newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
+ gc_sub_menu.append(newitem)
+ gc_sub_menu.show_all()
+ if connected_accounts_with_private_storage == 0:
+ newitem.set_sensitive(False)
+
+ connected_accounts_with_vcard = []
+ for account in gajim.connections:
+ if gajim.account_is_connected(account) and \
+ gajim.connections[account].vcard_supported:
+ connected_accounts_with_vcard.append(account)
+ if len(connected_accounts_with_vcard) > 1:
+ # 2 or more accounts? make submenus
+ profile_avatar_sub_menu = gtk.Menu()
+ for account in connected_accounts_with_vcard:
+ # profile, avatar
+ profile_avatar_item = gtk.MenuItem(_('of account %s') % account,
+ False)
+ profile_avatar_sub_menu.append(profile_avatar_item)
+ profile_avatar_item.connect('activate',
+ self.on_profile_avatar_menuitem_activate, account)
+ profile_avatar_menuitem.set_submenu(profile_avatar_sub_menu)
+ profile_avatar_sub_menu.show_all()
+ elif len(connected_accounts_with_vcard) == 1: # user has only one account
+ account = connected_accounts_with_vcard[0]
+ # profile, avatar
+ if not self.profile_avatar_menuitem_handler_id:
+ self.profile_avatar_menuitem_handler_id = \
+ profile_avatar_menuitem.connect('activate',
+ self.on_profile_avatar_menuitem_activate, account)
+
+ if len(connected_accounts_with_vcard) == 0:
+ profile_avatar_menuitem.set_sensitive(False)
+ else:
+ profile_avatar_menuitem.set_sensitive(True)
+
+ # Advanced Actions
+ if len(gajim.connections) == 0: # user has no accounts
+ advanced_menuitem.set_sensitive(False)
+ elif len(gajim.connections) == 1: # we have one acccount
+ account = gajim.connections.keys()[0]
+ advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
+ account)
+ self.advanced_menus.append(advanced_menuitem_menu)
+
+ self.add_history_manager_menuitem(advanced_menuitem_menu)
+
+ advanced_menuitem.set_submenu(advanced_menuitem_menu)
+ advanced_menuitem_menu.show_all()
+ else: # user has *more* than one account : build advanced submenus
+ advanced_sub_menu = gtk.Menu()
+ accounts = [] # Put accounts in a list to sort them
+ for account in gajim.connections:
+ accounts.append(account)
+ accounts.sort()
+ for account in accounts:
+ advanced_item = gtk.MenuItem(_('for account %s') % account, False)
+ advanced_sub_menu.append(advanced_item)
+ advanced_menuitem_menu = \
+ self.get_and_connect_advanced_menuitem_menu(account)
+ self.advanced_menus.append(advanced_menuitem_menu)
+ advanced_item.set_submenu(advanced_menuitem_menu)
+
+ self.add_history_manager_menuitem(advanced_sub_menu)
+
+ advanced_menuitem.set_submenu(advanced_sub_menu)
+ advanced_sub_menu.show_all()
+
+ self.actions_menu_needs_rebuild = False
+
+ def build_account_menu(self, account):
+ # we have to create our own set of icons for the menu
+ # using self.jabber_status_images is poopoo
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ state_images = gtkgui_helpers.load_iconset(path)
+
+ if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui')
+ account_context_menu = xml.get_object('account_context_menu')
+
+ status_menuitem = xml.get_object('status_menuitem')
+ start_chat_menuitem = xml.get_object('start_chat_menuitem')
+ join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem')
+ muc_icon = gtkgui_helpers.load_icon('muc_active')
+ if muc_icon:
+ join_group_chat_menuitem.set_image(muc_icon)
+ open_gmail_inbox_menuitem = xml.get_object('open_gmail_inbox_menuitem')
+ add_contact_menuitem = xml.get_object('add_contact_menuitem')
+ service_discovery_menuitem = xml.get_object(
+ 'service_discovery_menuitem')
+ execute_command_menuitem = xml.get_object('execute_command_menuitem')
+ edit_account_menuitem = xml.get_object('edit_account_menuitem')
+ sub_menu = gtk.Menu()
+ status_menuitem.set_submenu(sub_menu)
+
+ for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+ uf_show = helpers.get_uf_show(show, use_mnemonic=True)
+ item = gtk.ImageMenuItem(uf_show)
+ icon = state_images[show]
+ item.set_image(icon)
+ sub_menu.append(item)
+ con = gajim.connections[account]
+ if show == 'invisible' and con.connected > 1 and \
+ not con.privacy_rules_supported:
+ item.set_sensitive(False)
+ else:
+ item.connect('activate', self.change_status, account, show)
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ item = gtk.ImageMenuItem(_('_Change Status Message'))
+ gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
+ sub_menu.append(item)
+ item.connect('activate', self.on_change_status_message_activate,
+ account)
+ if gajim.connections[account].connected < 2:
+ item.set_sensitive(False)
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
+ item = gtk.ImageMenuItem(uf_show)
+ icon = state_images['offline']
+ item.set_image(icon)
+ sub_menu.append(item)
+ item.connect('activate', self.change_status, account, 'offline')
+
+ pep_menuitem = xml.get_object('pep_menuitem')
+ if gajim.connections[account].pep_supported:
+ pep_submenu = gtk.Menu()
+ pep_menuitem.set_submenu(pep_submenu)
+ def add_item(label, opt_name, func):
+ item = gtk.CheckMenuItem(label)
+ pep_submenu.append(item)
+ if not dbus_support.supported:
+ item.set_sensitive(False)
+ else:
+ activ = gajim.config.get_per('accounts', account, opt_name)
+ item.set_active(activ)
+ item.connect('toggled', func, account)
+
+ add_item(_('Publish Tune'), 'publish_tune',
+ self.on_publish_tune_toggled)
+ add_item(_('Publish Location'), 'publish_location',
+ self.on_publish_location_toggled)
+
+ pep_config = gtk.ImageMenuItem(_('Configure Services...'))
+ item = gtk.SeparatorMenuItem()
+ pep_submenu.append(item)
+ pep_config.set_sensitive(True)
+ pep_submenu.append(pep_config)
+ pep_config.connect('activate',
+ self.on_pep_services_menuitem_activate, account)
+ img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
+ gtk.ICON_SIZE_MENU)
+ pep_config.set_image(img)
+
+ else:
+ pep_menuitem.set_sensitive(False)
+
+ if not gajim.connections[account].gmail_url:
+ open_gmail_inbox_menuitem.set_no_show_all(True)
+ open_gmail_inbox_menuitem.hide()
+ else:
+ open_gmail_inbox_menuitem.connect('activate',
+ self.on_open_gmail_inbox, account)
+
+ edit_account_menuitem.connect('activate', self.on_edit_account,
+ account)
+ add_contact_menuitem.connect('activate', self.on_add_new_contact,
+ account)
+ service_discovery_menuitem.connect('activate',
+ self.on_service_disco_menuitem_activate, account)
+ hostname = gajim.config.get_per('accounts', account, 'hostname')
+ contact = gajim.contacts.create_contact(jid=hostname, account=account) # Fake contact
+ execute_command_menuitem.connect('activate',
+ self.on_execute_command, contact, account)
+
+ start_chat_menuitem.connect('activate',
+ self.on_new_chat_menuitem_activate, account)
+
+ gc_sub_menu = gtk.Menu() # gc is always a submenu
+ join_group_chat_menuitem.set_submenu(gc_sub_menu)
+ self.add_bookmarks_list(gc_sub_menu, account)
+
+ # make some items insensitive if account is offline
+ if gajim.connections[account].connected < 2:
+ for widget in (add_contact_menuitem, service_discovery_menuitem,
+ join_group_chat_menuitem, execute_command_menuitem, pep_menuitem,
+ start_chat_menuitem):
+ widget.set_sensitive(False)
+ else:
+ xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui')
+ account_context_menu = xml.get_object('zeroconf_context_menu')
+
+ status_menuitem = xml.get_object('status_menuitem')
+ zeroconf_properties_menuitem = xml.get_object(
+ 'zeroconf_properties_menuitem')
+ sub_menu = gtk.Menu()
+ status_menuitem.set_submenu(sub_menu)
+
+ for show in ('online', 'away', 'dnd', 'invisible'):
+ uf_show = helpers.get_uf_show(show, use_mnemonic=True)
+ item = gtk.ImageMenuItem(uf_show)
+ icon = state_images[show]
+ item.set_image(icon)
+ sub_menu.append(item)
+ item.connect('activate', self.change_status, account, show)
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ item = gtk.ImageMenuItem(_('_Change Status Message'))
+ gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
+ sub_menu.append(item)
+ item.connect('activate', self.on_change_status_message_activate,
+ account)
+ if gajim.connections[account].connected < 2:
+ item.set_sensitive(False)
+
+ uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
+ item = gtk.ImageMenuItem(uf_show)
+ icon = state_images['offline']
+ item.set_image(icon)
+ sub_menu.append(item)
+ item.connect('activate', self.change_status, account, 'offline')
+
+ zeroconf_properties_menuitem.connect('activate',
+ self.on_zeroconf_properties, account)
+
+ return account_context_menu
+
+ def make_account_menu(self, event, titer):
+ """
+ Make account's popup menu
+ """
+ model = self.modelfilter
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+
+ if account != 'all': # not in merged mode
+ menu = self.build_account_menu(account)
+ else:
+ menu = gtk.Menu()
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ accounts = [] # Put accounts in a list to sort them
+ for account in gajim.connections:
+ accounts.append(account)
+ accounts.sort()
+ for account in accounts:
+ state_images = gtkgui_helpers.load_iconset(path)
+ item = gtk.ImageMenuItem(account)
+ show = gajim.SHOW_LIST[gajim.connections[account].connected]
+ icon = state_images[show]
+ item.set_image(icon)
+ account_menu = self.build_account_menu(account)
+ item.set_submenu(account_menu)
+ menu.append(item)
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+
+ menu.attach_to_widget(self.tree, None)
+ menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
+
+ def make_group_menu(self, event, titer):
+ """
+ Make group's popup menu
+ """
+ model = self.modelfilter
+ path = model.get_path(titer)
+ group = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+
+ list_ = [] # list of (jid, account) tuples
+ list_online = [] # list of (jid, account) tuples
+
+ group = model[titer][C_JID]
+ for jid in gajim.contacts.get_jid_list(account):
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ if group in contact.get_shown_groups():
+ if contact.show not in ('offline', 'error'):
+ list_online.append((contact, account))
+ list_.append((contact, account))
+ menu = gtk.Menu()
+
+ # Make special context menu if group is Groupchats
+ if group == _('Groupchats'):
+ maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU)
+ maximize_menuitem.set_image(icon)
+ maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\
+ list_)
+ menu.append(maximize_menuitem)
+ else:
+ # Send Group Message
+ send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
+ send_group_message_item.set_image(icon)
+
+ send_group_message_submenu = gtk.Menu()
+ send_group_message_item.set_submenu(send_group_message_submenu)
+ menu.append(send_group_message_item)
+
+ group_message_to_all_item = gtk.MenuItem(_('To all users'))
+ send_group_message_submenu.append(group_message_to_all_item)
+
+ group_message_to_all_online_item = gtk.MenuItem(
+ _('To all online users'))
+ send_group_message_submenu.append(group_message_to_all_online_item)
+
+ group_message_to_all_online_item.connect('activate',
+ self.on_send_single_message_menuitem_activate, account, list_online)
+ group_message_to_all_item.connect('activate',
+ self.on_send_single_message_menuitem_activate, account, list_)
+
+ # Invite to
+ invite_menuitem = gtk.ImageMenuItem(_('In_vite to'))
+ muc_icon = gtkgui_helpers.load_icon('muc_active')
+ if muc_icon:
+ invite_menuitem.set_image(muc_icon)
+
+ gui_menu_builder.build_invite_submenu(invite_menuitem, list_online)
+ menu.append(invite_menuitem)
+
+ # Send Custom Status
+ send_custom_status_menuitem = gtk.ImageMenuItem(
+ _('Send Cus_tom Status'))
+ # add a special img for this menuitem
+ if helpers.group_is_blocked(account, group):
+ send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
+ 'offline'))
+ send_custom_status_menuitem.set_sensitive(False)
+ else:
+ icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
+ gtk.ICON_SIZE_MENU)
+ send_custom_status_menuitem.set_image(icon)
+ status_menuitems = gtk.Menu()
+ send_custom_status_menuitem.set_submenu(status_menuitems)
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
+ # icon MUST be different instance for every item
+ state_images = gtkgui_helpers.load_iconset(path)
+ status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
+ status_menuitem.connect('activate', self.on_send_custom_status,
+ list_, s, group)
+ icon = state_images[s]
+ status_menuitem.set_image(icon)
+ status_menuitems.append(status_menuitem)
+ menu.append(send_custom_status_menuitem)
+
+ # there is no singlemessage and custom status for zeroconf
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ send_custom_status_menuitem.set_sensitive(False)
+ send_group_message_item.set_sensitive(False)
+
+ if not group in helpers.special_groups:
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ # Rename
+ rename_item = gtk.ImageMenuItem(_('Re_name'))
+ # add a special img for rename menuitem
+ gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input')
+ menu.append(rename_item)
+ rename_item.connect('activate', self.on_rename, 'group', group,
+ account)
+
+ # Block group
+ is_blocked = False
+ if self.regroup:
+ for g_account in gajim.connections:
+ if helpers.group_is_blocked(g_account, group):
+ is_blocked = True
+ else:
+ if helpers.group_is_blocked(account, group):
+ is_blocked = True
+
+ if is_blocked and gajim.connections[account].privacy_rules_supported:
+ unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
+ unblock_menuitem.set_image(icon)
+ unblock_menuitem.connect('activate', self.on_unblock, list_, group)
+ menu.append(unblock_menuitem)
+ else:
+ block_menuitem = gtk.ImageMenuItem(_('_Block'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
+ block_menuitem.set_image(icon)
+ block_menuitem.connect('activate', self.on_block, list_, group)
+ menu.append(block_menuitem)
+ if not gajim.connections[account].privacy_rules_supported:
+ block_menuitem.set_sensitive(False)
+
+ # Remove group
+ remove_item = gtk.ImageMenuItem(_('_Remove'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
+ remove_item.set_image(icon)
+ menu.append(remove_item)
+ remove_item.connect('activate', self.on_remove_group_item_activated,
+ group, account)
+
+ # unsensitive if account is not connected
+ if gajim.connections[account].connected < 2:
+ rename_item.set_sensitive(False)
+
+ # General group cannot be changed
+ if group == _('General'):
+ rename_item.set_sensitive(False)
+ block_menuitem.set_sensitive(False)
+ remove_item.set_sensitive(False)
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+
+ menu.attach_to_widget(self.tree, None)
+ menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
+
+ def make_contact_menu(self, event, titer):
+ """
+ Make contact's popup menu
+ """
+ model = self.modelfilter
+ jid = model[titer][C_JID].decode('utf-8')
+ tree_path = model.get_path(titer)
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ menu = gui_menu_builder.get_contact_menu(contact, account)
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+ menu.attach_to_widget(self.tree, None)
+ menu.popup(None, None, None, event_button, event.time)
+
+ def make_multiple_contact_menu(self, event, iters):
+ """
+ Make group's popup menu
+ """
+ model = self.modelfilter
+ list_ = [] # list of (jid, account) tuples
+ one_account_offline = False
+ is_blocked = True
+ privacy_rules_supported = True
+ for titer in iters:
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ if gajim.connections[account].connected < 2:
+ one_account_offline = True
+ if not gajim.connections[account].privacy_rules_supported:
+ privacy_rules_supported = False
+ contact = gajim.contacts.get_contact_with_highest_priority(account,
+ jid)
+ if helpers.jid_is_blocked(account, jid):
+ is_blocked = False
+ list_.append((contact, account))
+
+ menu = gtk.Menu()
+ account = None
+ for (contact, current_account) in list_:
+ # check that we use the same account for every sender
+ if account is not None and account != current_account:
+ account = None
+ break
+ account = current_account
+ if account is not None:
+ send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
+ send_group_message_item.set_image(icon)
+ menu.append(send_group_message_item)
+ send_group_message_item.connect('activate',
+ self.on_send_single_message_menuitem_activate, account, list_)
+
+ # Invite to Groupchat
+ invite_item = gtk.ImageMenuItem(_('In_vite to'))
+ muc_icon = gtkgui_helpers.load_icon('muc_active')
+ if muc_icon:
+ invite_item.set_image(muc_icon)
+
+ gui_menu_builder.build_invite_submenu(invite_item, list_)
+ menu.append(invite_item)
+
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ # Manage Transport submenu
+ item = gtk.ImageMenuItem(_('_Manage Contacts'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ manage_contacts_submenu = gtk.Menu()
+ item.set_submenu(manage_contacts_submenu)
+ menu.append(item)
+
+ # Edit Groups
+ edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)
+ edit_groups_item.set_image(icon)
+ manage_contacts_submenu.append(edit_groups_item)
+ edit_groups_item.connect('activate', self.on_edit_groups, list_)
+
+ item = gtk.SeparatorMenuItem() # separator
+ manage_contacts_submenu.append(item)
+
+ # Block
+ if is_blocked and privacy_rules_supported:
+ unblock_menuitem = gtk.ImageMenuItem(_('_Unblock'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
+ unblock_menuitem.set_image(icon)
+ unblock_menuitem.connect('activate', self.on_unblock, list_)
+ manage_contacts_submenu.append(unblock_menuitem)
+ else:
+ block_menuitem = gtk.ImageMenuItem(_('_Block'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
+ block_menuitem.set_image(icon)
+ block_menuitem.connect('activate', self.on_block, list_)
+ manage_contacts_submenu.append(block_menuitem)
+
+ if not privacy_rules_supported:
+ block_menuitem.set_sensitive(False)
+
+ # Remove
+ remove_item = gtk.ImageMenuItem(_('_Remove'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
+ remove_item.set_image(icon)
+ manage_contacts_submenu.append(remove_item)
+ remove_item.connect('activate', self.on_req_usub, list_)
+ # unsensitive remove if one account is not connected
+ if one_account_offline:
+ remove_item.set_sensitive(False)
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+
+ menu.attach_to_widget(self.tree, None)
+ menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
+
+ def make_transport_menu(self, event, titer):
+ """
+ Make transport's popup menu
+ """
+ model = self.modelfilter
+ jid = model[titer][C_JID].decode('utf-8')
+ path = model.get_path(titer)
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ menu = gtk.Menu()
+
+ # Send single message
+ item = gtk.ImageMenuItem(_('Send Single Message'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ item.connect('activate',
+ self.on_send_single_message_menuitem_activate, account, contact)
+ menu.append(item)
+
+ blocked = False
+ if helpers.jid_is_blocked(account, jid):
+ blocked = True
+
+ # Send Custom Status
+ send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status'))
+ # add a special img for this menuitem
+ if blocked:
+ send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
+ 'offline'))
+ send_custom_status_menuitem.set_sensitive(False)
+ else:
+ if account in gajim.interface.status_sent_to_users and \
+ jid in gajim.interface.status_sent_to_users[account]:
+ send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
+ gajim.interface.status_sent_to_users[account][jid]))
+ else:
+ icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK,
+ gtk.ICON_SIZE_MENU)
+ send_custom_status_menuitem.set_image(icon)
+ status_menuitems = gtk.Menu()
+ send_custom_status_menuitem.set_submenu(status_menuitems)
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
+ # icon MUST be different instance for every item
+ state_images = gtkgui_helpers.load_iconset(path)
+ status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
+ status_menuitem.connect('activate', self.on_send_custom_status,
+ [(contact, account)], s)
+ icon = state_images[s]
+ status_menuitem.set_image(icon)
+ status_menuitems.append(status_menuitem)
+ menu.append(send_custom_status_menuitem)
+
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ # Execute Command
+ item = gtk.ImageMenuItem(_('Execute Command...'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ menu.append(item)
+ item.connect('activate', self.on_execute_command, contact, account,
+ contact.resource)
+ if gajim.account_is_disconnected(account):
+ item.set_sensitive(False)
+
+ # Manage Transport submenu
+ item = gtk.ImageMenuItem(_('_Manage Transport'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ manage_transport_submenu = gtk.Menu()
+ item.set_submenu(manage_transport_submenu)
+ menu.append(item)
+
+ # Modify Transport
+ item = gtk.ImageMenuItem(_('_Modify Transport'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ manage_transport_submenu.append(item)
+ item.connect('activate', self.on_edit_agent, contact, account)
+ if gajim.account_is_disconnected(account):
+ item.set_sensitive(False)
+
+ # Rename
+ item = gtk.ImageMenuItem(_('_Rename'))
+ # add a special img for rename menuitem
+ gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
+ manage_transport_submenu.append(item)
+ item.connect('activate', self.on_rename, 'agent', jid, account)
+ if gajim.account_is_disconnected(account):
+ item.set_sensitive(False)
+
+ item = gtk.SeparatorMenuItem() # separator
+ manage_transport_submenu.append(item)
+
+ # Block
+ if blocked:
+ item = gtk.ImageMenuItem(_('_Unblock'))
+ item.connect('activate', self.on_unblock, [(contact, account)])
+ else:
+ item = gtk.ImageMenuItem(_('_Block'))
+ item.connect('activate', self.on_block, [(contact, account)])
+
+ icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ manage_transport_submenu.append(item)
+ if gajim.account_is_disconnected(account):
+ item.set_sensitive(False)
+
+ # Remove
+ item = gtk.ImageMenuItem(_('_Remove'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ manage_transport_submenu.append(item)
+ item.connect('activate', self.on_remove_agent, [(contact, account)])
+ if gajim.account_is_disconnected(account):
+ item.set_sensitive(False)
+
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ # Information
+ information_menuitem = gtk.ImageMenuItem(_('_Information'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU)
+ information_menuitem.set_image(icon)
+ menu.append(information_menuitem)
+ information_menuitem.connect('activate', self.on_info, contact, account)
+
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+
+ menu.attach_to_widget(self.tree, None)
+ menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
+
+ def make_groupchat_menu(self, event, titer):
+ model = self.modelfilter
+
+ jid = model[titer][C_JID].decode('utf-8')
+ account = model[titer][C_ACCOUNT].decode('utf-8')
+ contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
+ menu = gtk.Menu()
+
+ if jid in gajim.interface.minimized_controls[account]:
+ maximize_menuitem = gtk.ImageMenuItem(_('_Maximize'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU)
+ maximize_menuitem.set_image(icon)
+ maximize_menuitem.connect('activate', self.on_groupchat_maximized, \
+ jid, account)
+ menu.append(maximize_menuitem)
+
+ if not gajim.gc_connected[account].get(jid, False):
+ connect_menuitem = gtk.ImageMenuItem(_('_Reconnect'))
+ connect_icon = gtk.image_new_from_stock(gtk.STOCK_CONNECT, \
+ gtk.ICON_SIZE_MENU)
+ connect_menuitem.set_image(connect_icon)
+ connect_menuitem.connect('activate', self.on_reconnect, jid, account)
+ menu.append(connect_menuitem)
+ disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect'))
+ disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \
+ gtk.ICON_SIZE_MENU)
+ disconnect_menuitem.set_image(disconnect_icon)
+ disconnect_menuitem.connect('activate', self.on_disconnect, jid, account)
+ menu.append(disconnect_menuitem)
+
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ history_menuitem = gtk.ImageMenuItem(_('_History'))
+ history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \
+ gtk.ICON_SIZE_MENU)
+ history_menuitem.set_image(history_icon)
+ history_menuitem .connect('activate', self.on_history, \
+ contact, account)
+ menu.append(history_menuitem)
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+
+ menu.attach_to_widget(self.tree, None)
+ menu.connect('selection-done', gtkgui_helpers.destroy_widget)
+ menu.show_all()
+ menu.popup(None, None, None, event_button, event.time)
+
+ def get_and_connect_advanced_menuitem_menu(self, account):
+ """
+ Add FOR ACCOUNT options
+ """
+ xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui')
+ advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu')
+
+ xml_console_menuitem = xml.get_object('xml_console_menuitem')
+ privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem')
+ administrator_menuitem = xml.get_object('administrator_menuitem')
+ send_server_message_menuitem = xml.get_object(
+ 'send_server_message_menuitem')
+ set_motd_menuitem = xml.get_object('set_motd_menuitem')
+ update_motd_menuitem = xml.get_object('update_motd_menuitem')
+ delete_motd_menuitem = xml.get_object('delete_motd_menuitem')
+
+ xml_console_menuitem.connect('activate',
+ self.on_xml_console_menuitem_activate, account)
+
+ if gajim.connections[account] and gajim.connections[account].\
+ privacy_rules_supported:
+ privacy_lists_menuitem.connect('activate',
+ self.on_privacy_lists_menuitem_activate, account)
+ else:
+ privacy_lists_menuitem.set_sensitive(False)
+
+ if gajim.connections[account].is_zeroconf:
+ administrator_menuitem.set_sensitive(False)
+ send_server_message_menuitem.set_sensitive(False)
+ set_motd_menuitem.set_sensitive(False)
+ update_motd_menuitem.set_sensitive(False)
+ delete_motd_menuitem.set_sensitive(False)
+ else:
+ send_server_message_menuitem.connect('activate',
+ self.on_send_server_message_menuitem_activate, account)
+
+ set_motd_menuitem.connect('activate',
+ self.on_set_motd_menuitem_activate, account)
+
+ update_motd_menuitem.connect('activate',
+ self.on_update_motd_menuitem_activate, account)
+
+ delete_motd_menuitem.connect('activate',
+ self.on_delete_motd_menuitem_activate, account)
+
+ advanced_menuitem_menu.show_all()
+
+ return advanced_menuitem_menu
+
+ def add_history_manager_menuitem(self, menu):
+ """
+ Add a seperator and History Manager menuitem BELOW for account menuitems
+ """
+ item = gtk.SeparatorMenuItem() # separator
+ menu.append(item)
+
+ # History manager
+ item = gtk.ImageMenuItem(_('History Manager'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL,
+ gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ menu.append(item)
+ item.connect('activate', self.on_history_manager_menuitem_activate)
+
+ def add_bookmarks_list(self, gc_sub_menu, account):
+ """
+ Show join new group chat item and bookmarks list for an account
+ """
+ item = gtk.ImageMenuItem(_('_Join New Group Chat'))
+ icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
+ item.set_image(icon)
+ item.connect('activate', self.on_join_gc_activate, account)
+
+ gc_sub_menu.append(item)
+
+ # User has at least one bookmark.
+ if gajim.connections[account].bookmarks:
+ item = gtk.SeparatorMenuItem()
+ gc_sub_menu.append(item)
+
+ for bookmark in gajim.connections[account].bookmarks:
+ # Do not use underline.
+ item = gtk.MenuItem(bookmark['name'], False)
+ item.connect('activate', self.on_bookmark_menuitem_activate,
+ account, bookmark)
+ gc_sub_menu.append(item)
+
+ def set_actions_menu_needs_rebuild(self):
+ self.actions_menu_needs_rebuild = True
+
+ def show_appropriate_context_menu(self, event, iters):
+ # iters must be all of the same type
+ model = self.modelfilter
+ type_ = model[iters[0]][C_TYPE]
+ for titer in iters[1:]:
+ if model[titer][C_TYPE] != type_:
+ return
+ if type_ == 'group' and len(iters) == 1:
+ self.make_group_menu(event, iters[0])
+ if type_ == 'groupchat' and len(iters) == 1:
+ self.make_groupchat_menu(event, iters[0])
+ elif type_ == 'agent' and len(iters) == 1:
+ self.make_transport_menu(event, iters[0])
+ elif type_ in ('contact', 'self_contact') and len(iters) == 1:
+ self.make_contact_menu(event, iters[0])
+ elif type_ == 'contact':
+ self.make_multiple_contact_menu(event, iters)
+ elif type_ == 'account' and len(iters) == 1:
+ self.make_account_menu(event, iters[0])
+
+ def show_treeview_menu(self, event):
+ try:
+ model, list_of_paths = self.tree.get_selection().get_selected_rows()
+ except TypeError:
+ self.tree.get_selection().unselect_all()
+ return
+ if not len(list_of_paths):
+ # no row is selected
+ return
+ if len(list_of_paths) > 1:
+ iters = []
+ for path in list_of_paths:
+ iters.append(model.get_iter(path))
+ else:
+ path = list_of_paths[0]
+ iters = [model.get_iter(path)]
+ self.show_appropriate_context_menu(event, iters)
+
+ return True
+
+ def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier):
+ """
+ Bring up the conference join dialog, when CTRL+J accelerator is being
+ activated
+ """
+ # find a connected account:
+ for account in gajim.connections:
+ if gajim.account_is_connected(account):
+ break
+ self.on_join_gc_activate(None, account)
+ return True
################################################################################
###
################################################################################
- def __init__(self):
- self.filtering = False
- self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui')
- self.window = self.xml.get_object('roster_window')
- self.hpaned = self.xml.get_object('roster_hpaned')
- gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
- gajim.interface.msg_win_mgr.connect('window-delete',
- self.on_message_window_delete)
- self.advanced_menus = [] # We keep them to destroy them
- if gajim.config.get('roster_window_skip_taskbar'):
- self.window.set_property('skip-taskbar-hint', True)
- self.tree = self.xml.get_object('roster_treeview')
- sel = self.tree.get_selection()
- sel.set_mode(gtk.SELECTION_MULTIPLE)
- #sel.connect('changed',
- # self.on_treeview_selection_changed)
-
- self._last_selected_contact = [] # holds a list of (jid, account) tupples
- self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
- 'closed': {}}
-
- self.last_save_dir = None
- self.editing_path = None # path of row with cell in edit mode
- self.add_new_contact_handler_id = False
- self.service_disco_handler_id = False
- self.new_chat_menuitem_handler_id = False
- self.single_message_menuitem_handler_id = False
- self.profile_avatar_menuitem_handler_id = False
- self.actions_menu_needs_rebuild = True
- self.regroup = gajim.config.get('mergeaccounts')
- self.clicked_path = None # Used remember on wich row we clicked
- if len(gajim.connections) < 2: # Do not merge accounts if only one exists
- self.regroup = False
- #FIXME: When list_accel_closures will be wrapped in pygtk
- # no need of this variable
- self.have_new_chat_accel = False # Is the "Ctrl+N" shown ?
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('roster_width'),
- gajim.config.get('roster_height'))
- gtkgui_helpers.move_window(self.window,
- gajim.config.get('roster_x-position'),
- gajim.config.get('roster_y-position'))
-
- self.popups_notification_height = 0
- self.popup_notification_windows = []
-
- # Remove contact from roster when last event opened
- # { (contact, account): { backend: boolean }
- self.contacts_to_be_removed = {}
- gajim.events.event_removed_subscribe(self.on_event_removed)
-
- # when this value become 0 we quit main application. If it's more than 0
- # it means we are waiting for this number of accounts to disconnect before
- # quitting
- self.quit_on_next_offline = -1
-
- # uf_show, img, show, sensitive
- liststore = gtk.ListStore(str, gtk.Image, str, bool)
- self.status_combobox = self.xml.get_object('status_combobox')
-
- cell = cell_renderer_image.CellRendererImage(0, 1)
- self.status_combobox.pack_start(cell, False)
-
- # img to show is in in 2nd column of liststore
- self.status_combobox.add_attribute(cell, 'image', 1)
- # if it will be sensitive or not it is in the fourth column
- # all items in the 'row' must have sensitive to False
- # if we want False (so we add it for img_cell too)
- self.status_combobox.add_attribute(cell, 'sensitive', 3)
-
- cell = gtk.CellRendererText()
- cell.set_property('xpad', 5) # padding for status text
- self.status_combobox.pack_start(cell, True)
- # text to show is in in first column of liststore
- self.status_combobox.add_attribute(cell, 'text', 0)
- # if it will be sensitive or not it is in the fourth column
- self.status_combobox.add_attribute(cell, 'sensitive', 3)
-
- self.status_combobox.set_row_separator_func(self._iter_is_separator)
-
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show)
- liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
- show], show, True])
- # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
- liststore.append(['SEPARATOR', None, '', True])
-
- path = gtkgui_helpers.get_icon_path('gajim-kbd_input')
- img = gtk.Image()
- img.set_from_file(path)
- # sensitivity to False because by default we're offline
- self.status_message_menuitem_iter = liststore.append(
- [_('Change Status Message...'), img, '', False])
- # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
- liststore.append(['SEPARATOR', None, '', True])
-
- uf_show = helpers.get_uf_show('offline')
- liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
- 'offline'], 'offline', True])
-
- status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd',
- 'invisible', 'separator1', 'change_status_msg', 'separator2',
- 'offline']
- self.status_combobox.set_model(liststore)
-
- # default to offline
- number_of_menuitem = status_combobox_items.index('offline')
- self.status_combobox.set_active(number_of_menuitem)
-
- # holds index to previously selected item so if "change status message..."
- # is selected we can fallback to previously selected item and not stay
- # with that item selected
- self.previous_status_combobox_active = number_of_menuitem
-
- showOffline = gajim.config.get('showoffline')
- showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online')
-
- w = self.xml.get_object('show_offline_contacts_menuitem')
- w.set_active(showOffline)
- if showOnlyChatAndOnline:
- w.set_sensitive(False)
-
- w = self.xml.get_object('show_only_active_contacts_menuitem')
- w.set_active(showOnlyChatAndOnline)
- if showOffline:
- w.set_sensitive(False)
-
- show_transports_group = gajim.config.get('show_transports_group')
- self.xml.get_object('show_transports_menuitem').set_active(
- show_transports_group)
-
- self.xml.get_object('show_roster_menuitem').set_active(True)
-
- # columns
-
- # this col has 3 cells:
- # first one img, second one text, third is sec pixbuf
- col = gtk.TreeViewColumn()
-
- def add_avatar_renderer():
- render_pixbuf = gtk.CellRendererPixbuf() # avatar img
- col.pack_start(render_pixbuf, expand=False)
- col.add_attribute(render_pixbuf, 'pixbuf', C_AVATAR_PIXBUF)
- col.set_cell_data_func(render_pixbuf,
- self._fill_avatar_pixbuf_renderer, None)
-
- if gajim.config.get('avatar_position_in_roster') == 'left':
- add_avatar_renderer()
-
- render_image = cell_renderer_image.CellRendererImage(0, 0)
- # show img or +-
- col.pack_start(render_image, expand=False)
- col.add_attribute(render_image, 'image', C_IMG)
- col.set_cell_data_func(render_image, self._iconCellDataFunc, None)
-
- render_text = gtk.CellRendererText() # contact or group or account name
- render_text.set_property('ellipsize', pango.ELLIPSIZE_END)
- col.pack_start(render_text, expand=True)
- col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name
- col.set_cell_data_func(render_text, self._nameCellDataFunc, None)
-
- render_pixbuf = gtk.CellRendererPixbuf()
- col.pack_start(render_pixbuf, expand=False)
- col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF)
- col.set_cell_data_func(render_pixbuf,
- self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF)
-
- render_pixbuf = gtk.CellRendererPixbuf()
- col.pack_start(render_pixbuf, expand=False)
- col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF)
- col.set_cell_data_func(render_pixbuf,
- self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF)
-
- render_pixbuf = gtk.CellRendererPixbuf()
- col.pack_start(render_pixbuf, expand=False)
- col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF)
- col.set_cell_data_func(render_pixbuf,
- self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF)
-
- render_pixbuf = gtk.CellRendererPixbuf()
- col.pack_start(render_pixbuf, expand=False)
- col.add_attribute(render_pixbuf, 'pixbuf', C_LOCATION_PIXBUF)
- col.set_cell_data_func(render_pixbuf,
- self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF)
-
- self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF,
- 'activity': C_ACTIVITY_PIXBUF,
- 'tune': C_TUNE_PIXBUF,
- 'location': C_LOCATION_PIXBUF}
-
- if gajim.config.get('avatar_position_in_roster') == 'right':
- add_avatar_renderer()
-
- render_pixbuf = gtk.CellRendererPixbuf() # tls/ssl img
- col.pack_start(render_pixbuf, expand=False)
- col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF)
- col.set_cell_data_func(render_pixbuf,
- self._fill_padlock_pixbuf_renderer, None)
- self.tree.append_column(col)
-
- # do not show gtk arrows workaround
- col = gtk.TreeViewColumn()
- render_pixbuf = gtk.CellRendererPixbuf()
- col.pack_start(render_pixbuf, expand=False)
- self.tree.append_column(col)
- col.set_visible(False)
- self.tree.set_expander_column(col)
-
- # set search function
- self.tree.set_search_equal_func(self._search_roster_func)
-
- # signals
- self.TARGET_TYPE_URI_LIST = 80
- TARGETS = [('MY_TREE_MODEL_ROW',
- gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)]
- TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
- ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)]
- self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS,
- gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY)
- self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT)
- self.tree.connect('drag_begin', self.drag_begin)
- self.tree.connect('drag_end', self.drag_end)
- self.tree.connect('drag_drop', self.drag_drop)
- self.tree.connect('drag_data_get', self.drag_data_get_data)
- self.tree.connect('drag_data_received', self.drag_data_received_data)
- self.dragging = False
- self.xml.connect_signals(self)
- self.combobox_callback_active = True
-
- self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
- self.tooltip = tooltips.RosterTooltip()
- # Workaroung: For strange reasons signal is behaving like row-changed
- self._toggeling_row = False
- self.setup_and_draw_roster()
-
- if gajim.config.get('show_roster_on_startup'):
- self.window.show_all()
- else:
- if gajim.config.get('trayicon') != 'always':
- # Without trayicon, user should see the roster!
- self.window.show_all()
- gajim.config.set('show_roster_on_startup', True)
-
- if len(gajim.connections) == 0: # if we have no account
- def _open_wizard():
- gajim.interface.instances['account_creation_wizard'] = \
- config.AccountCreationWizardWindow()
- # Open wizard only after roster is created, so we can make it transient
- # for the roster window
- gobject.idle_add(_open_wizard)
- if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'):
- # Create zeroconf in config file
- from common.zeroconf import connection_zeroconf
- connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
-
- # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
- # conference.
- accel_group = gtk.accel_groups_from_object(self.window)[0]
- accel_group.connect_group(gtk.keysyms.j, gtk.gdk.CONTROL_MASK,
- gtk.ACCEL_MASK, self.on_ctrl_j)
-
-# vim: se ts=3:
+ def __init__(self):
+ self.filtering = False
+ self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui')
+ self.window = self.xml.get_object('roster_window')
+ self.hpaned = self.xml.get_object('roster_hpaned')
+ gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
+ gajim.interface.msg_win_mgr.connect('window-delete',
+ self.on_message_window_delete)
+ self.advanced_menus = [] # We keep them to destroy them
+ if gajim.config.get('roster_window_skip_taskbar'):
+ self.window.set_property('skip-taskbar-hint', True)
+ self.tree = self.xml.get_object('roster_treeview')
+ sel = self.tree.get_selection()
+ sel.set_mode(gtk.SELECTION_MULTIPLE)
+ #sel.connect('changed',
+ # self.on_treeview_selection_changed)
+
+ self._last_selected_contact = [] # holds a list of (jid, account) tupples
+ self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
+ 'closed': {}}
+
+ self.last_save_dir = None
+ self.editing_path = None # path of row with cell in edit mode
+ self.add_new_contact_handler_id = False
+ self.service_disco_handler_id = False
+ self.new_chat_menuitem_handler_id = False
+ self.single_message_menuitem_handler_id = False
+ self.profile_avatar_menuitem_handler_id = False
+ self.actions_menu_needs_rebuild = True
+ self.regroup = gajim.config.get('mergeaccounts')
+ self.clicked_path = None # Used remember on wich row we clicked
+ if len(gajim.connections) < 2: # Do not merge accounts if only one exists
+ self.regroup = False
+ #FIXME: When list_accel_closures will be wrapped in pygtk
+ # no need of this variable
+ self.have_new_chat_accel = False # Is the "Ctrl+N" shown ?
+ gtkgui_helpers.resize_window(self.window,
+ gajim.config.get('roster_width'),
+ gajim.config.get('roster_height'))
+ gtkgui_helpers.move_window(self.window,
+ gajim.config.get('roster_x-position'),
+ gajim.config.get('roster_y-position'))
+
+ self.popups_notification_height = 0
+ self.popup_notification_windows = []
+
+ # Remove contact from roster when last event opened
+ # { (contact, account): { backend: boolean }
+ self.contacts_to_be_removed = {}
+ gajim.events.event_removed_subscribe(self.on_event_removed)
+
+ # when this value become 0 we quit main application. If it's more than 0
+ # it means we are waiting for this number of accounts to disconnect before
+ # quitting
+ self.quit_on_next_offline = -1
+
+ # uf_show, img, show, sensitive
+ liststore = gtk.ListStore(str, gtk.Image, str, bool)
+ self.status_combobox = self.xml.get_object('status_combobox')
+
+ cell = cell_renderer_image.CellRendererImage(0, 1)
+ self.status_combobox.pack_start(cell, False)
+
+ # img to show is in in 2nd column of liststore
+ self.status_combobox.add_attribute(cell, 'image', 1)
+ # if it will be sensitive or not it is in the fourth column
+ # all items in the 'row' must have sensitive to False
+ # if we want False (so we add it for img_cell too)
+ self.status_combobox.add_attribute(cell, 'sensitive', 3)
+
+ cell = gtk.CellRendererText()
+ cell.set_property('xpad', 5) # padding for status text
+ self.status_combobox.pack_start(cell, True)
+ # text to show is in in first column of liststore
+ self.status_combobox.add_attribute(cell, 'text', 0)
+ # if it will be sensitive or not it is in the fourth column
+ self.status_combobox.add_attribute(cell, 'sensitive', 3)
+
+ self.status_combobox.set_row_separator_func(self._iter_is_separator)
+
+ for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+ uf_show = helpers.get_uf_show(show)
+ liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
+ show], show, True])
+ # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
+ liststore.append(['SEPARATOR', None, '', True])
+
+ path = gtkgui_helpers.get_icon_path('gajim-kbd_input')
+ img = gtk.Image()
+ img.set_from_file(path)
+ # sensitivity to False because by default we're offline
+ self.status_message_menuitem_iter = liststore.append(
+ [_('Change Status Message...'), img, '', False])
+ # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
+ liststore.append(['SEPARATOR', None, '', True])
+
+ uf_show = helpers.get_uf_show('offline')
+ liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
+ 'offline'], 'offline', True])
+
+ status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd',
+ 'invisible', 'separator1', 'change_status_msg', 'separator2',
+ 'offline']
+ self.status_combobox.set_model(liststore)
+
+ # default to offline
+ number_of_menuitem = status_combobox_items.index('offline')
+ self.status_combobox.set_active(number_of_menuitem)
+
+ # holds index to previously selected item so if "change status message..."
+ # is selected we can fallback to previously selected item and not stay
+ # with that item selected
+ self.previous_status_combobox_active = number_of_menuitem
+
+ showOffline = gajim.config.get('showoffline')
+ showOnlyChatAndOnline = gajim.config.get('show_only_chat_and_online')
+
+ w = self.xml.get_object('show_offline_contacts_menuitem')
+ w.set_active(showOffline)
+ if showOnlyChatAndOnline:
+ w.set_sensitive(False)
+
+ w = self.xml.get_object('show_only_active_contacts_menuitem')
+ w.set_active(showOnlyChatAndOnline)
+ if showOffline:
+ w.set_sensitive(False)
+
+ show_transports_group = gajim.config.get('show_transports_group')
+ self.xml.get_object('show_transports_menuitem').set_active(
+ show_transports_group)
+
+ self.xml.get_object('show_roster_menuitem').set_active(True)
+
+ # columns
+
+ # this col has 3 cells:
+ # first one img, second one text, third is sec pixbuf
+ col = gtk.TreeViewColumn()
+
+ def add_avatar_renderer():
+ render_pixbuf = gtk.CellRendererPixbuf() # avatar img
+ col.pack_start(render_pixbuf, expand=False)
+ col.add_attribute(render_pixbuf, 'pixbuf', C_AVATAR_PIXBUF)
+ col.set_cell_data_func(render_pixbuf,
+ self._fill_avatar_pixbuf_renderer, None)
+
+ if gajim.config.get('avatar_position_in_roster') == 'left':
+ add_avatar_renderer()
+
+ render_image = cell_renderer_image.CellRendererImage(0, 0)
+ # show img or +-
+ col.pack_start(render_image, expand=False)
+ col.add_attribute(render_image, 'image', C_IMG)
+ col.set_cell_data_func(render_image, self._iconCellDataFunc, None)
+
+ render_text = gtk.CellRendererText() # contact or group or account name
+ render_text.set_property('ellipsize', pango.ELLIPSIZE_END)
+ col.pack_start(render_text, expand=True)
+ col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name
+ col.set_cell_data_func(render_text, self._nameCellDataFunc, None)
+
+ render_pixbuf = gtk.CellRendererPixbuf()
+ col.pack_start(render_pixbuf, expand=False)
+ col.add_attribute(render_pixbuf, 'pixbuf', C_MOOD_PIXBUF)
+ col.set_cell_data_func(render_pixbuf,
+ self._fill_pep_pixbuf_renderer, C_MOOD_PIXBUF)
+
+ render_pixbuf = gtk.CellRendererPixbuf()
+ col.pack_start(render_pixbuf, expand=False)
+ col.add_attribute(render_pixbuf, 'pixbuf', C_ACTIVITY_PIXBUF)
+ col.set_cell_data_func(render_pixbuf,
+ self._fill_pep_pixbuf_renderer, C_ACTIVITY_PIXBUF)
+
+ render_pixbuf = gtk.CellRendererPixbuf()
+ col.pack_start(render_pixbuf, expand=False)
+ col.add_attribute(render_pixbuf, 'pixbuf', C_TUNE_PIXBUF)
+ col.set_cell_data_func(render_pixbuf,
+ self._fill_pep_pixbuf_renderer, C_TUNE_PIXBUF)
+
+ render_pixbuf = gtk.CellRendererPixbuf()
+ col.pack_start(render_pixbuf, expand=False)
+ col.add_attribute(render_pixbuf, 'pixbuf', C_LOCATION_PIXBUF)
+ col.set_cell_data_func(render_pixbuf,
+ self._fill_pep_pixbuf_renderer, C_LOCATION_PIXBUF)
+
+ self._pep_type_to_model_column = {'mood': C_MOOD_PIXBUF,
+ 'activity': C_ACTIVITY_PIXBUF,
+ 'tune': C_TUNE_PIXBUF,
+ 'location': C_LOCATION_PIXBUF}
+
+ if gajim.config.get('avatar_position_in_roster') == 'right':
+ add_avatar_renderer()
+
+ render_pixbuf = gtk.CellRendererPixbuf() # tls/ssl img
+ col.pack_start(render_pixbuf, expand=False)
+ col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF)
+ col.set_cell_data_func(render_pixbuf,
+ self._fill_padlock_pixbuf_renderer, None)
+ self.tree.append_column(col)
+
+ # do not show gtk arrows workaround
+ col = gtk.TreeViewColumn()
+ render_pixbuf = gtk.CellRendererPixbuf()
+ col.pack_start(render_pixbuf, expand=False)
+ self.tree.append_column(col)
+ col.set_visible(False)
+ self.tree.set_expander_column(col)
+
+ # set search function
+ self.tree.set_search_equal_func(self._search_roster_func)
+
+ # signals
+ self.TARGET_TYPE_URI_LIST = 80
+ TARGETS = [('MY_TREE_MODEL_ROW',
+ gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0)]
+ TARGETS2 = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
+ ('text/uri-list', 0, self.TARGET_TYPE_URI_LIST)]
+ self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, TARGETS,
+ gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_COPY)
+ self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT)
+ self.tree.connect('drag_begin', self.drag_begin)
+ self.tree.connect('drag_end', self.drag_end)
+ self.tree.connect('drag_drop', self.drag_drop)
+ self.tree.connect('drag_data_get', self.drag_data_get_data)
+ self.tree.connect('drag_data_received', self.drag_data_received_data)
+ self.dragging = False
+ self.xml.connect_signals(self)
+ self.combobox_callback_active = True
+
+ self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
+ self.tooltip = tooltips.RosterTooltip()
+ # Workaroung: For strange reasons signal is behaving like row-changed
+ self._toggeling_row = False
+ self.setup_and_draw_roster()
+
+ if gajim.config.get('show_roster_on_startup'):
+ self.window.show_all()
+ else:
+ if gajim.config.get('trayicon') != 'always':
+ # Without trayicon, user should see the roster!
+ self.window.show_all()
+ gajim.config.set('show_roster_on_startup', True)
+
+ if len(gajim.connections) == 0: # if we have no account
+ def _open_wizard():
+ gajim.interface.instances['account_creation_wizard'] = \
+ config.AccountCreationWizardWindow()
+ # Open wizard only after roster is created, so we can make it transient
+ # for the roster window
+ gobject.idle_add(_open_wizard)
+ if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'):
+ # Create zeroconf in config file
+ from common.zeroconf import connection_zeroconf
+ connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
+
+ # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
+ # conference.
+ accel_group = gtk.accel_groups_from_object(self.window)[0]
+ accel_group.connect_group(gtk.keysyms.j, gtk.gdk.CONTROL_MASK,
+ gtk.ACCEL_MASK, self.on_ctrl_j)
diff --git a/src/search_window.py b/src/search_window.py
index 08eb4276c..06362b651 100644
--- a/src/search_window.py
+++ b/src/search_window.py
@@ -31,205 +31,203 @@ import config
import dataforms_widget
class SearchWindow:
- def __init__(self, account, jid):
- """
- Create new window
- """
- # an account object
- self.account = account
- self.jid = jid
-
- # retrieving widgets from xml
- self.xml = gtkgui_helpers.get_gtk_builder('search_window.ui')
- self.window = self.xml.get_object('search_window')
- for name in ('label', 'progressbar', 'search_vbox', 'search_button',
- 'add_contact_button', 'information_button'):
- self.__dict__[name] = self.xml.get_object(name)
-
- # displaying the window
- self.xml.connect_signals(self)
- self.window.show_all()
- self.request_form()
- self.pulse_id = gobject.timeout_add(80, self.pulse_callback)
-
- self.is_form = None
-
- # Is there a jid column in results ? if -1: no, else column number
- self.jid_column = -1
-
- def request_form(self):
- gajim.connections[self.account].request_search_fields(self.jid)
-
- def pulse_callback(self):
- self.progressbar.pulse()
- return True
-
- def on_search_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.window.destroy()
-
- def on_search_window_destroy(self, widget):
- if self.pulse_id:
- gobject.source_remove(self.pulse_id)
- del gajim.interface.instances[self.account]['search'][self.jid]
-
- def on_close_button_clicked(self, button):
- self.window.destroy()
-
- def on_search_button_clicked(self, button):
- if self.is_form:
- self.data_form_widget.data_form.type = 'submit'
- gajim.connections[self.account].send_search_form(self.jid,
- self.data_form_widget.data_form.get_purged(), True)
- else:
- infos = self.data_form_widget.get_infos()
- if 'instructions' in infos:
- del infos['instructions']
- gajim.connections[self.account].send_search_form(self.jid, infos,
- False)
-
- self.search_vbox.remove(self.data_form_widget)
-
- self.progressbar.show()
- self.label.set_text(_('Waiting for results'))
- self.label.show()
- self.pulse_id = gobject.timeout_add(80, self.pulse_callback)
- self.search_button.hide()
-
- def on_add_contact_button_clicked(self, widget):
- (model, iter_) = self.result_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][self.jid_column]
- dialogs.AddNewContactWindow(self.account, jid)
-
- def on_information_button_clicked(self, widget):
- (model, iter_) = self.result_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][self.jid_column]
- if jid in gajim.interface.instances[self.account]['infos']:
- gajim.interface.instances[self.account]['infos'][jid].window.present()
- else:
- contact = gajim.contacts.create_contact(jid=jid, account=self.account)
- gajim.interface.instances[self.account]['infos'][jid] = \
- vcard.VcardWindow(contact, self.account)
-
- def on_form_arrived(self, form, is_form):
- if self.pulse_id:
- gobject.source_remove(self.pulse_id)
- self.progressbar.hide()
- self.label.hide()
-
- if is_form:
- self.is_form = True
- self.data_form_widget = dataforms_widget.DataFormWidget()
- self.dataform = dataforms.ExtendForm(node = form)
- self.data_form_widget.set_sensitive(True)
- try:
- self.data_form_widget.data_form = self.dataform
- except dataforms.Error:
- self.label.set_text(_('Error in received dataform'))
- self.label.show()
- return
- if self.data_form_widget.title:
- self.window.set_title('%s - Search - Gajim' % \
- self.data_form_widget.title)
- else:
- self.is_form = False
- self.data_form_widget = config.FakeDataForm(form)
-
- self.data_form_widget.show_all()
- self.search_vbox.pack_start(self.data_form_widget)
-
- def on_result_treeview_cursor_changed(self, treeview):
- if self.jid_column == -1:
- return
- (model, iter_) = treeview.get_selection().get_selected()
- if not iter_:
- return
- if model[iter_][self.jid_column]:
- self.add_contact_button.set_sensitive(True)
- self.information_button.set_sensitive(True)
- else:
- self.add_contact_button.set_sensitive(False)
- self.information_button.set_sensitive(False)
-
- def on_result_arrived(self, form, is_form):
- if self.pulse_id:
- gobject.source_remove(self.pulse_id)
- self.progressbar.hide()
- self.label.hide()
-
- if not is_form:
- if not form:
- self.label.set_text(_('No result'))
- self.label.show()
- return
- # We suppose all items have the same fields
- sw = gtk.ScrolledWindow()
- sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- self.result_treeview = gtk.TreeView()
- self.result_treeview.connect('cursor-changed',
- self.on_result_treeview_cursor_changed)
- sw.add(self.result_treeview)
- # Create model
- fieldtypes = [str]*len(form[0])
- model = gtk.ListStore(*fieldtypes)
- # Copy data to model
- for item in form:
- model.append(item.values())
- # Create columns
- counter = 0
- for field in form[0].keys():
- self.result_treeview.append_column(
- gtk.TreeViewColumn(field, gtk.CellRendererText(),
- text = counter))
- if field == 'jid':
- self.jid_column = counter
- counter += 1
- self.result_treeview.set_model(model)
- sw.show_all()
- self.search_vbox.pack_start(sw)
- if self.jid_column > -1:
- self.add_contact_button.show()
- self.information_button.show()
- return
-
- self.dataform = dataforms.ExtendForm(node = form)
- if len(self.dataform.items) == 0:
- # No result
- self.label.set_text(_('No result'))
- self.label.show()
- return
-
- self.data_form_widget.set_sensitive(True)
- try:
- self.data_form_widget.data_form = self.dataform
- except dataforms.Error:
- self.label.set_text(_('Error in received dataform'))
- self.label.show()
- return
-
- self.result_treeview = self.data_form_widget.records_treeview
- selection = self.result_treeview.get_selection()
- selection.set_mode(gtk.SELECTION_SINGLE)
- self.result_treeview.connect('cursor-changed',
- self.on_result_treeview_cursor_changed)
-
- counter = 0
- for field in self.dataform.items[0].fields:
- if field.var == 'jid':
- self.jid_column = counter
- break
- counter += 1
- self.search_vbox.pack_start(self.data_form_widget)
- self.data_form_widget.show()
- if self.jid_column > -1:
- self.add_contact_button.show()
- self.information_button.show()
- if self.data_form_widget.title:
- self.window.set_title('%s - Search - Gajim' % \
- self.data_form_widget.title)
-
-# vim: se ts=3:
+ def __init__(self, account, jid):
+ """
+ Create new window
+ """
+ # an account object
+ self.account = account
+ self.jid = jid
+
+ # retrieving widgets from xml
+ self.xml = gtkgui_helpers.get_gtk_builder('search_window.ui')
+ self.window = self.xml.get_object('search_window')
+ for name in ('label', 'progressbar', 'search_vbox', 'search_button',
+ 'add_contact_button', 'information_button'):
+ self.__dict__[name] = self.xml.get_object(name)
+
+ # displaying the window
+ self.xml.connect_signals(self)
+ self.window.show_all()
+ self.request_form()
+ self.pulse_id = gobject.timeout_add(80, self.pulse_callback)
+
+ self.is_form = None
+
+ # Is there a jid column in results ? if -1: no, else column number
+ self.jid_column = -1
+
+ def request_form(self):
+ gajim.connections[self.account].request_search_fields(self.jid)
+
+ def pulse_callback(self):
+ self.progressbar.pulse()
+ return True
+
+ def on_search_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def on_search_window_destroy(self, widget):
+ if self.pulse_id:
+ gobject.source_remove(self.pulse_id)
+ del gajim.interface.instances[self.account]['search'][self.jid]
+
+ def on_close_button_clicked(self, button):
+ self.window.destroy()
+
+ def on_search_button_clicked(self, button):
+ if self.is_form:
+ self.data_form_widget.data_form.type = 'submit'
+ gajim.connections[self.account].send_search_form(self.jid,
+ self.data_form_widget.data_form.get_purged(), True)
+ else:
+ infos = self.data_form_widget.get_infos()
+ if 'instructions' in infos:
+ del infos['instructions']
+ gajim.connections[self.account].send_search_form(self.jid, infos,
+ False)
+
+ self.search_vbox.remove(self.data_form_widget)
+
+ self.progressbar.show()
+ self.label.set_text(_('Waiting for results'))
+ self.label.show()
+ self.pulse_id = gobject.timeout_add(80, self.pulse_callback)
+ self.search_button.hide()
+
+ def on_add_contact_button_clicked(self, widget):
+ (model, iter_) = self.result_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ jid = model[iter_][self.jid_column]
+ dialogs.AddNewContactWindow(self.account, jid)
+
+ def on_information_button_clicked(self, widget):
+ (model, iter_) = self.result_treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ jid = model[iter_][self.jid_column]
+ if jid in gajim.interface.instances[self.account]['infos']:
+ gajim.interface.instances[self.account]['infos'][jid].window.present()
+ else:
+ contact = gajim.contacts.create_contact(jid=jid, account=self.account)
+ gajim.interface.instances[self.account]['infos'][jid] = \
+ vcard.VcardWindow(contact, self.account)
+
+ def on_form_arrived(self, form, is_form):
+ if self.pulse_id:
+ gobject.source_remove(self.pulse_id)
+ self.progressbar.hide()
+ self.label.hide()
+
+ if is_form:
+ self.is_form = True
+ self.data_form_widget = dataforms_widget.DataFormWidget()
+ self.dataform = dataforms.ExtendForm(node = form)
+ self.data_form_widget.set_sensitive(True)
+ try:
+ self.data_form_widget.data_form = self.dataform
+ except dataforms.Error:
+ self.label.set_text(_('Error in received dataform'))
+ self.label.show()
+ return
+ if self.data_form_widget.title:
+ self.window.set_title('%s - Search - Gajim' % \
+ self.data_form_widget.title)
+ else:
+ self.is_form = False
+ self.data_form_widget = config.FakeDataForm(form)
+
+ self.data_form_widget.show_all()
+ self.search_vbox.pack_start(self.data_form_widget)
+
+ def on_result_treeview_cursor_changed(self, treeview):
+ if self.jid_column == -1:
+ return
+ (model, iter_) = treeview.get_selection().get_selected()
+ if not iter_:
+ return
+ if model[iter_][self.jid_column]:
+ self.add_contact_button.set_sensitive(True)
+ self.information_button.set_sensitive(True)
+ else:
+ self.add_contact_button.set_sensitive(False)
+ self.information_button.set_sensitive(False)
+
+ def on_result_arrived(self, form, is_form):
+ if self.pulse_id:
+ gobject.source_remove(self.pulse_id)
+ self.progressbar.hide()
+ self.label.hide()
+
+ if not is_form:
+ if not form:
+ self.label.set_text(_('No result'))
+ self.label.show()
+ return
+ # We suppose all items have the same fields
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.result_treeview = gtk.TreeView()
+ self.result_treeview.connect('cursor-changed',
+ self.on_result_treeview_cursor_changed)
+ sw.add(self.result_treeview)
+ # Create model
+ fieldtypes = [str]*len(form[0])
+ model = gtk.ListStore(*fieldtypes)
+ # Copy data to model
+ for item in form:
+ model.append(item.values())
+ # Create columns
+ counter = 0
+ for field in form[0].keys():
+ self.result_treeview.append_column(
+ gtk.TreeViewColumn(field, gtk.CellRendererText(),
+ text = counter))
+ if field == 'jid':
+ self.jid_column = counter
+ counter += 1
+ self.result_treeview.set_model(model)
+ sw.show_all()
+ self.search_vbox.pack_start(sw)
+ if self.jid_column > -1:
+ self.add_contact_button.show()
+ self.information_button.show()
+ return
+
+ self.dataform = dataforms.ExtendForm(node = form)
+ if len(self.dataform.items) == 0:
+ # No result
+ self.label.set_text(_('No result'))
+ self.label.show()
+ return
+
+ self.data_form_widget.set_sensitive(True)
+ try:
+ self.data_form_widget.data_form = self.dataform
+ except dataforms.Error:
+ self.label.set_text(_('Error in received dataform'))
+ self.label.show()
+ return
+
+ self.result_treeview = self.data_form_widget.records_treeview
+ selection = self.result_treeview.get_selection()
+ selection.set_mode(gtk.SELECTION_SINGLE)
+ self.result_treeview.connect('cursor-changed',
+ self.on_result_treeview_cursor_changed)
+
+ counter = 0
+ for field in self.dataform.items[0].fields:
+ if field.var == 'jid':
+ self.jid_column = counter
+ break
+ counter += 1
+ self.search_vbox.pack_start(self.data_form_widget)
+ self.data_form_widget.show()
+ if self.jid_column > -1:
+ self.add_contact_button.show()
+ self.information_button.show()
+ if self.data_form_widget.title:
+ self.window.set_title('%s - Search - Gajim' % \
+ self.data_form_widget.title)
diff --git a/src/secrets.py b/src/secrets.py
index 94247e606..17cb5b6e3 100644
--- a/src/secrets.py
+++ b/src/secrets.py
@@ -32,90 +32,88 @@ secrets_filename = gajimpaths['SECRETS_FILE']
secrets_cache = None
class Secrets:
- def __init__(self, filename):
- self.filename = filename
- self.srs = {}
- self.pubkeys = {}
- self.privkeys = {}
+ def __init__(self, filename):
+ self.filename = filename
+ self.srs = {}
+ self.pubkeys = {}
+ self.privkeys = {}
- def cancel(self):
- raise exceptions.Cancelled
+ def cancel(self):
+ raise exceptions.Cancelled
- def save(self):
- f = open(secrets_filename, 'w')
- pickle.dump(self, f)
- f.close()
+ def save(self):
+ f = open(secrets_filename, 'w')
+ pickle.dump(self, f)
+ f.close()
- def retained_secrets(self, account, bare_jid):
- try:
- return self.srs[account][bare_jid]
- except KeyError:
- return []
+ def retained_secrets(self, account, bare_jid):
+ try:
+ return self.srs[account][bare_jid]
+ except KeyError:
+ return []
- # retained secrets are stored as a tuple of the secret and whether the user
- # has verified it
- def save_new_srs(self, account, jid, secret, verified):
- if not account in self.srs:
- self.srs[account] = {}
+ # retained secrets are stored as a tuple of the secret and whether the user
+ # has verified it
+ def save_new_srs(self, account, jid, secret, verified):
+ if not account in self.srs:
+ self.srs[account] = {}
- if not jid in self.srs[account]:
- self.srs[account][jid] = []
+ if not jid in self.srs[account]:
+ self.srs[account][jid] = []
- self.srs[account][jid].append((secret, verified))
+ self.srs[account][jid].append((secret, verified))
- self.save()
+ self.save()
- def find_srs(self, account, jid, srs):
- our_secrets = self.srs[account][jid]
- return [(x, y) for x, y in our_secrets if x == srs][0]
+ def find_srs(self, account, jid, srs):
+ our_secrets = self.srs[account][jid]
+ return [(x, y) for x, y in our_secrets if x == srs][0]
- # has the user verified this retained secret?
- def srs_verified(self, account, jid, srs):
- return self.find_srs(account, jid, srs)[1]
+ # has the user verified this retained secret?
+ def srs_verified(self, account, jid, srs):
+ return self.find_srs(account, jid, srs)[1]
- def replace_srs(self, account, jid, old_secret, new_secret, verified):
- our_secrets = self.srs[account][jid]
+ def replace_srs(self, account, jid, old_secret, new_secret, verified):
+ our_secrets = self.srs[account][jid]
- idx = our_secrets.index(self.find_srs(account, jid, old_secret))
+ idx = our_secrets.index(self.find_srs(account, jid, old_secret))
- our_secrets[idx] = (new_secret, verified)
+ our_secrets[idx] = (new_secret, verified)
- self.save()
+ self.save()
- # the public key associated with 'account'
- def my_pubkey(self, account):
- try:
- pk = self.privkeys[account]
- except KeyError:
- pk = Crypto.PublicKey.RSA.generate(384, crypto.random_bytes)
+ # the public key associated with 'account'
+ def my_pubkey(self, account):
+ try:
+ pk = self.privkeys[account]
+ except KeyError:
+ pk = Crypto.PublicKey.RSA.generate(384, crypto.random_bytes)
- self.privkeys[account] = pk
- self.save()
+ self.privkeys[account] = pk
+ self.save()
- return pk
+ return pk
def load_secrets(filename):
- f = open(filename, 'r')
+ f = open(filename, 'r')
- try:
- secrets = pickle.load(f)
- except KeyError:
- f.close()
- secrets = Secrets(filename)
+ try:
+ secrets = pickle.load(f)
+ except KeyError:
+ f.close()
+ secrets = Secrets(filename)
- return secrets
+ return secrets
def secrets():
- global secrets_cache
+ global secrets_cache
- if secrets_cache:
- return secrets_cache
+ if secrets_cache:
+ return secrets_cache
- if os.path.exists(secrets_filename):
- secrets_cache = load_secrets(secrets_filename)
- else:
- secrets_cache = Secrets(secrets_filename)
+ if os.path.exists(secrets_filename):
+ secrets_cache = load_secrets(secrets_filename)
+ else:
+ secrets_cache = Secrets(secrets_filename)
- return secrets_cache
-
-# vim: se ts=3:
+ return secrets_cache
diff --git a/src/session.py b/src/session.py
index 1725cc593..ad9f7c4f8 100644
--- a/src/session.py
+++ b/src/session.py
@@ -38,487 +38,485 @@ import dialogs
import negotiation
class ChatControlSession(stanza_session.EncryptedStanzaSession):
- def __init__(self, conn, jid, thread_id, type_='chat'):
- stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id,
- type_='chat')
-
- self.control = None
-
- def detach_from_control(self):
- if self.control:
- self.control.set_session(None)
-
- def acknowledge_termination(self):
- self.detach_from_control()
- stanza_session.EncryptedStanzaSession.acknowledge_termination(self)
-
- def terminate(self, send_termination = True):
- stanza_session.EncryptedStanzaSession.terminate(self, send_termination)
- self.detach_from_control()
-
- def get_chatstate(self, msg, msgtxt):
- """
- Extract chatstate from a <message/> stanza
- """
- composing_xep = None
- chatstate = None
-
- # chatstates - look for chatstate tags in a message if not delayed
- delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) is not None
- if not delayed:
- composing_xep = False
- children = msg.getChildren()
- for child in children:
- if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
- chatstate = child.getName()
- composing_xep = 'XEP-0085'
- break
- # No XEP-0085 support, fallback to XEP-0022
- if not chatstate:
- chatstate_child = msg.getTag('x', namespace=common.xmpp.NS_EVENT)
- if chatstate_child:
- chatstate = 'active'
- composing_xep = 'XEP-0022'
- if not msgtxt and chatstate_child.getTag('composing'):
- chatstate = 'composing'
-
- return (composing_xep, chatstate)
-
- def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg):
- """
- Dispatch a received <message> stanza
- """
- msg_type = msg.getType()
- subject = msg.getSubject()
- resource = gajim.get_resource_from_jid(full_jid_with_resource)
- if self.resource != resource:
- self.resource = resource
- if self.control and self.control.resource:
- self.control.change_resource(self.resource)
-
- if not msg_type or msg_type not in ('chat', 'groupchat', 'error'):
- msg_type = 'normal'
-
- msg_id = None
-
- # XEP-0172 User Nickname
- user_nick = msg.getTagData('nick')
- if not user_nick:
- user_nick = ''
-
- form_node = None
- for xtag in msg.getTags('x'):
- if xtag.getNamespace() == common.xmpp.NS_DATA:
- form_node = xtag
- break
-
- composing_xep, chatstate = self.get_chatstate(msg, msgtxt)
-
- xhtml = msg.getXHTML()
-
- if msg_type == 'chat':
- if not msg.getTag('body') and chatstate is None:
- return
-
- log_type = 'chat_msg_recv'
- else:
- log_type = 'single_msg_recv'
-
- if self.is_loggable() and msgtxt:
- try:
- msg_id = gajim.logger.write(log_type, full_jid_with_resource,
- msgtxt, tim=tim, subject=subject)
- except exceptions.PysqliteOperationalError, e:
- self.conn.dispatch('ERROR', (_('Disk WriteError'), str(e)))
- except exceptions.DatabaseMalformed:
- pritext = _('Database Error')
- sectext = _('The database file (%s) cannot be read. Try to repair '
- 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
- 'it (all history will be lost).') % common.logger.LOG_DB_PATH
- self.conn.dispatch('ERROR', (pritext, sectext))
-
- treat_as = gajim.config.get('treat_incoming_messages')
- if treat_as:
- msg_type = treat_as
-
- jid = gajim.get_jid_without_resource(full_jid_with_resource)
- resource = gajim.get_resource_from_jid(full_jid_with_resource)
-
- if gajim.config.get('ignore_incoming_xhtml'):
- xhtml = None
- if gajim.jid_is_transport(jid):
- jid = jid.replace('@', '')
-
- groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
- self.conn.name)
-
- if not groupchat_control and \
- jid in gajim.interface.minimized_controls[self.conn.name]:
- groupchat_control = gajim.interface.minimized_controls[self.conn.name]\
- [jid]
-
- pm = False
- if groupchat_control and groupchat_control.type_id == \
- message_control.TYPE_GC and resource:
- # It's a Private message
- pm = True
- msg_type = 'pm'
-
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- self.conn.name, jid)
-
- # does this resource have the highest priority of any available?
- is_highest = not highest_contact or not highest_contact.resource or \
- resource == highest_contact.resource or highest_contact.show == \
- 'offline'
-
- # Handle chat states
- contact = gajim.contacts.get_contact(self.conn.name, jid, resource)
- if contact:
- if contact.composing_xep != 'XEP-0085': # We cache xep85 support
- contact.composing_xep = composing_xep
- if self.control and self.control.type_id == message_control.TYPE_CHAT:
- if chatstate is not None:
- # other peer sent us reply, so he supports jep85 or jep22
- contact.chatstate = chatstate
- if contact.our_chatstate == 'ask': # we were jep85 disco?
- contact.our_chatstate = 'active' # no more
- self.control.handle_incoming_chatstate()
- elif contact.chatstate != 'active':
- # got no valid jep85 answer, peer does not support it
- contact.chatstate = False
- elif chatstate == 'active':
- # Brand new message, incoming.
- contact.our_chatstate = chatstate
- contact.chatstate = chatstate
- if msg_id: # Do not overwrite an existing msg_id with None
- contact.msg_id = msg_id
-
- # THIS MUST BE AFTER chatstates handling
- # AND BEFORE playsound (else we ear sounding on chatstates!)
- if not msgtxt: # empty message text
- return
-
- if gajim.config.get_per('accounts', self.conn.name,
- 'ignore_unknown_contacts') and not gajim.contacts.get_contacts(
- self.conn.name, jid) and not pm:
- return
-
- if not contact:
- # contact is not in the roster, create a fake one to display
- # notification
- contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
- account=self.conn.name, resource=resource)
-
- advanced_notif_num = notify.get_advanced_notification('message_received',
- self.conn.name, contact)
-
- if not pm and is_highest:
- jid_of_control = jid
- else:
- jid_of_control = full_jid_with_resource
-
- if not self.control:
- ctrl = gajim.interface.msg_win_mgr.get_control(jid_of_control,
- self.conn.name)
- if ctrl:
- self.control = ctrl
- self.control.set_session(self)
-
- # Is it a first or next message received ?
- first = False
- if not self.control and not gajim.events.get_events(self.conn.name, \
- jid_of_control, [msg_type]):
- first = True
-
- if pm:
- nickname = resource
- if self.control:
- # print if a control is open
- self.control.print_conversation(msgtxt, tim=tim, xhtml=xhtml,
- encrypted=encrypted)
- else:
- # otherwise pass it off to the control to be queued
- groupchat_control.on_private_message(nickname, msgtxt, tim,
- xhtml, self, msg_id=msg_id, encrypted=encrypted)
- else:
- self.roster_message(jid, msgtxt, tim, encrypted, msg_type,
- subject, resource, msg_id, user_nick, advanced_notif_num,
- xhtml=xhtml, form_node=form_node)
-
- nickname = gajim.get_name_from_jid(self.conn.name, jid)
-
- # Check and do wanted notifications
- msg = msgtxt
- if subject:
- msg = _('Subject: %s') % subject + '\n' + msg
- focused = False
-
- if self.control:
- parent_win = self.control.parent_win
- if parent_win and self.control == parent_win.get_active_control() and \
- parent_win.window.has_focus:
- focused = True
-
- notify.notify('new_message', jid_of_control, self.conn.name, [msg_type,
- first, nickname, msg, focused], advanced_notif_num)
-
- if gajim.interface.remote_ctrl:
- gajim.interface.remote_ctrl.raise_signal('NewMessage', (self.conn.name,
- [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject,
- chatstate, msg_id, composing_xep, user_nick, xhtml, form_node]))
-
- def roster_message(self, jid, msg, tim, encrypted=False, msg_type='',
- subject=None, resource='', msg_id=None, user_nick='',
- advanced_notif_num=None, xhtml=None, form_node=None):
- """
- Display the message or show notification in the roster
- """
- contact = None
- # if chat window will be for specific resource
- resource_for_chat = resource
-
- fjid = jid
-
- # Try to catch the contact with correct resource
- if resource:
- fjid = jid + '/' + resource
- contact = gajim.contacts.get_contact(self.conn.name, jid, resource)
-
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- self.conn.name, jid)
- if not contact:
- # If there is another resource, it may be a message from an invisible
- # resource
- lcontact = gajim.contacts.get_contacts(self.conn.name, jid)
- if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
- lcontact[0].show != 'offline')) and jid.find('@') > 0:
- contact = gajim.contacts.copy_contact(highest_contact)
- contact.resource = resource
- if resource:
- fjid = jid + '/' + resource
- contact.priority = 0
- contact.show = 'offline'
- contact.status = ''
- gajim.contacts.add_contact(self.conn.name, contact)
-
- else:
- # Default to highest prio
- fjid = jid
- resource_for_chat = None
- contact = highest_contact
-
- if not contact:
- # contact is not in roster
- contact = gajim.interface.roster.add_to_not_in_the_roster(
- self.conn.name, jid, user_nick)
-
- if not self.control:
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.conn.name)
- if ctrl:
- self.control = ctrl
- self.control.set_session(self)
- else:
- # if no control exists and message comes from highest prio, the new
- # control shouldn't have a resource
- if highest_contact and contact.resource == highest_contact.resource\
- and not jid == gajim.get_jid_from_account(self.conn.name):
- fjid = jid
- resource_for_chat = None
-
- # Do we have a queue?
- no_queue = len(gajim.events.get_events(self.conn.name, fjid)) == 0
-
- popup = helpers.allow_popup_window(self.conn.name, advanced_notif_num)
-
- if msg_type == 'normal' and popup: # it's single message to be autopopuped
- dialogs.SingleMessageWindow(self.conn.name, contact.jid,
- action='receive', from_whom=jid, subject=subject, message=msg,
- resource=resource, session=self, form_node=form_node)
- return
-
- # We print if window is opened and it's not a single message
- if self.control and msg_type != 'normal':
- typ = ''
-
- if msg_type == 'error':
- typ = 'error'
-
- self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted,
- subject=subject, xhtml=xhtml)
-
- if msg_id:
- gajim.logger.set_read_messages([msg_id])
-
- return
-
- # We save it in a queue
- type_ = 'chat'
- event_type = 'message_received'
-
- if msg_type == 'normal':
- type_ = 'normal'
- event_type = 'single_message_received'
-
- show_in_roster = notify.get_show_in_roster(event_type, self.conn.name,
- contact, self)
- show_in_systray = notify.get_show_in_systray(event_type, self.conn.name,
- contact)
-
- event = gajim.events.create_event(type_, (msg, subject, msg_type, tim,
- encrypted, resource, msg_id, xhtml, self, form_node),
- show_in_roster=show_in_roster, show_in_systray=show_in_systray)
-
- gajim.events.add_event(self.conn.name, fjid, event)
-
- if popup:
- if not self.control:
- self.control = gajim.interface.new_chat(contact,
- self.conn.name, resource=resource_for_chat, session=self)
-
- if len(gajim.events.get_events(self.conn.name, fjid)):
- self.control.read_queue()
- else:
- if no_queue: # We didn't have a queue: we change icons
- gajim.interface.roster.draw_contact(jid, self.conn.name)
-
- gajim.interface.roster.show_title() # we show the * or [n]
- # Select the big brother contact in roster, it's visible because it has
- # events.
- family = gajim.contacts.get_metacontacts_family(self.conn.name, jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- gajim.contacts.get_nearby_family_and_big_brother(family,
- self.conn.name)
- else:
- bb_jid, bb_account = jid, self.conn.name
- gajim.interface.roster.select_contact(bb_jid, bb_account)
-
- # ---- ESessions stuff ---
-
- def handle_negotiation(self, form):
- if form.getField('accept') and not form['accept'] in ('1', 'true'):
- self.cancelled_negotiation()
- return
-
- # encrypted session states. these are described in stanza_session.py
-
- try:
- # bob responds
- if form.getType() == 'form' and 'security' in form.asDict():
- # we don't support 3-message negotiation as the responder
- if 'dhkeys' in form.asDict():
- self.fail_bad_negotiation('3 message negotiation not supported '
- 'when responding', ('dhkeys',))
- return
-
- negotiated, not_acceptable, ask_user = self.verify_options_bob(form)
-
- if ask_user:
- def accept_nondefault_options(is_checked):
- self.dialog.destroy()
- negotiated.update(ask_user)
- self.respond_e2e_bob(form, negotiated, not_acceptable)
-
- def reject_nondefault_options():
- self.dialog.destroy()
- for key in ask_user.keys():
- not_acceptable.append(key)
- self.respond_e2e_bob(form, negotiated, not_acceptable)
-
- self.dialog = dialogs.YesNoDialog(_('Confirm these session '
- 'options'),
- _('''The remote client wants to negotiate an session with these features:
-
- %s
-
- Are these options acceptable?''') % (negotiation.describe_features(
- ask_user)),
- on_response_yes=accept_nondefault_options,
- on_response_no=reject_nondefault_options)
- else:
- self.respond_e2e_bob(form, negotiated, not_acceptable)
-
- return
-
- # alice accepts
- elif self.status == 'requested-e2e' and form.getType() == 'submit':
- negotiated, not_acceptable, ask_user = self.verify_options_alice(
- form)
-
- if ask_user:
- def accept_nondefault_options(is_checked):
- dialog.destroy()
-
- negotiated.update(ask_user)
-
- try:
- self.accept_e2e_alice(form, negotiated)
- except exceptions.NegotiationError, details:
- self.fail_bad_negotiation(details)
-
- def reject_nondefault_options():
- self.reject_negotiation()
- dialog.destroy()
-
- dialog = dialogs.YesNoDialog(_('Confirm these session options'),
- _('The remote client selected these options:\n\n%s\n\n'
- 'Continue with the session?') % (
- negotiation.describe_features(ask_user)),
- on_response_yes = accept_nondefault_options,
- on_response_no = reject_nondefault_options)
- else:
- try:
- self.accept_e2e_alice(form, negotiated)
- except exceptions.NegotiationError, details:
- self.fail_bad_negotiation(details)
-
- return
- elif self.status == 'responded-e2e' and form.getType() == 'result':
- try:
- self.accept_e2e_bob(form)
- except exceptions.NegotiationError, details:
- self.fail_bad_negotiation(details)
-
- return
- elif self.status == 'identified-alice' and form.getType() == 'result':
- try:
- self.final_steps_alice(form)
- except exceptions.NegotiationError, details:
- self.fail_bad_negotiation(details)
-
- return
- except exceptions.Cancelled:
- # user cancelled the negotiation
-
- self.reject_negotiation()
-
- return
-
- if form.getField('terminate') and\
- form.getField('terminate').getValue() in ('1', 'true'):
- self.acknowledge_termination()
-
- self.conn.delete_session(str(self.jid), self.thread_id)
-
- return
-
- # non-esession negotiation. this isn't very useful, but i'm keeping it
- # around to test my test suite.
- if form.getType() == 'form':
- if not self.control:
- jid, resource = gajim.get_room_and_nick_from_fjid(self.jid)
-
- account = self.conn.name
- contact = gajim.contacts.get_contact(account, self.jid, resource)
-
- if not contact:
- contact = gajim.contacts.create_contact(jid=jid, account=account,
- resource=resource, show=self.conn.get_status())
-
- gajim.interface.new_chat(contact, account, resource=resource,
- session=self)
-
- negotiation.FeatureNegotiationWindow(account, self.jid, self, form)
-
-# vim: se ts=3:
+ def __init__(self, conn, jid, thread_id, type_='chat'):
+ stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id,
+ type_='chat')
+
+ self.control = None
+
+ def detach_from_control(self):
+ if self.control:
+ self.control.set_session(None)
+
+ def acknowledge_termination(self):
+ self.detach_from_control()
+ stanza_session.EncryptedStanzaSession.acknowledge_termination(self)
+
+ def terminate(self, send_termination = True):
+ stanza_session.EncryptedStanzaSession.terminate(self, send_termination)
+ self.detach_from_control()
+
+ def get_chatstate(self, msg, msgtxt):
+ """
+ Extract chatstate from a <message/> stanza
+ """
+ composing_xep = None
+ chatstate = None
+
+ # chatstates - look for chatstate tags in a message if not delayed
+ delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) is not None
+ if not delayed:
+ composing_xep = False
+ children = msg.getChildren()
+ for child in children:
+ if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
+ chatstate = child.getName()
+ composing_xep = 'XEP-0085'
+ break
+ # No XEP-0085 support, fallback to XEP-0022
+ if not chatstate:
+ chatstate_child = msg.getTag('x', namespace=common.xmpp.NS_EVENT)
+ if chatstate_child:
+ chatstate = 'active'
+ composing_xep = 'XEP-0022'
+ if not msgtxt and chatstate_child.getTag('composing'):
+ chatstate = 'composing'
+
+ return (composing_xep, chatstate)
+
+ def received(self, full_jid_with_resource, msgtxt, tim, encrypted, msg):
+ """
+ Dispatch a received <message> stanza
+ """
+ msg_type = msg.getType()
+ subject = msg.getSubject()
+ resource = gajim.get_resource_from_jid(full_jid_with_resource)
+ if self.resource != resource:
+ self.resource = resource
+ if self.control and self.control.resource:
+ self.control.change_resource(self.resource)
+
+ if not msg_type or msg_type not in ('chat', 'groupchat', 'error'):
+ msg_type = 'normal'
+
+ msg_id = None
+
+ # XEP-0172 User Nickname
+ user_nick = msg.getTagData('nick')
+ if not user_nick:
+ user_nick = ''
+
+ form_node = None
+ for xtag in msg.getTags('x'):
+ if xtag.getNamespace() == common.xmpp.NS_DATA:
+ form_node = xtag
+ break
+
+ composing_xep, chatstate = self.get_chatstate(msg, msgtxt)
+
+ xhtml = msg.getXHTML()
+
+ if msg_type == 'chat':
+ if not msg.getTag('body') and chatstate is None:
+ return
+
+ log_type = 'chat_msg_recv'
+ else:
+ log_type = 'single_msg_recv'
+
+ if self.is_loggable() and msgtxt:
+ try:
+ msg_id = gajim.logger.write(log_type, full_jid_with_resource,
+ msgtxt, tim=tim, subject=subject)
+ except exceptions.PysqliteOperationalError, e:
+ self.conn.dispatch('ERROR', (_('Disk WriteError'), str(e)))
+ except exceptions.DatabaseMalformed:
+ pritext = _('Database Error')
+ sectext = _('The database file (%s) cannot be read. Try to repair '
+ 'it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove '
+ 'it (all history will be lost).') % common.logger.LOG_DB_PATH
+ self.conn.dispatch('ERROR', (pritext, sectext))
+
+ treat_as = gajim.config.get('treat_incoming_messages')
+ if treat_as:
+ msg_type = treat_as
+
+ jid = gajim.get_jid_without_resource(full_jid_with_resource)
+ resource = gajim.get_resource_from_jid(full_jid_with_resource)
+
+ if gajim.config.get('ignore_incoming_xhtml'):
+ xhtml = None
+ if gajim.jid_is_transport(jid):
+ jid = jid.replace('@', '')
+
+ groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
+ self.conn.name)
+
+ if not groupchat_control and \
+ jid in gajim.interface.minimized_controls[self.conn.name]:
+ groupchat_control = gajim.interface.minimized_controls[self.conn.name]\
+ [jid]
+
+ pm = False
+ if groupchat_control and groupchat_control.type_id == \
+ message_control.TYPE_GC and resource:
+ # It's a Private message
+ pm = True
+ msg_type = 'pm'
+
+ highest_contact = gajim.contacts.get_contact_with_highest_priority(
+ self.conn.name, jid)
+
+ # does this resource have the highest priority of any available?
+ is_highest = not highest_contact or not highest_contact.resource or \
+ resource == highest_contact.resource or highest_contact.show == \
+ 'offline'
+
+ # Handle chat states
+ contact = gajim.contacts.get_contact(self.conn.name, jid, resource)
+ if contact:
+ if contact.composing_xep != 'XEP-0085': # We cache xep85 support
+ contact.composing_xep = composing_xep
+ if self.control and self.control.type_id == message_control.TYPE_CHAT:
+ if chatstate is not None:
+ # other peer sent us reply, so he supports jep85 or jep22
+ contact.chatstate = chatstate
+ if contact.our_chatstate == 'ask': # we were jep85 disco?
+ contact.our_chatstate = 'active' # no more
+ self.control.handle_incoming_chatstate()
+ elif contact.chatstate != 'active':
+ # got no valid jep85 answer, peer does not support it
+ contact.chatstate = False
+ elif chatstate == 'active':
+ # Brand new message, incoming.
+ contact.our_chatstate = chatstate
+ contact.chatstate = chatstate
+ if msg_id: # Do not overwrite an existing msg_id with None
+ contact.msg_id = msg_id
+
+ # THIS MUST BE AFTER chatstates handling
+ # AND BEFORE playsound (else we ear sounding on chatstates!)
+ if not msgtxt: # empty message text
+ return
+
+ if gajim.config.get_per('accounts', self.conn.name,
+ 'ignore_unknown_contacts') and not gajim.contacts.get_contacts(
+ self.conn.name, jid) and not pm:
+ return
+
+ if not contact:
+ # contact is not in the roster, create a fake one to display
+ # notification
+ contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
+ account=self.conn.name, resource=resource)
+
+ advanced_notif_num = notify.get_advanced_notification('message_received',
+ self.conn.name, contact)
+
+ if not pm and is_highest:
+ jid_of_control = jid
+ else:
+ jid_of_control = full_jid_with_resource
+
+ if not self.control:
+ ctrl = gajim.interface.msg_win_mgr.get_control(jid_of_control,
+ self.conn.name)
+ if ctrl:
+ self.control = ctrl
+ self.control.set_session(self)
+
+ # Is it a first or next message received ?
+ first = False
+ if not self.control and not gajim.events.get_events(self.conn.name, \
+ jid_of_control, [msg_type]):
+ first = True
+
+ if pm:
+ nickname = resource
+ if self.control:
+ # print if a control is open
+ self.control.print_conversation(msgtxt, tim=tim, xhtml=xhtml,
+ encrypted=encrypted)
+ else:
+ # otherwise pass it off to the control to be queued
+ groupchat_control.on_private_message(nickname, msgtxt, tim,
+ xhtml, self, msg_id=msg_id, encrypted=encrypted)
+ else:
+ self.roster_message(jid, msgtxt, tim, encrypted, msg_type,
+ subject, resource, msg_id, user_nick, advanced_notif_num,
+ xhtml=xhtml, form_node=form_node)
+
+ nickname = gajim.get_name_from_jid(self.conn.name, jid)
+
+ # Check and do wanted notifications
+ msg = msgtxt
+ if subject:
+ msg = _('Subject: %s') % subject + '\n' + msg
+ focused = False
+
+ if self.control:
+ parent_win = self.control.parent_win
+ if parent_win and self.control == parent_win.get_active_control() and \
+ parent_win.window.has_focus:
+ focused = True
+
+ notify.notify('new_message', jid_of_control, self.conn.name, [msg_type,
+ first, nickname, msg, focused], advanced_notif_num)
+
+ if gajim.interface.remote_ctrl:
+ gajim.interface.remote_ctrl.raise_signal('NewMessage', (self.conn.name,
+ [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject,
+ chatstate, msg_id, composing_xep, user_nick, xhtml, form_node]))
+
+ def roster_message(self, jid, msg, tim, encrypted=False, msg_type='',
+ subject=None, resource='', msg_id=None, user_nick='',
+ advanced_notif_num=None, xhtml=None, form_node=None):
+ """
+ Display the message or show notification in the roster
+ """
+ contact = None
+ # if chat window will be for specific resource
+ resource_for_chat = resource
+
+ fjid = jid
+
+ # Try to catch the contact with correct resource
+ if resource:
+ fjid = jid + '/' + resource
+ contact = gajim.contacts.get_contact(self.conn.name, jid, resource)
+
+ highest_contact = gajim.contacts.get_contact_with_highest_priority(
+ self.conn.name, jid)
+ if not contact:
+ # If there is another resource, it may be a message from an invisible
+ # resource
+ lcontact = gajim.contacts.get_contacts(self.conn.name, jid)
+ if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
+ lcontact[0].show != 'offline')) and jid.find('@') > 0:
+ contact = gajim.contacts.copy_contact(highest_contact)
+ contact.resource = resource
+ if resource:
+ fjid = jid + '/' + resource
+ contact.priority = 0
+ contact.show = 'offline'
+ contact.status = ''
+ gajim.contacts.add_contact(self.conn.name, contact)
+
+ else:
+ # Default to highest prio
+ fjid = jid
+ resource_for_chat = None
+ contact = highest_contact
+
+ if not contact:
+ # contact is not in roster
+ contact = gajim.interface.roster.add_to_not_in_the_roster(
+ self.conn.name, jid, user_nick)
+
+ if not self.control:
+ ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.conn.name)
+ if ctrl:
+ self.control = ctrl
+ self.control.set_session(self)
+ else:
+ # if no control exists and message comes from highest prio, the new
+ # control shouldn't have a resource
+ if highest_contact and contact.resource == highest_contact.resource\
+ and not jid == gajim.get_jid_from_account(self.conn.name):
+ fjid = jid
+ resource_for_chat = None
+
+ # Do we have a queue?
+ no_queue = len(gajim.events.get_events(self.conn.name, fjid)) == 0
+
+ popup = helpers.allow_popup_window(self.conn.name, advanced_notif_num)
+
+ if msg_type == 'normal' and popup: # it's single message to be autopopuped
+ dialogs.SingleMessageWindow(self.conn.name, contact.jid,
+ action='receive', from_whom=jid, subject=subject, message=msg,
+ resource=resource, session=self, form_node=form_node)
+ return
+
+ # We print if window is opened and it's not a single message
+ if self.control and msg_type != 'normal':
+ typ = ''
+
+ if msg_type == 'error':
+ typ = 'error'
+
+ self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted,
+ subject=subject, xhtml=xhtml)
+
+ if msg_id:
+ gajim.logger.set_read_messages([msg_id])
+
+ return
+
+ # We save it in a queue
+ type_ = 'chat'
+ event_type = 'message_received'
+
+ if msg_type == 'normal':
+ type_ = 'normal'
+ event_type = 'single_message_received'
+
+ show_in_roster = notify.get_show_in_roster(event_type, self.conn.name,
+ contact, self)
+ show_in_systray = notify.get_show_in_systray(event_type, self.conn.name,
+ contact)
+
+ event = gajim.events.create_event(type_, (msg, subject, msg_type, tim,
+ encrypted, resource, msg_id, xhtml, self, form_node),
+ show_in_roster=show_in_roster, show_in_systray=show_in_systray)
+
+ gajim.events.add_event(self.conn.name, fjid, event)
+
+ if popup:
+ if not self.control:
+ self.control = gajim.interface.new_chat(contact,
+ self.conn.name, resource=resource_for_chat, session=self)
+
+ if len(gajim.events.get_events(self.conn.name, fjid)):
+ self.control.read_queue()
+ else:
+ if no_queue: # We didn't have a queue: we change icons
+ gajim.interface.roster.draw_contact(jid, self.conn.name)
+
+ gajim.interface.roster.show_title() # we show the * or [n]
+ # Select the big brother contact in roster, it's visible because it has
+ # events.
+ family = gajim.contacts.get_metacontacts_family(self.conn.name, jid)
+ if family:
+ nearby_family, bb_jid, bb_account = \
+ gajim.contacts.get_nearby_family_and_big_brother(family,
+ self.conn.name)
+ else:
+ bb_jid, bb_account = jid, self.conn.name
+ gajim.interface.roster.select_contact(bb_jid, bb_account)
+
+ # ---- ESessions stuff ---
+
+ def handle_negotiation(self, form):
+ if form.getField('accept') and not form['accept'] in ('1', 'true'):
+ self.cancelled_negotiation()
+ return
+
+ # encrypted session states. these are described in stanza_session.py
+
+ try:
+ # bob responds
+ if form.getType() == 'form' and 'security' in form.asDict():
+ # we don't support 3-message negotiation as the responder
+ if 'dhkeys' in form.asDict():
+ self.fail_bad_negotiation('3 message negotiation not supported '
+ 'when responding', ('dhkeys',))
+ return
+
+ negotiated, not_acceptable, ask_user = self.verify_options_bob(form)
+
+ if ask_user:
+ def accept_nondefault_options(is_checked):
+ self.dialog.destroy()
+ negotiated.update(ask_user)
+ self.respond_e2e_bob(form, negotiated, not_acceptable)
+
+ def reject_nondefault_options():
+ self.dialog.destroy()
+ for key in ask_user.keys():
+ not_acceptable.append(key)
+ self.respond_e2e_bob(form, negotiated, not_acceptable)
+
+ self.dialog = dialogs.YesNoDialog(_('Confirm these session '
+ 'options'),
+ _('''The remote client wants to negotiate an session with these features:
+
+%s
+
+Are these options acceptable?''') % (negotiation.describe_features(
+ ask_user)),
+ on_response_yes=accept_nondefault_options,
+ on_response_no=reject_nondefault_options)
+ else:
+ self.respond_e2e_bob(form, negotiated, not_acceptable)
+
+ return
+
+ # alice accepts
+ elif self.status == 'requested-e2e' and form.getType() == 'submit':
+ negotiated, not_acceptable, ask_user = self.verify_options_alice(
+ form)
+
+ if ask_user:
+ def accept_nondefault_options(is_checked):
+ dialog.destroy()
+
+ negotiated.update(ask_user)
+
+ try:
+ self.accept_e2e_alice(form, negotiated)
+ except exceptions.NegotiationError, details:
+ self.fail_bad_negotiation(details)
+
+ def reject_nondefault_options():
+ self.reject_negotiation()
+ dialog.destroy()
+
+ dialog = dialogs.YesNoDialog(_('Confirm these session options'),
+ _('The remote client selected these options:\n\n%s\n\n'
+ 'Continue with the session?') % (
+ negotiation.describe_features(ask_user)),
+ on_response_yes = accept_nondefault_options,
+ on_response_no = reject_nondefault_options)
+ else:
+ try:
+ self.accept_e2e_alice(form, negotiated)
+ except exceptions.NegotiationError, details:
+ self.fail_bad_negotiation(details)
+
+ return
+ elif self.status == 'responded-e2e' and form.getType() == 'result':
+ try:
+ self.accept_e2e_bob(form)
+ except exceptions.NegotiationError, details:
+ self.fail_bad_negotiation(details)
+
+ return
+ elif self.status == 'identified-alice' and form.getType() == 'result':
+ try:
+ self.final_steps_alice(form)
+ except exceptions.NegotiationError, details:
+ self.fail_bad_negotiation(details)
+
+ return
+ except exceptions.Cancelled:
+ # user cancelled the negotiation
+
+ self.reject_negotiation()
+
+ return
+
+ if form.getField('terminate') and\
+ form.getField('terminate').getValue() in ('1', 'true'):
+ self.acknowledge_termination()
+
+ self.conn.delete_session(str(self.jid), self.thread_id)
+
+ return
+
+ # non-esession negotiation. this isn't very useful, but i'm keeping it
+ # around to test my test suite.
+ if form.getType() == 'form':
+ if not self.control:
+ jid, resource = gajim.get_room_and_nick_from_fjid(self.jid)
+
+ account = self.conn.name
+ contact = gajim.contacts.get_contact(account, self.jid, resource)
+
+ if not contact:
+ contact = gajim.contacts.create_contact(jid=jid, account=account,
+ resource=resource, show=self.conn.get_status())
+
+ gajim.interface.new_chat(contact, account, resource=resource,
+ session=self)
+
+ negotiation.FeatureNegotiationWindow(account, self.jid, self, form)
diff --git a/src/statusicon.py b/src/statusicon.py
index 38c0e0936..f7a97a2f3 100644
--- a/src/statusicon.py
+++ b/src/statusicon.py
@@ -39,395 +39,393 @@ from common import helpers
from common import pep
class StatusIcon:
- """
- Class for the notification area icon
- """
-
- def __init__(self):
- self.single_message_handler_id = None
- self.new_chat_handler_id = None
- # click somewhere else does not popdown menu. workaround this.
- self.added_hide_menuitem = False
- self.status = 'offline'
- self.xml = gtkgui_helpers.get_gtk_builder('systray_context_menu.ui')
- self.systray_context_menu = self.xml.get_object('systray_context_menu')
- self.xml.connect_signals(self)
- self.popup_menus = []
- self.status_icon = None
- self.tooltip = tooltips.NotificationAreaTooltip()
-
- def subscribe_events(self):
- """
- Register listeners to the events class
- """
- gajim.events.event_added_subscribe(self.on_event_added)
- gajim.events.event_removed_subscribe(self.on_event_removed)
-
- def 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)
- self.subscribe_events()
-
- def on_status_icon_right_clicked(self, widget, event_button, event_time):
- self.make_menu(event_button, event_time)
-
- def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
- self.tooltip.populate()
- tooltip.set_custom(self.tooltip.hbox)
- return True
-
- def hide_icon(self):
- self.status_icon.set_visible(False)
- self.unsubscribe_events()
-
- def on_status_icon_left_clicked(self, widget):
- self.on_left_click()
-
- def set_img(self):
- """
- Apart from image, we also update tooltip text here
- """
- if not gajim.interface.systray_enabled:
- return
- if gajim.events.get_nb_systray_events():
- self.status_icon.set_blinking(True)
- else:
- self.status_icon.set_blinking(False)
-
- # FIXME: do not always use 16x16 (ask actually used size and use that)
- image = gajim.interface.jabber_state_images['16'][self.status]
- if image.get_storage_type() == gtk.IMAGE_PIXBUF:
- self.status_icon.set_from_pixbuf(image.get_pixbuf())
- # FIXME: oops they forgot to support GIF animation?
- # or they were lazy to get it to work under Windows! WTF!
- elif image.get_storage_type() == gtk.IMAGE_ANIMATION:
- self.status_icon.set_from_pixbuf(
- image.get_animation().get_static_image())
- # self.img_tray.set_from_animation(image.get_animation())
-
- def change_status(self, global_status):
- """
- Set tray image to 'global_status'
- """
- # change image and status, only if it is different
- if global_status is not None and self.status != global_status:
- self.status = global_status
- self.set_img()
-
- def 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_object('chat_with_menuitem')
- single_message_menuitem = self.xml.get_object(
- 'single_message_menuitem')
- status_menuitem = self.xml.get_object('status_menu')
- join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
- sounds_mute_menuitem = self.xml.get_object('sounds_mute_menuitem')
-
- if self.single_message_handler_id:
- single_message_menuitem.handler_disconnect(
- self.single_message_handler_id)
- self.single_message_handler_id = None
- if self.new_chat_handler_id:
- chat_with_menuitem.disconnect(self.new_chat_handler_id)
- self.new_chat_handler_id = None
-
- sub_menu = gtk.Menu()
- self.popup_menus.append(sub_menu)
- status_menuitem.set_submenu(sub_menu)
-
- gc_sub_menu = gtk.Menu() # gc is always a submenu
- join_gc_menuitem.set_submenu(gc_sub_menu)
-
- # We need our own set of status icons, let's make 'em!
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- state_images = gtkgui_helpers.load_iconset(path)
-
- if 'muc_active' in state_images:
- join_gc_menuitem.set_image(state_images['muc_active'])
-
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic = True)
- item = gtk.ImageMenuItem(uf_show)
- item.set_image(state_images[show])
- sub_menu.append(item)
- item.connect('activate', self.on_show_menuitem_activate, show)
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
-
- item = gtk.ImageMenuItem(_('_Change Status Message...'))
- gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate)
-
- connected_accounts = gajim.get_number_of_connected_accounts()
- if connected_accounts < 1:
- item.set_sensitive(False)
-
- connected_accounts_with_private_storage = 0
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
-
- uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
- item = gtk.ImageMenuItem(uf_show)
- item.set_image(state_images['offline'])
- sub_menu.append(item)
- item.connect('activate', self.on_show_menuitem_activate, 'offline')
-
- iskey = connected_accounts > 0 and not (connected_accounts == 1 and
- gajim.connections[gajim.connections.keys()[0]].is_zeroconf)
- chat_with_menuitem.set_sensitive(iskey)
- single_message_menuitem.set_sensitive(iskey)
- join_gc_menuitem.set_sensitive(iskey)
-
- accounts_list = sorted(gajim.contacts.get_accounts())
- # items that get shown whether an account is zeroconf or not
- if connected_accounts > 1: # 2 or more connections? make submenus
- account_menu_for_chat_with = gtk.Menu()
- chat_with_menuitem.set_submenu(account_menu_for_chat_with)
- self.popup_menus.append(account_menu_for_chat_with)
-
- for account in accounts_list:
- if gajim.account_is_connected(account):
- # for chat_with
- item = gtk.MenuItem(_('using account %s') % account)
- account_menu_for_chat_with.append(item)
- item.connect('activate', self.on_new_chat, account)
-
- elif connected_accounts == 1: # one account
- # one account connected, no need to show 'as jid'
- for account in gajim.connections:
- if gajim.connections[account].connected > 1:
- # for start chat
- self.new_chat_handler_id = chat_with_menuitem.connect(
- 'activate', self.on_new_chat, account)
- break # No other connected account
-
- # menu items that don't apply to zeroconf connections
- if connected_accounts == 1 or (connected_accounts == 2 and \
- gajim.zeroconf_is_connected()):
- # only one 'real' (non-zeroconf) account is connected, don't need
- # submenus
- for account in gajim.connections:
- if gajim.account_is_connected(account) and \
- not gajim.config.get_per('accounts', account, 'is_zeroconf'):
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
-
- # for single message
- single_message_menuitem.remove_submenu()
- self.single_message_handler_id = single_message_menuitem.\
- connect('activate',
- self.on_single_message_menuitem_activate, account)
- # join gc
- gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
- account)
- break # No other account connected
- else:
- # 2 or more 'real' accounts are connected, make submenus
- account_menu_for_single_message = gtk.Menu()
- single_message_menuitem.set_submenu(
- account_menu_for_single_message)
- self.popup_menus.append(account_menu_for_single_message)
-
- for account in accounts_list:
- if gajim.connections[account].is_zeroconf or \
- not gajim.account_is_connected(account):
- continue
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
- # for single message
- item = gtk.MenuItem(_('using account %s') % account)
- item.connect('activate',
- self.on_single_message_menuitem_activate, account)
- account_menu_for_single_message.append(item)
-
- # join gc
- gc_item = gtk.MenuItem(_('using account %s') % account, False)
- gc_sub_menu.append(gc_item)
- gc_menuitem_menu = gtk.Menu()
- gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu,
- account)
- gc_item.set_submenu(gc_menuitem_menu)
- gc_sub_menu.show_all()
-
- newitem = gtk.SeparatorMenuItem() # separator
- gc_sub_menu.append(newitem)
- newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
- img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
- newitem.set_image(img)
- newitem.connect('activate',
- gajim.interface.roster.on_manage_bookmarks_menuitem_activate)
- gc_sub_menu.append(newitem)
- if connected_accounts_with_private_storage == 0:
- newitem.set_sensitive(False)
-
- sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
-
- if os.name == 'nt':
- if self.added_hide_menuitem is False:
- self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
- item = gtk.MenuItem(_('Hide this menu'))
- self.systray_context_menu.prepend(item)
- self.added_hide_menuitem = True
-
- self.systray_context_menu.show_all()
- self.systray_context_menu.popup(None, None, None, 0,
- event_time)
-
- def on_show_all_events_menuitem_activate(self, widget):
- events = gajim.events.get_systray_events()
- for account in events:
- for jid in events[account]:
- for event in events[account][jid]:
- gajim.interface.handle_event(account, jid, event.type_)
-
- def on_sounds_mute_menuitem_activate(self, widget):
- gajim.config.set('sounds_on', not widget.get_active())
- gajim.interface.save_config()
-
- def on_show_roster_menuitem_activate(self, widget):
- win = gajim.interface.roster.window
- win.present()
-
- def on_preferences_menuitem_activate(self, widget):
- if 'preferences' in gajim.interface.instances:
- gajim.interface.instances['preferences'].window.present()
- else:
- gajim.interface.instances['preferences'] = config.PreferencesWindow()
-
- def on_quit_menuitem_activate(self, widget):
- gajim.interface.roster.on_quit_request()
-
- def on_left_click(self):
- win = gajim.interface.roster.window
- if len(gajim.events.get_systray_events()) == 0:
- # No pending events, so toggle visible/hidden for roster window
- if win.get_property('visible') and (win.get_property(
- 'has-toplevel-focus') or os.name == 'nt'):
- # visible in ANY virtual desktop?
-
- # we could be in another VD right now. eg vd2
- # and we want to show it in vd2
- if not gtkgui_helpers.possibly_move_window_in_current_desktop(win):
- win.hide() # else we hide it from VD that was visible in
- else:
- win.show_all()
- if not gajim.config.get('roster_window_skip_taskbar'):
- win.set_property('skip-taskbar-hint', False)
- win.present_with_time(gtk.get_current_event_time())
- else:
- self.handle_first_event()
-
- def handle_first_event(self):
- account, jid, event = gajim.events.get_first_systray_event()
- if not event:
- return
- gajim.interface.handle_event(account, jid, event.type_)
-
- def on_middle_click(self):
- """
- Middle click raises window to have complete focus (fe. get kbd events)
- but if already raised, it hides it
- """
- win = gajim.interface.roster.window
- if win.is_active(): # is it fully raised? (eg does it receive kbd events?)
- win.hide()
- else:
- win.present()
-
- def on_clicked(self, widget, event):
- self.on_tray_leave_notify_event(widget, None)
- if event.type != gtk.gdk.BUTTON_PRESS:
- return
- if event.button == 1: # Left click
- self.on_left_click()
- elif event.button == 2: # middle click
- self.on_middle_click()
- elif event.button == 3: # right click
- self.make_menu(event.button, event.time)
-
- def on_show_menuitem_activate(self, widget, show):
- # we all add some fake (we cannot select those nor have them as show)
- # but this helps to align with roster's status_combobox index positions
- l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR',
- 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline']
- index = l.index(show)
- if not helpers.statuses_unified():
- gajim.interface.roster.status_combobox.set_active(index + 2)
- return
- current = gajim.interface.roster.status_combobox.get_active()
- if index != current:
- gajim.interface.roster.status_combobox.set_active(index)
-
- def on_change_status_message_activate(self, widget):
- model = gajim.interface.roster.status_combobox.get_model()
- active = gajim.interface.roster.status_combobox.get_active()
- status = model[active][2].decode('utf-8')
- def on_response(message, pep_dict):
- if message is None: # None if user press Cancel
- return
- accounts = gajim.connections.keys()
- for acct in accounts:
- if not gajim.config.get_per('accounts', acct,
- 'sync_with_global_status'):
- continue
- show = gajim.SHOW_LIST[gajim.connections[acct].connected]
- gajim.interface.roster.send_status(acct, show, message)
- gajim.interface.roster.send_pep(acct, pep_dict)
- dlg = dialogs.ChangeStatusMessageDialog(on_response, status)
- dlg.dialog.present()
-
-# vim: se ts=3:
+ """
+ Class for the notification area icon
+ """
+
+ def __init__(self):
+ self.single_message_handler_id = None
+ self.new_chat_handler_id = None
+ # click somewhere else does not popdown menu. workaround this.
+ self.added_hide_menuitem = False
+ self.status = 'offline'
+ self.xml = gtkgui_helpers.get_gtk_builder('systray_context_menu.ui')
+ self.systray_context_menu = self.xml.get_object('systray_context_menu')
+ self.xml.connect_signals(self)
+ self.popup_menus = []
+ self.status_icon = None
+ self.tooltip = tooltips.NotificationAreaTooltip()
+
+ def 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)
+ self.subscribe_events()
+
+ def on_status_icon_right_clicked(self, widget, event_button, event_time):
+ self.make_menu(event_button, event_time)
+
+ def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
+ self.tooltip.populate()
+ tooltip.set_custom(self.tooltip.hbox)
+ return True
+
+ def hide_icon(self):
+ self.status_icon.set_visible(False)
+ self.unsubscribe_events()
+
+ def on_status_icon_left_clicked(self, widget):
+ self.on_left_click()
+
+ def set_img(self):
+ """
+ Apart from image, we also update tooltip text here
+ """
+ if not gajim.interface.systray_enabled:
+ return
+ if gajim.events.get_nb_systray_events():
+ self.status_icon.set_blinking(True)
+ else:
+ self.status_icon.set_blinking(False)
+
+ # FIXME: do not always use 16x16 (ask actually used size and use that)
+ image = gajim.interface.jabber_state_images['16'][self.status]
+ if image.get_storage_type() == gtk.IMAGE_PIXBUF:
+ self.status_icon.set_from_pixbuf(image.get_pixbuf())
+ # FIXME: oops they forgot to support GIF animation?
+ # or they were lazy to get it to work under Windows! WTF!
+ elif image.get_storage_type() == gtk.IMAGE_ANIMATION:
+ self.status_icon.set_from_pixbuf(
+ image.get_animation().get_static_image())
+ # self.img_tray.set_from_animation(image.get_animation())
+
+ def change_status(self, global_status):
+ """
+ Set tray image to 'global_status'
+ """
+ # change image and status, only if it is different
+ if global_status is not None and self.status != global_status:
+ self.status = global_status
+ self.set_img()
+
+ def 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_object('chat_with_menuitem')
+ single_message_menuitem = self.xml.get_object(
+ 'single_message_menuitem')
+ status_menuitem = self.xml.get_object('status_menu')
+ join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
+ sounds_mute_menuitem = self.xml.get_object('sounds_mute_menuitem')
+
+ if self.single_message_handler_id:
+ single_message_menuitem.handler_disconnect(
+ self.single_message_handler_id)
+ self.single_message_handler_id = None
+ if self.new_chat_handler_id:
+ chat_with_menuitem.disconnect(self.new_chat_handler_id)
+ self.new_chat_handler_id = None
+
+ sub_menu = gtk.Menu()
+ self.popup_menus.append(sub_menu)
+ status_menuitem.set_submenu(sub_menu)
+
+ gc_sub_menu = gtk.Menu() # gc is always a submenu
+ join_gc_menuitem.set_submenu(gc_sub_menu)
+
+ # We need our own set of status icons, let's make 'em!
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ state_images = gtkgui_helpers.load_iconset(path)
+
+ if 'muc_active' in state_images:
+ join_gc_menuitem.set_image(state_images['muc_active'])
+
+ for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+ uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+ item = gtk.ImageMenuItem(uf_show)
+ item.set_image(state_images[show])
+ sub_menu.append(item)
+ item.connect('activate', self.on_show_menuitem_activate, show)
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ item = gtk.ImageMenuItem(_('_Change Status Message...'))
+ gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
+ sub_menu.append(item)
+ item.connect('activate', self.on_change_status_message_activate)
+
+ connected_accounts = gajim.get_number_of_connected_accounts()
+ if connected_accounts < 1:
+ item.set_sensitive(False)
+
+ connected_accounts_with_private_storage = 0
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
+ item = gtk.ImageMenuItem(uf_show)
+ item.set_image(state_images['offline'])
+ sub_menu.append(item)
+ item.connect('activate', self.on_show_menuitem_activate, 'offline')
+
+ iskey = connected_accounts > 0 and not (connected_accounts == 1 and
+ gajim.connections[gajim.connections.keys()[0]].is_zeroconf)
+ chat_with_menuitem.set_sensitive(iskey)
+ single_message_menuitem.set_sensitive(iskey)
+ join_gc_menuitem.set_sensitive(iskey)
+
+ accounts_list = sorted(gajim.contacts.get_accounts())
+ # items that get shown whether an account is zeroconf or not
+ if connected_accounts > 1: # 2 or more connections? make submenus
+ account_menu_for_chat_with = gtk.Menu()
+ chat_with_menuitem.set_submenu(account_menu_for_chat_with)
+ self.popup_menus.append(account_menu_for_chat_with)
+
+ for account in accounts_list:
+ if gajim.account_is_connected(account):
+ # for chat_with
+ item = gtk.MenuItem(_('using account %s') % account)
+ account_menu_for_chat_with.append(item)
+ item.connect('activate', self.on_new_chat, account)
+
+ elif connected_accounts == 1: # one account
+ # one account connected, no need to show 'as jid'
+ for account in gajim.connections:
+ if gajim.connections[account].connected > 1:
+ # for start chat
+ self.new_chat_handler_id = chat_with_menuitem.connect(
+ 'activate', self.on_new_chat, account)
+ break # No other connected account
+
+ # menu items that don't apply to zeroconf connections
+ if connected_accounts == 1 or (connected_accounts == 2 and \
+ gajim.zeroconf_is_connected()):
+ # only one 'real' (non-zeroconf) account is connected, don't need
+ # submenus
+ for account in gajim.connections:
+ if gajim.account_is_connected(account) and \
+ not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ if gajim.connections[account].private_storage_supported:
+ connected_accounts_with_private_storage += 1
+
+ # for single message
+ single_message_menuitem.remove_submenu()
+ self.single_message_handler_id = single_message_menuitem.\
+ connect('activate',
+ self.on_single_message_menuitem_activate, account)
+ # join gc
+ gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
+ account)
+ break # No other account connected
+ else:
+ # 2 or more 'real' accounts are connected, make submenus
+ account_menu_for_single_message = gtk.Menu()
+ single_message_menuitem.set_submenu(
+ account_menu_for_single_message)
+ self.popup_menus.append(account_menu_for_single_message)
+
+ for account in accounts_list:
+ if gajim.connections[account].is_zeroconf or \
+ not gajim.account_is_connected(account):
+ continue
+ if gajim.connections[account].private_storage_supported:
+ connected_accounts_with_private_storage += 1
+ # for single message
+ item = gtk.MenuItem(_('using account %s') % account)
+ item.connect('activate',
+ self.on_single_message_menuitem_activate, account)
+ account_menu_for_single_message.append(item)
+
+ # join gc
+ gc_item = gtk.MenuItem(_('using account %s') % account, False)
+ gc_sub_menu.append(gc_item)
+ gc_menuitem_menu = gtk.Menu()
+ gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu,
+ account)
+ gc_item.set_submenu(gc_menuitem_menu)
+ gc_sub_menu.show_all()
+
+ newitem = gtk.SeparatorMenuItem() # separator
+ gc_sub_menu.append(newitem)
+ newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
+ img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
+ newitem.set_image(img)
+ newitem.connect('activate',
+ gajim.interface.roster.on_manage_bookmarks_menuitem_activate)
+ gc_sub_menu.append(newitem)
+ if connected_accounts_with_private_storage == 0:
+ newitem.set_sensitive(False)
+
+ sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
+
+ if os.name == 'nt':
+ if self.added_hide_menuitem is False:
+ self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
+ item = gtk.MenuItem(_('Hide this menu'))
+ self.systray_context_menu.prepend(item)
+ self.added_hide_menuitem = True
+
+ self.systray_context_menu.show_all()
+ self.systray_context_menu.popup(None, None, None, 0,
+ event_time)
+
+ def on_show_all_events_menuitem_activate(self, widget):
+ events = gajim.events.get_systray_events()
+ for account in events:
+ for jid in events[account]:
+ for event in events[account][jid]:
+ gajim.interface.handle_event(account, jid, event.type_)
+
+ def on_sounds_mute_menuitem_activate(self, widget):
+ gajim.config.set('sounds_on', not widget.get_active())
+ gajim.interface.save_config()
+
+ def on_show_roster_menuitem_activate(self, widget):
+ win = gajim.interface.roster.window
+ win.present()
+
+ def on_preferences_menuitem_activate(self, widget):
+ if 'preferences' in gajim.interface.instances:
+ gajim.interface.instances['preferences'].window.present()
+ else:
+ gajim.interface.instances['preferences'] = config.PreferencesWindow()
+
+ def on_quit_menuitem_activate(self, widget):
+ gajim.interface.roster.on_quit_request()
+
+ def on_left_click(self):
+ win = gajim.interface.roster.window
+ if len(gajim.events.get_systray_events()) == 0:
+ # No pending events, so toggle visible/hidden for roster window
+ if win.get_property('visible') and (win.get_property(
+ 'has-toplevel-focus') or os.name == 'nt'):
+ # visible in ANY virtual desktop?
+
+ # we could be in another VD right now. eg vd2
+ # and we want to show it in vd2
+ if not gtkgui_helpers.possibly_move_window_in_current_desktop(win):
+ win.hide() # else we hide it from VD that was visible in
+ else:
+ win.show_all()
+ if not gajim.config.get('roster_window_skip_taskbar'):
+ win.set_property('skip-taskbar-hint', False)
+ win.present_with_time(gtk.get_current_event_time())
+ else:
+ self.handle_first_event()
+
+ def handle_first_event(self):
+ account, jid, event = gajim.events.get_first_systray_event()
+ if not event:
+ return
+ gajim.interface.handle_event(account, jid, event.type_)
+
+ def on_middle_click(self):
+ """
+ Middle click raises window to have complete focus (fe. get kbd events)
+ but if already raised, it hides it
+ """
+ win = gajim.interface.roster.window
+ if win.is_active(): # is it fully raised? (eg does it receive kbd events?)
+ win.hide()
+ else:
+ win.present()
+
+ def on_clicked(self, widget, event):
+ self.on_tray_leave_notify_event(widget, None)
+ if event.type != gtk.gdk.BUTTON_PRESS:
+ return
+ if event.button == 1: # Left click
+ self.on_left_click()
+ elif event.button == 2: # middle click
+ self.on_middle_click()
+ elif event.button == 3: # right click
+ self.make_menu(event.button, event.time)
+
+ def on_show_menuitem_activate(self, widget, show):
+ # we all add some fake (we cannot select those nor have them as show)
+ # but this helps to align with roster's status_combobox index positions
+ l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR',
+ 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline']
+ index = l.index(show)
+ if not helpers.statuses_unified():
+ gajim.interface.roster.status_combobox.set_active(index + 2)
+ return
+ current = gajim.interface.roster.status_combobox.get_active()
+ if index != current:
+ gajim.interface.roster.status_combobox.set_active(index)
+
+ def on_change_status_message_activate(self, widget):
+ model = gajim.interface.roster.status_combobox.get_model()
+ active = gajim.interface.roster.status_combobox.get_active()
+ status = model[active][2].decode('utf-8')
+ def on_response(message, pep_dict):
+ if message is None: # None if user press Cancel
+ return
+ accounts = gajim.connections.keys()
+ for acct in accounts:
+ if not gajim.config.get_per('accounts', acct,
+ 'sync_with_global_status'):
+ continue
+ show = gajim.SHOW_LIST[gajim.connections[acct].connected]
+ gajim.interface.roster.send_status(acct, show, message)
+ gajim.interface.roster.send_pep(acct, pep_dict)
+ dlg = dialogs.ChangeStatusMessageDialog(on_response, status)
+ dlg.dialog.present()
diff --git a/src/tooltips.py b/src/tooltips.py
index aeaf06b1c..86d759ce8 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -41,737 +41,735 @@ from common import helpers
from common.pep import MOODS, ACTIVITIES
class BaseTooltip:
- """
- Base Tooltip class
-
- Usage:
- tooltip = BaseTooltip()
- ....
- tooltip.show_tooltip(data, widget_height, widget_y_position)
- ....
- if tooltip.timeout != 0:
- tooltip.hide_tooltip()
-
- * data - the text to be displayed (extenders override this argument and
- display more complex contents)
- * widget_height - the height of the widget on which we want to show tooltip
- * widget_y_position - the vertical position of the widget on the screen
-
- Tooltip is displayed aligned centered to the mouse poiner and 4px below the widget.
- In case tooltip goes below the visible area it is shown above the widget.
- """
-
- def __init__(self):
- self.timeout = 0
- self.preferred_position = [0, 0]
- self.win = None
- self.id = None
- self.cur_data = None
- self.check_last_time = None
-
- def populate(self, data):
- """
- This method must be overriden by all extenders. This is the most simple
- implementation: show data as value of a label
- """
- self.create_window()
- self.win.add(gtk.Label(data))
-
- def create_window(self):
- """
- Create a popup window each time tooltip is requested
- """
- self.win = gtk.Window(gtk.WINDOW_POPUP)
- self.win.set_border_width(3)
- self.win.set_resizable(False)
- self.win.set_name('gtk-tooltips')
- self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)
-
- self.win.set_events(gtk.gdk.POINTER_MOTION_MASK)
- self.win.connect_after('expose_event', self.expose)
- self.win.connect('size-request', self.on_size_request)
- self.win.connect('motion-notify-event', self.motion_notify_event)
- self.screen = self.win.get_screen()
-
- def _get_icon_name_for_tooltip(self, contact):
- """
- Helper function used for tooltip contacts/acounts
-
- Tooltip on account has fake contact with sub == '', in this case we show
- real status of the account
- """
- if contact.ask == 'subscribe':
- return 'requested'
- elif contact.sub in ('both', 'to', ''):
- return contact.show
- return 'not in roster'
-
- def motion_notify_event(self, widget, event):
- self.hide_tooltip()
-
- def on_size_request(self, widget, requisition):
- half_width = requisition.width / 2 + 1
- if self.preferred_position[0] < half_width:
- self.preferred_position[0] = 0
- elif self.preferred_position[0] + requisition.width > \
- self.screen.get_width() + half_width:
- self.preferred_position[0] = self.screen.get_width() - \
- requisition.width
- elif not self.check_last_time:
- self.preferred_position[0] -= half_width
- if self.preferred_position[1] + requisition.height > \
- self.screen.get_height():
- # flip tooltip up
- self.preferred_position[1] -= requisition.height + \
- self.widget_height + 8
- if self.preferred_position[1] < 0:
- self.preferred_position[1] = 0
- self.win.move(self.preferred_position[0], self.preferred_position[1])
-
- def expose(self, widget, event):
- style = self.win.get_style()
- size = self.win.get_size()
- style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
- None, self.win, 'tooltip', 0, 0, -1, 1)
- style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
- None, self.win, 'tooltip', 0, size[1] - 1, -1, 1)
- style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
- None, self.win, 'tooltip', 0, 0, 1, -1)
- style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
- None, self.win, 'tooltip', size[0] - 1, 0, 1, -1)
- return True
-
- def show_tooltip(self, data, widget_height, widget_y_position):
- """
- Show tooltip on widget
-
- Data contains needed data for tooltip contents.
- widget_height is the height of the widget on which we show the tooltip.
- widget_y_position is vertical position of the widget on the screen.
- """
- self.cur_data = data
- # set tooltip contents
- self.populate(data)
-
- # get the X position of mouse pointer on the screen
- pointer_x = self.screen.get_display().get_pointer()[1]
-
- # get the prefered X position of the tooltip on the screen in case this position is >
- # than the height of the screen, tooltip will be shown above the widget
- preferred_y = widget_y_position + widget_height + 4
-
- self.preferred_position = [pointer_x, preferred_y]
- self.widget_height = widget_height
- self.win.ensure_style()
- self.win.show_all()
-
- def hide_tooltip(self):
- if self.timeout > 0:
- gobject.source_remove(self.timeout)
- self.timeout = 0
- if self.win:
- self.win.destroy()
- self.win = None
- self.id = None
- self.cur_data = None
- self.check_last_time = None
+ """
+ Base Tooltip class
+
+ Usage:
+ tooltip = BaseTooltip()
+ ....
+ tooltip.show_tooltip(data, widget_height, widget_y_position)
+ ....
+ if tooltip.timeout != 0:
+ tooltip.hide_tooltip()
+
+ * data - the text to be displayed (extenders override this argument and
+ display more complex contents)
+ * widget_height - the height of the widget on which we want to show tooltip
+ * widget_y_position - the vertical position of the widget on the screen
+
+ Tooltip is displayed aligned centered to the mouse poiner and 4px below the widget.
+ In case tooltip goes below the visible area it is shown above the widget.
+ """
+
+ def __init__(self):
+ self.timeout = 0
+ self.preferred_position = [0, 0]
+ self.win = None
+ self.id = None
+ self.cur_data = None
+ self.check_last_time = None
+
+ def populate(self, data):
+ """
+ This method must be overriden by all extenders. This is the most simple
+ implementation: show data as value of a label
+ """
+ self.create_window()
+ self.win.add(gtk.Label(data))
+
+ def create_window(self):
+ """
+ Create a popup window each time tooltip is requested
+ """
+ self.win = gtk.Window(gtk.WINDOW_POPUP)
+ self.win.set_border_width(3)
+ self.win.set_resizable(False)
+ self.win.set_name('gtk-tooltips')
+ self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP)
+
+ self.win.set_events(gtk.gdk.POINTER_MOTION_MASK)
+ self.win.connect_after('expose_event', self.expose)
+ self.win.connect('size-request', self.on_size_request)
+ self.win.connect('motion-notify-event', self.motion_notify_event)
+ self.screen = self.win.get_screen()
+
+ def _get_icon_name_for_tooltip(self, contact):
+ """
+ Helper function used for tooltip contacts/acounts
+
+ Tooltip on account has fake contact with sub == '', in this case we show
+ real status of the account
+ """
+ if contact.ask == 'subscribe':
+ return 'requested'
+ elif contact.sub in ('both', 'to', ''):
+ return contact.show
+ return 'not in roster'
+
+ def motion_notify_event(self, widget, event):
+ self.hide_tooltip()
+
+ def on_size_request(self, widget, requisition):
+ half_width = requisition.width / 2 + 1
+ if self.preferred_position[0] < half_width:
+ self.preferred_position[0] = 0
+ elif self.preferred_position[0] + requisition.width > \
+ self.screen.get_width() + half_width:
+ self.preferred_position[0] = self.screen.get_width() - \
+ requisition.width
+ elif not self.check_last_time:
+ self.preferred_position[0] -= half_width
+ if self.preferred_position[1] + requisition.height > \
+ self.screen.get_height():
+ # flip tooltip up
+ self.preferred_position[1] -= requisition.height + \
+ self.widget_height + 8
+ if self.preferred_position[1] < 0:
+ self.preferred_position[1] = 0
+ self.win.move(self.preferred_position[0], self.preferred_position[1])
+
+ def expose(self, widget, event):
+ style = self.win.get_style()
+ size = self.win.get_size()
+ style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
+ None, self.win, 'tooltip', 0, 0, -1, 1)
+ style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
+ None, self.win, 'tooltip', 0, size[1] - 1, -1, 1)
+ style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
+ None, self.win, 'tooltip', 0, 0, 1, -1)
+ style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT,
+ None, self.win, 'tooltip', size[0] - 1, 0, 1, -1)
+ return True
+
+ def show_tooltip(self, data, widget_height, widget_y_position):
+ """
+ Show tooltip on widget
+
+ Data contains needed data for tooltip contents.
+ widget_height is the height of the widget on which we show the tooltip.
+ widget_y_position is vertical position of the widget on the screen.
+ """
+ self.cur_data = data
+ # set tooltip contents
+ self.populate(data)
+
+ # get the X position of mouse pointer on the screen
+ pointer_x = self.screen.get_display().get_pointer()[1]
+
+ # get the prefered X position of the tooltip on the screen in case this position is >
+ # than the height of the screen, tooltip will be shown above the widget
+ preferred_y = widget_y_position + widget_height + 4
+
+ self.preferred_position = [pointer_x, preferred_y]
+ self.widget_height = widget_height
+ self.win.ensure_style()
+ self.win.show_all()
+
+ def hide_tooltip(self):
+ if self.timeout > 0:
+ gobject.source_remove(self.timeout)
+ self.timeout = 0
+ if self.win:
+ self.win.destroy()
+ self.win = None
+ self.id = None
+ self.cur_data = None
+ self.check_last_time = None
class StatusTable:
- """
- Contains methods for creating status table. This is used in Roster and
- NotificationArea tooltips
- """
-
- def __init__(self):
- self.current_row = 1
- self.table = None
- self.text_label = None
- self.spacer_label = ' '
-
- def create_table(self):
- self.table = gtk.Table(4, 1)
- self.table.set_property('column-spacing', 2)
-
- def add_text_row(self, text, col_inc = 0):
- self.current_row += 1
- self.text_label = gtk.Label()
- self.text_label.set_line_wrap(True)
- self.text_label.set_alignment(0, 0)
- self.text_label.set_selectable(False)
- self.text_label.set_markup(text)
- self.table.attach(self.text_label, 1 + col_inc, 4, self.current_row,
- self.current_row + 1)
-
- def get_status_info(self, resource, priority, show, status):
- str_status = resource + ' (' + unicode(priority) + ')'
- if status:
- status = status.strip()
- if status != '':
- # make sure 'status' is unicode before we send to to reduce_chars
- if isinstance(status, str):
- status = unicode(status, encoding='utf-8')
- # reduce to 100 chars, 1 line
- status = helpers.reduce_chars_newlines(status, 100, 1)
- str_status = gobject.markup_escape_text(str_status)
- status = gobject.markup_escape_text(status)
- str_status += ' - <i>' + status + '</i>'
- return str_status
-
- def add_status_row(self, file_path, show, str_status, status_time=None,
- show_lock=False, indent=True):
- """
- Append a new row with status icon to the table
- """
- self.current_row += 1
- state_file = show.replace(' ', '_')
- files = []
- files.append(os.path.join(file_path, state_file + '.png'))
- files.append(os.path.join(file_path, state_file + '.gif'))
- image = gtk.Image()
- image.set_from_pixbuf(None)
- for f in files:
- if os.path.exists(f):
- image.set_from_file(f)
- break
- spacer = gtk.Label(self.spacer_label)
- image.set_alignment(1, 0.5)
- if indent:
- self.table.attach(spacer, 1, 2, self.current_row,
- self.current_row + 1, 0, 0, 0, 0)
- self.table.attach(image, 2, 3, self.current_row,
- self.current_row + 1, gtk.FILL, gtk.FILL, 2, 0)
- status_label = gtk.Label()
- status_label.set_markup(str_status)
- status_label.set_alignment(0, 0)
- status_label.set_line_wrap(True)
- self.table.attach(status_label, 3, 4, self.current_row,
- self.current_row + 1, gtk.FILL | gtk.EXPAND, 0, 0, 0)
- if show_lock:
- lock_image = gtk.Image()
- lock_image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION,
- gtk.ICON_SIZE_MENU)
- self.table.attach(lock_image, 4, 5, self.current_row,
- self.current_row + 1, 0, 0, 0, 0)
+ """
+ Contains methods for creating status table. This is used in Roster and
+ NotificationArea tooltips
+ """
+
+ def __init__(self):
+ self.current_row = 1
+ self.table = None
+ self.text_label = None
+ self.spacer_label = ' '
+
+ def create_table(self):
+ self.table = gtk.Table(4, 1)
+ self.table.set_property('column-spacing', 2)
+
+ def add_text_row(self, text, col_inc = 0):
+ self.current_row += 1
+ self.text_label = gtk.Label()
+ self.text_label.set_line_wrap(True)
+ self.text_label.set_alignment(0, 0)
+ self.text_label.set_selectable(False)
+ self.text_label.set_markup(text)
+ self.table.attach(self.text_label, 1 + col_inc, 4, self.current_row,
+ self.current_row + 1)
+
+ def get_status_info(self, resource, priority, show, status):
+ str_status = resource + ' (' + unicode(priority) + ')'
+ if status:
+ status = status.strip()
+ if status != '':
+ # make sure 'status' is unicode before we send to to reduce_chars
+ if isinstance(status, str):
+ status = unicode(status, encoding='utf-8')
+ # reduce to 100 chars, 1 line
+ status = helpers.reduce_chars_newlines(status, 100, 1)
+ str_status = gobject.markup_escape_text(str_status)
+ status = gobject.markup_escape_text(status)
+ str_status += ' - <i>' + status + '</i>'
+ return str_status
+
+ def add_status_row(self, file_path, show, str_status, status_time=None,
+ show_lock=False, indent=True):
+ """
+ Append a new row with status icon to the table
+ """
+ self.current_row += 1
+ state_file = show.replace(' ', '_')
+ files = []
+ files.append(os.path.join(file_path, state_file + '.png'))
+ files.append(os.path.join(file_path, state_file + '.gif'))
+ image = gtk.Image()
+ image.set_from_pixbuf(None)
+ for f in files:
+ if os.path.exists(f):
+ image.set_from_file(f)
+ break
+ spacer = gtk.Label(self.spacer_label)
+ image.set_alignment(1, 0.5)
+ if indent:
+ self.table.attach(spacer, 1, 2, self.current_row,
+ self.current_row + 1, 0, 0, 0, 0)
+ self.table.attach(image, 2, 3, self.current_row,
+ self.current_row + 1, gtk.FILL, gtk.FILL, 2, 0)
+ status_label = gtk.Label()
+ status_label.set_markup(str_status)
+ status_label.set_alignment(0, 0)
+ status_label.set_line_wrap(True)
+ self.table.attach(status_label, 3, 4, self.current_row,
+ self.current_row + 1, gtk.FILL | gtk.EXPAND, 0, 0, 0)
+ if show_lock:
+ lock_image = gtk.Image()
+ lock_image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION,
+ gtk.ICON_SIZE_MENU)
+ self.table.attach(lock_image, 4, 5, self.current_row,
+ self.current_row + 1, 0, 0, 0, 0)
class NotificationAreaTooltip(BaseTooltip, StatusTable):
- """
- Tooltip that is shown in the notification area
- """
-
- def __init__(self):
- BaseTooltip.__init__(self)
- StatusTable.__init__(self)
-
- def fill_table_with_accounts(self, accounts):
- iconset = gajim.config.get('iconset')
- if not iconset:
- iconset = 'dcraven'
- file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for acct in accounts:
- message = acct['message']
- # before reducing the chars we should assure we send unicode, else
- # there are possible pango TBs on 'set_markup'
- if isinstance(message, str):
- message = unicode(message, encoding = 'utf-8')
- message = helpers.reduce_chars_newlines(message, 100, 1)
- message = gobject.markup_escape_text(message)
- if acct['name'] in gajim.con_types and \
- gajim.con_types[acct['name']] in ('tls', 'ssl'):
- show_lock = True
- else:
- show_lock = False
- if message:
- self.add_status_row(file_path, acct['show'],
- gobject.markup_escape_text(acct['name']) + \
- ' - ' + message, show_lock=show_lock, indent=False)
- else:
- self.add_status_row(file_path, acct['show'],
- gobject.markup_escape_text(acct['name'])
- , show_lock=show_lock, indent=False)
- for line in acct['event_lines']:
- self.add_text_row(' ' + line, 1)
-
- def populate(self, data=''):
- self.create_window()
- self.create_table()
-
- accounts = helpers.get_notification_icon_tooltip_dict()
- self.table.resize(2, 1)
- self.fill_table_with_accounts(accounts)
- self.hbox = gtk.HBox()
- self.table.set_property('column-spacing', 1)
-
- self.hbox.add(self.table)
- self.hbox.show_all()
+ """
+ Tooltip that is shown in the notification area
+ """
+
+ def __init__(self):
+ BaseTooltip.__init__(self)
+ StatusTable.__init__(self)
+
+ def fill_table_with_accounts(self, accounts):
+ iconset = gajim.config.get('iconset')
+ if not iconset:
+ iconset = 'dcraven'
+ file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+ for acct in accounts:
+ message = acct['message']
+ # before reducing the chars we should assure we send unicode, else
+ # there are possible pango TBs on 'set_markup'
+ if isinstance(message, str):
+ message = unicode(message, encoding = 'utf-8')
+ message = helpers.reduce_chars_newlines(message, 100, 1)
+ message = gobject.markup_escape_text(message)
+ if acct['name'] in gajim.con_types and \
+ gajim.con_types[acct['name']] in ('tls', 'ssl'):
+ show_lock = True
+ else:
+ show_lock = False
+ if message:
+ self.add_status_row(file_path, acct['show'],
+ gobject.markup_escape_text(acct['name']) + \
+ ' - ' + message, show_lock=show_lock, indent=False)
+ else:
+ self.add_status_row(file_path, acct['show'],
+ gobject.markup_escape_text(acct['name'])
+ , show_lock=show_lock, indent=False)
+ for line in acct['event_lines']:
+ self.add_text_row(' ' + line, 1)
+
+ def populate(self, data=''):
+ self.create_window()
+ self.create_table()
+
+ accounts = helpers.get_notification_icon_tooltip_dict()
+ self.table.resize(2, 1)
+ self.fill_table_with_accounts(accounts)
+ self.hbox = gtk.HBox()
+ self.table.set_property('column-spacing', 1)
+
+ self.hbox.add(self.table)
+ self.hbox.show_all()
class GCTooltip(BaseTooltip):
- """
- Tooltip that is shown in the GC treeview
- """
-
- def __init__(self):
- self.account = None
- self.text_label = gtk.Label()
- self.text_label.set_line_wrap(True)
- self.text_label.set_alignment(0, 0)
- self.text_label.set_selectable(False)
- self.avatar_image = gtk.Image()
-
- BaseTooltip.__init__(self)
-
- def populate(self, contact):
- if not contact:
- return
- self.create_window()
- vcard_table = gtk.Table(3, 1)
- vcard_table.set_property('column-spacing', 2)
- vcard_table.set_homogeneous(False)
- vcard_current_row = 1
- properties = []
-
- nick_markup = '<b>' + \
- gobject.markup_escape_text(contact.get_shown_name()) \
- + '</b>'
- properties.append((nick_markup, None))
-
- if contact.status: # status message
- status = contact.status.strip()
- if status != '':
- # escape markup entities
- status = helpers.reduce_chars_newlines(status, 300, 5)
- status = '<i>' +\
- gobject.markup_escape_text(status) + '</i>'
- properties.append((status, None))
- else: # no status message, show SHOW instead
- show = helpers.get_uf_show(contact.show)
- show = '<i>' + show + '</i>'
- properties.append((show, None))
-
- if contact.jid.strip() != '':
- properties.append((_('Jabber ID: '), contact.jid))
-
- if hasattr(contact, 'resource') and contact.resource.strip() != '':
- properties.append((_('Resource: '),
- gobject.markup_escape_text(contact.resource) ))
- if contact.affiliation != 'none':
- uf_affiliation = helpers.get_uf_affiliation(contact.affiliation)
- affiliation_str = \
- _('%(owner_or_admin_or_member)s of this group chat') %\
- {'owner_or_admin_or_member': uf_affiliation}
- properties.append((affiliation_str, None))
-
- # Add avatar
- puny_name = helpers.sanitize_filename(contact.name)
- puny_room = helpers.sanitize_filename(contact.room_jid)
- file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_room,
- puny_name))
- if file_:
- self.avatar_image.set_from_file(file_)
- pix = self.avatar_image.get_pixbuf()
- pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip')
- self.avatar_image.set_from_pixbuf(pix)
- else:
- self.avatar_image.set_from_pixbuf(None)
- while properties:
- property_ = properties.pop(0)
- vcard_current_row += 1
- vertical_fill = gtk.FILL
- if not properties:
- vertical_fill |= gtk.EXPAND
- label = gtk.Label()
- label.set_alignment(0, 0)
- if property_[1]:
- label.set_markup(property_[0])
- vcard_table.attach(label, 1, 2, vcard_current_row,
- vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0)
- label = gtk.Label()
- label.set_alignment(0, 0)
- label.set_markup(property_[1])
- label.set_line_wrap(True)
- vcard_table.attach(label, 2, 3, vcard_current_row,
- vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
- vertical_fill, 0, 0)
- else:
- label.set_markup(property_[0])
- label.set_line_wrap(True)
- vcard_table.attach(label, 1, 3, vcard_current_row,
- vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
-
- self.avatar_image.set_alignment(0, 0)
- vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1,
- gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
- self.win.add(vcard_table)
+ """
+ Tooltip that is shown in the GC treeview
+ """
+
+ def __init__(self):
+ self.account = None
+ self.text_label = gtk.Label()
+ self.text_label.set_line_wrap(True)
+ self.text_label.set_alignment(0, 0)
+ self.text_label.set_selectable(False)
+ self.avatar_image = gtk.Image()
+
+ BaseTooltip.__init__(self)
+
+ def populate(self, contact):
+ if not contact:
+ return
+ self.create_window()
+ vcard_table = gtk.Table(3, 1)
+ vcard_table.set_property('column-spacing', 2)
+ vcard_table.set_homogeneous(False)
+ vcard_current_row = 1
+ properties = []
+
+ nick_markup = '<b>' + \
+ gobject.markup_escape_text(contact.get_shown_name()) \
+ + '</b>'
+ properties.append((nick_markup, None))
+
+ if contact.status: # status message
+ status = contact.status.strip()
+ if status != '':
+ # escape markup entities
+ status = helpers.reduce_chars_newlines(status, 300, 5)
+ status = '<i>' +\
+ gobject.markup_escape_text(status) + '</i>'
+ properties.append((status, None))
+ else: # no status message, show SHOW instead
+ show = helpers.get_uf_show(contact.show)
+ show = '<i>' + show + '</i>'
+ properties.append((show, None))
+
+ if contact.jid.strip() != '':
+ properties.append((_('Jabber ID: '), contact.jid))
+
+ if hasattr(contact, 'resource') and contact.resource.strip() != '':
+ properties.append((_('Resource: '),
+ gobject.markup_escape_text(contact.resource) ))
+ if contact.affiliation != 'none':
+ uf_affiliation = helpers.get_uf_affiliation(contact.affiliation)
+ affiliation_str = \
+ _('%(owner_or_admin_or_member)s of this group chat') %\
+ {'owner_or_admin_or_member': uf_affiliation}
+ properties.append((affiliation_str, None))
+
+ # Add avatar
+ puny_name = helpers.sanitize_filename(contact.name)
+ puny_room = helpers.sanitize_filename(contact.room_jid)
+ file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_room,
+ puny_name))
+ if file_:
+ self.avatar_image.set_from_file(file_)
+ pix = self.avatar_image.get_pixbuf()
+ pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip')
+ self.avatar_image.set_from_pixbuf(pix)
+ else:
+ self.avatar_image.set_from_pixbuf(None)
+ while properties:
+ property_ = properties.pop(0)
+ vcard_current_row += 1
+ vertical_fill = gtk.FILL
+ if not properties:
+ vertical_fill |= gtk.EXPAND
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ if property_[1]:
+ label.set_markup(property_[0])
+ vcard_table.attach(label, 1, 2, vcard_current_row,
+ vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0)
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ label.set_markup(property_[1])
+ label.set_line_wrap(True)
+ vcard_table.attach(label, 2, 3, vcard_current_row,
+ vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
+ vertical_fill, 0, 0)
+ else:
+ label.set_markup(property_[0])
+ label.set_line_wrap(True)
+ vcard_table.attach(label, 1, 3, vcard_current_row,
+ vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
+
+ self.avatar_image.set_alignment(0, 0)
+ vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1,
+ gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
+ self.win.add(vcard_table)
class RosterTooltip(NotificationAreaTooltip):
- """
- Tooltip that is shown in the roster treeview
- """
-
- def __init__(self):
- self.account = None
- self.image = gtk.Image()
- self.image.set_alignment(0, 0)
- # padding is independent of the total length and better than alignment
- self.image.set_padding(1, 2)
- self.avatar_image = gtk.Image()
- NotificationAreaTooltip.__init__(self)
-
- def populate(self, contacts):
- self.create_window()
-
- self.create_table()
- if not contacts or len(contacts) == 0:
- # Tooltip for merged accounts row
- accounts = helpers.get_notification_icon_tooltip_dict()
- self.table.resize(2, 1)
- self.spacer_label = ''
- self.fill_table_with_accounts(accounts)
- self.win.add(self.table)
- return
-
- # primary contact
- prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts(
- contacts)
-
- puny_jid = helpers.sanitize_filename(prim_contact.jid)
- table_size = 3
-
- file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_jid))
- if file_:
- self.avatar_image.set_from_file(file_)
- pix = self.avatar_image.get_pixbuf()
- pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip')
- self.avatar_image.set_from_pixbuf(pix)
- table_size = 4
- else:
- self.avatar_image.set_from_pixbuf(None)
- vcard_table = gtk.Table(table_size, 1)
- vcard_table.set_property('column-spacing', 2)
- vcard_table.set_homogeneous(False)
- vcard_current_row = 1
- properties = []
-
- name_markup = u'<span weight="bold">' + \
- gobject.markup_escape_text(prim_contact.get_shown_name())\
- + '</span>'
- if self.account and helpers.jid_is_blocked(self.account,
- prim_contact.jid):
- name_markup += _(' [blocked]')
- if self.account and \
- self.account in gajim.interface.minimized_controls and \
- prim_contact.jid in gajim.interface.minimized_controls[self.account]:
- name_markup += _(' [minimized]')
- properties.append((name_markup, None))
-
- num_resources = 0
- # put contacts in dict, where key is priority
- contacts_dict = {}
- for contact in contacts:
- if contact.resource:
- num_resources += 1
- if contact.priority in contacts_dict:
- contacts_dict[contact.priority].append(contact)
- else:
- contacts_dict[contact.priority] = [contact]
-
- if num_resources > 1:
- properties.append((_('Status: '), ' '))
- transport = gajim.get_transport_name_from_jid(
- prim_contact.jid)
- if transport:
- file_path = os.path.join(helpers.get_transport_path(transport),
- '16x16')
- else:
- iconset = gajim.config.get('iconset')
- if not iconset:
- iconset = 'dcraven'
- file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
-
- contact_keys = sorted(contacts_dict.keys())
- contact_keys.reverse()
- for priority in contact_keys:
- for acontact in contacts_dict[priority]:
- status_line = self.get_status_info(acontact.resource,
- acontact.priority, acontact.show, acontact.status)
-
- icon_name = self._get_icon_name_for_tooltip(acontact)
- self.add_status_row(file_path, icon_name, status_line,
- acontact.last_status_time)
- properties.append((self.table, None))
-
- else: # only one resource
- if contact.show:
- show = helpers.get_uf_show(contact.show)
- if not self.check_last_time and self.account:
- if contact.show == 'offline':
- if not contact.last_status_time:
- gajim.connections[self.account].request_last_status_time(
- contact.jid, '')
- else:
- self.check_last_time = contact.last_status_time
- elif contact.resource:
- gajim.connections[self.account].request_last_status_time(
- contact.jid, contact.resource)
- if contact.last_activity_time:
- self.check_last_time = contact.last_activity_time
- else:
- self.check_last_time = None
- if contact.last_status_time:
- vcard_current_row += 1
- if contact.show == 'offline':
- text = ' - ' + _('Last status: %s')
- else:
- text = _(' since %s')
-
- if time.strftime('%j', time.localtime())== \
- time.strftime('%j', contact.last_status_time):
- # it's today, show only the locale hour representation
- local_time = time.strftime('%X',
- contact.last_status_time)
- else:
- # time.strftime returns locale encoded string
- local_time = time.strftime('%c',
- contact.last_status_time)
- local_time = local_time.decode(
- locale.getpreferredencoding())
- text = text % local_time
- show += text
- if self.account and \
- prim_contact.jid in gajim.gc_connected[self.account]:
- if gajim.gc_connected[self.account][prim_contact.jid]:
- show = _('Connected')
- else:
- show = _('Disconnected')
- show = '<i>' + show + '</i>'
- # we append show below
-
- if contact.status:
- status = contact.status.strip()
- if status:
- # reduce long status
- # (no more than 300 chars on line and no more than 5 lines)
- # status is wrapped
- status = helpers.reduce_chars_newlines(status, 300, 5)
- # escape markup entities.
- status = gobject.markup_escape_text(status)
- properties.append(('<i>%s</i>' % status, None))
- properties.append((show, None))
-
- self._append_pep_info(contact, properties)
-
- properties.append((_('Jabber ID: '), prim_contact.jid ))
-
- # contact has only one ressource
- if num_resources == 1 and contact.resource:
- properties.append((_('Resource: '),
- gobject.markup_escape_text(contact.resource) +\
- ' (' + unicode(contact.priority) + ')'))
-
- if self.account and prim_contact.sub and prim_contact.sub != 'both' and\
- prim_contact.jid not in gajim.gc_connected[self.account]:
- # ('both' is the normal sub so we don't show it)
- properties.append(( _('Subscription: '),
- gobject.markup_escape_text(helpers.get_uf_sub(prim_contact.sub))))
-
- if prim_contact.keyID:
- keyID = None
- if len(prim_contact.keyID) == 8:
- keyID = prim_contact.keyID
- elif len(prim_contact.keyID) == 16:
- keyID = prim_contact.keyID[8:]
- if keyID:
- properties.append((_('OpenPGP: '),
- gobject.markup_escape_text(keyID)))
-
- if contact.last_activity_time:
- text = _(' since %s')
-
- if time.strftime('%j', time.localtime())== \
- time.strftime('%j', contact.last_activity_time):
- # it's today, show only the locale hour representation
- local_time = time.strftime('%I:%M %p',
- contact.last_activity_time)
- else:
- # time.strftime returns locale encoded string
- local_time = time.strftime('%c',
- contact.last_activity_time)
- local_time = local_time.decode(
- locale.getpreferredencoding())
- text = text % local_time
- properties.append(('Idle' + text,None))
-
- while properties:
- property_ = properties.pop(0)
- vcard_current_row += 1
- vertical_fill = gtk.FILL
- if not properties and table_size == 4:
- vertical_fill |= gtk.EXPAND
- label = gtk.Label()
- label.set_alignment(0, 0)
- if property_[1]:
- label.set_markup(property_[0])
- vcard_table.attach(label, 1, 2, vcard_current_row,
- vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0)
- label = gtk.Label()
- label.set_alignment(0, 0)
- label.set_markup(property_[1])
- label.set_line_wrap(True)
- vcard_table.attach(label, 2, 3, vcard_current_row,
- vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
- vertical_fill, 0, 0)
- else:
- if isinstance(property_[0], (unicode, str)): # FIXME: rm unicode?
- label.set_markup(property_[0])
- label.set_line_wrap(True)
- else:
- label = property_[0]
- vcard_table.attach(label, 1, 3, vcard_current_row,
- vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
- self.avatar_image.set_alignment(0, 0)
- if table_size == 4:
- vcard_table.attach(self.avatar_image, 3, 4, 2,
- vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
- self.win.add(vcard_table)
-
- def update_last_time(self, last_time):
- if not self.check_last_time or time.strftime('%x %I:%M %p', last_time) !=\
- time.strftime('%x %I:%M %p', self.check_last_time):
- self.win.destroy()
- self.win = None
- self.populate(self.cur_data)
- self.win.ensure_style()
- self.win.show_all()
-
- def _append_pep_info(self, contact, properties):
- """
- Append Tune, Mood, Activity, Location information of the specified contact
- to the given property list.
- """
- if 'mood' in contact.pep:
- mood = contact.pep['mood'].asMarkupText()
- mood_string = _('Mood:') + ' %s' % mood
- properties.append((mood_string, None))
-
- if 'activity' in contact.pep:
- activity = contact.pep['activity'].asMarkupText()
- activity_string = _('Activity:') + ' %s' % activity
- properties.append((activity_string, None))
-
- if 'tune' in contact.pep:
- tune = contact.pep['tune'].asMarkupText()
- tune_string = _('Tune:') + ' %s' % tune
- properties.append((tune_string, None))
-
- if 'location' in contact.pep:
- location = contact.pep['location'].asMarkupText()
- location_string = _('Location:') + ' %s' % location
- properties.append((location_string, None))
+ """
+ Tooltip that is shown in the roster treeview
+ """
+
+ def __init__(self):
+ self.account = None
+ self.image = gtk.Image()
+ self.image.set_alignment(0, 0)
+ # padding is independent of the total length and better than alignment
+ self.image.set_padding(1, 2)
+ self.avatar_image = gtk.Image()
+ NotificationAreaTooltip.__init__(self)
+
+ def populate(self, contacts):
+ self.create_window()
+
+ self.create_table()
+ if not contacts or len(contacts) == 0:
+ # Tooltip for merged accounts row
+ accounts = helpers.get_notification_icon_tooltip_dict()
+ self.table.resize(2, 1)
+ self.spacer_label = ''
+ self.fill_table_with_accounts(accounts)
+ self.win.add(self.table)
+ return
+
+ # primary contact
+ prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts(
+ contacts)
+
+ puny_jid = helpers.sanitize_filename(prim_contact.jid)
+ table_size = 3
+
+ file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH, puny_jid))
+ if file_:
+ self.avatar_image.set_from_file(file_)
+ pix = self.avatar_image.get_pixbuf()
+ pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip')
+ self.avatar_image.set_from_pixbuf(pix)
+ table_size = 4
+ else:
+ self.avatar_image.set_from_pixbuf(None)
+ vcard_table = gtk.Table(table_size, 1)
+ vcard_table.set_property('column-spacing', 2)
+ vcard_table.set_homogeneous(False)
+ vcard_current_row = 1
+ properties = []
+
+ name_markup = u'<span weight="bold">' + \
+ gobject.markup_escape_text(prim_contact.get_shown_name())\
+ + '</span>'
+ if self.account and helpers.jid_is_blocked(self.account,
+ prim_contact.jid):
+ name_markup += _(' [blocked]')
+ if self.account and \
+ self.account in gajim.interface.minimized_controls and \
+ prim_contact.jid in gajim.interface.minimized_controls[self.account]:
+ name_markup += _(' [minimized]')
+ properties.append((name_markup, None))
+
+ num_resources = 0
+ # put contacts in dict, where key is priority
+ contacts_dict = {}
+ for contact in contacts:
+ if contact.resource:
+ num_resources += 1
+ if contact.priority in contacts_dict:
+ contacts_dict[contact.priority].append(contact)
+ else:
+ contacts_dict[contact.priority] = [contact]
+
+ if num_resources > 1:
+ properties.append((_('Status: '), ' '))
+ transport = gajim.get_transport_name_from_jid(
+ prim_contact.jid)
+ if transport:
+ file_path = os.path.join(helpers.get_transport_path(transport),
+ '16x16')
+ else:
+ iconset = gajim.config.get('iconset')
+ if not iconset:
+ iconset = 'dcraven'
+ file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
+
+ contact_keys = sorted(contacts_dict.keys())
+ contact_keys.reverse()
+ for priority in contact_keys:
+ for acontact in contacts_dict[priority]:
+ status_line = self.get_status_info(acontact.resource,
+ acontact.priority, acontact.show, acontact.status)
+
+ icon_name = self._get_icon_name_for_tooltip(acontact)
+ self.add_status_row(file_path, icon_name, status_line,
+ acontact.last_status_time)
+ properties.append((self.table, None))
+
+ else: # only one resource
+ if contact.show:
+ show = helpers.get_uf_show(contact.show)
+ if not self.check_last_time and self.account:
+ if contact.show == 'offline':
+ if not contact.last_status_time:
+ gajim.connections[self.account].request_last_status_time(
+ contact.jid, '')
+ else:
+ self.check_last_time = contact.last_status_time
+ elif contact.resource:
+ gajim.connections[self.account].request_last_status_time(
+ contact.jid, contact.resource)
+ if contact.last_activity_time:
+ self.check_last_time = contact.last_activity_time
+ else:
+ self.check_last_time = None
+ if contact.last_status_time:
+ vcard_current_row += 1
+ if contact.show == 'offline':
+ text = ' - ' + _('Last status: %s')
+ else:
+ text = _(' since %s')
+
+ if time.strftime('%j', time.localtime())== \
+ time.strftime('%j', contact.last_status_time):
+ # it's today, show only the locale hour representation
+ local_time = time.strftime('%X',
+ contact.last_status_time)
+ else:
+ # time.strftime returns locale encoded string
+ local_time = time.strftime('%c',
+ contact.last_status_time)
+ local_time = local_time.decode(
+ locale.getpreferredencoding())
+ text = text % local_time
+ show += text
+ if self.account and \
+ prim_contact.jid in gajim.gc_connected[self.account]:
+ if gajim.gc_connected[self.account][prim_contact.jid]:
+ show = _('Connected')
+ else:
+ show = _('Disconnected')
+ show = '<i>' + show + '</i>'
+ # we append show below
+
+ if contact.status:
+ status = contact.status.strip()
+ if status:
+ # reduce long status
+ # (no more than 300 chars on line and no more than 5 lines)
+ # status is wrapped
+ status = helpers.reduce_chars_newlines(status, 300, 5)
+ # escape markup entities.
+ status = gobject.markup_escape_text(status)
+ properties.append(('<i>%s</i>' % status, None))
+ properties.append((show, None))
+
+ self._append_pep_info(contact, properties)
+
+ properties.append((_('Jabber ID: '), prim_contact.jid ))
+
+ # contact has only one ressource
+ if num_resources == 1 and contact.resource:
+ properties.append((_('Resource: '),
+ gobject.markup_escape_text(contact.resource) +\
+ ' (' + unicode(contact.priority) + ')'))
+
+ if self.account and prim_contact.sub and prim_contact.sub != 'both' and\
+ prim_contact.jid not in gajim.gc_connected[self.account]:
+ # ('both' is the normal sub so we don't show it)
+ properties.append(( _('Subscription: '),
+ gobject.markup_escape_text(helpers.get_uf_sub(prim_contact.sub))))
+
+ if prim_contact.keyID:
+ keyID = None
+ if len(prim_contact.keyID) == 8:
+ keyID = prim_contact.keyID
+ elif len(prim_contact.keyID) == 16:
+ keyID = prim_contact.keyID[8:]
+ if keyID:
+ properties.append((_('OpenPGP: '),
+ gobject.markup_escape_text(keyID)))
+
+ if contact.last_activity_time:
+ text = _(' since %s')
+
+ if time.strftime('%j', time.localtime())== \
+ time.strftime('%j', contact.last_activity_time):
+ # it's today, show only the locale hour representation
+ local_time = time.strftime('%I:%M %p',
+ contact.last_activity_time)
+ else:
+ # time.strftime returns locale encoded string
+ local_time = time.strftime('%c',
+ contact.last_activity_time)
+ local_time = local_time.decode(
+ locale.getpreferredencoding())
+ text = text % local_time
+ properties.append(('Idle' + text,None))
+
+ while properties:
+ property_ = properties.pop(0)
+ vcard_current_row += 1
+ vertical_fill = gtk.FILL
+ if not properties and table_size == 4:
+ vertical_fill |= gtk.EXPAND
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ if property_[1]:
+ label.set_markup(property_[0])
+ vcard_table.attach(label, 1, 2, vcard_current_row,
+ vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0)
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ label.set_markup(property_[1])
+ label.set_line_wrap(True)
+ vcard_table.attach(label, 2, 3, vcard_current_row,
+ vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
+ vertical_fill, 0, 0)
+ else:
+ if isinstance(property_[0], (unicode, str)): # FIXME: rm unicode?
+ label.set_markup(property_[0])
+ label.set_line_wrap(True)
+ else:
+ label = property_[0]
+ vcard_table.attach(label, 1, 3, vcard_current_row,
+ vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
+ self.avatar_image.set_alignment(0, 0)
+ if table_size == 4:
+ vcard_table.attach(self.avatar_image, 3, 4, 2,
+ vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
+ self.win.add(vcard_table)
+
+ def update_last_time(self, last_time):
+ if not self.check_last_time or time.strftime('%x %I:%M %p', last_time) !=\
+ time.strftime('%x %I:%M %p', self.check_last_time):
+ self.win.destroy()
+ self.win = None
+ self.populate(self.cur_data)
+ self.win.ensure_style()
+ self.win.show_all()
+
+ def _append_pep_info(self, contact, properties):
+ """
+ Append Tune, Mood, Activity, Location information of the specified contact
+ to the given property list.
+ """
+ if 'mood' in contact.pep:
+ mood = contact.pep['mood'].asMarkupText()
+ mood_string = _('Mood:') + ' %s' % mood
+ properties.append((mood_string, None))
+
+ if 'activity' in contact.pep:
+ activity = contact.pep['activity'].asMarkupText()
+ activity_string = _('Activity:') + ' %s' % activity
+ properties.append((activity_string, None))
+
+ if 'tune' in contact.pep:
+ tune = contact.pep['tune'].asMarkupText()
+ tune_string = _('Tune:') + ' %s' % tune
+ properties.append((tune_string, None))
+
+ if 'location' in contact.pep:
+ location = contact.pep['location'].asMarkupText()
+ location_string = _('Location:') + ' %s' % location
+ properties.append((location_string, None))
class FileTransfersTooltip(BaseTooltip):
- """
- Tooltip that is shown in the notification area
- """
-
- def __init__(self):
- BaseTooltip.__init__(self)
-
- def populate(self, file_props):
- ft_table = gtk.Table(2, 1)
- ft_table.set_property('column-spacing', 2)
- current_row = 1
- self.create_window()
- properties = []
- name = file_props['name']
- if file_props['type'] == 'r':
- file_name = os.path.split(file_props['file-name'])[1]
- else:
- file_name = file_props['name']
- properties.append((_('Name: '),
- gobject.markup_escape_text(file_name)))
- if file_props['type'] == 'r':
- type_ = _('Download')
- actor = _('Sender: ')
- sender = unicode(file_props['sender']).split('/')[0]
- name = gajim.contacts.get_first_contact_from_jid(
- file_props['tt_account'], sender).get_shown_name()
- else:
- type_ = _('Upload')
- actor = _('Recipient: ')
- receiver = file_props['receiver']
- if hasattr(receiver, 'name'):
- name = receiver.get_shown_name()
- else:
- name = receiver.split('/')[0]
- properties.append((_('Type: '), type_))
- properties.append((actor, gobject.markup_escape_text(name)))
-
- transfered_len = file_props.get('received-len', 0)
- properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len)))
- status = ''
- if 'started' not in file_props or not file_props['started']:
- status = _('Not started')
- elif 'connected' in file_props:
- if 'stopped' in file_props and \
- file_props['stopped'] == True:
- status = _('Stopped')
- elif file_props['completed']:
- status = _('Completed')
- elif file_props['connected'] == False:
- if file_props['completed']:
- status = _('Completed')
- else:
- if 'paused' in file_props and \
- file_props['paused'] == True:
- status = _('?transfer status:Paused')
- elif 'stalled' in file_props and \
- file_props['stalled'] == True:
- #stalled is not paused. it is like 'frozen' it stopped alone
- status = _('Stalled')
- else:
- status = _('Transferring')
- else:
- status = _('Not started')
- properties.append((_('Status: '), status))
- if 'desc' in file_props:
- file_desc = file_props['desc']
- properties.append((_('Description: '), gobject.markup_escape_text(
- file_desc)))
- while properties:
- property_ = properties.pop(0)
- current_row += 1
- label = gtk.Label()
- label.set_alignment(0, 0)
- label.set_markup(property_[0])
- ft_table.attach(label, 1, 2, current_row, current_row + 1,
- gtk.FILL, gtk.FILL, 0, 0)
- label = gtk.Label()
- label.set_alignment(0, 0)
- label.set_line_wrap(True)
- label.set_markup(property_[1])
- ft_table.attach(label, 2, 3, current_row, current_row + 1,
- gtk.EXPAND | gtk.FILL, gtk.FILL, 0, 0)
-
- self.win.add(ft_table)
+ """
+ Tooltip that is shown in the notification area
+ """
+
+ def __init__(self):
+ BaseTooltip.__init__(self)
+
+ def populate(self, file_props):
+ ft_table = gtk.Table(2, 1)
+ ft_table.set_property('column-spacing', 2)
+ current_row = 1
+ self.create_window()
+ properties = []
+ name = file_props['name']
+ if file_props['type'] == 'r':
+ file_name = os.path.split(file_props['file-name'])[1]
+ else:
+ file_name = file_props['name']
+ properties.append((_('Name: '),
+ gobject.markup_escape_text(file_name)))
+ if file_props['type'] == 'r':
+ type_ = _('Download')
+ actor = _('Sender: ')
+ sender = unicode(file_props['sender']).split('/')[0]
+ name = gajim.contacts.get_first_contact_from_jid(
+ file_props['tt_account'], sender).get_shown_name()
+ else:
+ type_ = _('Upload')
+ actor = _('Recipient: ')
+ receiver = file_props['receiver']
+ if hasattr(receiver, 'name'):
+ name = receiver.get_shown_name()
+ else:
+ name = receiver.split('/')[0]
+ properties.append((_('Type: '), type_))
+ properties.append((actor, gobject.markup_escape_text(name)))
+
+ transfered_len = file_props.get('received-len', 0)
+ properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len)))
+ status = ''
+ if 'started' not in file_props or not file_props['started']:
+ status = _('Not started')
+ elif 'connected' in file_props:
+ if 'stopped' in file_props and \
+ file_props['stopped'] == True:
+ status = _('Stopped')
+ elif file_props['completed']:
+ status = _('Completed')
+ elif file_props['connected'] == False:
+ if file_props['completed']:
+ status = _('Completed')
+ else:
+ if 'paused' in file_props and \
+ file_props['paused'] == True:
+ status = _('?transfer status:Paused')
+ elif 'stalled' in file_props and \
+ file_props['stalled'] == True:
+ #stalled is not paused. it is like 'frozen' it stopped alone
+ status = _('Stalled')
+ else:
+ status = _('Transferring')
+ else:
+ status = _('Not started')
+ properties.append((_('Status: '), status))
+ if 'desc' in file_props:
+ file_desc = file_props['desc']
+ properties.append((_('Description: '), gobject.markup_escape_text(
+ file_desc)))
+ while properties:
+ property_ = properties.pop(0)
+ current_row += 1
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ label.set_markup(property_[0])
+ ft_table.attach(label, 1, 2, current_row, current_row + 1,
+ gtk.FILL, gtk.FILL, 0, 0)
+ label = gtk.Label()
+ label.set_alignment(0, 0)
+ label.set_line_wrap(True)
+ label.set_markup(property_[1])
+ ft_table.attach(label, 2, 3, current_row, current_row + 1,
+ gtk.EXPAND | gtk.FILL, gtk.FILL, 0, 0)
+
+ self.win.add(ft_table)
class ServiceDiscoveryTooltip(BaseTooltip):
- """
- Tooltip that is shown when hovering over a service discovery row
- """
- def populate(self, status):
- self.create_window()
- label = gtk.Label()
- label.set_line_wrap(True)
- label.set_alignment(0, 0)
- label.set_selectable(False)
- if status == 1:
- label.set_text(
- _('This service has not yet responded with detailed information'))
- elif status == 2:
- label.set_text(
- _('This service could not respond with detailed information.\n'
- 'It is most likely legacy or broken'))
- self.win.add(label)
-
-# vim: se ts=3:
+ """
+ Tooltip that is shown when hovering over a service discovery row
+ """
+ def populate(self, status):
+ self.create_window()
+ label = gtk.Label()
+ label.set_line_wrap(True)
+ label.set_alignment(0, 0)
+ label.set_selectable(False)
+ if status == 1:
+ label.set_text(
+ _('This service has not yet responded with detailed information'))
+ elif status == 2:
+ label.set_text(
+ _('This service could not respond with detailed information.\n'
+ 'It is most likely legacy or broken'))
+ self.win.add(label)
diff --git a/src/vcard.py b/src/vcard.py
index a1605b91c..2f30f52a6 100644
--- a/src/vcard.py
+++ b/src/vcard.py
@@ -45,520 +45,518 @@ from common import gajim
from common.i18n import Q_
def get_avatar_pixbuf_encoded_mime(photo):
- """
- Return the pixbuf of the image
-
- Photo is a dictionary containing PHOTO information.
- """
- if not isinstance(photo, dict):
- return None, None, None
- img_decoded = None
- avatar_encoded = None
- avatar_mime_type = None
- if 'BINVAL' in photo:
- img_encoded = photo['BINVAL']
- avatar_encoded = img_encoded
- try:
- img_decoded = base64.decodestring(img_encoded)
- except Exception:
- pass
- if img_decoded:
- if 'TYPE' in photo:
- avatar_mime_type = photo['TYPE']
- pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
- else:
- pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data(
- img_decoded, want_type=True)
- else:
- pixbuf = None
- return pixbuf, avatar_encoded, avatar_mime_type
+ """
+ Return the pixbuf of the image
+
+ Photo is a dictionary containing PHOTO information.
+ """
+ if not isinstance(photo, dict):
+ return None, None, None
+ img_decoded = None
+ avatar_encoded = None
+ avatar_mime_type = None
+ if 'BINVAL' in photo:
+ img_encoded = photo['BINVAL']
+ avatar_encoded = img_encoded
+ try:
+ img_decoded = base64.decodestring(img_encoded)
+ except Exception:
+ pass
+ if img_decoded:
+ if 'TYPE' in photo:
+ avatar_mime_type = photo['TYPE']
+ pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
+ else:
+ pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data(
+ img_decoded, want_type=True)
+ else:
+ pixbuf = None
+ return pixbuf, avatar_encoded, avatar_mime_type
class VcardWindow:
- """
- Class for contact's information window
- """
-
- def __init__(self, contact, account, gc_contact = None):
- # the contact variable is the jid if vcard is true
- self.xml = gtkgui_helpers.get_gtk_builder('vcard_information_window.ui')
- self.window = self.xml.get_object('vcard_information_window')
- self.progressbar = self.xml.get_object('progressbar')
-
- self.contact = contact
- self.account = account
- self.gc_contact = gc_contact
-
- # Get real jid
- if gc_contact:
- # Don't use real jid if room is (semi-)anonymous
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(
- gc_contact.room_jid, account)
- if gc_contact.jid and not gc_control.is_anonymous:
- self.real_jid = gc_contact.jid
- self.real_jid_for_vcard = gc_contact.jid
- if gc_contact.resource:
- self.real_jid += '/' + gc_contact.resource
- else:
- self.real_jid = gc_contact.get_full_jid()
- self.real_jid_for_vcard = self.real_jid
- self.real_resource = gc_contact.name
- else:
- self.real_jid = contact.get_full_jid()
- self.real_resource = contact.resource
-
- puny_jid = helpers.sanitize_filename(contact.jid)
- local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
- '_local'
- for extension in ('.png', '.jpeg'):
- local_avatar_path = local_avatar_basepath + extension
- if os.path.isfile(local_avatar_path):
- image = self.xml.get_object('custom_avatar_image')
- image.set_from_file(local_avatar_path)
- image.show()
- self.xml.get_object('custom_avatar_label').show()
- break
- self.avatar_mime_type = None
- self.avatar_encoded = None
- self.vcard_arrived = False
- self.os_info_arrived = False
- self.entity_time_arrived = False
- self.update_progressbar_timeout_id = gobject.timeout_add(100,
- self.update_progressbar)
-
- self.fill_jabber_page()
- annotations = gajim.connections[self.account].annotations
- if self.contact.jid in annotations:
- buffer_ = self.xml.get_object('textview_annotation').get_buffer()
- buffer_.set_text(annotations[self.contact.jid])
-
- self.xml.connect_signals(self)
- self.window.show_all()
- self.xml.get_object('close_button').grab_focus()
-
- def update_progressbar(self):
- self.progressbar.pulse()
- return True # loop forever
-
- def on_vcard_information_window_destroy(self, widget):
- if self.update_progressbar_timeout_id is not None:
- gobject.source_remove(self.update_progressbar_timeout_id)
- del gajim.interface.instances[self.account]['infos'][self.contact.jid]
- buffer_ = self.xml.get_object('textview_annotation').get_buffer()
- annotation = buffer_.get_text(buffer_.get_start_iter(),
- buffer_.get_end_iter())
- connection = gajim.connections[self.account]
- if annotation != connection.annotations.get(self.contact.jid, ''):
- connection.annotations[self.contact.jid] = annotation
- connection.store_annotations()
-
- def on_vcard_information_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.window.destroy()
-
- def on_PHOTO_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- menu = gtk.Menu()
- menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
- menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.contact.jid, self.account, self.contact.get_shown_name())
- menu.append(menuitem)
- menu.connect('selection-done', lambda w:w.destroy())
- # show the menu
- menu.show_all()
- menu.popup(None, None, None, event.button, event.time)
-
- def set_value(self, entry_name, value):
- try:
- if value and entry_name == 'URL_label':
- widget = gtk.LinkButton(value, value)
- widget.set_alignment(0, 0)
- widget.show()
- table = self.xml.get_object('personal_info_table')
- table.attach(widget, 1, 4, 3, 4, yoptions = 0)
- else:
- self.xml.get_object(entry_name).set_text(value)
- except AttributeError:
- pass
-
- def set_values(self, vcard):
- for i in vcard.keys():
- if i == 'PHOTO' and self.xml.get_object('information_notebook').\
- get_n_pages() > 4:
- pixbuf, self.avatar_encoded, self.avatar_mime_type = \
- get_avatar_pixbuf_encoded_mime(vcard[i])
- image = self.xml.get_object('PHOTO_image')
- image.show()
- self.xml.get_object('user_avatar_label').show()
- if not pixbuf:
- image.set_from_icon_name('stock_person',
- gtk.ICON_SIZE_DIALOG)
- continue
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
- image.set_from_pixbuf(pixbuf)
- continue
- if i in ('ADR', 'TEL', 'EMAIL'):
- for entry in vcard[i]:
- add_on = '_HOME'
- if 'WORK' in entry:
- add_on = '_WORK'
- for j in entry.keys():
- self.set_value(i + add_on + '_' + j + '_label', entry[j])
- if isinstance(vcard[i], dict):
- for j in vcard[i].keys():
- self.set_value(i + '_' + j + '_label', vcard[i][j])
- else:
- if i == 'DESC':
- self.xml.get_object('DESC_textview').get_buffer().set_text(
- vcard[i], 0)
- elif i != 'jid': # Do not override jid_label
- self.set_value(i + '_label', vcard[i])
- self.vcard_arrived = True
- self.test_remove_progressbar()
-
- def test_remove_progressbar(self):
- if self.update_progressbar_timeout_id is not None and \
- self.vcard_arrived and self.os_info_arrived and self.entity_time_arrived:
- gobject.source_remove(self.update_progressbar_timeout_id)
- self.progressbar.hide()
- self.update_progressbar_timeout_id = None
-
- def set_last_status_time(self):
- self.fill_status_label()
-
- def set_os_info(self, resource, client_info, os_info):
- if self.xml.get_object('information_notebook').get_n_pages() < 5:
- return
- i = 0
- client = ''
- os = ''
- while i in self.os_info:
- if not self.os_info[i]['resource'] or \
- self.os_info[i]['resource'] == resource:
- self.os_info[i]['client'] = client_info
- self.os_info[i]['os'] = os_info
- if i > 0:
- client += '\n'
- os += '\n'
- client += self.os_info[i]['client']
- os += self.os_info[i]['os']
- i += 1
-
- if client == '':
- client = Q_('?Client:Unknown')
- if os == '':
- os = Q_('?OS:Unknown')
- self.xml.get_object('client_name_version_label').set_text(client)
- self.xml.get_object('os_label').set_text(os)
- self.os_info_arrived = True
- self.test_remove_progressbar()
-
- def set_entity_time(self, resource, time_info):
- if self.xml.get_object('information_notebook').get_n_pages() < 5:
- return
- i = 0
- time_s = ''
- while i in self.time_info:
- if not self.time_info[i]['resource'] or \
- self.time_info[i]['resource'] == resource:
- self.time_info[i]['time'] = time_info
- if i > 0:
- time_s += '\n'
- time_s += self.time_info[i]['time']
- i += 1
-
- if time_s == '':
- time_s = Q_('?Time:Unknown')
- self.xml.get_object('time_label').set_text(time_s)
- self.entity_time_arrived = True
- self.test_remove_progressbar()
-
- def fill_status_label(self):
- if self.xml.get_object('information_notebook').get_n_pages() < 5:
- return
- contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
- connected_contact_list = []
- for c in contact_list:
- if c.show not in ('offline', 'error'):
- connected_contact_list.append(c)
- if not connected_contact_list:
- # no connected contact, get the offline one
- connected_contact_list = contact_list
- # stats holds show and status message
- stats = ''
- if connected_contact_list:
- # Start with self.contact, as with resources
- stats = helpers.get_uf_show(self.contact.show)
- if self.contact.status:
- stats += ': ' + self.contact.status
- if self.contact.last_status_time:
- stats += '\n' + _('since %s') % time.strftime('%c',
- self.contact.last_status_time).decode(
- locale.getpreferredencoding())
- for c in connected_contact_list:
- if c.resource != self.contact.resource:
- stats += '\n'
- stats += helpers.get_uf_show(c.show)
- if c.status:
- stats += ': ' + c.status
- if c.last_status_time:
- stats += '\n' + _('since %s') % time.strftime('%c',
- c.last_status_time).decode(locale.getpreferredencoding())
- else: # Maybe gc_vcard ?
- stats = helpers.get_uf_show(self.contact.show)
- if self.contact.status:
- stats += ': ' + self.contact.status
- status_label = self.xml.get_object('status_label')
- status_label.set_max_width_chars(15)
- status_label.set_text(stats)
-
- status_label_eventbox = self.xml.get_object('status_label_eventbox')
- status_label_eventbox.set_tooltip_text(stats)
-
- def fill_jabber_page(self):
- self.xml.get_object('nickname_label').set_markup(
- '<b><span size="x-large">' +
- self.contact.get_shown_name() +
- '</span></b>')
- self.xml.get_object('jid_label').set_text(self.contact.jid)
-
- subscription_label = self.xml.get_object('subscription_label')
- ask_label = self.xml.get_object('ask_label')
- if self.gc_contact:
- self.xml.get_object('subscription_title_label').set_markup(_("<b>Role:</b>"))
- uf_role = helpers.get_uf_role(self.gc_contact.role)
- subscription_label.set_text(uf_role)
-
- self.xml.get_object('ask_title_label').set_markup(_("<b>Affiliation:</b>"))
- uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation)
- ask_label.set_text(uf_affiliation)
- else:
- uf_sub = helpers.get_uf_sub(self.contact.sub)
- subscription_label.set_text(uf_sub)
- eb = self.xml.get_object('subscription_label_eventbox')
- if self.contact.sub == 'from':
- tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence")
- elif self.contact.sub == 'to':
- tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours")
- elif self.contact.sub == 'both':
- tt_text = _("You and the contact are interested in each other's presence information")
- else: # None
- tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours")
- eb.set_tooltip_text(tt_text)
-
- uf_ask = helpers.get_uf_ask(self.contact.ask)
- ask_label.set_text(uf_ask)
- eb = self.xml.get_object('ask_label_eventbox')
- if self.contact.ask == 'subscribe':
- tt_text = _("You are waiting contact's answer about your subscription request")
- else:
- tt_text = _("There is no pending subscription request.")
- eb.set_tooltip_text(tt_text)
-
- resources = '%s (%s)' % (self.contact.resource, unicode(
- self.contact.priority))
- uf_resources = self.contact.resource + _(' resource with priority ')\
- + unicode(self.contact.priority)
- if not self.contact.status:
- self.contact.status = ''
-
- # Request list time status only if contact is offline
- if self.contact.show == 'offline':
- if self.gc_contact:
- j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
- gajim.connections[self.account].request_last_status_time(j, r,
- self.contact.jid)
- else:
- gajim.connections[self.account].request_last_status_time(
- self.contact.jid, self.contact.resource)
-
- # do not wait for os_info if contact is not connected or has error
- # additional check for observer is needed, as show is offline for him
- if self.contact.show in ('offline', 'error')\
- and not self.contact.is_observer():
- self.os_info_arrived = True
- else: # Request os info if contact is connected
- if self.gc_contact:
- j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
- gobject.idle_add(gajim.connections[self.account].request_os_info,
- j, r, self.contact.jid)
- else:
- gobject.idle_add(gajim.connections[self.account].request_os_info,
- self.contact.jid, self.contact.resource)
-
- # do not wait for entity_time if contact is not connected or has error
- # additional check for observer is needed, as show is offline for him
- if self.contact.show in ('offline', 'error')\
- and not self.contact.is_observer():
- self.entity_time_arrived = True
- else: # Request entity time if contact is connected
- if self.gc_contact:
- j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
- gobject.idle_add(gajim.connections[self.account].\
- request_entity_time, j, r, self.contact.jid)
- else:
- gobject.idle_add(gajim.connections[self.account].\
- request_entity_time, self.contact.jid, self.contact.resource)
-
- self.os_info = {0: {'resource': self.real_resource, 'client': '',
- 'os': ''}}
- self.time_info = {0: {'resource': self.real_resource, 'time': ''}}
- i = 1
- contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
- if contact_list:
- for c in contact_list:
- if c.resource != self.contact.resource:
- resources += '\n%s (%s)' % (c.resource,
- unicode(c.priority))
- uf_resources += '\n' + c.resource + \
- _(' resource with priority ') + unicode(c.priority)
- if c.show not in ('offline', 'error'):
- gobject.idle_add(
- gajim.connections[self.account].request_os_info, c.jid,
- c.resource)
- gobject.idle_add(gajim.connections[self.account].\
- request_entity_time, c.jid, c.resource)
- self.os_info[i] = {'resource': c.resource, 'client': '',
- 'os': ''}
- self.time_info[i] = {'resource': c.resource, 'time': ''}
- i += 1
-
- self.xml.get_object('resource_prio_label').set_text(resources)
- resource_prio_label_eventbox = self.xml.get_object(
- 'resource_prio_label_eventbox')
- resource_prio_label_eventbox.set_tooltip_text(uf_resources)
-
- self.fill_status_label()
-
- if self.gc_contact:
- # If we know the real jid, remove the resource from vcard request
- gajim.connections[self.account].request_vcard(self.real_jid_for_vcard,
- self.gc_contact.get_full_jid())
- else:
- gajim.connections[self.account].request_vcard(self.contact.jid)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
+ """
+ Class for contact's information window
+ """
+
+ def __init__(self, contact, account, gc_contact = None):
+ # the contact variable is the jid if vcard is true
+ self.xml = gtkgui_helpers.get_gtk_builder('vcard_information_window.ui')
+ self.window = self.xml.get_object('vcard_information_window')
+ self.progressbar = self.xml.get_object('progressbar')
+
+ self.contact = contact
+ self.account = account
+ self.gc_contact = gc_contact
+
+ # Get real jid
+ if gc_contact:
+ # Don't use real jid if room is (semi-)anonymous
+ gc_control = gajim.interface.msg_win_mgr.get_gc_control(
+ gc_contact.room_jid, account)
+ if gc_contact.jid and not gc_control.is_anonymous:
+ self.real_jid = gc_contact.jid
+ self.real_jid_for_vcard = gc_contact.jid
+ if gc_contact.resource:
+ self.real_jid += '/' + gc_contact.resource
+ else:
+ self.real_jid = gc_contact.get_full_jid()
+ self.real_jid_for_vcard = self.real_jid
+ self.real_resource = gc_contact.name
+ else:
+ self.real_jid = contact.get_full_jid()
+ self.real_resource = contact.resource
+
+ puny_jid = helpers.sanitize_filename(contact.jid)
+ local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
+ '_local'
+ for extension in ('.png', '.jpeg'):
+ local_avatar_path = local_avatar_basepath + extension
+ if os.path.isfile(local_avatar_path):
+ image = self.xml.get_object('custom_avatar_image')
+ image.set_from_file(local_avatar_path)
+ image.show()
+ self.xml.get_object('custom_avatar_label').show()
+ break
+ self.avatar_mime_type = None
+ self.avatar_encoded = None
+ self.vcard_arrived = False
+ self.os_info_arrived = False
+ self.entity_time_arrived = False
+ self.update_progressbar_timeout_id = gobject.timeout_add(100,
+ self.update_progressbar)
+
+ self.fill_jabber_page()
+ annotations = gajim.connections[self.account].annotations
+ if self.contact.jid in annotations:
+ buffer_ = self.xml.get_object('textview_annotation').get_buffer()
+ buffer_.set_text(annotations[self.contact.jid])
+
+ self.xml.connect_signals(self)
+ self.window.show_all()
+ self.xml.get_object('close_button').grab_focus()
+
+ def update_progressbar(self):
+ self.progressbar.pulse()
+ return True # loop forever
+
+ def on_vcard_information_window_destroy(self, widget):
+ if self.update_progressbar_timeout_id is not None:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ del gajim.interface.instances[self.account]['infos'][self.contact.jid]
+ buffer_ = self.xml.get_object('textview_annotation').get_buffer()
+ annotation = buffer_.get_text(buffer_.get_start_iter(),
+ buffer_.get_end_iter())
+ connection = gajim.connections[self.account]
+ if annotation != connection.annotations.get(self.contact.jid, ''):
+ connection.annotations[self.contact.jid] = annotation
+ connection.store_annotations()
+
+ def on_vcard_information_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def on_PHOTO_eventbox_button_press_event(self, widget, event):
+ """
+ If right-clicked, show popup
+ """
+ if event.button == 3: # right click
+ menu = gtk.Menu()
+ menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
+ menuitem.connect('activate',
+ gtkgui_helpers.on_avatar_save_as_menuitem_activate,
+ self.contact.jid, self.account, self.contact.get_shown_name())
+ menu.append(menuitem)
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+
+ def set_value(self, entry_name, value):
+ try:
+ if value and entry_name == 'URL_label':
+ widget = gtk.LinkButton(value, value)
+ widget.set_alignment(0, 0)
+ widget.show()
+ table = self.xml.get_object('personal_info_table')
+ table.attach(widget, 1, 4, 3, 4, yoptions = 0)
+ else:
+ self.xml.get_object(entry_name).set_text(value)
+ except AttributeError:
+ pass
+
+ def set_values(self, vcard):
+ for i in vcard.keys():
+ if i == 'PHOTO' and self.xml.get_object('information_notebook').\
+ get_n_pages() > 4:
+ pixbuf, self.avatar_encoded, self.avatar_mime_type = \
+ get_avatar_pixbuf_encoded_mime(vcard[i])
+ image = self.xml.get_object('PHOTO_image')
+ image.show()
+ self.xml.get_object('user_avatar_label').show()
+ if not pixbuf:
+ image.set_from_icon_name('stock_person',
+ gtk.ICON_SIZE_DIALOG)
+ continue
+ pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
+ image.set_from_pixbuf(pixbuf)
+ continue
+ if i in ('ADR', 'TEL', 'EMAIL'):
+ for entry in vcard[i]:
+ add_on = '_HOME'
+ if 'WORK' in entry:
+ add_on = '_WORK'
+ for j in entry.keys():
+ self.set_value(i + add_on + '_' + j + '_label', entry[j])
+ if isinstance(vcard[i], dict):
+ for j in vcard[i].keys():
+ self.set_value(i + '_' + j + '_label', vcard[i][j])
+ else:
+ if i == 'DESC':
+ self.xml.get_object('DESC_textview').get_buffer().set_text(
+ vcard[i], 0)
+ elif i != 'jid': # Do not override jid_label
+ self.set_value(i + '_label', vcard[i])
+ self.vcard_arrived = True
+ self.test_remove_progressbar()
+
+ def test_remove_progressbar(self):
+ if self.update_progressbar_timeout_id is not None and \
+ self.vcard_arrived and self.os_info_arrived and self.entity_time_arrived:
+ gobject.source_remove(self.update_progressbar_timeout_id)
+ self.progressbar.hide()
+ self.update_progressbar_timeout_id = None
+
+ def set_last_status_time(self):
+ self.fill_status_label()
+
+ def set_os_info(self, resource, client_info, os_info):
+ if self.xml.get_object('information_notebook').get_n_pages() < 5:
+ return
+ i = 0
+ client = ''
+ os = ''
+ while i in self.os_info:
+ if not self.os_info[i]['resource'] or \
+ self.os_info[i]['resource'] == resource:
+ self.os_info[i]['client'] = client_info
+ self.os_info[i]['os'] = os_info
+ if i > 0:
+ client += '\n'
+ os += '\n'
+ client += self.os_info[i]['client']
+ os += self.os_info[i]['os']
+ i += 1
+
+ if client == '':
+ client = Q_('?Client:Unknown')
+ if os == '':
+ os = Q_('?OS:Unknown')
+ self.xml.get_object('client_name_version_label').set_text(client)
+ self.xml.get_object('os_label').set_text(os)
+ self.os_info_arrived = True
+ self.test_remove_progressbar()
+
+ def set_entity_time(self, resource, time_info):
+ if self.xml.get_object('information_notebook').get_n_pages() < 5:
+ return
+ i = 0
+ time_s = ''
+ while i in self.time_info:
+ if not self.time_info[i]['resource'] or \
+ self.time_info[i]['resource'] == resource:
+ self.time_info[i]['time'] = time_info
+ if i > 0:
+ time_s += '\n'
+ time_s += self.time_info[i]['time']
+ i += 1
+
+ if time_s == '':
+ time_s = Q_('?Time:Unknown')
+ self.xml.get_object('time_label').set_text(time_s)
+ self.entity_time_arrived = True
+ self.test_remove_progressbar()
+
+ def fill_status_label(self):
+ if self.xml.get_object('information_notebook').get_n_pages() < 5:
+ return
+ contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
+ connected_contact_list = []
+ for c in contact_list:
+ if c.show not in ('offline', 'error'):
+ connected_contact_list.append(c)
+ if not connected_contact_list:
+ # no connected contact, get the offline one
+ connected_contact_list = contact_list
+ # stats holds show and status message
+ stats = ''
+ if connected_contact_list:
+ # Start with self.contact, as with resources
+ stats = helpers.get_uf_show(self.contact.show)
+ if self.contact.status:
+ stats += ': ' + self.contact.status
+ if self.contact.last_status_time:
+ stats += '\n' + _('since %s') % time.strftime('%c',
+ self.contact.last_status_time).decode(
+ locale.getpreferredencoding())
+ for c in connected_contact_list:
+ if c.resource != self.contact.resource:
+ stats += '\n'
+ stats += helpers.get_uf_show(c.show)
+ if c.status:
+ stats += ': ' + c.status
+ if c.last_status_time:
+ stats += '\n' + _('since %s') % time.strftime('%c',
+ c.last_status_time).decode(locale.getpreferredencoding())
+ else: # Maybe gc_vcard ?
+ stats = helpers.get_uf_show(self.contact.show)
+ if self.contact.status:
+ stats += ': ' + self.contact.status
+ status_label = self.xml.get_object('status_label')
+ status_label.set_max_width_chars(15)
+ status_label.set_text(stats)
+
+ status_label_eventbox = self.xml.get_object('status_label_eventbox')
+ status_label_eventbox.set_tooltip_text(stats)
+
+ def fill_jabber_page(self):
+ self.xml.get_object('nickname_label').set_markup(
+ '<b><span size="x-large">' +
+ self.contact.get_shown_name() +
+ '</span></b>')
+ self.xml.get_object('jid_label').set_text(self.contact.jid)
+
+ subscription_label = self.xml.get_object('subscription_label')
+ ask_label = self.xml.get_object('ask_label')
+ if self.gc_contact:
+ self.xml.get_object('subscription_title_label').set_markup(_("<b>Role:</b>"))
+ uf_role = helpers.get_uf_role(self.gc_contact.role)
+ subscription_label.set_text(uf_role)
+
+ self.xml.get_object('ask_title_label').set_markup(_("<b>Affiliation:</b>"))
+ uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation)
+ ask_label.set_text(uf_affiliation)
+ else:
+ uf_sub = helpers.get_uf_sub(self.contact.sub)
+ subscription_label.set_text(uf_sub)
+ eb = self.xml.get_object('subscription_label_eventbox')
+ if self.contact.sub == 'from':
+ tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence")
+ elif self.contact.sub == 'to':
+ tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours")
+ elif self.contact.sub == 'both':
+ tt_text = _("You and the contact are interested in each other's presence information")
+ else: # None
+ tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours")
+ eb.set_tooltip_text(tt_text)
+
+ uf_ask = helpers.get_uf_ask(self.contact.ask)
+ ask_label.set_text(uf_ask)
+ eb = self.xml.get_object('ask_label_eventbox')
+ if self.contact.ask == 'subscribe':
+ tt_text = _("You are waiting contact's answer about your subscription request")
+ else:
+ tt_text = _("There is no pending subscription request.")
+ eb.set_tooltip_text(tt_text)
+
+ resources = '%s (%s)' % (self.contact.resource, unicode(
+ self.contact.priority))
+ uf_resources = self.contact.resource + _(' resource with priority ')\
+ + unicode(self.contact.priority)
+ if not self.contact.status:
+ self.contact.status = ''
+
+ # Request list time status only if contact is offline
+ if self.contact.show == 'offline':
+ if self.gc_contact:
+ j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
+ gajim.connections[self.account].request_last_status_time(j, r,
+ self.contact.jid)
+ else:
+ gajim.connections[self.account].request_last_status_time(
+ self.contact.jid, self.contact.resource)
+
+ # do not wait for os_info if contact is not connected or has error
+ # additional check for observer is needed, as show is offline for him
+ if self.contact.show in ('offline', 'error')\
+ and not self.contact.is_observer():
+ self.os_info_arrived = True
+ else: # Request os info if contact is connected
+ if self.gc_contact:
+ j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
+ gobject.idle_add(gajim.connections[self.account].request_os_info,
+ j, r, self.contact.jid)
+ else:
+ gobject.idle_add(gajim.connections[self.account].request_os_info,
+ self.contact.jid, self.contact.resource)
+
+ # do not wait for entity_time if contact is not connected or has error
+ # additional check for observer is needed, as show is offline for him
+ if self.contact.show in ('offline', 'error')\
+ and not self.contact.is_observer():
+ self.entity_time_arrived = True
+ else: # Request entity time if contact is connected
+ if self.gc_contact:
+ j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
+ gobject.idle_add(gajim.connections[self.account].\
+ request_entity_time, j, r, self.contact.jid)
+ else:
+ gobject.idle_add(gajim.connections[self.account].\
+ request_entity_time, self.contact.jid, self.contact.resource)
+
+ self.os_info = {0: {'resource': self.real_resource, 'client': '',
+ 'os': ''}}
+ self.time_info = {0: {'resource': self.real_resource, 'time': ''}}
+ i = 1
+ contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
+ if contact_list:
+ for c in contact_list:
+ if c.resource != self.contact.resource:
+ resources += '\n%s (%s)' % (c.resource,
+ unicode(c.priority))
+ uf_resources += '\n' + c.resource + \
+ _(' resource with priority ') + unicode(c.priority)
+ if c.show not in ('offline', 'error'):
+ gobject.idle_add(
+ gajim.connections[self.account].request_os_info, c.jid,
+ c.resource)
+ gobject.idle_add(gajim.connections[self.account].\
+ request_entity_time, c.jid, c.resource)
+ self.os_info[i] = {'resource': c.resource, 'client': '',
+ 'os': ''}
+ self.time_info[i] = {'resource': c.resource, 'time': ''}
+ i += 1
+
+ self.xml.get_object('resource_prio_label').set_text(resources)
+ resource_prio_label_eventbox = self.xml.get_object(
+ 'resource_prio_label_eventbox')
+ resource_prio_label_eventbox.set_tooltip_text(uf_resources)
+
+ self.fill_status_label()
+
+ if self.gc_contact:
+ # If we know the real jid, remove the resource from vcard request
+ gajim.connections[self.account].request_vcard(self.real_jid_for_vcard,
+ self.gc_contact.get_full_jid())
+ else:
+ gajim.connections[self.account].request_vcard(self.contact.jid)
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
class ZeroconfVcardWindow:
- def __init__(self, contact, account, is_fake = False):
- # the contact variable is the jid if vcard is true
- self.xml = gtkgui_helpers.get_gtk_builder('zeroconf_information_window.ui')
- self.window = self.xml.get_object('zeroconf_information_window')
-
- self.contact = contact
- self.account = account
- self.is_fake = is_fake
-
- # self.avatar_mime_type = None
- # self.avatar_encoded = None
-
- self.fill_contact_page()
- self.fill_personal_page()
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_zeroconf_information_window_destroy(self, widget):
- del gajim.interface.instances[self.account]['infos'][self.contact.jid]
-
- def on_zeroconf_information_window_key_press_event(self, widget, event):
- if event.keyval == gtk.keysyms.Escape:
- self.window.destroy()
-
- def on_PHOTO_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- menu = gtk.Menu()
- menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
- menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.contact.jid, self.account, self.contact.get_shown_name())
- menu.append(menuitem)
- menu.connect('selection-done', lambda w:w.destroy())
- # show the menu
- menu.show_all()
- menu.popup(None, None, None, event.button, event.time)
-
- def set_value(self, entry_name, value):
- try:
- if value and entry_name == 'URL_label':
- widget = gtk.LinkButton(value, value)
- widget.set_alignment(0, 0)
- table = self.xml.get_object('personal_info_table')
- table.attach(widget, 1, 4, 3, 4, yoptions = 0)
- else:
- self.xml.get_object(entry_name).set_text(value)
- except AttributeError:
- pass
-
- def fill_status_label(self):
- if self.xml.get_object('information_notebook').get_n_pages() < 2:
- return
- contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
- # stats holds show and status message
- stats = ''
- one = True # Are we adding the first line ?
- if contact_list:
- for c in contact_list:
- if not one:
- stats += '\n'
- stats += helpers.get_uf_show(c.show)
- if c.status:
- stats += ': ' + c.status
- if c.last_status_time:
- stats += '\n' + _('since %s') % time.strftime('%c',
- c.last_status_time).decode(locale.getpreferredencoding())
- one = False
- else: # Maybe gc_vcard ?
- stats = helpers.get_uf_show(self.contact.show)
- if self.contact.status:
- stats += ': ' + self.contact.status
- status_label = self.xml.get_object('status_label')
- status_label.set_max_width_chars(15)
- status_label.set_text(stats)
-
- status_label_eventbox = self.xml.get_object('status_label_eventbox')
- status_label_eventbox.set_tooltip_text(stats)
-
- def fill_contact_page(self):
- self.xml.get_object('nickname_label').set_markup(
- '<b><span size="x-large">' +
- self.contact.get_shown_name() +
- '</span></b>')
- self.xml.get_object('local_jid_label').set_text(self.contact.jid)
-
- resources = '%s (%s)' % (self.contact.resource, unicode(
- self.contact.priority))
- uf_resources = self.contact.resource + _(' resource with priority ')\
- + unicode(self.contact.priority)
- if not self.contact.status:
- self.contact.status = ''
-
- self.xml.get_object('resource_prio_label').set_text(resources)
- resource_prio_label_eventbox = self.xml.get_object(
- 'resource_prio_label_eventbox')
- resource_prio_label_eventbox.set_tooltip_text(uf_resources)
-
- self.fill_status_label()
-
- def fill_personal_page(self):
- contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid)
- for key in ('1st', 'last', 'jid', 'email'):
- if key not in contact['txt_dict']:
- contact['txt_dict'][key] = ''
- self.xml.get_object('first_name_label').set_text(contact['txt_dict']['1st'])
- self.xml.get_object('last_name_label').set_text(contact['txt_dict']['last'])
- self.xml.get_object('jabber_id_label').set_text(contact['txt_dict']['jid'])
- self.xml.get_object('email_label').set_text(contact['txt_dict']['email'])
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
-# vim: se ts=3:
+ def __init__(self, contact, account, is_fake = False):
+ # the contact variable is the jid if vcard is true
+ self.xml = gtkgui_helpers.get_gtk_builder('zeroconf_information_window.ui')
+ self.window = self.xml.get_object('zeroconf_information_window')
+
+ self.contact = contact
+ self.account = account
+ self.is_fake = is_fake
+
+ # self.avatar_mime_type = None
+ # self.avatar_encoded = None
+
+ self.fill_contact_page()
+ self.fill_personal_page()
+
+ self.xml.connect_signals(self)
+ self.window.show_all()
+
+ def on_zeroconf_information_window_destroy(self, widget):
+ del gajim.interface.instances[self.account]['infos'][self.contact.jid]
+
+ def on_zeroconf_information_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def on_PHOTO_eventbox_button_press_event(self, widget, event):
+ """
+ If right-clicked, show popup
+ """
+ if event.button == 3: # right click
+ menu = gtk.Menu()
+ menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
+ menuitem.connect('activate',
+ gtkgui_helpers.on_avatar_save_as_menuitem_activate,
+ self.contact.jid, self.account, self.contact.get_shown_name())
+ menu.append(menuitem)
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+
+ def set_value(self, entry_name, value):
+ try:
+ if value and entry_name == 'URL_label':
+ widget = gtk.LinkButton(value, value)
+ widget.set_alignment(0, 0)
+ table = self.xml.get_object('personal_info_table')
+ table.attach(widget, 1, 4, 3, 4, yoptions = 0)
+ else:
+ self.xml.get_object(entry_name).set_text(value)
+ except AttributeError:
+ pass
+
+ def fill_status_label(self):
+ if self.xml.get_object('information_notebook').get_n_pages() < 2:
+ return
+ contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
+ # stats holds show and status message
+ stats = ''
+ one = True # Are we adding the first line ?
+ if contact_list:
+ for c in contact_list:
+ if not one:
+ stats += '\n'
+ stats += helpers.get_uf_show(c.show)
+ if c.status:
+ stats += ': ' + c.status
+ if c.last_status_time:
+ stats += '\n' + _('since %s') % time.strftime('%c',
+ c.last_status_time).decode(locale.getpreferredencoding())
+ one = False
+ else: # Maybe gc_vcard ?
+ stats = helpers.get_uf_show(self.contact.show)
+ if self.contact.status:
+ stats += ': ' + self.contact.status
+ status_label = self.xml.get_object('status_label')
+ status_label.set_max_width_chars(15)
+ status_label.set_text(stats)
+
+ status_label_eventbox = self.xml.get_object('status_label_eventbox')
+ status_label_eventbox.set_tooltip_text(stats)
+
+ def fill_contact_page(self):
+ self.xml.get_object('nickname_label').set_markup(
+ '<b><span size="x-large">' +
+ self.contact.get_shown_name() +
+ '</span></b>')
+ self.xml.get_object('local_jid_label').set_text(self.contact.jid)
+
+ resources = '%s (%s)' % (self.contact.resource, unicode(
+ self.contact.priority))
+ uf_resources = self.contact.resource + _(' resource with priority ')\
+ + unicode(self.contact.priority)
+ if not self.contact.status:
+ self.contact.status = ''
+
+ self.xml.get_object('resource_prio_label').set_text(resources)
+ resource_prio_label_eventbox = self.xml.get_object(
+ 'resource_prio_label_eventbox')
+ resource_prio_label_eventbox.set_tooltip_text(uf_resources)
+
+ self.fill_status_label()
+
+ def fill_personal_page(self):
+ contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid)
+ for key in ('1st', 'last', 'jid', 'email'):
+ if key not in contact['txt_dict']:
+ contact['txt_dict'][key] = ''
+ self.xml.get_object('first_name_label').set_text(contact['txt_dict']['1st'])
+ self.xml.get_object('last_name_label').set_text(contact['txt_dict']['last'])
+ self.xml.get_object('jabber_id_label').set_text(contact['txt_dict']['jid'])
+ self.xml.get_object('email_label').set_text(contact['txt_dict']['email'])
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
diff --git a/test/integration/__init__.py b/test/integration/__init__.py
index 0adf844f1..aaeacfbef 100644
--- a/test/integration/__init__.py
+++ b/test/integration/__init__.py
@@ -3,4 +3,4 @@
This package contains integration tests. Integration tests are tests
which require or include UI, network or both.
-''' \ No newline at end of file
+'''
diff --git a/test/integration/test_gui_event_integration.py b/test/integration/test_gui_event_integration.py
index a1eadea60..c5999389f 100644
--- a/test/integration/test_gui_event_integration.py
+++ b/test/integration/test_gui_event_integration.py
@@ -23,171 +23,169 @@ import roster_window
import notify
class TestStatusChange(unittest.TestCase):
- '''tests gajim.py's incredibly complex handle_event_notify'''
+ '''tests gajim.py's incredibly complex handle_event_notify'''
- def setUp(self):
-
- gajim.connections = {}
- gajim.contacts = contacts_module.LegacyContactsAPI()
- gajim.interface.roster = roster_window.RosterWindow()
+ def setUp(self):
- for acc in contacts:
- gajim.connections[acc] = MockConnection(acc)
+ gajim.connections = {}
+ gajim.contacts = contacts_module.LegacyContactsAPI()
+ gajim.interface.roster = roster_window.RosterWindow()
- gajim.interface.roster.fill_contacts_and_groups_dicts(contacts[acc],
- acc)
- gajim.interface.roster.add_account(acc)
- gajim.interface.roster.add_account_contacts(acc)
+ for acc in contacts:
+ gajim.connections[acc] = MockConnection(acc)
- self.assertEqual(0, len(notify.notifications))
+ gajim.interface.roster.fill_contacts_and_groups_dicts(contacts[acc],
+ acc)
+ gajim.interface.roster.add_account(acc)
+ gajim.interface.roster.add_account_contacts(acc)
- def tearDown(self):
- notify.notifications = []
+ self.assertEqual(0, len(notify.notifications))
- def contact_comes_online(self, account, jid, resource, prio):
- '''a remote contact comes online'''
- gajim.interface.handle_event_notify(account, (jid, 'online', "I'm back!",
- resource, prio, None, time.time(), None))
+ def tearDown(self):
+ notify.notifications = []
- contact = None
- for c in gajim.contacts.get_contacts(account, jid):
- if c.resource == resource:
- contact = c
- break
+ def contact_comes_online(self, account, jid, resource, prio):
+ '''a remote contact comes online'''
+ gajim.interface.handle_event_notify(account, (jid, 'online', "I'm back!",
+ resource, prio, None, time.time(), None))
- self.assertEqual('online', contact.show)
- self.assertEqual("I'm back!", contact.status)
- self.assertEqual(prio, contact.priority)
+ contact = None
+ for c in gajim.contacts.get_contacts(account, jid):
+ if c.resource == resource:
+ contact = c
+ break
- # the most recent notification is that the contact connected
- self.assertEqual('contact_connected', notify.notifications[-1][0])
+ self.assertEqual('online', contact.show)
+ self.assertEqual("I'm back!", contact.status)
+ self.assertEqual(prio, contact.priority)
- def contact_goes_offline(self, account, jid, resource, prio,
- still_exists = True):
- '''a remote contact goes offline.'''
- gajim.interface.handle_event_notify(account, (jid, 'offline', 'Goodbye!',
- resource, prio, None, time.time(), None))
+ # the most recent notification is that the contact connected
+ self.assertEqual('contact_connected', notify.notifications[-1][0])
- contact = None
- for c in gajim.contacts.get_contacts(account, jid):
- if c.resource == resource:
- contact = c
- break
+ def contact_goes_offline(self, account, jid, resource, prio,
+ still_exists = True):
+ '''a remote contact goes offline.'''
+ gajim.interface.handle_event_notify(account, (jid, 'offline', 'Goodbye!',
+ resource, prio, None, time.time(), None))
- if not still_exists:
- self.assert_(contact is None)
- return
+ contact = None
+ for c in gajim.contacts.get_contacts(account, jid):
+ if c.resource == resource:
+ contact = c
+ break
- self.assertEqual('offline', contact.show)
- self.assertEqual('Goodbye!', contact.status)
- self.assertEqual(prio, contact.priority)
+ if not still_exists:
+ self.assert_(contact is None)
+ return
- self.assertEqual('contact_disconnected', notify.notifications[-1][0])
+ self.assertEqual('offline', contact.show)
+ self.assertEqual('Goodbye!', contact.status)
+ self.assertEqual(prio, contact.priority)
- def user_starts_chatting(self, jid, account, resource=None):
- '''the user opens a chat window and starts talking'''
- ctrl = MockChatControl(jid, account)
- win = MockWindow()
- win.new_tab(ctrl)
- gajim.interface.msg_win_mgr._windows['test'] = win
+ self.assertEqual('contact_disconnected', notify.notifications[-1][0])
- if resource:
- jid = jid + '/' + resource
+ def user_starts_chatting(self, jid, account, resource=None):
+ '''the user opens a chat window and starts talking'''
+ ctrl = MockChatControl(jid, account)
+ win = MockWindow()
+ win.new_tab(ctrl)
+ gajim.interface.msg_win_mgr._windows['test'] = win
- # a basic session is started
- session = gajim.connections[account1].make_new_session(jid,
- '01234567890abcdef', cls=MockSession)
- ctrl.set_session(session)
+ if resource:
+ jid = jid + '/' + resource
- return ctrl
+ # a basic session is started
+ session = gajim.connections[account1].make_new_session(jid,
+ '01234567890abcdef', cls=MockSession)
+ ctrl.set_session(session)
- def user_starts_esession(self, jid, resource, account):
- '''the user opens a chat window and starts an encrypted session'''
- ctrl = self.user_starts_chatting(jid, account, resource)
- ctrl.session.status = 'active'
- ctrl.session.enable_encryption = True
+ return ctrl
- return ctrl
+ def user_starts_esession(self, jid, resource, account):
+ '''the user opens a chat window and starts an encrypted session'''
+ ctrl = self.user_starts_chatting(jid, account, resource)
+ ctrl.session.status = 'active'
+ ctrl.session.enable_encryption = True
- def test_contact_comes_online(self):
- jid = 'default1@gajim.org'
+ return ctrl
- # contact is offline initially
- contacts = gajim.contacts.get_contacts(account1, jid)
- self.assertEqual(1, len(contacts))
- self.assertEqual('offline', contacts[0].show)
- self.assertEqual('', contacts[0].status)
+ def test_contact_comes_online(self):
+ jid = 'default1@gajim.org'
- self.contact_comes_online(account1, jid, 'lowprio', 1)
+ # contact is offline initially
+ contacts = gajim.contacts.get_contacts(account1, jid)
+ self.assertEqual(1, len(contacts))
+ self.assertEqual('offline', contacts[0].show)
+ self.assertEqual('', contacts[0].status)
- def test_contact_goes_offline(self):
- jid = 'default1@gajim.org'
+ self.contact_comes_online(account1, jid, 'lowprio', 1)
- self.contact_comes_online(account1, jid, 'lowprio', 1)
+ def test_contact_goes_offline(self):
+ jid = 'default1@gajim.org'
- ctrl = self.user_starts_chatting(jid, account1)
- orig_sess = ctrl.session
+ self.contact_comes_online(account1, jid, 'lowprio', 1)
- self.contact_goes_offline(account1, jid, 'lowprio', 1)
+ ctrl = self.user_starts_chatting(jid, account1)
+ orig_sess = ctrl.session
- # session hasn't changed since we were talking to the bare jid
- self.assertEqual(orig_sess, ctrl.session)
+ self.contact_goes_offline(account1, jid, 'lowprio', 1)
- def test_two_resources_higher_comes_online(self):
- jid = 'default1@gajim.org'
+ # session hasn't changed since we were talking to the bare jid
+ self.assertEqual(orig_sess, ctrl.session)
- self.contact_comes_online(account1, jid, 'lowprio', 1)
+ def test_two_resources_higher_comes_online(self):
+ jid = 'default1@gajim.org'
- ctrl = self.user_starts_chatting(jid, account1)
+ self.contact_comes_online(account1, jid, 'lowprio', 1)
- self.contact_comes_online(account1, jid, 'highprio', 50)
+ ctrl = self.user_starts_chatting(jid, account1)
- # old session was dropped
- self.assertEqual(None, ctrl.session)
+ self.contact_comes_online(account1, jid, 'highprio', 50)
- def test_two_resources_higher_goes_offline(self):
- jid = 'default1@gajim.org'
+ # old session was dropped
+ self.assertEqual(None, ctrl.session)
- self.contact_comes_online(account1, jid, 'lowprio', 1)
- self.contact_comes_online(account1, jid, 'highprio', 50)
+ def test_two_resources_higher_goes_offline(self):
+ jid = 'default1@gajim.org'
- ctrl = self.user_starts_chatting(jid, account1)
+ self.contact_comes_online(account1, jid, 'lowprio', 1)
+ self.contact_comes_online(account1, jid, 'highprio', 50)
- self.contact_goes_offline(account1, jid, 'highprio', 50,
- still_exists=False)
+ ctrl = self.user_starts_chatting(jid, account1)
- # old session was dropped
- self.assertEqual(None, ctrl.session)
+ self.contact_goes_offline(account1, jid, 'highprio', 50,
+ still_exists=False)
- def test_two_resources_higher_comes_online_with_esession(self):
- jid = 'default1@gajim.org'
+ # old session was dropped
+ self.assertEqual(None, ctrl.session)
- self.contact_comes_online(account1, jid, 'lowprio', 1)
+ def test_two_resources_higher_comes_online_with_esession(self):
+ jid = 'default1@gajim.org'
- ctrl = self.user_starts_esession(jid, 'lowprio', account1)
+ self.contact_comes_online(account1, jid, 'lowprio', 1)
- self.contact_comes_online(account1, jid, 'highprio', 50)
+ ctrl = self.user_starts_esession(jid, 'lowprio', account1)
- # session was associated with the low priority full jid, so it should
- # have been removed from the control
- self.assertEqual(None, ctrl.session)
+ self.contact_comes_online(account1, jid, 'highprio', 50)
- def test_two_resources_higher_goes_offline_with_esession(self):
- jid = 'default1@gajim.org'
+ # session was associated with the low priority full jid, so it should
+ # have been removed from the control
+ self.assertEqual(None, ctrl.session)
- self.contact_comes_online(account1, jid, 'lowprio', 1)
- self.contact_comes_online(account1, jid, 'highprio', 50)
+ def test_two_resources_higher_goes_offline_with_esession(self):
+ jid = 'default1@gajim.org'
- ctrl = self.user_starts_esession(jid, 'highprio', account1)
+ self.contact_comes_online(account1, jid, 'lowprio', 1)
+ self.contact_comes_online(account1, jid, 'highprio', 50)
- self.contact_goes_offline(account1, jid, 'highprio', 50,
- still_exists=False)
+ ctrl = self.user_starts_esession(jid, 'highprio', account1)
- # session was associated with the high priority full jid, so it should
- # have been removed from the control
- self.assertEqual(None, ctrl.session)
+ self.contact_goes_offline(account1, jid, 'highprio', 50,
+ still_exists=False)
-if __name__ == '__main__':
- unittest.main()
+ # session was associated with the high priority full jid, so it should
+ # have been removed from the control
+ self.assertEqual(None, ctrl.session)
-# vim: se ts=3:
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/integration/test_resolver.py b/test/integration/test_resolver.py
index 1836d3c7b..d80ffee87 100644
--- a/test/integration/test_resolver.py
+++ b/test/integration/test_resolver.py
@@ -16,87 +16,85 @@ NONSENSE_NAME = 'sfsdfsdfsdf.sdfs.fsd'
JABBERCZ_TXT_NAME = '_xmppconnect.jabber.cz'
JABBERCZ_SRV_NAME = '_xmpp-client._tcp.jabber.cz'
-TEST_LIST = [(GMAIL_SRV_NAME, 'srv', True),
- (NONSENSE_NAME, 'srv', False),
- (JABBERCZ_SRV_NAME, 'srv', True)]
+TEST_LIST = [(GMAIL_SRV_NAME, 'srv', True),
+ (NONSENSE_NAME, 'srv', False),
+ (JABBERCZ_SRV_NAME, 'srv', True)]
class TestResolver(unittest.TestCase):
- '''
- Test for LibAsyncNSResolver and NSLookupResolver. Requires working
- network connection.
- '''
- def setUp(self):
- self.idlequeue_thread = IdleQueueThread()
- self.idlequeue_thread.start()
-
- self.iq = self.idlequeue_thread.iq
- self._reset()
- self.resolver = None
-
- def tearDown(self):
- self.idlequeue_thread.stop_thread()
- self.idlequeue_thread.join()
-
- def _reset(self):
- self.flag = False
- self.expect_results = False
- self.nslookup = False
- self.resolver = None
-
- def testLibAsyncNSResolver(self):
- self._reset()
- if not resolver.USE_LIBASYNCNS:
- print 'testLibAsyncResolver: libasyncns-python not installed'
- return
- self.resolver = resolver.LibAsyncNSResolver()
-
- for name, type, expect_results in TEST_LIST:
- self.expect_results = expect_results
- self._runLANSR(name, type)
- self.flag = False
-
- def _runLANSR(self, name, type):
- self.resolver.resolve(
- host = name,
- type = type,
- on_ready = self._myonready)
- while not self.flag:
- time.sleep(1)
- self.resolver.process()
-
- def _myonready(self, name, result_set):
- if __name__ == '__main__':
- from pprint import pprint
- pprint('on_ready called ...')
- pprint('hostname: %s' % name)
- pprint('result set: %s' % result_set)
- pprint('res.resolved_hosts: %s' % self.resolver.resolved_hosts)
- pprint('')
- if self.expect_results:
- self.assert_(len(result_set) > 0)
- else:
- self.assert_(result_set == [])
- self.flag = True
- if self.nslookup:
- self._testNSLR()
-
- def testNSLookupResolver(self):
- self._reset()
- self.nslookup = True
- self.resolver = resolver.NSLookupResolver(self.iq)
- self.test_list = TEST_LIST
- self._testNSLR()
-
- def _testNSLR(self):
- if self.test_list == []:
- return
- name, type, self.expect_results = self.test_list.pop()
- self.resolver.resolve(
- host = name,
- type = type,
- on_ready = self._myonready)
+ '''
+ Test for LibAsyncNSResolver and NSLookupResolver. Requires working
+ network connection.
+ '''
+ def setUp(self):
+ self.idlequeue_thread = IdleQueueThread()
+ self.idlequeue_thread.start()
+
+ self.iq = self.idlequeue_thread.iq
+ self._reset()
+ self.resolver = None
+
+ def tearDown(self):
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def _reset(self):
+ self.flag = False
+ self.expect_results = False
+ self.nslookup = False
+ self.resolver = None
+
+ def testLibAsyncNSResolver(self):
+ self._reset()
+ if not resolver.USE_LIBASYNCNS:
+ print 'testLibAsyncResolver: libasyncns-python not installed'
+ return
+ self.resolver = resolver.LibAsyncNSResolver()
+
+ for name, type, expect_results in TEST_LIST:
+ self.expect_results = expect_results
+ self._runLANSR(name, type)
+ self.flag = False
+
+ def _runLANSR(self, name, type):
+ self.resolver.resolve(
+ host = name,
+ type = type,
+ on_ready = self._myonready)
+ while not self.flag:
+ time.sleep(1)
+ self.resolver.process()
+
+ def _myonready(self, name, result_set):
+ if __name__ == '__main__':
+ from pprint import pprint
+ pprint('on_ready called ...')
+ pprint('hostname: %s' % name)
+ pprint('result set: %s' % result_set)
+ pprint('res.resolved_hosts: %s' % self.resolver.resolved_hosts)
+ pprint('')
+ if self.expect_results:
+ self.assert_(len(result_set) > 0)
+ else:
+ self.assert_(result_set == [])
+ self.flag = True
+ if self.nslookup:
+ self._testNSLR()
+
+ def testNSLookupResolver(self):
+ self._reset()
+ self.nslookup = True
+ self.resolver = resolver.NSLookupResolver(self.iq)
+ self.test_list = TEST_LIST
+ self._testNSLR()
+
+ def _testNSLR(self):
+ if self.test_list == []:
+ return
+ name, type, self.expect_results = self.test_list.pop()
+ self.resolver.resolve(
+ host = name,
+ type = type,
+ on_ready = self._myonready)
if __name__ == '__main__':
- unittest.main()
-
-# vim: se ts=3:
+ unittest.main()
diff --git a/test/integration/test_roster.py b/test/integration/test_roster.py
index 0be85aa11..8b71df122 100644
--- a/test/integration/test_roster.py
+++ b/test/integration/test_roster.py
@@ -17,189 +17,187 @@ gajim.get_jid_from_account = lambda acc: 'myjid@' + acc
class TestRosterWindow(unittest.TestCase):
- def setUp(self):
- gajim.interface = MockInterface()
-
- self.C_NAME = roster_window.C_NAME
- self.C_TYPE = roster_window.C_TYPE
- self.C_JID = roster_window.C_JID
- self.C_ACCOUNT = roster_window.C_ACCOUNT
-
- # Add after creating RosterWindow
- # We want to test the filling explicitly
- gajim.contacts = contacts_module.LegacyContactsAPI()
- gajim.connections = {}
- self.roster = roster_window.RosterWindow()
-
- for acc in contacts:
- gajim.connections[acc] = MockConnection(acc)
- gajim.contacts.add_account(acc)
-
- ### Custom assertions
- def assert_all_contacts_are_in_roster(self, acc):
- for jid in contacts[acc]:
- self.assert_contact_is_in_roster(jid, acc)
-
- def assert_contact_is_in_roster(self, jid, account):
- contacts = gajim.contacts.get_contacts(account, jid)
- # check for all resources
- for contact in contacts:
- iters = self.roster._get_contact_iter(jid, account,
- model=self.roster.model)
-
- if jid != gajim.get_jid_from_account(account):
- # We don't care for groups of SelfContact
- self.assertTrue(len(iters) == len(contact.get_shown_groups()),
- msg='Contact is not in all his groups')
-
- # Are we big brother?
- bb_jid = None
- bb_account = None
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- self.roster._get_nearby_family_and_big_brother(family, account)
-
- is_in_nearby_family = (jid, account) in (
- (data['jid'], data['account']) for data in nearby_family)
- self.assertTrue(is_in_nearby_family,
- msg='Contact not in his own nearby family')
-
- is_big_brother = (bb_jid, bb_account) == (jid, account)
-
- # check for each group tag
- for titerC in iters:
- self.assertTrue(self.roster.model.iter_is_valid(titerC),
- msg='Contact iter invalid')
-
- c_model = self.roster.model[titerC]
- self.assertEquals(contact.get_shown_name(), c_model[self.C_NAME],
- msg='Contact name missmatch')
- self.assertEquals(contact.jid, c_model[self.C_JID],
- msg='Jid missmatch')
-
- if not self.roster.regroup:
- self.assertEquals(account, c_model[self.C_ACCOUNT],
- msg='Account missmatch')
-
- # Check for correct nesting
- parent_iter = self.roster.model.iter_parent(titerC)
- p_model = self.roster.model[parent_iter]
- if family:
- if is_big_brother:
- self.assertTrue(p_model[self.C_TYPE] == 'group',
- msg='Big Brother is not on top')
- else:
- self.assertTrue(p_model[self.C_TYPE] == 'contact',
- msg='Little Brother brother has no BigB')
- else:
- if jid == gajim.get_jid_from_account(account):
- self.assertTrue(p_model[self.C_TYPE] == 'account',
- msg='SelfContact is not on top')
- else:
- self.assertTrue(p_model[self.C_TYPE] == 'group',
- msg='Contact not found in a group')
-
- def assert_group_is_in_roster(self, group, account):
- #TODO
- pass
-
- def assert_account_is_in_roster(self, acc):
- titerA = self.roster._get_account_iter(acc, model=self.roster.model)
- self.assertTrue(self.roster.model.iter_is_valid(titerA),
- msg='Account iter is invalid')
-
- acc_model = self.roster.model[titerA]
- self.assertEquals(acc_model[self.C_TYPE], 'account',
- msg='No account found')
-
- if not self.roster.regroup:
- self.assertEquals(acc_model[self.C_ACCOUNT], acc,
- msg='Account not found')
-
- self_jid = gajim.get_jid_from_account(acc)
- self.assertEquals(acc_model[self.C_JID], self_jid,
- msg='Account JID not found in account row')
-
- def assert_model_is_in_sync(self):
- #TODO: check that iter_n_children returns the correct numbers
- pass
-
- # tests
- def test_fill_contacts_and_groups_dicts(self):
- for acc in contacts:
- self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc)
-
- for jid in contacts[acc]:
- instances = gajim.contacts.get_contacts(acc, jid)
-
- # Created a contact for each single jid?
- self.assertTrue(len(instances) == 1)
-
- # Contacts kept their info
- contact = instances[0]
- self.assertEquals(contact.groups, contacts[acc][jid]['groups'],
- msg='Group Missmatch')
-
- groups = contacts[acc][jid]['groups'] or ['General',]
-
- def test_fill_roster_model(self):
- for acc in contacts:
- self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc)
-
- self.roster.add_account(acc)
- self.assert_account_is_in_roster(acc)
-
- self.roster.add_account_contacts(acc)
- self.assert_all_contacts_are_in_roster(acc)
-
- self.assert_model_is_in_sync()
+ def setUp(self):
+ gajim.interface = MockInterface()
+
+ self.C_NAME = roster_window.C_NAME
+ self.C_TYPE = roster_window.C_TYPE
+ self.C_JID = roster_window.C_JID
+ self.C_ACCOUNT = roster_window.C_ACCOUNT
+
+ # Add after creating RosterWindow
+ # We want to test the filling explicitly
+ gajim.contacts = contacts_module.LegacyContactsAPI()
+ gajim.connections = {}
+ self.roster = roster_window.RosterWindow()
+
+ for acc in contacts:
+ gajim.connections[acc] = MockConnection(acc)
+ gajim.contacts.add_account(acc)
+
+ ### Custom assertions
+ def assert_all_contacts_are_in_roster(self, acc):
+ for jid in contacts[acc]:
+ self.assert_contact_is_in_roster(jid, acc)
+
+ def assert_contact_is_in_roster(self, jid, account):
+ contacts = gajim.contacts.get_contacts(account, jid)
+ # check for all resources
+ for contact in contacts:
+ iters = self.roster._get_contact_iter(jid, account,
+ model=self.roster.model)
+
+ if jid != gajim.get_jid_from_account(account):
+ # We don't care for groups of SelfContact
+ self.assertTrue(len(iters) == len(contact.get_shown_groups()),
+ msg='Contact is not in all his groups')
+
+ # Are we big brother?
+ bb_jid = None
+ bb_account = None
+ family = gajim.contacts.get_metacontacts_family(account, jid)
+ if family:
+ nearby_family, bb_jid, bb_account = \
+ self.roster._get_nearby_family_and_big_brother(family, account)
+
+ is_in_nearby_family = (jid, account) in (
+ (data['jid'], data['account']) for data in nearby_family)
+ self.assertTrue(is_in_nearby_family,
+ msg='Contact not in his own nearby family')
+
+ is_big_brother = (bb_jid, bb_account) == (jid, account)
+
+ # check for each group tag
+ for titerC in iters:
+ self.assertTrue(self.roster.model.iter_is_valid(titerC),
+ msg='Contact iter invalid')
+
+ c_model = self.roster.model[titerC]
+ self.assertEquals(contact.get_shown_name(), c_model[self.C_NAME],
+ msg='Contact name missmatch')
+ self.assertEquals(contact.jid, c_model[self.C_JID],
+ msg='Jid missmatch')
+
+ if not self.roster.regroup:
+ self.assertEquals(account, c_model[self.C_ACCOUNT],
+ msg='Account missmatch')
+
+ # Check for correct nesting
+ parent_iter = self.roster.model.iter_parent(titerC)
+ p_model = self.roster.model[parent_iter]
+ if family:
+ if is_big_brother:
+ self.assertTrue(p_model[self.C_TYPE] == 'group',
+ msg='Big Brother is not on top')
+ else:
+ self.assertTrue(p_model[self.C_TYPE] == 'contact',
+ msg='Little Brother brother has no BigB')
+ else:
+ if jid == gajim.get_jid_from_account(account):
+ self.assertTrue(p_model[self.C_TYPE] == 'account',
+ msg='SelfContact is not on top')
+ else:
+ self.assertTrue(p_model[self.C_TYPE] == 'group',
+ msg='Contact not found in a group')
+
+ def assert_group_is_in_roster(self, group, account):
+ #TODO
+ pass
+
+ def assert_account_is_in_roster(self, acc):
+ titerA = self.roster._get_account_iter(acc, model=self.roster.model)
+ self.assertTrue(self.roster.model.iter_is_valid(titerA),
+ msg='Account iter is invalid')
+
+ acc_model = self.roster.model[titerA]
+ self.assertEquals(acc_model[self.C_TYPE], 'account',
+ msg='No account found')
+
+ if not self.roster.regroup:
+ self.assertEquals(acc_model[self.C_ACCOUNT], acc,
+ msg='Account not found')
+
+ self_jid = gajim.get_jid_from_account(acc)
+ self.assertEquals(acc_model[self.C_JID], self_jid,
+ msg='Account JID not found in account row')
+
+ def assert_model_is_in_sync(self):
+ #TODO: check that iter_n_children returns the correct numbers
+ pass
+
+ # tests
+ def test_fill_contacts_and_groups_dicts(self):
+ for acc in contacts:
+ self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc)
+
+ for jid in contacts[acc]:
+ instances = gajim.contacts.get_contacts(acc, jid)
+
+ # Created a contact for each single jid?
+ self.assertTrue(len(instances) == 1)
+
+ # Contacts kept their info
+ contact = instances[0]
+ self.assertEquals(contact.groups, contacts[acc][jid]['groups'],
+ msg='Group Missmatch')
+
+ groups = contacts[acc][jid]['groups'] or ['General',]
+
+ def test_fill_roster_model(self):
+ for acc in contacts:
+ self.roster.fill_contacts_and_groups_dicts(contacts[acc], acc)
+
+ self.roster.add_account(acc)
+ self.assert_account_is_in_roster(acc)
+
+ self.roster.add_account_contacts(acc)
+ self.assert_all_contacts_are_in_roster(acc)
+
+ self.assert_model_is_in_sync()
class TestRosterWindowRegrouped(TestRosterWindow):
- def setUp(self):
- gajim.config.set('mergeaccounts', True)
- TestRosterWindow.setUp(self)
+ def setUp(self):
+ gajim.config.set('mergeaccounts', True)
+ TestRosterWindow.setUp(self)
- def test_toggle_regroup(self):
- self.roster.regroup = not self.roster.regroup
- self.roster.setup_and_draw_roster()
- self.roster.regroup = not self.roster.regroup
- self.roster.setup_and_draw_roster()
+ def test_toggle_regroup(self):
+ self.roster.regroup = not self.roster.regroup
+ self.roster.setup_and_draw_roster()
+ self.roster.regroup = not self.roster.regroup
+ self.roster.setup_and_draw_roster()
class TestRosterWindowMetaContacts(TestRosterWindowRegrouped):
- def test_receive_metacontact_data(self):
- for complete_data in metacontact_data:
- t_acc = complete_data[0]['account']
- t_jid = complete_data[0]['jid']
- data = complete_data[1:]
- for brother in data:
- acc = brother['account']
- jid = brother['jid']
- gajim.contacts.add_metacontact(t_acc, t_jid, acc, jid)
- self.roster.setup_and_draw_roster()
+ def test_receive_metacontact_data(self):
+ for complete_data in metacontact_data:
+ t_acc = complete_data[0]['account']
+ t_jid = complete_data[0]['jid']
+ data = complete_data[1:]
+ for brother in data:
+ acc = brother['account']
+ jid = brother['jid']
+ gajim.contacts.add_metacontact(t_acc, t_jid, acc, jid)
+ self.roster.setup_and_draw_roster()
- def test_connect_new_metacontact(self):
- self.test_fill_roster_model()
+ def test_connect_new_metacontact(self):
+ self.test_fill_roster_model()
- jid = u'coolstuff@gajim.org'
- contact = gajim.contacts.create_contact(jid, account1)
- gajim.contacts.add_contact(account1, contact)
- self.roster.add_contact(jid, account1)
- self.roster.chg_contact_status(contact, 'offline', '', account1)
+ jid = u'coolstuff@gajim.org'
+ contact = gajim.contacts.create_contact(jid, account1)
+ gajim.contacts.add_contact(account1, contact)
+ self.roster.add_contact(jid, account1)
+ self.roster.chg_contact_status(contact, 'offline', '', account1)
- gajim.contacts.add_metacontact(account1, u'samejid@gajim.org',
- account1, jid)
- self.roster.chg_contact_status(contact, 'online', '', account1)
+ gajim.contacts.add_metacontact(account1, u'samejid@gajim.org',
+ account1, jid)
+ self.roster.chg_contact_status(contact, 'online', '', account1)
- self.assert_model_is_in_sync()
+ self.assert_model_is_in_sync()
if __name__ == '__main__':
- unittest.main()
-
-# vim: se ts=3:
+ unittest.main()
diff --git a/test/integration/test_xmpp_client_nb.py b/test/integration/test_xmpp_client_nb.py
index 24a54a3ca..2efdcfe91 100644
--- a/test/integration/test_xmpp_client_nb.py
+++ b/test/integration/test_xmpp_client_nb.py
@@ -24,148 +24,146 @@ xmpp_server_port = ('gajim.org', 5222)
credentials = ['unittest', 'testtest', 'res']
class TestNonBlockingClient(unittest.TestCase):
- '''
- Test Cases class for NonBlockingClient.
- '''
- def setUp(self):
- ''' IdleQueue thread is run and dummy connection is created. '''
- self.idlequeue_thread = IdleQueueThread()
- self.connection = MockConnection() # for dummy callbacks
- self.idlequeue_thread.start()
-
- def tearDown(self):
- ''' IdleQueue thread is stopped. '''
- self.idlequeue_thread.stop_thread()
- self.idlequeue_thread.join()
-
- self.client = None
-
- def open_stream(self, server_port, wrong_pass=False):
- '''
- Method opening the XMPP connection. It returns when <stream:features>
- is received from server.
-
- :param server_port: tuple of (hostname, port) for where the client should
- connect.
- '''
-
- class TempConnection():
- def get_password(self, cb):
- if wrong_pass:
- cb('wrong pass')
- else:
- cb(credentials[1])
- def on_connect_failure(self):
- pass
-
- self.client = client_nb.NonBlockingClient(
- domain=server_port[0],
- idlequeue=self.idlequeue_thread.iq,
- caller=Mock(realClass=TempConnection))
-
- self.client.connect(
- hostname=server_port[0],
- port=server_port[1],
- on_connect=lambda *args: self.connection.on_connect(True, *args),
- on_connect_failure=lambda *args: self.connection.on_connect(
- False, *args))
-
- self.assert_(self.connection.wait(),
- msg='waiting for callback from client constructor')
-
- # if on_connect was called, client has to be connected and vice versa
- if self.connection.connect_succeeded:
- self.assert_(self.client.get_connect_type())
- else:
- self.assert_(not self.client.get_connect_type())
-
- def client_auth(self, username, password, resource, sasl):
- '''
- Method authenticating connected client with supplied credentials. Returns
- when authentication is over.
-
- :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication
- :todo: to check and be more specific about when it returns
- (bind, session..)
- '''
- self.client.auth(username, password, resource, sasl,
- on_auth=self.connection.on_auth)
-
- self.assert_(self.connection.wait(), msg='waiting for authentication')
-
- def do_disconnect(self):
- '''
- Does disconnecting of connected client. Returns when TCP connection is
- closed.
- '''
- self.client.RegisterDisconnectHandler(self.connection.set_event)
- self.client.disconnect()
-
- self.assertTrue(self.connection.wait(), msg='waiting for disconnecting')
-
- def test_proper_connect_sasl(self):
- '''
- The ideal testcase - client is connected, authenticated with SASL and
- then disconnected.
- '''
- self.open_stream(xmpp_server_port)
-
- # if client is not connected, lets raise the AssertionError
- self.assert_(self.client.get_connect_type())
- # client.disconnect() is already called from NBClient via
- # _on_connected_failure, no need to call it here
-
- self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1)
- self.assert_(self.connection.con)
- self.assert_(self.connection.auth=='sasl', msg='Unable to auth via SASL')
-
- self.do_disconnect()
-
- def test_proper_connect_oldauth(self):
- '''
- The ideal testcase - client is connected, authenticated with old auth and
- then disconnected.
- '''
- self.open_stream(xmpp_server_port)
- self.assert_(self.client.get_connect_type())
- self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0)
- self.assert_(self.connection.con)
- features = self.client.Dispatcher.Stream.features
- if not features.getTag('auth'):
- print "Server doesn't support old authentication type, ignoring test"
- else:
- self.assert_(self.connection.auth=='old_auth',
- msg='Unable to auth via old_auth')
- self.do_disconnect()
-
- def test_connect_to_nonexisting_host(self):
- '''
- Connect to nonexisting host. DNS request for A records should return
- nothing.
- '''
- self.open_stream(('fdsfsdf.fdsf.fss', 5222))
- self.assert_(not self.client.get_connect_type())
-
- def test_connect_to_wrong_port(self):
- '''
- Connect to nonexisting server. DNS request for A records should return an
- IP but there shouldn't be XMPP server running on specified port.
- '''
- self.open_stream((xmpp_server_port[0], 31337))
- self.assert_(not self.client.get_connect_type())
-
- def test_connect_with_wrong_creds(self):
- '''
- Connecting with invalid password.
- '''
- self.open_stream(xmpp_server_port, wrong_pass=True)
- self.assert_(self.client.get_connect_type())
- self.client_auth(credentials[0], 'wrong pass', credentials[2], sasl=1)
- self.assert_(self.connection.auth is None)
- self.do_disconnect()
+ '''
+ Test Cases class for NonBlockingClient.
+ '''
+ def setUp(self):
+ ''' IdleQueue thread is run and dummy connection is created. '''
+ self.idlequeue_thread = IdleQueueThread()
+ self.connection = MockConnection() # for dummy callbacks
+ self.idlequeue_thread.start()
+
+ def tearDown(self):
+ ''' IdleQueue thread is stopped. '''
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ self.client = None
+
+ def open_stream(self, server_port, wrong_pass=False):
+ '''
+ Method opening the XMPP connection. It returns when <stream:features>
+ is received from server.
+
+ :param server_port: tuple of (hostname, port) for where the client should
+ connect.
+ '''
+
+ class TempConnection():
+ def get_password(self, cb):
+ if wrong_pass:
+ cb('wrong pass')
+ else:
+ cb(credentials[1])
+ def on_connect_failure(self):
+ pass
+
+ self.client = client_nb.NonBlockingClient(
+ domain=server_port[0],
+ idlequeue=self.idlequeue_thread.iq,
+ caller=Mock(realClass=TempConnection))
+
+ self.client.connect(
+ hostname=server_port[0],
+ port=server_port[1],
+ on_connect=lambda *args: self.connection.on_connect(True, *args),
+ on_connect_failure=lambda *args: self.connection.on_connect(
+ False, *args))
+
+ self.assert_(self.connection.wait(),
+ msg='waiting for callback from client constructor')
+
+ # if on_connect was called, client has to be connected and vice versa
+ if self.connection.connect_succeeded:
+ self.assert_(self.client.get_connect_type())
+ else:
+ self.assert_(not self.client.get_connect_type())
+
+ def client_auth(self, username, password, resource, sasl):
+ '''
+ Method authenticating connected client with supplied credentials. Returns
+ when authentication is over.
+
+ :param sasl: whether to use sasl (sasl=1) or old (sasl=0) authentication
+ :todo: to check and be more specific about when it returns
+ (bind, session..)
+ '''
+ self.client.auth(username, password, resource, sasl,
+ on_auth=self.connection.on_auth)
+
+ self.assert_(self.connection.wait(), msg='waiting for authentication')
+
+ def do_disconnect(self):
+ '''
+ Does disconnecting of connected client. Returns when TCP connection is
+ closed.
+ '''
+ self.client.RegisterDisconnectHandler(self.connection.set_event)
+ self.client.disconnect()
+
+ self.assertTrue(self.connection.wait(), msg='waiting for disconnecting')
+
+ def test_proper_connect_sasl(self):
+ '''
+ The ideal testcase - client is connected, authenticated with SASL and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+
+ # if client is not connected, lets raise the AssertionError
+ self.assert_(self.client.get_connect_type())
+ # client.disconnect() is already called from NBClient via
+ # _on_connected_failure, no need to call it here
+
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=1)
+ self.assert_(self.connection.con)
+ self.assert_(self.connection.auth=='sasl', msg='Unable to auth via SASL')
+
+ self.do_disconnect()
+
+ def test_proper_connect_oldauth(self):
+ '''
+ The ideal testcase - client is connected, authenticated with old auth and
+ then disconnected.
+ '''
+ self.open_stream(xmpp_server_port)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], credentials[1], credentials[2], sasl=0)
+ self.assert_(self.connection.con)
+ features = self.client.Dispatcher.Stream.features
+ if not features.getTag('auth'):
+ print "Server doesn't support old authentication type, ignoring test"
+ else:
+ self.assert_(self.connection.auth=='old_auth',
+ msg='Unable to auth via old_auth')
+ self.do_disconnect()
+
+ def test_connect_to_nonexisting_host(self):
+ '''
+ Connect to nonexisting host. DNS request for A records should return
+ nothing.
+ '''
+ self.open_stream(('fdsfsdf.fdsf.fss', 5222))
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_to_wrong_port(self):
+ '''
+ Connect to nonexisting server. DNS request for A records should return an
+ IP but there shouldn't be XMPP server running on specified port.
+ '''
+ self.open_stream((xmpp_server_port[0], 31337))
+ self.assert_(not self.client.get_connect_type())
+
+ def test_connect_with_wrong_creds(self):
+ '''
+ Connecting with invalid password.
+ '''
+ self.open_stream(xmpp_server_port, wrong_pass=True)
+ self.assert_(self.client.get_connect_type())
+ self.client_auth(credentials[0], 'wrong pass', credentials[2], sasl=1)
+ self.assert_(self.connection.auth is None)
+ self.do_disconnect()
if __name__ == '__main__':
- unittest.main()
-
-# vim: se ts=3:
+ unittest.main()
diff --git a/test/integration/test_xmpp_transports_nb.py b/test/integration/test_xmpp_transports_nb.py
index ef9908903..f7de8acf9 100644
--- a/test/integration/test_xmpp_transports_nb.py
+++ b/test/integration/test_xmpp_transports_nb.py
@@ -14,265 +14,263 @@ from common.xmpp import transports_nb
class AbstractTransportTest(unittest.TestCase):
- ''' Encapsulates Idlequeue instantiation for transports and more...'''
-
- def setUp(self):
- ''' IdleQueue thread is run and dummy connection is created. '''
- self.idlequeue_thread = IdleQueueThread()
- self.idlequeue_thread.start()
- self._setup_hook()
-
- def tearDown(self):
- ''' IdleQueue thread is stopped. '''
- self._teardown_hook()
- self.idlequeue_thread.stop_thread()
- self.idlequeue_thread.join()
-
- def _setup_hook(self):
- pass
-
- def _teardown_hook(self):
- pass
-
- def expect_receive(self, expected, count=1, msg=None):
- '''
- Returns a callback function that will assert whether the data passed to
- it equals the one specified when calling this function.
-
- Can be used to make sure transport dispatch correct data.
- '''
- def receive(data, *args, **kwargs):
- self.assertEqual(data, expected, msg=msg)
- self._expected_count -= 1
- self._expected_count = count
- return receive
-
- def have_received_expected(self):
- '''
- Plays together with expect_receive(). Will return true if expected_rcv
- callback was called as often as specified
- '''
- return self._expected_count == 0
+ ''' Encapsulates Idlequeue instantiation for transports and more...'''
+
+ def setUp(self):
+ ''' IdleQueue thread is run and dummy connection is created. '''
+ self.idlequeue_thread = IdleQueueThread()
+ self.idlequeue_thread.start()
+ self._setup_hook()
+
+ def tearDown(self):
+ ''' IdleQueue thread is stopped. '''
+ self._teardown_hook()
+ self.idlequeue_thread.stop_thread()
+ self.idlequeue_thread.join()
+
+ def _setup_hook(self):
+ pass
+
+ def _teardown_hook(self):
+ pass
+
+ def expect_receive(self, expected, count=1, msg=None):
+ '''
+ Returns a callback function that will assert whether the data passed to
+ it equals the one specified when calling this function.
+
+ Can be used to make sure transport dispatch correct data.
+ '''
+ def receive(data, *args, **kwargs):
+ self.assertEqual(data, expected, msg=msg)
+ self._expected_count -= 1
+ self._expected_count = count
+ return receive
+
+ def have_received_expected(self):
+ '''
+ Plays together with expect_receive(). Will return true if expected_rcv
+ callback was called as often as specified
+ '''
+ return self._expected_count == 0
class TestNonBlockingTCP(AbstractTransportTest):
- '''
- Test class for NonBlockingTCP. Will actually try to connect to an existing
- XMPP server.
- '''
- class MockClient(IdleMock):
- ''' Simple client to test transport functionality '''
- def __init__(self, idlequeue, testcase):
- self.idlequeue = idlequeue
- self.testcase = testcase
- IdleMock.__init__(self)
-
- def do_connect(self, establish_tls=False, proxy_dict=None):
- try:
- ips = socket.getaddrinfo('gajim.org', 5222,
- socket.AF_UNSPEC,socket.SOCK_STREAM)
- ip = ips[0]
- except socket.error, e:
- self.testcase.fail(msg=str(e))
-
- self.socket = transports_nb.NonBlockingTCP(
- raise_event=lambda event_type, data: self.testcase.assertTrue(
- event_type and data),
- on_disconnect=lambda: self.on_success(mode='SocketDisconnect'),
- idlequeue=self.idlequeue,
- estabilish_tls=establish_tls,
- certs=('../data/other/cacerts.pem', 'tmp/cacerts.pem'),
- proxy_dict=proxy_dict)
-
- self.socket.PlugIn(self)
-
- self.socket.connect(conn_5tuple=ip,
- on_connect=lambda: self.on_success(mode='TCPconnect'),
- on_connect_failure=self.on_failure)
- self.testcase.assertTrue(self.wait(), msg='Connection timed out')
-
- def do_disconnect(self):
- self.socket.disconnect()
- self.testcase.assertTrue(self.wait(), msg='Disconnect timed out')
-
- def on_failure(self, err_message):
- self.set_event()
- self.testcase.fail(msg=err_message)
-
- def on_success(self, mode, data=None):
- if mode == "TCPconnect":
- pass
- if mode == "SocketDisconnect":
- pass
- self.set_event()
-
- def _setup_hook(self):
- self.client = self.MockClient(idlequeue=self.idlequeue_thread.iq,
- testcase=self)
-
- def _teardown_hook(self):
- if self.client.socket.state == 'CONNECTED':
- self.client.do_disconnect()
-
- def test_connect_disconnect_plain(self):
- ''' Establish plain connection '''
- self.client.do_connect(establish_tls=False)
- self.assertEquals(self.client.socket.state, 'CONNECTED')
- self.client.do_disconnect()
- self.assertEquals(self.client.socket.state, 'DISCONNECTED')
-
-# def test_connect_disconnect_ssl(self):
-# ''' Establish SSL (not TLS) connection '''
-# self.client.do_connect(establish_tls=True)
-# self.assertEquals(self.client.socket.state, 'CONNECTED')
-# self.client.do_disconnect()
-# self.assertEquals(self.client.socket.state, 'DISCONNECTED')
-
- def test_do_receive(self):
- ''' Test _do_receive method by overwriting socket.recv '''
- self.client.do_connect()
- sock = self.client.socket
-
- # transport shall receive data
- data = "Please don't fail"
- sock._recv = lambda buffer: data
- sock.onreceive(self.expect_receive(data))
- sock._do_receive()
- self.assertTrue(self.have_received_expected(), msg='Did not receive data')
- self.assert_(self.client.socket.state == 'CONNECTED')
-
- # transport shall do nothing as an non-fatal SSL is simulated
- sock._recv = lambda buffer: None
- sock.onreceive(self.assertFalse) # we did not receive anything...
- sock._do_receive()
- self.assert_(self.client.socket.state == 'CONNECTED')
-
- # transport shall disconnect as remote side closed the connection
- sock._recv = lambda buffer: ''
- sock.onreceive(self.assertFalse) # we did not receive anything...
- sock._do_receive()
- self.assert_(self.client.socket.state == 'DISCONNECTED')
-
- def test_do_send(self):
- ''' Test _do_send method by overwriting socket.send '''
- self.client.do_connect()
- sock = self.client.socket
-
- outgoing = [] # what we have actually send to our socket.socket
- data_part1 = "Please don't "
- data_part2 = "fail!"
- data_complete = data_part1 + data_part2
-
- # Simulate everything could be send in one go
- def _send_all(data):
- outgoing.append(data)
- return len(data)
- sock._send = _send_all
- sock.send(data_part1)
- sock.send(data_part2)
- sock._do_send()
- sock._do_send()
- self.assertTrue(self.client.socket.state == 'CONNECTED')
- self.assertTrue(data_part1 in outgoing and data_part2 in outgoing)
- self.assertFalse(sock.sendqueue and sock.sendbuff,
- msg='There is still unsend data in buffers')
-
- # Simulate data could only be sent in chunks
- self.chunk_count = 0
- outgoing = []
- def _send_chunks(data):
- if self.chunk_count == 0:
- outgoing.append(data_part1)
- self.chunk_count += 1
- return len(data_part1)
- else:
- outgoing.append(data_part2)
- return len(data_part2)
- sock._send = _send_chunks
- sock.send(data_complete)
- sock._do_send() # process first chunk
- sock._do_send() # process the second one
- self.assertTrue(self.client.socket.state == 'CONNECTED')
- self.assertTrue(data_part1 in outgoing and data_part2 in outgoing)
- self.assertFalse(sock.sendqueue and sock.sendbuff,
- msg='There is still unsend data in buffers')
+ '''
+ Test class for NonBlockingTCP. Will actually try to connect to an existing
+ XMPP server.
+ '''
+ class MockClient(IdleMock):
+ ''' Simple client to test transport functionality '''
+ def __init__(self, idlequeue, testcase):
+ self.idlequeue = idlequeue
+ self.testcase = testcase
+ IdleMock.__init__(self)
+
+ def do_connect(self, establish_tls=False, proxy_dict=None):
+ try:
+ ips = socket.getaddrinfo('gajim.org', 5222,
+ socket.AF_UNSPEC,socket.SOCK_STREAM)
+ ip = ips[0]
+ except socket.error, e:
+ self.testcase.fail(msg=str(e))
+
+ self.socket = transports_nb.NonBlockingTCP(
+ raise_event=lambda event_type, data: self.testcase.assertTrue(
+ event_type and data),
+ on_disconnect=lambda: self.on_success(mode='SocketDisconnect'),
+ idlequeue=self.idlequeue,
+ estabilish_tls=establish_tls,
+ certs=('../data/other/cacerts.pem', 'tmp/cacerts.pem'),
+ proxy_dict=proxy_dict)
+
+ self.socket.PlugIn(self)
+
+ self.socket.connect(conn_5tuple=ip,
+ on_connect=lambda: self.on_success(mode='TCPconnect'),
+ on_connect_failure=self.on_failure)
+ self.testcase.assertTrue(self.wait(), msg='Connection timed out')
+
+ def do_disconnect(self):
+ self.socket.disconnect()
+ self.testcase.assertTrue(self.wait(), msg='Disconnect timed out')
+
+ def on_failure(self, err_message):
+ self.set_event()
+ self.testcase.fail(msg=err_message)
+
+ def on_success(self, mode, data=None):
+ if mode == "TCPconnect":
+ pass
+ if mode == "SocketDisconnect":
+ pass
+ self.set_event()
+
+ def _setup_hook(self):
+ self.client = self.MockClient(idlequeue=self.idlequeue_thread.iq,
+ testcase=self)
+
+ def _teardown_hook(self):
+ if self.client.socket.state == 'CONNECTED':
+ self.client.do_disconnect()
+
+ def test_connect_disconnect_plain(self):
+ ''' Establish plain connection '''
+ self.client.do_connect(establish_tls=False)
+ self.assertEquals(self.client.socket.state, 'CONNECTED')
+ self.client.do_disconnect()
+ self.assertEquals(self.client.socket.state, 'DISCONNECTED')
+
+# def test_connect_disconnect_ssl(self):
+# ''' Establish SSL (not TLS) connection '''
+# self.client.do_connect(establish_tls=True)
+# self.assertEquals(self.client.socket.state, 'CONNECTED')
+# self.client.do_disconnect()
+# self.assertEquals(self.client.socket.state, 'DISCONNECTED')
+
+ def test_do_receive(self):
+ ''' Test _do_receive method by overwriting socket.recv '''
+ self.client.do_connect()
+ sock = self.client.socket
+
+ # transport shall receive data
+ data = "Please don't fail"
+ sock._recv = lambda buffer: data
+ sock.onreceive(self.expect_receive(data))
+ sock._do_receive()
+ self.assertTrue(self.have_received_expected(), msg='Did not receive data')
+ self.assert_(self.client.socket.state == 'CONNECTED')
+
+ # transport shall do nothing as an non-fatal SSL is simulated
+ sock._recv = lambda buffer: None
+ sock.onreceive(self.assertFalse) # we did not receive anything...
+ sock._do_receive()
+ self.assert_(self.client.socket.state == 'CONNECTED')
+
+ # transport shall disconnect as remote side closed the connection
+ sock._recv = lambda buffer: ''
+ sock.onreceive(self.assertFalse) # we did not receive anything...
+ sock._do_receive()
+ self.assert_(self.client.socket.state == 'DISCONNECTED')
+
+ def test_do_send(self):
+ ''' Test _do_send method by overwriting socket.send '''
+ self.client.do_connect()
+ sock = self.client.socket
+
+ outgoing = [] # what we have actually send to our socket.socket
+ data_part1 = "Please don't "
+ data_part2 = "fail!"
+ data_complete = data_part1 + data_part2
+
+ # Simulate everything could be send in one go
+ def _send_all(data):
+ outgoing.append(data)
+ return len(data)
+ sock._send = _send_all
+ sock.send(data_part1)
+ sock.send(data_part2)
+ sock._do_send()
+ sock._do_send()
+ self.assertTrue(self.client.socket.state == 'CONNECTED')
+ self.assertTrue(data_part1 in outgoing and data_part2 in outgoing)
+ self.assertFalse(sock.sendqueue and sock.sendbuff,
+ msg='There is still unsend data in buffers')
+
+ # Simulate data could only be sent in chunks
+ self.chunk_count = 0
+ outgoing = []
+ def _send_chunks(data):
+ if self.chunk_count == 0:
+ outgoing.append(data_part1)
+ self.chunk_count += 1
+ return len(data_part1)
+ else:
+ outgoing.append(data_part2)
+ return len(data_part2)
+ sock._send = _send_chunks
+ sock.send(data_complete)
+ sock._do_send() # process first chunk
+ sock._do_send() # process the second one
+ self.assertTrue(self.client.socket.state == 'CONNECTED')
+ self.assertTrue(data_part1 in outgoing and data_part2 in outgoing)
+ self.assertFalse(sock.sendqueue and sock.sendbuff,
+ msg='There is still unsend data in buffers')
class TestNonBlockingHTTP(AbstractTransportTest):
- ''' Test class for NonBlockingHTTP transport'''
-
- bosh_http_dict = {
- 'http_uri': 'http://gajim.org:5280/http-bind',
- 'http_version': 'HTTP/1.1',
- 'http_persistent': True,
- 'add_proxy_headers': False
- }
-
- def _get_transport(self, http_dict, proxy_dict=None):
- return transports_nb.NonBlockingHTTP(
- raise_event=None,
- on_disconnect=None,
- idlequeue=self.idlequeue_thread.iq,
- estabilish_tls=False,
- certs=None,
- on_http_request_possible=lambda: None,
- on_persistent_fallback=None,
- http_dict=http_dict,
- proxy_dict=proxy_dict,
- )
-
- def test_parse_own_http_message(self):
- ''' Build a HTTP message and try to parse it afterwards '''
- transport = self._get_transport(self.bosh_http_dict)
-
- data = "<test>Please don't fail!</test>"
- http_message = transport.build_http_message(data)
- statusline, headers, http_body, buffer_rest = transport.parse_http_message(
- http_message)
-
- self.assertFalse(bool(buffer_rest))
- self.assertTrue(statusline and isinstance(statusline, list))
- self.assertTrue(headers and isinstance(headers, dict))
- self.assertEqual(data, http_body, msg='Input and output are different')
-
- def test_receive_http_message(self):
- ''' Let _on_receive handle some http messages '''
- transport = self._get_transport(self.bosh_http_dict)
-
- header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +
- "Content-Length: 88\r\n\r\n")
- payload = "<test>Please don't fail!</test>"
- body = "<body xmlns='http://jabber.org/protocol/httpbind'>%s</body>" \
- % payload
- message = "%s%s" % (header, body)
-
- # try to receive in one go
- transport.onreceive(self.expect_receive(body, msg='Failed: In one go'))
- transport._on_receive(message)
- self.assertTrue(self.have_received_expected(), msg='Failed: In one go')
-
- def test_receive_http_message_in_chunks(self):
- ''' Let _on_receive handle some chunked http messages '''
- transport = self._get_transport(self.bosh_http_dict)
-
- payload = "<test>Please don't fail!\n\n</test>"
- body = "<body xmlns='http://jabber.org/protocol/httpbind'>%s</body>" \
- % payload
- header = "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +\
- "Content-Length: %i\r\n\r\n" % len(body)
- message = "%s%s" % (header, body)
-
- chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \
- message[73:85], message[85:]
- nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x"
- chunks = (chunk1, chunk2, chunk3, chunk4, nextmessage_chunk)
-
- transport.onreceive(self.expect_receive(body, msg='Failed: In chunks'))
- for chunk in chunks:
- transport._on_receive(chunk)
- self.assertTrue(self.have_received_expected(), msg='Failed: In chunks')
+ ''' Test class for NonBlockingHTTP transport'''
+
+ bosh_http_dict = {
+ 'http_uri': 'http://gajim.org:5280/http-bind',
+ 'http_version': 'HTTP/1.1',
+ 'http_persistent': True,
+ 'add_proxy_headers': False
+ }
+
+ def _get_transport(self, http_dict, proxy_dict=None):
+ return transports_nb.NonBlockingHTTP(
+ raise_event=None,
+ on_disconnect=None,
+ idlequeue=self.idlequeue_thread.iq,
+ estabilish_tls=False,
+ certs=None,
+ on_http_request_possible=lambda: None,
+ on_persistent_fallback=None,
+ http_dict=http_dict,
+ proxy_dict=proxy_dict,
+ )
+
+ def test_parse_own_http_message(self):
+ ''' Build a HTTP message and try to parse it afterwards '''
+ transport = self._get_transport(self.bosh_http_dict)
+
+ data = "<test>Please don't fail!</test>"
+ http_message = transport.build_http_message(data)
+ statusline, headers, http_body, buffer_rest = transport.parse_http_message(
+ http_message)
+
+ self.assertFalse(bool(buffer_rest))
+ self.assertTrue(statusline and isinstance(statusline, list))
+ self.assertTrue(headers and isinstance(headers, dict))
+ self.assertEqual(data, http_body, msg='Input and output are different')
+
+ def test_receive_http_message(self):
+ ''' Let _on_receive handle some http messages '''
+ transport = self._get_transport(self.bosh_http_dict)
+
+ header = ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +
+ "Content-Length: 88\r\n\r\n")
+ payload = "<test>Please don't fail!</test>"
+ body = "<body xmlns='http://jabber.org/protocol/httpbind'>%s</body>" \
+ % payload
+ message = "%s%s" % (header, body)
+
+ # try to receive in one go
+ transport.onreceive(self.expect_receive(body, msg='Failed: In one go'))
+ transport._on_receive(message)
+ self.assertTrue(self.have_received_expected(), msg='Failed: In one go')
+
+ def test_receive_http_message_in_chunks(self):
+ ''' Let _on_receive handle some chunked http messages '''
+ transport = self._get_transport(self.bosh_http_dict)
+
+ payload = "<test>Please don't fail!\n\n</test>"
+ body = "<body xmlns='http://jabber.org/protocol/httpbind'>%s</body>" \
+ % payload
+ header = "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\n" +\
+ "Content-Length: %i\r\n\r\n" % len(body)
+ message = "%s%s" % (header, body)
+
+ chunk1, chunk2, chunk3, chunk4 = message[:20], message[20:73], \
+ message[73:85], message[85:]
+ nextmessage_chunk = "\r\n\r\nHTTP/1.1 200 OK\r\nContent-Type: text/x"
+ chunks = (chunk1, chunk2, chunk3, chunk4, nextmessage_chunk)
+
+ transport.onreceive(self.expect_receive(body, msg='Failed: In chunks'))
+ for chunk in chunks:
+ transport._on_receive(chunk)
+ self.assertTrue(self.have_received_expected(), msg='Failed: In chunks')
if __name__ == '__main__':
- unittest.main()
-
-# vim: se ts=3:
+ unittest.main()
diff --git a/test/lib/__init__.py b/test/lib/__init__.py
index 7d0b1bd98..8d211a03e 100644
--- a/test/lib/__init__.py
+++ b/test/lib/__init__.py
@@ -7,8 +7,8 @@ shortargs = 'hnv:'
longargs = 'help no-x verbose='
opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split())
for o, a in opts:
- if o in ('-n', '--no-x'):
- use_x = False
+ if o in ('-n', '--no-x'):
+ use_x = False
gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../..')
@@ -25,28 +25,26 @@ import __builtin__
__builtin__._ = lambda x: x
def setup_env():
- # wipe config directory
- if os.path.isdir(configdir):
- import shutil
- shutil.rmtree(configdir)
+ # wipe config directory
+ if os.path.isdir(configdir):
+ import shutil
+ shutil.rmtree(configdir)
- os.mkdir(configdir)
+ os.mkdir(configdir)
- import common.configpaths
- common.configpaths.gajimpaths.init(configdir)
- common.configpaths.gajimpaths.init_profile()
+ import common.configpaths
+ common.configpaths.gajimpaths.init(configdir)
+ common.configpaths.gajimpaths.init_profile()
- # for some reason common.gajim needs to be imported before xmpppy?
- from common import gajim
+ # for some reason common.gajim needs to be imported before xmpppy?
+ from common import gajim
- import logging
- logging.basicConfig()
+ import logging
+ logging.basicConfig()
- gajim.DATA_DIR = gajim_root + '/data'
- gajim.use_x = use_x
+ gajim.DATA_DIR = gajim_root + '/data'
+ gajim.use_x = use_x
- if use_x:
- import gtkgui_helpers
- gtkgui_helpers.GUI_DIR = gajim_root + '/data/gui'
-
-# vim: se ts=3:
+ if use_x:
+ import gtkgui_helpers
+ gtkgui_helpers.GUI_DIR = gajim_root + '/data/gui'
diff --git a/test/lib/data.py b/test/lib/data.py
index c713de0ac..af2a87057 100755
--- a/test/lib/data.py
+++ b/test/lib/data.py
@@ -5,75 +5,73 @@ account3 = u'dingdong.org'
contacts = {}
contacts[account1] = {
- u'myjid@'+account1: {
- 'ask': None, 'groups': [], 'name': None, 'resources': {},
- 'subscription': u'both'},
- u'default1@gajim.org': {
- 'ask': None, 'groups': [], 'name': None, 'resources': {},
- 'subscription': u'both'},
- u'default2@gajim.org': {
- 'ask': None, 'groups': [u'GroupA',], 'name': None, 'resources': {},
- 'subscription': u'both'},
- u'Cool"chârßéµö@gajim.org': {
- 'ask': None, 'groups': [u'<Cool"chârßéµö', u'GroupB'],
- 'name': None, 'resources': {}, 'subscription': u'both'},
- u'samejid@gajim.org': {
- 'ask': None, 'groups': [u'GroupA',], 'name': None, 'resources': {},
- 'subscription': u'both'}
+ u'myjid@'+account1: {
+ 'ask': None, 'groups': [], 'name': None, 'resources': {},
+ 'subscription': u'both'},
+ u'default1@gajim.org': {
+ 'ask': None, 'groups': [], 'name': None, 'resources': {},
+ 'subscription': u'both'},
+ u'default2@gajim.org': {
+ 'ask': None, 'groups': [u'GroupA',], 'name': None, 'resources': {},
+ 'subscription': u'both'},
+ u'Cool"chârßéµö@gajim.org': {
+ 'ask': None, 'groups': [u'<Cool"chârßéµö', u'GroupB'],
+ 'name': None, 'resources': {}, 'subscription': u'both'},
+ u'samejid@gajim.org': {
+ 'ask': None, 'groups': [u'GroupA',], 'name': None, 'resources': {},
+ 'subscription': u'both'}
}
contacts[account2] = {
- u'myjid@'+account2: {
- 'ask': None, 'groups': [], 'name': None, 'resources': {},
- 'subscription': u'both'},
- u'default3@gajim.org': {
- 'ask': None, 'groups': [u'GroupC',], 'name': None, 'resources': {},
- 'subscription': u'both'},
- u'asksubfrom@gajim.org': {
- 'ask': u'subscribe', 'groups': [u'GroupA',], 'name': None,
- 'resources': {}, 'subscription': u'from'},
- u'subto@gajim.org': {
- 'ask': None, 'groups': [u'GroupB'], 'name': None, 'resources': {},
- 'subscription': u'to'},
- u'samejid@gajim.org': {
- 'ask': None, 'groups': [u'GroupA', u'GroupB'], 'name': None,
- 'resources': {}, 'subscription': u'both'}
+ u'myjid@'+account2: {
+ 'ask': None, 'groups': [], 'name': None, 'resources': {},
+ 'subscription': u'both'},
+ u'default3@gajim.org': {
+ 'ask': None, 'groups': [u'GroupC',], 'name': None, 'resources': {},
+ 'subscription': u'both'},
+ u'asksubfrom@gajim.org': {
+ 'ask': u'subscribe', 'groups': [u'GroupA',], 'name': None,
+ 'resources': {}, 'subscription': u'from'},
+ u'subto@gajim.org': {
+ 'ask': None, 'groups': [u'GroupB'], 'name': None, 'resources': {},
+ 'subscription': u'to'},
+ u'samejid@gajim.org': {
+ 'ask': None, 'groups': [u'GroupA', u'GroupB'], 'name': None,
+ 'resources': {}, 'subscription': u'both'}
}
contacts[account3] = {
- #u'guypsych0\\40h.com@msn.dingdong.org': {
- # 'ask': None, 'groups': [], 'name': None, 'resources': {},
- # 'subscription': u'both'},
- u'guypsych0%h.com@msn.delx.cjb.net': {
- 'ask': u'subscribe', 'groups': [], 'name': None,
- 'resources': {}, 'subscription': u'from'},
- #u'guypsych0%h.com@msn.jabber.wiretrip.org': {
- # 'ask': None, 'groups': [], 'name': None, 'resources': {},
- # 'subscription': u'to'},
- #u'guypsycho\\40g.com@gtalk.dingdong.org': {
- # 'ask': None, 'groups': [], 'name': None,
- # 'resources': {}, 'subscription': u'both'}
+ #u'guypsych0\\40h.com@msn.dingdong.org': {
+ # 'ask': None, 'groups': [], 'name': None, 'resources': {},
+ # 'subscription': u'both'},
+ u'guypsych0%h.com@msn.delx.cjb.net': {
+ 'ask': u'subscribe', 'groups': [], 'name': None,
+ 'resources': {}, 'subscription': u'from'},
+ #u'guypsych0%h.com@msn.jabber.wiretrip.org': {
+ # 'ask': None, 'groups': [], 'name': None, 'resources': {},
+ # 'subscription': u'to'},
+ #u'guypsycho\\40g.com@gtalk.dingdong.org': {
+ # 'ask': None, 'groups': [], 'name': None,
+ # 'resources': {}, 'subscription': u'both'}
}
# We have contacts that are not in roster but only specified in the metadata
metacontact_data = [
- [{'account': account3,
- 'jid': u'guypsych0\\40h.com@msn.dingdong.org',
- 'order': 0},
- {'account': account3,
- 'jid': u'guypsych0%h.com@msn.delx.cjb.net',
- 'order': 0},
- {'account': account3,
- 'jid': u'guypsych0%h.com@msn.jabber.wiretrip.org',
- 'order': 0},
- {'account': account3,
- 'jid': u'guypsycho\\40g.com@gtalk.dingdong.org',
- 'order': 0}],
+ [{'account': account3,
+ 'jid': u'guypsych0\\40h.com@msn.dingdong.org',
+ 'order': 0},
+ {'account': account3,
+ 'jid': u'guypsych0%h.com@msn.delx.cjb.net',
+ 'order': 0},
+ {'account': account3,
+ 'jid': u'guypsych0%h.com@msn.jabber.wiretrip.org',
+ 'order': 0},
+ {'account': account3,
+ 'jid': u'guypsycho\\40g.com@gtalk.dingdong.org',
+ 'order': 0}],
- [{'account': account1,
- 'jid': u'samejid@gajim.org',
- 'order': 0},
- {'account': account2,
- 'jid': u'samejid@gajim.org',
- 'order': 0}]
- ]
-
-# vim: se ts=3:
+ [{'account': account1,
+ 'jid': u'samejid@gajim.org',
+ 'order': 0},
+ {'account': account2,
+ 'jid': u'samejid@gajim.org',
+ 'order': 0}]
+ ]
diff --git a/test/lib/gajim_mocks.py b/test/lib/gajim_mocks.py
index 9607a77c9..2f0037a00 100644
--- a/test/lib/gajim_mocks.py
+++ b/test/lib/gajim_mocks.py
@@ -8,142 +8,140 @@ from common import gajim
from common.connection_handlers import ConnectionHandlersBase
class MockConnection(Mock, ConnectionHandlersBase):
- def __init__(self, account, *args):
- Mock.__init__(self, *args)
- ConnectionHandlersBase.__init__(self)
-
- self.name = account
- self.connected = 2
- self.pep = {}
- self.blocked_contacts = {}
- self.blocked_groups = {}
- self.sessions = {}
-
- gajim.interface.instances[account] = {'infos': {}, 'disco': {},
- 'gc_config': {}, 'search': {}}
- gajim.interface.minimized_controls[account] = {}
- gajim.contacts.add_account(account)
- gajim.groups[account] = {}
- gajim.gc_connected[account] = {}
- gajim.automatic_rooms[account] = {}
- gajim.newly_added[account] = []
- gajim.to_be_removed[account] = []
- gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name')
- gajim.block_signed_in_notifications[account] = True
- gajim.sleeper_state[account] = 0
- gajim.encrypted_chats[account] = []
- gajim.last_message_time[account] = {}
- gajim.status_before_autoaway[account] = ''
- gajim.transport_avatar[account] = {}
- gajim.gajim_optional_features[account] = []
- gajim.caps_hash[account] = ''
-
- gajim.connections[account] = self
+ def __init__(self, account, *args):
+ Mock.__init__(self, *args)
+ ConnectionHandlersBase.__init__(self)
+
+ self.name = account
+ self.connected = 2
+ self.pep = {}
+ self.blocked_contacts = {}
+ self.blocked_groups = {}
+ self.sessions = {}
+
+ gajim.interface.instances[account] = {'infos': {}, 'disco': {},
+ 'gc_config': {}, 'search': {}}
+ gajim.interface.minimized_controls[account] = {}
+ gajim.contacts.add_account(account)
+ gajim.groups[account] = {}
+ gajim.gc_connected[account] = {}
+ gajim.automatic_rooms[account] = {}
+ gajim.newly_added[account] = []
+ gajim.to_be_removed[account] = []
+ gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name')
+ gajim.block_signed_in_notifications[account] = True
+ gajim.sleeper_state[account] = 0
+ gajim.encrypted_chats[account] = []
+ gajim.last_message_time[account] = {}
+ gajim.status_before_autoaway[account] = ''
+ gajim.transport_avatar[account] = {}
+ gajim.gajim_optional_features[account] = []
+ gajim.caps_hash[account] = ''
+
+ gajim.connections[account] = self
class MockWindow(Mock):
- def __init__(self, *args):
- Mock.__init__(self, *args)
- self.window = Mock()
- self._controls = {}
+ def __init__(self, *args):
+ Mock.__init__(self, *args)
+ self.window = Mock()
+ self._controls = {}
- def get_control(self, jid, account):
- try:
- return self._controls[account][jid]
- except KeyError:
- return None
+ def get_control(self, jid, account):
+ try:
+ return self._controls[account][jid]
+ except KeyError:
+ return None
- def has_control(self, jid, acct):
- return self.get_control(jid, acct) is not None
+ def has_control(self, jid, acct):
+ return self.get_control(jid, acct) is not None
- def new_tab(self, ctrl):
- account = ctrl.account
- jid = ctrl.jid
+ def new_tab(self, ctrl):
+ account = ctrl.account
+ jid = ctrl.jid
- if account not in self._controls:
- self._controls[account] = {}
+ if account not in self._controls:
+ self._controls[account] = {}
- if jid not in self._controls[account]:
- self._controls[account][jid] = {}
+ if jid not in self._controls[account]:
+ self._controls[account][jid] = {}
- self._controls[account][jid] = ctrl
+ self._controls[account][jid] = ctrl
- def __nonzero__(self):
- return True
+ def __nonzero__(self):
+ return True
class MockChatControl(Mock):
- def __init__(self, jid, account, *args):
- Mock.__init__(self, *args)
+ def __init__(self, jid, account, *args):
+ Mock.__init__(self, *args)
- self.jid = jid
- self.account = account
+ self.jid = jid
+ self.account = account
- self.parent_win = MockWindow({'get_active_control': self})
- self.session = None
+ self.parent_win = MockWindow({'get_active_control': self})
+ self.session = None
- def set_session(self, sess):
- self.session = sess
+ def set_session(self, sess):
+ self.session = sess
- def __nonzero__(self):
- return True
+ def __nonzero__(self):
+ return True
- def __eq__(self, other):
- return self is other
+ def __eq__(self, other):
+ return self is other
class MockInterface(Mock):
- def __init__(self, *args):
- Mock.__init__(self, *args)
- gajim.interface = self
- self.msg_win_mgr = Mock()
- self.roster = Mock()
+ def __init__(self, *args):
+ Mock.__init__(self, *args)
+ gajim.interface = self
+ self.msg_win_mgr = Mock()
+ self.roster = Mock()
- self.remote_ctrl = None
- self.instances = {}
- self.minimized_controls = {}
- self.status_sent_to_users = Mock()
+ self.remote_ctrl = None
+ self.instances = {}
+ self.minimized_controls = {}
+ self.status_sent_to_users = Mock()
- if gajim.use_x:
- self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
- 'closed': {}}
+ if gajim.use_x:
+ self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
+ 'closed': {}}
- import gtkgui_helpers
- gtkgui_helpers.make_jabber_state_images()
- else:
- self.jabber_state_images = {'16': Mock(), '32': Mock(),
- 'opened': Mock(), 'closed': Mock()}
+ import gtkgui_helpers
+ gtkgui_helpers.make_jabber_state_images()
+ else:
+ self.jabber_state_images = {'16': Mock(), '32': Mock(),
+ 'opened': Mock(), 'closed': Mock()}
class MockLogger(Mock):
- def __init__(self):
- Mock.__init__(self, {'write': None, 'get_transports_type': {}})
+ def __init__(self):
+ Mock.__init__(self, {'write': None, 'get_transports_type': {}})
class MockContact(Mock):
- def __nonzero__(self):
- return True
+ def __nonzero__(self):
+ return True
import random
class MockSession(Mock):
- def __init__(self, conn, jid, thread_id, type_):
- Mock.__init__(self)
+ def __init__(self, conn, jid, thread_id, type_):
+ Mock.__init__(self)
- self.conn = conn
- self.jid = jid
- self.type = type_
- self.thread_id = thread_id
+ self.conn = conn
+ self.jid = jid
+ self.type = type_
+ self.thread_id = thread_id
- if not self.thread_id:
- self.thread_id = '%0x' % random.randint(0, 10000)
+ if not self.thread_id:
+ self.thread_id = '%0x' % random.randint(0, 10000)
- def __repr__(self):
- return '<MockSession %s>' % self.thread_id
+ def __repr__(self):
+ return '<MockSession %s>' % self.thread_id
- def __nonzero__(self):
- return True
+ def __nonzero__(self):
+ return True
- def __eq__(self, other):
- return self is other
-
-# vim: se ts=3:
+ def __eq__(self, other):
+ return self is other
diff --git a/test/lib/mock.py b/test/lib/mock.py
index 2c87d53f8..de078b52b 100644
--- a/test/lib/mock.py
+++ b/test/lib/mock.py
@@ -464,5 +464,3 @@ CALLABLE = callable
-
-# vim: se ts=3:
diff --git a/test/lib/notify.py b/test/lib/notify.py
index f14100af3..b13d03678 100644
--- a/test/lib/notify.py
+++ b/test/lib/notify.py
@@ -3,15 +3,13 @@
notifications = []
def notify(event, jid, account, parameters, advanced_notif_num = None):
- notifications.append((event, jid, account, parameters, advanced_notif_num))
+ notifications.append((event, jid, account, parameters, advanced_notif_num))
def get_advanced_notification(event, account, contact):
- return None
+ return None
def get_show_in_roster(event, account, contact, session = None):
- return True
+ return True
def get_show_in_systray(event, account, contact, type_ = None):
- return True
-
-# vim: se ts=3: \ No newline at end of file
+ return True
diff --git a/test/lib/xmpp_mocks.py b/test/lib/xmpp_mocks.py
index bf60ae6dc..0b4055949 100644
--- a/test/lib/xmpp_mocks.py
+++ b/test/lib/xmpp_mocks.py
@@ -12,86 +12,84 @@ IDLEQUEUE_INTERVAL = 0.2 # polling interval. 200ms is used in Gajim as default
IDLEMOCK_TIMEOUT = 30 # how long we wait for an event
class IdleQueueThread(threading.Thread):
- '''
- Thread for regular processing of idlequeue.
- '''
- def __init__(self):
- self.iq = idlequeue.IdleQueue()
- self.stop = threading.Event() # Event to stop the thread main loop.
- self.stop.clear()
- threading.Thread.__init__(self)
-
- def run(self):
- while not self.stop.isSet():
- self.iq.process()
- time.sleep(IDLEQUEUE_INTERVAL)
-
- def stop_thread(self):
- self.stop.set()
-
-
+ '''
+ Thread for regular processing of idlequeue.
+ '''
+ def __init__(self):
+ self.iq = idlequeue.IdleQueue()
+ self.stop = threading.Event() # Event to stop the thread main loop.
+ self.stop.clear()
+ threading.Thread.__init__(self)
+
+ def run(self):
+ while not self.stop.isSet():
+ self.iq.process()
+ time.sleep(IDLEQUEUE_INTERVAL)
+
+ def stop_thread(self):
+ self.stop.set()
+
+
class IdleMock:
- '''
- Serves as template for testing objects that are normally controlled by GUI.
- Allows to wait for asynchronous callbacks with wait() method.
- '''
- def __init__(self):
- self._event = threading.Event()
- self._event.clear()
-
- def wait(self):
- '''
- Block until some callback sets the event and clearing the event
- subsequently.
- Returns True if event was set, False on timeout
- '''
- self._event.wait(IDLEMOCK_TIMEOUT)
- if self._event.isSet():
- self._event.clear()
- return True
- else:
- return False
-
- def set_event(self):
- self._event.set()
+ '''
+ Serves as template for testing objects that are normally controlled by GUI.
+ Allows to wait for asynchronous callbacks with wait() method.
+ '''
+ def __init__(self):
+ self._event = threading.Event()
+ self._event.clear()
+
+ def wait(self):
+ '''
+ Block until some callback sets the event and clearing the event
+ subsequently.
+ Returns True if event was set, False on timeout
+ '''
+ self._event.wait(IDLEMOCK_TIMEOUT)
+ if self._event.isSet():
+ self._event.clear()
+ return True
+ else:
+ return False
+
+ def set_event(self):
+ self._event.set()
class MockConnection(IdleMock, Mock):
- '''
- Class simulating Connection class from src/common/connection.py
-
- It is derived from Mock in order to avoid defining all methods
- from real Connection that are called from NBClient or Dispatcher
- ( _event_dispatcher for example)
- '''
-
- def __init__(self, *args):
- self.connect_succeeded = True
- IdleMock.__init__(self)
- Mock.__init__(self, *args)
-
- def on_connect(self, success, *args):
- '''
- Method called after connecting - after receiving <stream:features>
- from server (NOT after TLS stream restart) or connect failure
- '''
- self.connect_succeeded = success
- self.set_event()
-
-
- def on_auth(self, con, auth):
- '''
- Method called after authentication, regardless of the result.
-
- :Parameters:
- con : NonBlockingClient
- reference to authenticated object
- auth : string
- type of authetication in case of success ('old_auth', 'sasl') or
- None in case of auth failure
- '''
- self.auth_connection = con
- self.auth = auth
- self.set_event()
-
-# vim: se ts=3:
+ '''
+ Class simulating Connection class from src/common/connection.py
+
+ It is derived from Mock in order to avoid defining all methods
+ from real Connection that are called from NBClient or Dispatcher
+ ( _event_dispatcher for example)
+ '''
+
+ def __init__(self, *args):
+ self.connect_succeeded = True
+ IdleMock.__init__(self)
+ Mock.__init__(self, *args)
+
+ def on_connect(self, success, *args):
+ '''
+ Method called after connecting - after receiving <stream:features>
+ from server (NOT after TLS stream restart) or connect failure
+ '''
+ self.connect_succeeded = success
+ self.set_event()
+
+
+ def on_auth(self, con, auth):
+ '''
+ Method called after authentication, regardless of the result.
+
+ :Parameters:
+ con : NonBlockingClient
+ reference to authenticated object
+ auth : string
+ type of authetication in case of success ('old_auth', 'sasl') or
+ None in case of auth failure
+ '''
+ self.auth_connection = con
+ self.auth = auth
+ self.set_event()
diff --git a/test/runtests.py b/test/runtests.py
index 6bca37228..c2b0cbaa5 100755
--- a/test/runtests.py
+++ b/test/runtests.py
@@ -14,55 +14,53 @@ use_x = True
verbose = 1
try:
- shortargs = 'hnv:'
- longargs = 'help no-x verbose='
- opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split())
+ shortargs = 'hnv:'
+ longargs = 'help no-x verbose='
+ opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split())
except getopt.error, msg:
- print msg
- print 'for help use --help'
- sys.exit(2)
+ print msg
+ print 'for help use --help'
+ sys.exit(2)
for o, a in opts:
- if o in ('-h', '--help'):
- print 'runtests [--help] [--no-x] [--verbose level]'
- sys.exit()
- elif o in ('-n', '--no-x'):
- use_x = False
- elif o in ('-v', '--verbose'):
- try:
- verbose = int(a)
- except Exception:
- print 'verbose must be a number >= 0'
- sys.exit(2)
+ if o in ('-h', '--help'):
+ print 'runtests [--help] [--no-x] [--verbose level]'
+ sys.exit()
+ elif o in ('-n', '--no-x'):
+ use_x = False
+ elif o in ('-v', '--verbose'):
+ try:
+ verbose = int(a)
+ except Exception:
+ print 'verbose must be a number >= 0'
+ sys.exit(2)
# new test modules need to be added manually
modules = ( 'unit.test_xmpp_dispatcher_nb',
- 'unit.test_xmpp_transports_nb',
- 'unit.test_protocol_caps',
- 'unit.test_caps_cache',
- 'unit.test_contacts',
- 'unit.test_sessions',
- 'unit.test_account',
- 'unit.test_gui_interface',
- )
+ 'unit.test_xmpp_transports_nb',
+ 'unit.test_protocol_caps',
+ 'unit.test_caps_cache',
+ 'unit.test_contacts',
+ 'unit.test_sessions',
+ 'unit.test_account',
+ 'unit.test_gui_interface',
+ )
#modules = ()
if use_x:
- modules += ('integration.test_gui_event_integration',
- 'integration.test_roster',
- 'integration.test_resolver',
- 'integration.test_xmpp_client_nb',
- 'integration.test_xmpp_transports_nb'
- )
+ modules += ('integration.test_gui_event_integration',
+ 'integration.test_roster',
+ 'integration.test_resolver',
+ 'integration.test_xmpp_client_nb',
+ 'integration.test_xmpp_transports_nb'
+ )
nb_errors = 0
nb_failures = 0
for mod in modules:
- suite = unittest.defaultTestLoader.loadTestsFromName(mod)
- result = unittest.TextTestRunner(verbosity=verbose).run(suite)
- nb_errors += len(result.errors)
- nb_failures += len(result.failures)
+ suite = unittest.defaultTestLoader.loadTestsFromName(mod)
+ result = unittest.TextTestRunner(verbosity=verbose).run(suite)
+ nb_errors += len(result.errors)
+ nb_failures += len(result.failures)
sys.exit(nb_errors + nb_failures)
-
-# vim: se ts=3:
diff --git a/test/unit/__init__.py b/test/unit/__init__.py
index 8e5f2ad7c..0252a7b2f 100644
--- a/test/unit/__init__.py
+++ b/test/unit/__init__.py
@@ -2,4 +2,4 @@
This package just contains plain unit tests
-''' \ No newline at end of file
+'''
diff --git a/test/unit/test_account.py b/test/unit/test_account.py
index d655aae2f..962403752 100644
--- a/test/unit/test_account.py
+++ b/test/unit/test_account.py
@@ -10,12 +10,12 @@ from common.account import Account
class Test(unittest.TestCase):
- def testInstantiate(self):
- account = Account(name='MyAcc', contacts=None, gc_contacts=None)
-
- self.assertEquals('MyAcc', account.name)
- self.assertTrue(account.gc_contacts is None)
- self.assertTrue(account.contacts is None)
+ def testInstantiate(self):
+ account = Account(name='MyAcc', contacts=None, gc_contacts=None)
+
+ self.assertEquals('MyAcc', account.name)
+ self.assertTrue(account.gc_contacts is None)
+ self.assertTrue(account.contacts is None)
if __name__ == "__main__":
- unittest.main() \ No newline at end of file
+ unittest.main()
diff --git a/test/unit/test_caps_cache.py b/test/unit/test_caps_cache.py
index bb6069bc0..468d8f1c0 100644
--- a/test/unit/test_caps_cache.py
+++ b/test/unit/test_caps_cache.py
@@ -15,137 +15,135 @@ from mock import Mock
class CommonCapsTest(unittest.TestCase):
- def setUp(self):
- self.caps_method = 'sha-1'
- self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw='
- self.client_caps = (self.caps_method, self.caps_hash)
+ def setUp(self):
+ self.caps_method = 'sha-1'
+ self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw='
+ self.client_caps = (self.caps_method, self.caps_hash)
- self.node = "http://gajim.org"
- self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'}
+ self.node = "http://gajim.org"
+ self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'}
- self.identities = [self.identity]
- self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported!
+ self.identities = [self.identity]
+ self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported!
- # Simulate a filled db
- db_caps_cache = [
- (self.caps_method, self.caps_hash, self.identities, self.features),
- ('old', self.node + '#' + self.caps_hash, self.identities, self.features)]
- self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache})
+ # Simulate a filled db
+ db_caps_cache = [
+ (self.caps_method, self.caps_hash, self.identities, self.features),
+ ('old', self.node + '#' + self.caps_hash, self.identities, self.features)]
+ self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache})
- self.cc = caps.CapsCache(self.logger)
- caps.capscache = self.cc
+ self.cc = caps.CapsCache(self.logger)
+ caps.capscache = self.cc
class TestCapsCache(CommonCapsTest):
- def test_set_retrieve(self):
- ''' Test basic set / retrieve cycle '''
+ def test_set_retrieve(self):
+ ''' Test basic set / retrieve cycle '''
- self.cc[self.client_caps].identities = self.identities
- self.cc[self.client_caps].features = self.features
+ self.cc[self.client_caps].identities = self.identities
+ self.cc[self.client_caps].features = self.features
- self.assert_(NS_MUC in self.cc[self.client_caps].features)
- self.assert_(NS_PING not in self.cc[self.client_caps].features)
+ self.assert_(NS_MUC in self.cc[self.client_caps].features)
+ self.assert_(NS_PING not in self.cc[self.client_caps].features)
- identities = self.cc[self.client_caps].identities
+ identities = self.cc[self.client_caps].identities
- self.assertEqual(1, len(identities))
+ self.assertEqual(1, len(identities))
- identity = identities[0]
- self.assertEqual('client', identity['category'])
- self.assertEqual('pc', identity['type'])
+ identity = identities[0]
+ self.assertEqual('client', identity['category'])
+ self.assertEqual('pc', identity['type'])
- def test_set_and_store(self):
- ''' Test client_caps update gets logged into db '''
+ def test_set_and_store(self):
+ ''' Test client_caps update gets logged into db '''
- item = self.cc[self.client_caps]
- item.set_and_store(self.identities, self.features)
+ item = self.cc[self.client_caps]
+ item.set_and_store(self.identities, self.features)
- self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method,
- self.caps_hash, self.identities, self.features)
+ self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method,
+ self.caps_hash, self.identities, self.features)
- def test_initialize_from_db(self):
- ''' Read cashed dummy data from db '''
- self.assertEqual(self.cc[self.client_caps].status, caps.NEW)
- self.cc.initialize_from_db()
- self.assertEqual(self.cc[self.client_caps].status, caps.CACHED)
+ def test_initialize_from_db(self):
+ ''' Read cashed dummy data from db '''
+ self.assertEqual(self.cc[self.client_caps].status, caps.NEW)
+ self.cc.initialize_from_db()
+ self.assertEqual(self.cc[self.client_caps].status, caps.CACHED)
- def test_preload_triggering_query(self):
- ''' Make sure that preload issues a disco '''
- connection = Mock()
- client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
+ def test_preload_triggering_query(self):
+ ''' Make sure that preload issues a disco '''
+ connection = Mock()
+ client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
- self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
- client_caps)
+ self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
+ client_caps)
- self.assertEqual(1, len(connection.mockGetAllCalls()))
+ self.assertEqual(1, len(connection.mockGetAllCalls()))
- def test_no_preload_query_if_cashed(self):
- ''' Preload must not send a query if the data is already cached '''
- connection = Mock()
- client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
+ def test_no_preload_query_if_cashed(self):
+ ''' Preload must not send a query if the data is already cached '''
+ connection = Mock()
+ client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
- self.cc.initialize_from_db()
- self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
- client_caps)
+ self.cc.initialize_from_db()
+ self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
+ client_caps)
- self.assertEqual(0, len(connection.mockGetAllCalls()))
+ self.assertEqual(0, len(connection.mockGetAllCalls()))
- def test_hash(self):
- '''tests the hash computation'''
- computed_hash = caps.compute_caps_hash(self.identities, self.features)
- self.assertEqual(self.caps_hash, computed_hash)
+ def test_hash(self):
+ '''tests the hash computation'''
+ computed_hash = caps.compute_caps_hash(self.identities, self.features)
+ self.assertEqual(self.caps_hash, computed_hash)
class TestClientCaps(CommonCapsTest):
- def setUp(self):
- CommonCapsTest.setUp(self)
- self.client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
+ def setUp(self):
+ CommonCapsTest.setUp(self)
+ self.client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
- def test_query_by_get_discover_strategy(self):
- ''' Client must be queried if the data is unkown '''
- connection = Mock()
- discover = self.client_caps.get_discover_strategy()
- discover(connection, "test@gajim.org")
+ def test_query_by_get_discover_strategy(self):
+ ''' Client must be queried if the data is unkown '''
+ connection = Mock()
+ discover = self.client_caps.get_discover_strategy()
+ discover(connection, "test@gajim.org")
- connection.mockCheckCall(0, "discoverInfo", "test@gajim.org",
- "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
+ connection.mockCheckCall(0, "discoverInfo", "test@gajim.org",
+ "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
- def test_client_supports(self):
- self.assertTrue(caps.client_supports(self.client_caps, NS_PING),
- msg="Assume supported, if we don't have caps")
+ def test_client_supports(self):
+ self.assertTrue(caps.client_supports(self.client_caps, NS_PING),
+ msg="Assume supported, if we don't have caps")
- self.assertFalse(caps.client_supports(self.client_caps, NS_XHTML_IM),
- msg="Must not assume blacklisted feature is supported on default")
+ self.assertFalse(caps.client_supports(self.client_caps, NS_XHTML_IM),
+ msg="Must not assume blacklisted feature is supported on default")
- self.cc.initialize_from_db()
+ self.cc.initialize_from_db()
- self.assertFalse(caps.client_supports(self.client_caps, NS_PING),
- msg="Must return false on unsupported feature")
+ self.assertFalse(caps.client_supports(self.client_caps, NS_PING),
+ msg="Must return false on unsupported feature")
- self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM),
- msg="Must return True on supported feature")
+ self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM),
+ msg="Must return True on supported feature")
- self.assertTrue(caps.client_supports(self.client_caps, NS_MUC),
- msg="Must return True on supported feature")
+ self.assertTrue(caps.client_supports(self.client_caps, NS_MUC),
+ msg="Must return True on supported feature")
class TestOldClientCaps(TestClientCaps):
- def setUp(self):
- TestClientCaps.setUp(self)
- self.client_caps = caps.OldClientCaps(self.caps_hash, self.node)
+ def setUp(self):
+ TestClientCaps.setUp(self)
+ self.client_caps = caps.OldClientCaps(self.caps_hash, self.node)
- def test_query_by_get_discover_strategy(self):
- ''' Client must be queried if the data is unknown '''
- connection = Mock()
- discover = self.client_caps.get_discover_strategy()
- discover(connection, "test@gajim.org")
+ def test_query_by_get_discover_strategy(self):
+ ''' Client must be queried if the data is unknown '''
+ connection = Mock()
+ discover = self.client_caps.get_discover_strategy()
+ discover(connection, "test@gajim.org")
- connection.mockCheckCall(0, "discoverInfo", "test@gajim.org")
+ connection.mockCheckCall(0, "discoverInfo", "test@gajim.org")
if __name__ == '__main__':
- unittest.main()
-
-# vim: se ts=3:
+ unittest.main()
diff --git a/test/unit/test_contacts.py b/test/unit/test_contacts.py
index ecfe793c8..957f54e42 100644
--- a/test/unit/test_contacts.py
+++ b/test/unit/test_contacts.py
@@ -12,118 +12,118 @@ from common.xmpp import NS_MUC
from common import caps_cache
class TestCommonContact(unittest.TestCase):
-
- def setUp(self):
- self.contact = CommonContact(jid='', account="", resource='', show='',
- status='', name='', our_chatstate=None, composing_xep=None,
- chatstate=None, client_caps=None)
-
- def test_default_client_supports(self):
- '''
- Test the caps support method of contacts.
- See test_caps for more enhanced tests.
- '''
- caps_cache.capscache = caps_cache.CapsCache()
- self.assertTrue(self.contact.supports(NS_MUC),
- msg="Must not backtrace on simple check for supported feature")
-
- self.contact.client_caps = caps_cache.NullClientCaps()
-
- self.assertTrue(self.contact.supports(NS_MUC),
- msg="Must not backtrace on simple check for supported feature")
-
-
+
+ def setUp(self):
+ self.contact = CommonContact(jid='', account="", resource='', show='',
+ status='', name='', our_chatstate=None, composing_xep=None,
+ chatstate=None, client_caps=None)
+
+ def test_default_client_supports(self):
+ '''
+ Test the caps support method of contacts.
+ See test_caps for more enhanced tests.
+ '''
+ caps_cache.capscache = caps_cache.CapsCache()
+ self.assertTrue(self.contact.supports(NS_MUC),
+ msg="Must not backtrace on simple check for supported feature")
+
+ self.contact.client_caps = caps_cache.NullClientCaps()
+
+ self.assertTrue(self.contact.supports(NS_MUC),
+ msg="Must not backtrace on simple check for supported feature")
+
+
class TestContact(TestCommonContact):
-
- def setUp(self):
- TestCommonContact.setUp(self)
- self.contact = Contact(jid="test@gajim.org", account="account")
-
- def test_attributes_available(self):
- '''This test supports the migration from the old to the new contact
- domain model by smoke testing that no attribute values are lost'''
-
- attributes = ["jid", "resource", "show", "status", "name", "our_chatstate",
- "composing_xep", "chatstate", "client_caps", "priority", "sub"]
- for attr in attributes:
- self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr)
-
+
+ def setUp(self):
+ TestCommonContact.setUp(self)
+ self.contact = Contact(jid="test@gajim.org", account="account")
+
+ def test_attributes_available(self):
+ '''This test supports the migration from the old to the new contact
+ domain model by smoke testing that no attribute values are lost'''
+
+ attributes = ["jid", "resource", "show", "status", "name", "our_chatstate",
+ "composing_xep", "chatstate", "client_caps", "priority", "sub"]
+ for attr in attributes:
+ self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr)
+
class TestGC_Contact(TestCommonContact):
- def setUp(self):
- TestCommonContact.setUp(self)
- self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account")
-
- def test_attributes_available(self):
- '''This test supports the migration from the old to the new contact
- domain model by asserting no attributes have been lost'''
-
- attributes = ["jid", "resource", "show", "status", "name", "our_chatstate",
- "composing_xep", "chatstate", "client_caps", "role", "room_jid"]
- for attr in attributes:
- self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr)
-
-
+ def setUp(self):
+ TestCommonContact.setUp(self)
+ self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account")
+
+ def test_attributes_available(self):
+ '''This test supports the migration from the old to the new contact
+ domain model by asserting no attributes have been lost'''
+
+ attributes = ["jid", "resource", "show", "status", "name", "our_chatstate",
+ "composing_xep", "chatstate", "client_caps", "role", "room_jid"]
+ for attr in attributes:
+ self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr)
+
+
class TestContacts(unittest.TestCase):
-
- def setUp(self):
- self.contacts = LegacyContactsAPI()
-
- def test_create_add_get_contact(self):
- jid = 'test@gajim.org'
- account = "account"
-
- contact = self.contacts.create_contact(jid=jid, account=account)
- self.contacts.add_contact(account, contact)
-
- retrieved_contact = self.contacts.get_contact(account, jid)
- self.assertEqual(contact, retrieved_contact, "Contact must be known")
-
- self.contacts.remove_contact(account, contact)
-
- retrieved_contact = self.contacts.get_contact(account, jid)
- self.assertNotEqual(contact, retrieved_contact,
- msg="Contact must not be known any longer")
-
-
- def test_copy_contact(self):
- jid = 'test@gajim.org'
- account = "account"
-
- contact = self.contacts.create_contact(jid=jid, account=account)
- copy = self.contacts.copy_contact(contact)
- self.assertFalse(contact is copy, msg="Must not be the same")
-
- # Not yet implemented to remain backwart compatible
- # self.assertEqual(contact, copy, msg="Must be equal")
-
- def test_legacy_accounts_handling(self):
- self.contacts.add_account("one")
- self.contacts.add_account("two")
-
- self.contacts.change_account_name("two", "old")
- self.contacts.remove_account("one")
-
- self.assertEqual(["old"], self.contacts.get_accounts())
-
- def test_legacy_contacts_from_groups(self):
- jid1 = "test1@gajim.org"
- jid2 = "test2@gajim.org"
- account = "account"
- group = "GroupA"
-
- contact1 = self.contacts.create_contact(jid=jid1, account=account,
- groups=[group])
- self.contacts.add_contact(account, contact1)
-
- contact2 = self.contacts.create_contact(jid=jid2, account=account,
- groups=[group])
- self.contacts.add_contact(account, contact2)
-
- self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group)))
- self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, '')))
-
-
+
+ def setUp(self):
+ self.contacts = LegacyContactsAPI()
+
+ def test_create_add_get_contact(self):
+ jid = 'test@gajim.org'
+ account = "account"
+
+ contact = self.contacts.create_contact(jid=jid, account=account)
+ self.contacts.add_contact(account, contact)
+
+ retrieved_contact = self.contacts.get_contact(account, jid)
+ self.assertEqual(contact, retrieved_contact, "Contact must be known")
+
+ self.contacts.remove_contact(account, contact)
+
+ retrieved_contact = self.contacts.get_contact(account, jid)
+ self.assertNotEqual(contact, retrieved_contact,
+ msg="Contact must not be known any longer")
+
+
+ def test_copy_contact(self):
+ jid = 'test@gajim.org'
+ account = "account"
+
+ contact = self.contacts.create_contact(jid=jid, account=account)
+ copy = self.contacts.copy_contact(contact)
+ self.assertFalse(contact is copy, msg="Must not be the same")
+
+ # Not yet implemented to remain backwart compatible
+ # self.assertEqual(contact, copy, msg="Must be equal")
+
+ def test_legacy_accounts_handling(self):
+ self.contacts.add_account("one")
+ self.contacts.add_account("two")
+
+ self.contacts.change_account_name("two", "old")
+ self.contacts.remove_account("one")
+
+ self.assertEqual(["old"], self.contacts.get_accounts())
+
+ def test_legacy_contacts_from_groups(self):
+ jid1 = "test1@gajim.org"
+ jid2 = "test2@gajim.org"
+ account = "account"
+ group = "GroupA"
+
+ contact1 = self.contacts.create_contact(jid=jid1, account=account,
+ groups=[group])
+ self.contacts.add_contact(account, contact1)
+
+ contact2 = self.contacts.create_contact(jid=jid2, account=account,
+ groups=[group])
+ self.contacts.add_contact(account, contact2)
+
+ self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group)))
+ self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, '')))
+
+
if __name__ == "__main__":
- unittest.main() \ No newline at end of file
+ unittest.main()
diff --git a/test/unit/test_gui_interface.py b/test/unit/test_gui_interface.py
index d4f1ef4a4..b892f7bd0 100644
--- a/test/unit/test_gui_interface.py
+++ b/test/unit/test_gui_interface.py
@@ -9,7 +9,7 @@ lib.setup_env()
from common import logging_helpers
logging_helpers.set_quiet()
-from common import gajim
+from common import gajim
from gajim_mocks import MockLogger
gajim.logger = MockLogger()
@@ -18,98 +18,98 @@ from gui_interface import Interface
class TestInterface(unittest.TestCase):
- def test_instantiation(self):
- ''' Test that we can proper initialize and do not fail on globals '''
- interface = Interface()
- interface.run()
-
- def test_dispatch(self):
- ''' Test dispatcher forwarding network events to handler_* methods '''
- sut = Interface()
-
- success = sut.dispatch('No Such Event', None, None)
- self.assertFalse(success, msg="Unexisting event handled")
-
- success = sut.dispatch('STANZA_ARRIVED', None, None)
- self.assertTrue(success, msg="Existing event must be handled")
-
- def test_register_unregister_single_handler(self):
- ''' Register / Unregister a custom event handler '''
- sut = Interface()
- event = 'TESTS_ARE_COOL_EVENT'
-
- self.called = False
- def handler(account, data):
- self.assertEqual(account, 'account')
- self.assertEqual(data, 'data')
- self.called = True
-
- self.assertFalse(self.called)
- sut.register_handler('TESTS_ARE_COOL_EVENT', handler)
- sut.dispatch(event, 'account', 'data')
- self.assertTrue(self.called, msg="Handler should have been called")
-
- self.called = False
- sut.unregister_handler('TESTS_ARE_COOL_EVENT', handler)
- sut.dispatch(event, 'account', 'data')
- self.assertFalse(self.called, msg="Handler should no longer be called")
-
-
- def test_dispatch_to_multiple_handlers(self):
- ''' Register and dispatch a single event to multiple handlers '''
- sut = Interface()
- event = 'SINGLE_EVENT'
-
- self.called_a = False
- self.called_b = False
-
- def handler_a(account, data):
- self.assertFalse(self.called_a, msg="One must only be notified once")
- self.called_a = True
-
- def handler_b(account, data):
- self.assertFalse(self.called_b, msg="One must only be notified once")
- self.called_b = True
-
- sut.register_handler(event, handler_a)
- sut.register_handler(event, handler_b)
-
- # register again
- sut.register_handler('SOME_OTHER_EVENT', handler_b)
- sut.register_handler(event, handler_a)
-
- sut.dispatch(event, 'account', 'data')
- self.assertTrue(self.called_a and self.called_b,
- msg="Both handlers should have been called")
-
- def test_links_regexp_entire(self):
- sut = Interface()
- def assert_matches_all(str_):
- m = sut.basic_pattern_re.match(str_)
-
- # the match should equal the string
- str_span = (0, len(str_))
- self.assertEqual(m.span(), str_span)
-
- # these entire strings should be parsed as links
- assert_matches_all('http://google.com/')
- assert_matches_all('http://google.com')
- assert_matches_all('http://www.google.ca/search?q=xmpp')
-
- assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3')
-
- assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)')
- assert_matches_all(
- 'http://en.wikipedia.org/wiki/Protocol_%28computing%29')
-
- assert_matches_all('mailto:test@example.org')
-
- assert_matches_all('xmpp:example-node@example.com')
- assert_matches_all('xmpp:example-node@example.com/some-resource')
- assert_matches_all('xmpp:example-node@example.com?message')
- assert_matches_all('xmpp://guest@example.com/support@example.com?message')
+ def test_instantiation(self):
+ ''' Test that we can proper initialize and do not fail on globals '''
+ interface = Interface()
+ interface.run()
+
+ def test_dispatch(self):
+ ''' Test dispatcher forwarding network events to handler_* methods '''
+ sut = Interface()
+
+ success = sut.dispatch('No Such Event', None, None)
+ self.assertFalse(success, msg="Unexisting event handled")
+
+ success = sut.dispatch('STANZA_ARRIVED', None, None)
+ self.assertTrue(success, msg="Existing event must be handled")
+
+ def test_register_unregister_single_handler(self):
+ ''' Register / Unregister a custom event handler '''
+ sut = Interface()
+ event = 'TESTS_ARE_COOL_EVENT'
+
+ self.called = False
+ def handler(account, data):
+ self.assertEqual(account, 'account')
+ self.assertEqual(data, 'data')
+ self.called = True
+
+ self.assertFalse(self.called)
+ sut.register_handler('TESTS_ARE_COOL_EVENT', handler)
+ sut.dispatch(event, 'account', 'data')
+ self.assertTrue(self.called, msg="Handler should have been called")
+
+ self.called = False
+ sut.unregister_handler('TESTS_ARE_COOL_EVENT', handler)
+ sut.dispatch(event, 'account', 'data')
+ self.assertFalse(self.called, msg="Handler should no longer be called")
+
+
+ def test_dispatch_to_multiple_handlers(self):
+ ''' Register and dispatch a single event to multiple handlers '''
+ sut = Interface()
+ event = 'SINGLE_EVENT'
+
+ self.called_a = False
+ self.called_b = False
+
+ def handler_a(account, data):
+ self.assertFalse(self.called_a, msg="One must only be notified once")
+ self.called_a = True
+
+ def handler_b(account, data):
+ self.assertFalse(self.called_b, msg="One must only be notified once")
+ self.called_b = True
+
+ sut.register_handler(event, handler_a)
+ sut.register_handler(event, handler_b)
+
+ # register again
+ sut.register_handler('SOME_OTHER_EVENT', handler_b)
+ sut.register_handler(event, handler_a)
+
+ sut.dispatch(event, 'account', 'data')
+ self.assertTrue(self.called_a and self.called_b,
+ msg="Both handlers should have been called")
+
+ def test_links_regexp_entire(self):
+ sut = Interface()
+ def assert_matches_all(str_):
+ m = sut.basic_pattern_re.match(str_)
+
+ # the match should equal the string
+ str_span = (0, len(str_))
+ self.assertEqual(m.span(), str_span)
+
+ # these entire strings should be parsed as links
+ assert_matches_all('http://google.com/')
+ assert_matches_all('http://google.com')
+ assert_matches_all('http://www.google.ca/search?q=xmpp')
+
+ assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3')
+
+ assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)')
+ assert_matches_all(
+ 'http://en.wikipedia.org/wiki/Protocol_%28computing%29')
+
+ assert_matches_all('mailto:test@example.org')
+
+ assert_matches_all('xmpp:example-node@example.com')
+ assert_matches_all('xmpp:example-node@example.com/some-resource')
+ assert_matches_all('xmpp:example-node@example.com?message')
+ assert_matches_all('xmpp://guest@example.com/support@example.com?message')
if __name__ == "__main__":
- #import sys;sys.argv = ['', 'Test.test']
- unittest.main() \ No newline at end of file
+ #import sys;sys.argv = ['', 'Test.test']
+ unittest.main()
diff --git a/test/unit/test_protocol_caps.py b/test/unit/test_protocol_caps.py
index f2c7ae509..c527c148d 100644
--- a/test/unit/test_protocol_caps.py
+++ b/test/unit/test_protocol_caps.py
@@ -17,59 +17,57 @@ from common.xmpp import protocol
class TestableConnectionCaps(caps.ConnectionCaps):
- def __init__(self, *args, **kwargs):
- self._mocked_contacts = {}
- caps.ConnectionCaps.__init__(self, *args, **kwargs)
+ def __init__(self, *args, **kwargs):
+ self._mocked_contacts = {}
+ caps.ConnectionCaps.__init__(self, *args, **kwargs)
- def _get_contact_or_gc_contact_for_jid(self, jid):
- """
- Overwrite to decouple form contact handling
- """
- if jid not in self._mocked_contacts:
- self._mocked_contacts[jid] = Mock(realClass=Contact)
- self._mocked_contacts[jid].jid = jid
- return self._mocked_contacts[jid]
+ def _get_contact_or_gc_contact_for_jid(self, jid):
+ """
+ Overwrite to decouple form contact handling
+ """
+ if jid not in self._mocked_contacts:
+ self._mocked_contacts[jid] = Mock(realClass=Contact)
+ self._mocked_contacts[jid].jid = jid
+ return self._mocked_contacts[jid]
- def discoverInfo(self, *args, **kwargs):
- pass
+ def discoverInfo(self, *args, **kwargs):
+ pass
- def get_mocked_contact_for_jid(self, jid):
- return self._mocked_contacts[jid]
+ def get_mocked_contact_for_jid(self, jid):
+ return self._mocked_contacts[jid]
class TestConnectionCaps(unittest.TestCase):
- def test_capsPresenceCB(self):
- jid = "user@server.com/a"
- connection_caps = TestableConnectionCaps("account",
- self._build_assertering_dispatcher_function("CAPS_RECEIVED", jid),
- Mock(), caps_cache.create_suitable_client_caps)
-
- xml = """<presence from='user@server.com/a' to='%s' id='123'>
- <c node='http://gajim.org' ver='pRCD6cgQ4SDqNMCjdhRV6TECx5o='
- hash='sha-1' xmlns='http://jabber.org/protocol/caps'/>
- </presence>
- """ % (jid)
- iq = protocol.Iq(node=simplexml.XML2Node(xml))
- connection_caps._capsPresenceCB(None, iq)
-
- self.assertTrue(self._dispatcher_called, msg="Must have received caps")
-
- client_caps = connection_caps.get_mocked_contact_for_jid(jid).client_caps
- self.assertTrue(client_caps, msg="Client caps must be set")
- self.assertFalse(isinstance(client_caps, caps_cache.NullClientCaps),
- msg="On receive of proper caps, we must not use the fallback")
-
- def _build_assertering_dispatcher_function(self, expected_event, jid):
- self._dispatcher_called = False
- def dispatch(event, data):
- self.assertFalse(self._dispatcher_called, msg="Must only be called once")
- self._dispatcher_called = True
- self.assertEqual(expected_event, event)
- self.assertEqual(jid, data[0])
- return dispatch
-
-if __name__ == '__main__':
- unittest.main()
+ def test_capsPresenceCB(self):
+ jid = "user@server.com/a"
+ connection_caps = TestableConnectionCaps("account",
+ self._build_assertering_dispatcher_function("CAPS_RECEIVED", jid),
+ Mock(), caps_cache.create_suitable_client_caps)
+
+ xml = """<presence from='user@server.com/a' to='%s' id='123'>
+ <c node='http://gajim.org' ver='pRCD6cgQ4SDqNMCjdhRV6TECx5o='
+ hash='sha-1' xmlns='http://jabber.org/protocol/caps'/>
+ </presence>
+ """ % (jid)
+ iq = protocol.Iq(node=simplexml.XML2Node(xml))
+ connection_caps._capsPresenceCB(None, iq)
+
+ self.assertTrue(self._dispatcher_called, msg="Must have received caps")
+
+ client_caps = connection_caps.get_mocked_contact_for_jid(jid).client_caps
+ self.assertTrue(client_caps, msg="Client caps must be set")
+ self.assertFalse(isinstance(client_caps, caps_cache.NullClientCaps),
+ msg="On receive of proper caps, we must not use the fallback")
+
+ def _build_assertering_dispatcher_function(self, expected_event, jid):
+ self._dispatcher_called = False
+ def dispatch(event, data):
+ self.assertFalse(self._dispatcher_called, msg="Must only be called once")
+ self._dispatcher_called = True
+ self.assertEqual(expected_event, event)
+ self.assertEqual(jid, data[0])
+ return dispatch
-# vim: se ts=3:
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/unit/test_sessions.py b/test/unit/test_sessions.py
index 724bac56d..05a4351a7 100644
--- a/test/unit/test_sessions.py
+++ b/test/unit/test_sessions.py
@@ -24,191 +24,189 @@ gajim.interface = MockInterface()
account_name = 'test'
class TestStanzaSession(unittest.TestCase):
- ''' Testclass for common/stanzasession.py '''
+ ''' Testclass for common/stanzasession.py '''
- def setUp(self):
- self.jid = 'test@example.org/Gajim'
- self.conn = MockConnection(account_name, {'send_stanza': None})
- self.sess = StanzaSession(self.conn, self.jid, None, 'chat')
+ def setUp(self):
+ self.jid = 'test@example.org/Gajim'
+ self.conn = MockConnection(account_name, {'send_stanza': None})
+ self.sess = StanzaSession(self.conn, self.jid, None, 'chat')
- def test_generate_thread_id(self):
- # thread_id is a string
- self.assert_(isinstance(self.sess.thread_id, str))
+ def test_generate_thread_id(self):
+ # thread_id is a string
+ self.assert_(isinstance(self.sess.thread_id, str))
- # it should be somewhat long, to avoid clashes
- self.assert_(len(self.sess.thread_id) >= 32)
+ # it should be somewhat long, to avoid clashes
+ self.assert_(len(self.sess.thread_id) >= 32)
- def test_is_loggable(self):
- # by default a session should be loggable
- # (unless the no_log_for setting says otherwise)
- self.assert_(self.sess.is_loggable())
+ def test_is_loggable(self):
+ # by default a session should be loggable
+ # (unless the no_log_for setting says otherwise)
+ self.assert_(self.sess.is_loggable())
- def test_terminate(self):
- # termination is sent by default
- self.sess.last_send = time.time()
- self.sess.terminate()
+ def test_terminate(self):
+ # termination is sent by default
+ self.sess.last_send = time.time()
+ self.sess.terminate()
- self.assertEqual(None, self.sess.status)
+ self.assertEqual(None, self.sess.status)
- calls = self.conn.mockGetNamedCalls('send_stanza')
- msg = calls[0].getParam(0)
+ calls = self.conn.mockGetNamedCalls('send_stanza')
+ msg = calls[0].getParam(0)
- self.assertEqual(msg.getThread(), self.sess.thread_id)
+ self.assertEqual(msg.getThread(), self.sess.thread_id)
- def test_terminate_without_sending(self):
- # no termination is sent if no messages have been sent in the session
- self.sess.terminate()
+ def test_terminate_without_sending(self):
+ # no termination is sent if no messages have been sent in the session
+ self.sess.terminate()
- self.assertEqual(None, self.sess.status)
+ self.assertEqual(None, self.sess.status)
- calls = self.conn.mockGetNamedCalls('send_stanza')
- self.assertEqual(0, len(calls))
+ calls = self.conn.mockGetNamedCalls('send_stanza')
+ self.assertEqual(0, len(calls))
- def test_terminate_no_remote_xep_201(self):
- # no termination is sent if only messages without thread ids have been
- # received
- self.sess.last_send = time.time()
- self.sess.last_receive = time.time()
- self.sess.terminate()
+ def test_terminate_no_remote_xep_201(self):
+ # no termination is sent if only messages without thread ids have been
+ # received
+ self.sess.last_send = time.time()
+ self.sess.last_receive = time.time()
+ self.sess.terminate()
- self.assertEqual(None, self.sess.status)
+ self.assertEqual(None, self.sess.status)
- calls = self.conn.mockGetNamedCalls('send_stanza')
- self.assertEqual(0, len(calls))
+ calls = self.conn.mockGetNamedCalls('send_stanza')
+ self.assertEqual(0, len(calls))
class TestChatControlSession(unittest.TestCase):
- ''' Testclass for session.py '''
+ ''' Testclass for session.py '''
- def setUp(self):
- self.jid = 'test@example.org/Gajim'
- self.conn = MockConnection(account_name, {'send_stanza': None})
- self.sess = ChatControlSession(self.conn, self.jid, None)
- gajim.logger = MockLogger()
-
- # initially there are no events
- self.assertEqual(0, len(gajim.events.get_events(account_name)))
+ def setUp(self):
+ self.jid = 'test@example.org/Gajim'
+ self.conn = MockConnection(account_name, {'send_stanza': None})
+ self.sess = ChatControlSession(self.conn, self.jid, None)
+ gajim.logger = MockLogger()
+
+ # initially there are no events
+ self.assertEqual(0, len(gajim.events.get_events(account_name)))
- # no notifications have been sent
- self.assertEqual(0, len(notify.notifications))
+ # no notifications have been sent
+ self.assertEqual(0, len(notify.notifications))
- def tearDown(self):
- # remove all events and notifications that were added
- gajim.events._events = {}
- notify.notifications = []
+ def tearDown(self):
+ # remove all events and notifications that were added
+ gajim.events._events = {}
+ notify.notifications = []
- def receive_chat_msg(self, jid, msgtxt):
- '''simulate receiving a chat message from jid'''
- msg = xmpp.Message()
- msg.setBody(msgtxt)
- msg.setType('chat')
+ def receive_chat_msg(self, jid, msgtxt):
+ '''simulate receiving a chat message from jid'''
+ msg = xmpp.Message()
+ msg.setBody(msgtxt)
+ msg.setType('chat')
- tim = time.localtime()
- encrypted = False
- self.sess.received(jid, msgtxt, tim, encrypted, msg)
+ tim = time.localtime()
+ encrypted = False
+ self.sess.received(jid, msgtxt, tim, encrypted, msg)
- # ----- custom assertions -----
- def assert_new_message_notification(self):
- '''a new_message notification has been sent'''
- self.assertEqual(1, len(notify.notifications))
- notif = notify.notifications[0]
- self.assertEqual('new_message', notif[0])
+ # ----- custom assertions -----
+ def assert_new_message_notification(self):
+ '''a new_message notification has been sent'''
+ self.assertEqual(1, len(notify.notifications))
+ notif = notify.notifications[0]
+ self.assertEqual('new_message', notif[0])
- def assert_first_message_notification(self):
- '''this message was treated as a first message'''
- self.assert_new_message_notification()
- notif = notify.notifications[0]
- params = notif[3]
- first = params[1]
- self.assert_(first, 'message should have been treated as a first message')
+ def assert_first_message_notification(self):
+ '''this message was treated as a first message'''
+ self.assert_new_message_notification()
+ notif = notify.notifications[0]
+ params = notif[3]
+ first = params[1]
+ self.assert_(first, 'message should have been treated as a first message')
- def assert_not_first_message_notification(self):
- '''this message was not treated as a first message'''
- self.assert_new_message_notification()
- notif = notify.notifications[0]
- params = notif[3]
- first = params[1]
- self.assert_(not first,
- 'message was unexpectedly treated as a first message')
+ def assert_not_first_message_notification(self):
+ '''this message was not treated as a first message'''
+ self.assert_new_message_notification()
+ notif = notify.notifications[0]
+ params = notif[3]
+ first = params[1]
+ self.assert_(not first,
+ 'message was unexpectedly treated as a first message')
- # ----- tests -----
- def test_receive_nocontrol(self):
- '''test receiving a message in a blank state'''
- jid = 'bct@necronomicorp.com/Gajim'
- msgtxt = 'testing one two three'
+ # ----- tests -----
+ def test_receive_nocontrol(self):
+ '''test receiving a message in a blank state'''
+ jid = 'bct@necronomicorp.com/Gajim'
+ msgtxt = 'testing one two three'
- self.receive_chat_msg(jid, msgtxt)
+ self.receive_chat_msg(jid, msgtxt)
- # message was logged
- calls = gajim.logger.mockGetNamedCalls('write')
- self.assertEqual(1, len(calls))
+ # message was logged
+ calls = gajim.logger.mockGetNamedCalls('write')
+ self.assertEqual(1, len(calls))
- # no ChatControl was open and autopopup was off
- # so the message goes into the event queue
- self.assertEqual(1, len(gajim.events.get_events(account_name)))
+ # no ChatControl was open and autopopup was off
+ # so the message goes into the event queue
+ self.assertEqual(1, len(gajim.events.get_events(account_name)))
- self.assert_first_message_notification()
+ self.assert_first_message_notification()
- # no control is attached to the session
- self.assertEqual(None, self.sess.control)
+ # no control is attached to the session
+ self.assertEqual(None, self.sess.control)
- def test_receive_already_has_control(self):
- '''test receiving a message with a session already attached to a
- control'''
+ def test_receive_already_has_control(self):
+ '''test receiving a message with a session already attached to a
+ control'''
- jid = 'bct@necronomicorp.com/Gajim'
- msgtxt = 'testing one two three'
+ jid = 'bct@necronomicorp.com/Gajim'
+ msgtxt = 'testing one two three'
- self.sess.control = MockChatControl(jid, account_name)
+ self.sess.control = MockChatControl(jid, account_name)
- self.receive_chat_msg(jid, msgtxt)
+ self.receive_chat_msg(jid, msgtxt)
- # message was logged
- calls = gajim.logger.mockGetNamedCalls('write')
- self.assertEqual(1, len(calls))
+ # message was logged
+ calls = gajim.logger.mockGetNamedCalls('write')
+ self.assertEqual(1, len(calls))
- # the message does not go into the event queue
- self.assertEqual(0, len(gajim.events.get_events(account_name)))
+ # the message does not go into the event queue
+ self.assertEqual(0, len(gajim.events.get_events(account_name)))
- self.assert_not_first_message_notification()
+ self.assert_not_first_message_notification()
- # message was printed to the control
- calls = self.sess.control.mockGetNamedCalls('print_conversation')
- self.assertEqual(1, len(calls))
+ # message was printed to the control
+ calls = self.sess.control.mockGetNamedCalls('print_conversation')
+ self.assertEqual(1, len(calls))
- def test_received_orphaned_control(self):
- '''test receiving a message when a control that doesn't have a session
- attached exists'''
+ def test_received_orphaned_control(self):
+ '''test receiving a message when a control that doesn't have a session
+ attached exists'''
- jid = 'bct@necronomicorp.com'
- fjid = jid + '/Gajim'
- msgtxt = 'testing one two three'
+ jid = 'bct@necronomicorp.com'
+ fjid = jid + '/Gajim'
+ msgtxt = 'testing one two three'
- ctrl = MockChatControl(jid, account_name)
- gajim.interface.msg_win_mgr = Mock({'get_control': ctrl})
- gajim.interface.msg_win_mgr.mockSetExpectation('get_control',
- expectParams(jid, account_name))
+ ctrl = MockChatControl(jid, account_name)
+ gajim.interface.msg_win_mgr = Mock({'get_control': ctrl})
+ gajim.interface.msg_win_mgr.mockSetExpectation('get_control',
+ expectParams(jid, account_name))
- self.receive_chat_msg(fjid, msgtxt)
+ self.receive_chat_msg(fjid, msgtxt)
- # message was logged
- calls = gajim.logger.mockGetNamedCalls('write')
- self.assertEqual(1, len(calls))
+ # message was logged
+ calls = gajim.logger.mockGetNamedCalls('write')
+ self.assertEqual(1, len(calls))
- # the message does not go into the event queue
- self.assertEqual(0, len(gajim.events.get_events(account_name)))
+ # the message does not go into the event queue
+ self.assertEqual(0, len(gajim.events.get_events(account_name)))
- self.assert_not_first_message_notification()
+ self.assert_not_first_message_notification()
- # this session is now attached to that control
- self.assertEqual(self.sess, ctrl.session)
- self.assertEqual(ctrl, self.sess.control, 'foo')
+ # this session is now attached to that control
+ self.assertEqual(self.sess, ctrl.session)
+ self.assertEqual(ctrl, self.sess.control, 'foo')
- # message was printed to the control
- calls = ctrl.mockGetNamedCalls('print_conversation')
- self.assertEqual(1, len(calls))
+ # message was printed to the control
+ calls = ctrl.mockGetNamedCalls('print_conversation')
+ self.assertEqual(1, len(calls))
if __name__ == '__main__':
unittest.main()
-
-# vim: se ts=3:
diff --git a/test/unit/test_xmpp_dispatcher_nb.py b/test/unit/test_xmpp_dispatcher_nb.py
index 7d57cae6e..660b6d572 100644
--- a/test/unit/test_xmpp_dispatcher_nb.py
+++ b/test/unit/test_xmpp_dispatcher_nb.py
@@ -12,88 +12,86 @@ from common.xmpp import dispatcher_nb
from common.xmpp import protocol
class TestDispatcherNB(unittest.TestCase):
- '''
- Test class for NonBlocking dispatcher. Tested dispatcher will be plugged
- into a mock client
- '''
- def setUp(self):
- self.dispatcher = dispatcher_nb.XMPPDispatcher()
-
- # Setup mock client
- self.client = Mock()
- self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one?
- self.client._caller = Mock()
- self.client.defaultNamespace = protocol.NS_CLIENT
- self.client.Connection = Mock() # mock transport
- self.con = self.client.Connection
-
- def tearDown(self):
- # Unplug if needed
- if hasattr(self.dispatcher, '_owner'):
- self.dispatcher.PlugOut()
-
- def _simulate_connect(self):
- self.dispatcher.PlugIn(self.client) # client is owner
- # Simulate that we have established a connection
- self.dispatcher.StreamInit()
- self.dispatcher.ProcessNonBlocking("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'>")
-
- def test_unbound_namespace_prefix(self):
- '''tests our handling of a message with an unbound namespace prefix'''
- self._simulate_connect()
-
- msgs = []
- def _got_message(conn, msg):
- msgs.append(msg)
- self.dispatcher.RegisterHandler('message', _got_message)
-
- # should be able to parse a normal message
- self.dispatcher.ProcessNonBlocking('<message><body>hello</body></message>')
- self.assertEqual(1, len(msgs))
-
- self.dispatcher.ProcessNonBlocking('<message><x:y/></message>')
- self.assertEqual(2, len(msgs))
- # we should not have been disconnected after that message
- self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')))
- self.assertEqual(0, len(self.con.mockGetNamedCalls('disconnect')))
-
- # we should be able to keep parsing
- self.dispatcher.ProcessNonBlocking('<message><body>still here?</body></message>')
- self.assertEqual(3, len(msgs))
-
- def test_process_non_blocking(self):
- ''' Check for ProcessNonBlocking return types '''
- self._simulate_connect()
- process = self.dispatcher.ProcessNonBlocking
-
- # length of data expected
- data = "Please don't fail"
- result = process(data)
- self.assertEqual(result, len(data))
-
- # no data processed, link shall still be active
- result = process('')
- self.assertEqual(result, '0')
- self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')) +
- len(self.con.mockGetNamedCalls('disconnect')))
-
- # simulate disconnect
- result = process('</stream:stream>')
- self.assertEqual(1, len(self.client.mockGetNamedCalls('disconnect')))
-
- def test_return_stanza_handler(self):
- ''' Test sasl_error_conditions transformation in protocol.py '''
- # quick'n dirty...I wasn't aware of it existance and thought it would
- # always fail :-)
- self._simulate_connect()
- stanza = "<iq type='get' />"
- def send(data):
- self.assertEqual(str(data), '<iq xmlns="jabber:client" type="error"><error code="501" type="cancel"><feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">The feature requested is not implemented by the recipient or server and therefore cannot be processed.</text></error></iq>')
- self.client.send = send
- self.dispatcher.ProcessNonBlocking(stanza)
+ '''
+ Test class for NonBlocking dispatcher. Tested dispatcher will be plugged
+ into a mock client
+ '''
+ def setUp(self):
+ self.dispatcher = dispatcher_nb.XMPPDispatcher()
+
+ # Setup mock client
+ self.client = Mock()
+ self.client.__str__ = lambda: 'Mock' # FIXME: why do I need this one?
+ self.client._caller = Mock()
+ self.client.defaultNamespace = protocol.NS_CLIENT
+ self.client.Connection = Mock() # mock transport
+ self.con = self.client.Connection
+
+ def tearDown(self):
+ # Unplug if needed
+ if hasattr(self.dispatcher, '_owner'):
+ self.dispatcher.PlugOut()
+
+ def _simulate_connect(self):
+ self.dispatcher.PlugIn(self.client) # client is owner
+ # Simulate that we have established a connection
+ self.dispatcher.StreamInit()
+ self.dispatcher.ProcessNonBlocking("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'>")
+
+ def test_unbound_namespace_prefix(self):
+ '''tests our handling of a message with an unbound namespace prefix'''
+ self._simulate_connect()
+
+ msgs = []
+ def _got_message(conn, msg):
+ msgs.append(msg)
+ self.dispatcher.RegisterHandler('message', _got_message)
+
+ # should be able to parse a normal message
+ self.dispatcher.ProcessNonBlocking('<message><body>hello</body></message>')
+ self.assertEqual(1, len(msgs))
+
+ self.dispatcher.ProcessNonBlocking('<message><x:y/></message>')
+ self.assertEqual(2, len(msgs))
+ # we should not have been disconnected after that message
+ self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')))
+ self.assertEqual(0, len(self.con.mockGetNamedCalls('disconnect')))
+
+ # we should be able to keep parsing
+ self.dispatcher.ProcessNonBlocking('<message><body>still here?</body></message>')
+ self.assertEqual(3, len(msgs))
+
+ def test_process_non_blocking(self):
+ ''' Check for ProcessNonBlocking return types '''
+ self._simulate_connect()
+ process = self.dispatcher.ProcessNonBlocking
+
+ # length of data expected
+ data = "Please don't fail"
+ result = process(data)
+ self.assertEqual(result, len(data))
+
+ # no data processed, link shall still be active
+ result = process('')
+ self.assertEqual(result, '0')
+ self.assertEqual(0, len(self.con.mockGetNamedCalls('pollend')) +
+ len(self.con.mockGetNamedCalls('disconnect')))
+
+ # simulate disconnect
+ result = process('</stream:stream>')
+ self.assertEqual(1, len(self.client.mockGetNamedCalls('disconnect')))
+
+ def test_return_stanza_handler(self):
+ ''' Test sasl_error_conditions transformation in protocol.py '''
+ # quick'n dirty...I wasn't aware of it existance and thought it would
+ # always fail :-)
+ self._simulate_connect()
+ stanza = "<iq type='get' />"
+ def send(data):
+ self.assertEqual(str(data), '<iq xmlns="jabber:client" type="error"><error code="501" type="cancel"><feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /><text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">The feature requested is not implemented by the recipient or server and therefore cannot be processed.</text></error></iq>')
+ self.client.send = send
+ self.dispatcher.ProcessNonBlocking(stanza)
if __name__ == '__main__':
- unittest.main()
-
-# vim: se ts=3:
+ unittest.main()
diff --git a/test/unit/test_xmpp_transports_nb.py b/test/unit/test_xmpp_transports_nb.py
index b81f60d61..633549560 100644
--- a/test/unit/test_xmpp_transports_nb.py
+++ b/test/unit/test_xmpp_transports_nb.py
@@ -11,70 +11,68 @@ from common.xmpp import transports_nb
class TestModuleLevelFunctions(unittest.TestCase):
- '''
- Test class for functions defined at module level
- '''
- def test_urisplit(self):
- def check_uri(uri, proto, host, port, path):
- _proto, _host, _port, _path = transports_nb.urisplit(uri)
- self.assertEqual(proto, _proto)
- self.assertEqual(host, _host)
- self.assertEqual(path, _path)
- self.assertEqual(port, _port)
-
- check_uri('http://httpcm.jabber.org:5280/webclient', proto='http',
- host='httpcm.jabber.org', port=5280, path='/webclient')
-
- check_uri('http://httpcm.jabber.org/webclient', proto='http',
- host='httpcm.jabber.org', port=80, path='/webclient')
-
- check_uri('https://httpcm.jabber.org/webclient', proto='https',
- host='httpcm.jabber.org', port=443, path='/webclient')
+ '''
+ Test class for functions defined at module level
+ '''
+ def test_urisplit(self):
+ def check_uri(uri, proto, host, port, path):
+ _proto, _host, _port, _path = transports_nb.urisplit(uri)
+ self.assertEqual(proto, _proto)
+ self.assertEqual(host, _host)
+ self.assertEqual(path, _path)
+ self.assertEqual(port, _port)
- def test_get_proxy_data_from_dict(self):
- def check_dict(proxy_dict, host, port, user, passwd):
- _host, _port, _user, _passwd = transports_nb.get_proxy_data_from_dict(
- proxy_dict)
- self.assertEqual(_host, host)
- self.assertEqual(_port, port)
- self.assertEqual(_user, user)
- self.assertEqual(_passwd, passwd)
+ check_uri('http://httpcm.jabber.org:5280/webclient', proto='http',
+ host='httpcm.jabber.org', port=5280, path='/webclient')
- bosh_dict = {'bosh_content': u'text/xml; charset=utf-8',
- 'bosh_hold': 2,
- 'bosh_http_pipelining': False,
- 'bosh_uri': u'http://gajim.org:5280/http-bind',
- 'bosh_useproxy': False,
- 'bosh_wait': 30,
- 'bosh_wait_for_restart_response': False,
- 'host': u'172.16.99.11',
- 'pass': u'pass',
- 'port': 3128,
- 'type': u'bosh',
- 'useauth': True,
- 'user': u'user'}
- check_dict(bosh_dict, host=u'gajim.org', port=5280, user=u'user',
- passwd=u'pass')
+ check_uri('http://httpcm.jabber.org/webclient', proto='http',
+ host='httpcm.jabber.org', port=80, path='/webclient')
- proxy_dict = {'bosh_content': u'text/xml; charset=utf-8',
- 'bosh_hold': 2,
- 'bosh_http_pipelining': False,
- 'bosh_port': 5280,
- 'bosh_uri': u'',
- 'bosh_useproxy': True,
- 'bosh_wait': 30,
- 'bosh_wait_for_restart_response': False,
- 'host': u'172.16.99.11',
- 'pass': u'pass',
- 'port': 3128,
- 'type': 'socks5',
- 'useauth': True,
- 'user': u'user'}
- check_dict(proxy_dict, host=u'172.16.99.11', port=3128, user=u'user',
- passwd=u'pass')
-
-
-if __name__ == '__main__':
- unittest.main()
+ check_uri('https://httpcm.jabber.org/webclient', proto='https',
+ host='httpcm.jabber.org', port=443, path='/webclient')
+
+ def test_get_proxy_data_from_dict(self):
+ def check_dict(proxy_dict, host, port, user, passwd):
+ _host, _port, _user, _passwd = transports_nb.get_proxy_data_from_dict(
+ proxy_dict)
+ self.assertEqual(_host, host)
+ self.assertEqual(_port, port)
+ self.assertEqual(_user, user)
+ self.assertEqual(_passwd, passwd)
+
+ bosh_dict = {'bosh_content': u'text/xml; charset=utf-8',
+ 'bosh_hold': 2,
+ 'bosh_http_pipelining': False,
+ 'bosh_uri': u'http://gajim.org:5280/http-bind',
+ 'bosh_useproxy': False,
+ 'bosh_wait': 30,
+ 'bosh_wait_for_restart_response': False,
+ 'host': u'172.16.99.11',
+ 'pass': u'pass',
+ 'port': 3128,
+ 'type': u'bosh',
+ 'useauth': True,
+ 'user': u'user'}
+ check_dict(bosh_dict, host=u'gajim.org', port=5280, user=u'user',
+ passwd=u'pass')
-# vim: se ts=3:
+ proxy_dict = {'bosh_content': u'text/xml; charset=utf-8',
+ 'bosh_hold': 2,
+ 'bosh_http_pipelining': False,
+ 'bosh_port': 5280,
+ 'bosh_uri': u'',
+ 'bosh_useproxy': True,
+ 'bosh_wait': 30,
+ 'bosh_wait_for_restart_response': False,
+ 'host': u'172.16.99.11',
+ 'pass': u'pass',
+ 'port': 3128,
+ 'type': 'socks5',
+ 'useauth': True,
+ 'user': u'user'}
+ check_dict(proxy_dict, host=u'172.16.99.11', port=3128, user=u'user',
+ passwd=u'pass')
+
+
+if __name__ == '__main__':
+ unittest.main()