From fe67de9bf102fd34b9477e455da1959365c29293 Mon Sep 17 00:00:00 2001 From: Thibaut GIRKA Date: Sun, 29 Nov 2009 21:39:50 +0100 Subject: [PATCH 01/15] [Jingle] Add converters and simplify pipelines configuration-side --- src/common/jingle_rtp.py | 33 ++++++++++++++++++-------------- src/common/multimedia_helpers.py | 6 ++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index e64c175c0..09159bfc6 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -252,18 +252,21 @@ class JingleAudio(JingleRTPContent): self.p2psession.set_codec_preferences(codecs) # the local parts + # TODO: Add queues? try: - self.sink = gst.parse_bin_from_description(gajim.config.get('audio_output_device'), True) - except: - self.session.connection.dispatch('ERROR', (_("Audio configuration error"), - _("Couldn't setup audio output. Check your audio configuration."))) - - try: - src_bin = gst.parse_bin_from_description(gajim.config.get('audio_input_device'), True) + src_bin = gst.parse_bin_from_description('%s ! audioconvert' + % gajim.config.get('audio_input_device'), True) except: self.session.connection.dispatch('ERROR', (_("Audio configuration error"), _("Couldn't setup audio input. Check your audio configuration."))) + try: + self.sink = gst.parse_bin_from_description('audioconvert ! %s' + % gajim.config.get('audio_output_device'), True) + except: + self.session.connection.dispatch('ERROR', (_("Audio configuration error"), + _("Couldn't setup audio output. Check your audio configuration."))) + self.mic_volume = src_bin.get_by_name('gajim_vol') self.mic_volume.set_property('volume', 1) @@ -291,24 +294,26 @@ class JingleVideo(JingleRTPContent): # the local parts try: - src_bin = gst.parse_bin_from_description(gajim.config.get('video_input_device'), True) + src_bin = gst.parse_bin_from_description('%s ! videoscale ! ffmpegcolorspace' + % gajim.config.get('video_input_device'), True) except: self.session.connection.dispatch('ERROR', (_("Video configuration error"), _("Couldn't setup video input. Check your video configuration."))) - caps = gst.element_factory_make('capsfilter') - caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240')) + #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) try: - self.sink = gst.parse_bin_from_description(gajim.config.get('video_output_device'), True) + self.sink = gst.parse_bin_from_description('videoscale ! ffmpegcolorspace ! %s' + % gajim.config.get('video_output_device'), True) except: self.session.connection.dispatch('ERROR', (_("Video configuration error"), _("Couldn't setup video output. Check your video configuration."))) self.pipeline.add(self.sink) - caps.get_pad('src').link(self.p2psession.get_property('sink-pad')) + 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: diff --git a/src/common/multimedia_helpers.py b/src/common/multimedia_helpers.py index 59d8b635f..4a2a4fb2e 100644 --- a/src/common/multimedia_helpers.py +++ b/src/common/multimedia_helpers.py @@ -78,11 +78,9 @@ class VideoInputManager(DeviceManager): self.detect_element('videotestsrc', _('Video test'), '%s is-live=true') # Auto src - self.detect_element('autovideosrc', _('Autodetect'), - '%s ! videoscale ! ffmpegcolorspace') + self.detect_element('autovideosrc', _('Autodetect')) # V4L2 src ; TODO: Figure out why it doesn't work - self.detect_element('v4l2src', _('V4L2: %s'), - '%s ! videoscale ! ffmpegcolorspace') + self.detect_element('v4l2src', _('V4L2: %s')) # Funny things, just to test... # self.devices['GOOM'] = 'audiotestsrc ! goom' # self.devices['screen'] = 'ximagesrc' From c44fde896f4c2c5895316d6b422e4bfcb5acc127 Mon Sep 17 00:00:00 2001 From: Thibaut GIRKA Date: Sun, 29 Nov 2009 22:45:34 +0100 Subject: [PATCH 02/15] [Jingle] Handle description-info ; catch only glib.GError when parsing bins --- src/common/jingle_rtp.py | 14 ++++++-------- src/common/jingle_session.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index 09159bfc6..d445d0efb 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -19,6 +19,7 @@ import gobject import xmpp import farsight, gst +from glib import GError import gajim @@ -35,12 +36,12 @@ class JingleRTPContent(JingleContent): self._dtmf_running = False self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO, 'video': farsight.MEDIA_TYPE_VIDEO}[media] - self.got_codecs = False self.candidates_ready = False # True when local candidates are prepared self.callbacks['session-initiate'] += [self.__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, @@ -177,8 +178,6 @@ class JingleRTPContent(JingleContent): """ Get peer codecs from what we get from peer """ - if self.got_codecs: - return codecs = [] for codec in content.getTag('description').iterTags('payload-type'): @@ -197,7 +196,6 @@ class JingleRTPContent(JingleContent): # glib.GError: There was no intersection between the remote codecs and # the local ones self.p2pstream.set_remote_codecs(codecs) - self.got_codecs = True def iter_codecs(self): codecs = self.p2psession.get_property('codecs') @@ -256,14 +254,14 @@ class JingleAudio(JingleRTPContent): try: src_bin = gst.parse_bin_from_description('%s ! audioconvert' % gajim.config.get('audio_input_device'), True) - except: + except GError: self.session.connection.dispatch('ERROR', (_("Audio configuration error"), _("Couldn't setup audio input. Check your audio configuration."))) try: self.sink = gst.parse_bin_from_description('audioconvert ! %s' % gajim.config.get('audio_output_device'), True) - except: + except GError: self.session.connection.dispatch('ERROR', (_("Audio configuration error"), _("Couldn't setup audio output. Check your audio configuration."))) @@ -296,7 +294,7 @@ class JingleVideo(JingleRTPContent): try: src_bin = gst.parse_bin_from_description('%s ! videoscale ! ffmpegcolorspace' % gajim.config.get('video_input_device'), True) - except: + except GError: self.session.connection.dispatch('ERROR', (_("Video configuration error"), _("Couldn't setup video input. Check your video configuration."))) #caps = gst.element_factory_make('capsfilter') @@ -308,7 +306,7 @@ class JingleVideo(JingleRTPContent): try: self.sink = gst.parse_bin_from_description('videoscale ! ffmpegcolorspace ! %s' % gajim.config.get('video_output_device'), True) - except: + except GError: self.session.connection.dispatch('ERROR', (_("Video configuration error"), _("Couldn't setup video output. Check your video configuration."))) self.pipeline.add(self.sink) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index 5fcc19029..fb020e3db 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -21,7 +21,7 @@ Handles Jingle sessions (XEP 0166) # * security preconditions # * actions: # - content-modify -# - description-info, session-info +# - session-info # - security-info # - transport-accept, transport-reject # - Tie-breaking From 684f45b148fa5f4ae4f6637adac2009b1a1f7e7e Mon Sep 17 00:00:00 2001 From: Alexander Cherniuk Date: Mon, 30 Nov 2009 12:53:26 +0200 Subject: [PATCH 03/15] Small refactoring bits --- src/common/contacts.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/common/contacts.py b/src/common/contacts.py index 25ffb8237..8f8397047 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -31,7 +31,7 @@ from common import caps from common.account import Account -import common.gajim +import common.gajim class XMPPEntity(object): """ @@ -175,9 +175,7 @@ class Contact(CommonContact): def is_transport(self): # if not '@' or '@' starts the jid then contact is transport - if self.jid.find('@') <= 0: - return True - return False + return self.jid.find('@') <= 0 class GC_Contact(CommonContact): @@ -443,18 +441,14 @@ class Contacts_New(): """ Remove all contacts for a given jid """ - if jid not in self._contacts: - return - del self._contacts[jid] + if jid in self._contacts: + del self._contacts[jid] def get_contacts(self, jid): """ Return the list of contact instances for this jid """ - if jid in self._contacts: - return self._contacts[jid] - else: - return [] + return self._contacts.get(jid, []) def get_contact(self, jid, resource=None): ### WARNING ### @@ -472,7 +466,6 @@ class Contacts_New(): for c in self._contacts[jid]: if c.resource == resource: return c - return None def iter_contacts(self): for jid in self._contacts.keys(): @@ -492,7 +485,6 @@ class Contacts_New(): def get_first_contact_from_jid(self, jid): if jid in self._contacts: return self._contacts[jid][0] - return None def get_contacts_from_group(self, group): """ @@ -538,9 +530,8 @@ class GC_Contacts(): del self._rooms[gc_contact.room_jid] def remove_room(self, room_jid): - if room_jid not in self._rooms: - return - del self._rooms[room_jid] + if room_jid in self._rooms: + del self._rooms[room_jid] def get_gc_list(self): return self._rooms.keys() @@ -643,7 +634,7 @@ class MetacontactManager(): def remove_metacontact(self, account, jid): if not account in self._metacontacts_tags: - return None + return found = None for tag in self._metacontacts_tags[account]: From a0fea76ff2a4263691b96dbac07d3aa1adac81c1 Mon Sep 17 00:00:00 2001 From: Alexander Cherniuk Date: Mon, 30 Nov 2009 14:58:43 +0200 Subject: [PATCH 04/15] BaseException.message is deprecated since 2.6. Fixes #5465 --- src/command_system/errors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/command_system/errors.py b/src/command_system/errors.py index 877a22acb..f687b72e3 100644 --- a/src/command_system/errors.py +++ b/src/command_system/errors.py @@ -20,13 +20,15 @@ class BaseError(Exception): """ def __init__(self, message, command=None, name=None): + self.message = message + self.command = command self.name = name if command and not name: self.name = command.first_name - super(BaseError, self).__init__(message) + super(BaseError, self).__init__() class DefinitionError(BaseError): """ From badbe820a1a3f116d56235507d42228d949bea92 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 30 Nov 2009 14:32:59 +0100 Subject: [PATCH 05/15] prevent traceback when writing in a closed gc control. Fixes #5455 --- src/htmltextview.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/htmltextview.py b/src/htmltextview.py index 8b65d8f87..11b69e4b6 100644 --- a/src/htmltextview.py +++ b/src/htmltextview.py @@ -892,8 +892,10 @@ class HtmlTextView(gtk.TextView): def on_text_buffer_mark_set(self, location, mark, unused_data): bounds = self.get_buffer().get_selection_bounds() if bounds: - clipboard = self.get_clipboard(gtk.gdk.SELECTION_PRIMARY) - clipboard.set_text(self.get_selected_text()) + # 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() From 66fdb1490d4f7ffe8e43afd35f6572344962e0ed Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 30 Nov 2009 16:36:47 +0100 Subject: [PATCH 06/15] don't try to import pysqlite2, we depend on sqlite3 --- src/common/check_paths.py | 9 +-------- src/common/exceptions.py | 11 ----------- src/common/logger.py | 8 +------- src/common/optparser.py | 9 +-------- src/history_manager.py | 8 +------- 5 files changed, 4 insertions(+), 41 deletions(-) diff --git a/src/common/check_paths.py b/src/common/check_paths.py index c4864fc78..122ab5528 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -27,18 +27,11 @@ import os import sys import stat -import exceptions from common import gajim import logger # DO NOT MOVE ABOVE OF import gajim -try: - import sqlite3 as sqlite # python 2.5 -except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable +import sqlite3 as sqlite def create_log_db(): print _('creating logs database') diff --git a/src/common/exceptions.py b/src/common/exceptions.py index 75f5b8913..ddd928752 100644 --- a/src/common/exceptions.py +++ b/src/common/exceptions.py @@ -21,17 +21,6 @@ ## along with Gajim. If not, see . ## -class PysqliteNotAvailable(Exception): - """ - Sqlite2 is not installed or python bindings are missing - """ - - def __init__(self): - Exception.__init__(self) - - def __str__(self): - return _('pysqlite2 (aka python-pysqlite2) dependency is missing. Exiting...') - class PysqliteOperationalError(Exception): """ Sqlite2 raised pysqlite2.dbapi2.OperationalError diff --git a/src/common/logger.py b/src/common/logger.py index c484246db..507b29cfa 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -38,13 +38,7 @@ from cStringIO import StringIO import exceptions import gajim -try: - import sqlite3 as sqlite # python 2.5 -except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable +import sqlite3 as sqlite import configpaths LOG_DB_PATH = configpaths.gajimpaths['LOG_DB'] diff --git a/src/common/optparser.py b/src/common/optparser.py index 33aaa8c9d..fa7b99daa 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -34,14 +34,7 @@ from common import gajim from common import helpers from common import caps -import exceptions -try: - import sqlite3 as sqlite # python 2.5 -except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable +import sqlite3 as sqlite import logger class OptionsParser: diff --git a/src/history_manager.py b/src/history_manager.py index 5d171b75c..c75f120b2 100644 --- a/src/history_manager.py +++ b/src/history_manager.py @@ -102,13 +102,7 @@ C_NICKNAME ) = range(2, 6) -try: - import sqlite3 as sqlite # python 2.5 -except ImportError: - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: - raise exceptions.PysqliteNotAvailable +import sqlite3 as sqlite class HistoryManager: From b65590bacdd2116e2ce8ad7892085865b22482b9 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 30 Nov 2009 16:37:08 +0100 Subject: [PATCH 07/15] don't traceback when gst is not installed --- src/config.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/config.py b/src/config.py index 2ff9ff73b..32acea179 100644 --- a/src/config.py +++ b/src/config.py @@ -59,7 +59,12 @@ from common.zeroconf import connection_zeroconf from common import dataforms from common import GnuPG -from common.multimedia_helpers import AudioInputManager, AudioOutputManager, VideoInputManager, VideoOutputManager +try: + from common.multimedia_helpers import AudioInputManager, AudioOutputManager + from common.multimedia_helpers import VideoInputManager, VideoOutputManager + HAS_GST = True +except ImportError: + HAS_GST = False from common.exceptions import GajimGeneralException @@ -431,10 +436,16 @@ class PreferencesWindow: if gajim.config.get(opt_name + '_device') == value: combobox.set_active(index) - 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()) + if HAS_GST: + create_av_combobox('audio_input', AudioInputManager().get_devices()) + create_av_combobox('audio_output', AudioOutputManager().get_devices()) + create_av_combobox('video_input', VideoInputManager().get_devices()) + create_av_combobox('video_output', VideoOutputManager().get_devices()) + else: + for opt_name in ('audio_input', 'audio_output', 'video_input', + 'video_output'): + combobox = self.xml.get_widget(opt_name + '_combobox') + combobox.set_sensitive(False) ### Advanced tab ### # open links with if os.name == 'nt': From 5401b457d5bacc26128b766745503d787db72dc1 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Mon, 30 Nov 2009 21:10:24 +0100 Subject: [PATCH 08/15] add stun_server option in pref window. --- data/glade/preferences_window.glade | 72 +++++++++++++++++++++++++++-- src/common/config.py | 1 + src/common/jingle_rtp.py | 12 ++++- src/config.py | 11 ++++- 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/data/glade/preferences_window.glade b/data/glade/preferences_window.glade index d3ea20d10..c9d6a34b1 100644 --- a/data/glade/preferences_window.glade +++ b/data/glade/preferences_window.glade @@ -1962,7 +1962,6 @@ $T will be replaced by auto-not-available timeout 1 2 - @@ -1975,7 +1974,6 @@ $T will be replaced by auto-not-available timeout 2 1 2 - @@ -2066,7 +2064,6 @@ $T will be replaced by auto-not-available timeout 1 2 - @@ -2079,7 +2076,6 @@ $T will be replaced by auto-not-available timeout 2 1 2 - @@ -2102,6 +2098,74 @@ $T will be replaced by auto-not-available timeout 1 + + + True + 0 + none + + + True + 12 + + + True + 3 + 6 + 6 + + + True + 0 + STUN server: + + + GTK_FILL + + + + + True + <i>(example: stunserver.org)</i> + True + + + 2 + 3 + GTK_FILL + + + + + True + True + + + + 1 + 2 + + + + + + + + + True + <b>Connection</b> + True + + + label_item + + + + + False + 2 + + 5 diff --git a/src/common/config.py b/src/common/config.py index 5108e60b1..fb5f0ebfd 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -276,6 +276,7 @@ class Config: 'audio_output_device': [opt_str, 'autoaudiosink'], 'video_input_device': [opt_str, 'autovideosrc ! videoscale ! ffmpegcolorspace'], 'video_output_device': [opt_str, 'autovideosink'], + 'stun_server': [opt_str, '', _('STUN server to use when using jingle')], } __options_per_key = { diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index d445d0efb..3750a67d1 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -70,8 +70,16 @@ class JingleRTPContent(JingleContent): # FIXME: Consider a workaround, here... # pidgin and telepathy-gabble don't follow the XEP, and it won't work # due to bad controlling-mode - params = {'controlling-mode': self.session.weinitiate,# 'debug': False} - 'stun-ip': '69.0.208.27', 'debug': False} + params = {'controlling-mode': self.session.weinitiate, 'debug': False} + stun_server = gajim.config.get('stun-server') + 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) diff --git a/src/config.py b/src/config.py index 32acea179..df0673d7f 100644 --- a/src/config.py +++ b/src/config.py @@ -421,8 +421,7 @@ class PreferencesWindow: buf = self.xml.get_widget('msg_textview').get_buffer() buf.connect('changed', self.on_msg_textview_changed) - ### Style tab ### - # Audio + ### Audio / Video tab ### def create_av_combobox(opt_name, device_dict): combobox = self.xml.get_widget(opt_name + '_combobox') cell = gtk.CellRendererText() @@ -446,6 +445,11 @@ class PreferencesWindow: 'video_output'): combobox = self.xml.get_widget(opt_name + '_combobox') combobox.set_sensitive(False) + + # Connection + entry = self.xml.get_widget('stun_server_entry') + entry.set_text(gajim.config.get('stun_server')) + ### Advanced tab ### # open links with if os.name == 'nt': @@ -1057,6 +1061,9 @@ class PreferencesWindow: def on_video_output_combobox_changed(self, widget): self.on_av_combobox_changed(widget, 'video_output') + 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: From d295f9261db27daa99ac3c8c33abc39414fe5d53 Mon Sep 17 00:00:00 2001 From: Thibaut GIRKA Date: Mon, 30 Nov 2009 22:29:32 +0100 Subject: [PATCH 09/15] [Jingle] a bit of documentation/clarification ; added make_bin_from_config --- src/common/jingle.py | 1 - src/common/jingle_content.py | 3 +++ src/common/jingle_rtp.py | 45 ++++++++++++++++-------------------- src/common/jingle_session.py | 4 ++-- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/common/jingle.py b/src/common/jingle.py index 65803a2ed..f4c002ca6 100644 --- a/src/common/jingle.py +++ b/src/common/jingle.py @@ -25,7 +25,6 @@ Handles the jingle signalling protocol # - video integration # * config: # - codecs -# - STUN # * figure out why it doesn't work with pidgin: # That's maybe a bug in pidgin: diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index 703f1a054..03c5895a2 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -106,6 +106,9 @@ class JingleContent(object): payload=payload) def send_candidate(self, candidate): + """ + Send a transport candidate for a previously defined transport. + """ content = self.__content() content.addChild(self.transport.make_transport([candidate])) self.session.send_transport_info(content) diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index 3750a67d1..109882332 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -60,7 +60,7 @@ class JingleRTPContent(JingleContent): # conference self.conference = gst.element_factory_make('fsrtpconference') - self.conference.set_property("sdes-cname", self.session.ourjid) + self.conference.set_property('sdes-cname', self.session.ourjid) self.pipeline.add(self.conference) self.funnel = None @@ -88,6 +88,16 @@ class JingleRTPContent(JingleContent): 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): + try: + bin = gst.parse_bin_from_description(pipeline + % gajim.config.get(config_key), True) + except GError, error_str: + self.session.connection.dispatch('ERROR', + (_("%s configuration error") % text.capitalize(), + _("Couldn't setup %s. Check your configuration.\n\nError was:\n%s") + % (text, error_str))) + def add_remote_candidates(self, candidates): JingleContent.add_remote_candidates(self, candidates) # FIXME: connectivity should not be etablished yet @@ -96,6 +106,7 @@ class JingleRTPContent(JingleContent): 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 @@ -259,19 +270,11 @@ class JingleAudio(JingleRTPContent): # the local parts # TODO: Add queues? - try: - src_bin = gst.parse_bin_from_description('%s ! audioconvert' - % gajim.config.get('audio_input_device'), True) - except GError: - self.session.connection.dispatch('ERROR', (_("Audio configuration error"), - _("Couldn't setup audio input. Check your audio configuration."))) + src_bin = self.make_bin_from_config('audio_input_device', + '%s ! audioconvert', _("audio input")) - try: - self.sink = gst.parse_bin_from_description('audioconvert ! %s' - % gajim.config.get('audio_output_device'), True) - except GError: - self.session.connection.dispatch('ERROR', (_("Audio configuration error"), - _("Couldn't setup audio output. Check your audio configuration."))) + self.sink = self.make_bin_from_config('audio_output_device', + 'audioconvert ! %s', _("audio output")) self.mic_volume = src_bin.get_by_name('gajim_vol') self.mic_volume.set_property('volume', 1) @@ -299,24 +302,16 @@ class JingleVideo(JingleRTPContent): JingleRTPContent.setup_stream(self) # the local parts - try: - src_bin = gst.parse_bin_from_description('%s ! videoscale ! ffmpegcolorspace' - % gajim.config.get('video_input_device'), True) - except GError: - self.session.connection.dispatch('ERROR', (_("Video configuration error"), - _("Couldn't setup video input. Check your video configuration."))) + 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) - try: - self.sink = gst.parse_bin_from_description('videoscale ! ffmpegcolorspace ! %s' - % gajim.config.get('video_output_device'), True) - except GError: - self.session.connection.dispatch('ERROR', (_("Video configuration error"), - _("Couldn't setup video output. Check your video configuration."))) + self.sink = self.make_bin_from_config('video_output_device', + '%s ! videoscale ! ffmpegcolorspace', _("video output")) self.pipeline.add(self.sink) src_bin.get_pad('src').link(self.p2psession.get_property('sink-pad')) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index fb020e3db..f1129a79c 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -16,7 +16,6 @@ Handles Jingle sessions (XEP 0166) """ #TODO: -# * Have JingleContent here # * 'senders' attribute of 'content' element # * security preconditions # * actions: @@ -55,7 +54,8 @@ class TieBreak(Exception): class JingleSession(object): """ - This represents one jingle session + 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): From ee426377951268581987907be155691cf378d511 Mon Sep 17 00:00:00 2001 From: Alexander Cherniuk Date: Tue, 1 Dec 2009 00:07:36 +0200 Subject: [PATCH 10/15] Keep the doc-strings (even one-liners) unified, please --- src/common/jingle_rtp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index 109882332..980e3c16a 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -106,7 +106,9 @@ class JingleRTPContent(JingleContent): self.p2pstream.set_remote_candidates(candidates) def batch_dtmf(self, events): - """ Send several DTMF tones. """ + """ + Send several DTMF tones + """ if self._dtmf_running: raise Exception # TODO: Proper exception self._dtmf_running = True From 002b8a720f3a11a39042ab1f994ffa91ca618052 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 1 Dec 2009 15:50:58 +0100 Subject: [PATCH 11/15] finish using icon theme. Fixes #2378 --- data/pixmaps/Makefile.am | 10 --- data/pixmaps/gajim.icns | Bin 42918 -> 0 bytes data/pixmaps/person.png | Bin 594 -> 0 bytes .../hicolor/128x128/apps}/gajim_about.png | Bin .../16x16/actions/gajim-cam_active.png | Bin .../16x16/actions/gajim-cam_inactive.png | Bin .../hicolor/16x16/actions/gajim-kbd_input.png | Bin .../16x16/actions/gajim-mic_active.png | Bin .../16x16/actions/gajim-mic_inactive.png | Bin .../16x16/actions/gajim-muc_separator.png | Bin .../16x16/actions/gajim-receipt_missing.png | Bin .../16x16/actions/gajim-security_high.png | Bin .../16x16/actions/gajim-security_low.png | Bin .../32x32/categories/gajim-agent-aim.png | Bin .../categories/gajim-agent-bytestreams.png | Bin .../categories/gajim-agent-conference.png | Bin .../32x32/categories/gajim-agent-disc.png | Bin .../32x32/categories/gajim-agent-error.png | Bin .../32x32/categories/gajim-agent-facebook.png | Bin .../categories/gajim-agent-gadu-gadu.png | Bin .../32x32/categories/gajim-agent-http-ws.png | Bin .../32x32/categories/gajim-agent-icq.png | Bin .../32x32/categories/gajim-agent-irc.png | Bin .../32x32/categories/gajim-agent-jabber.png | Bin .../32x32/categories/gajim-agent-jud.png | Bin .../32x32/categories/gajim-agent-mail.png | Bin .../32x32/categories/gajim-agent-mrim.png | Bin .../32x32/categories/gajim-agent-msn.png | Bin .../32x32/categories/gajim-agent-pubsub.png | Bin .../32x32/categories/gajim-agent-rss.png | Bin .../32x32/categories/gajim-agent-sip.png | Bin .../32x32/categories/gajim-agent-sms.png | Bin .../32x32/categories/gajim-agent-tv.png | Bin .../32x32/categories/gajim-agent-weather.png | Bin .../32x32/categories/gajim-agent-yahoo.png | Bin .../48x48/actions/gajim-chat_msg_recv.png | Bin .../48x48/actions/gajim-connection_lost.png | Bin .../hicolor/48x48/actions/gajim-ft_done.png | Bin .../hicolor/48x48/actions/gajim-ft_error.png | Bin .../48x48/actions/gajim-ft_request.png | Bin .../48x48/actions/gajim-ft_stopped.png | Bin .../48x48/actions/gajim-gc_invitation.png | Bin .../48x48/actions/gajim-new_email_recv.png | Bin .../48x48/actions/gajim-priv_msg_recv.png | Bin .../48x48/actions/gajim-security_high.png | Bin .../48x48/actions/gajim-security_low.png | Bin .../48x48/actions/gajim-single_msg_recv.png | Bin .../actions/gajim-subscription_request.png | Bin .../48x48/actions/gajim-unsubscribed.png | Bin .../hicolor/64x64/apps}/gajim.png | Bin .../hicolor/scalable/apps}/gajim.svg | 0 src/chat_control.py | 28 +++---- src/config.py | 5 +- src/conversation_textview.py | 7 +- src/dialogs.py | 43 ++++------- src/disco.py | 73 +++++++++--------- src/gajim.py | 3 +- src/gtkgui_helpers.py | 9 +++ src/gui_interface.py | 60 +++++--------- src/gui_menu_builder.py | 6 +- src/history_manager.py | 3 +- src/htmltextview.py | 6 +- src/notify.py | 37 ++++----- src/roster_window.py | 24 ++---- src/statusicon.py | 5 +- 65 files changed, 126 insertions(+), 193 deletions(-) delete mode 100644 data/pixmaps/gajim.icns delete mode 100644 data/pixmaps/person.png rename {data/pixmaps => icons/hicolor/128x128/apps}/gajim_about.png (100%) rename data/pixmaps/cam_active.png => icons/hicolor/16x16/actions/gajim-cam_active.png (100%) rename data/pixmaps/cam_inactive.png => icons/hicolor/16x16/actions/gajim-cam_inactive.png (100%) rename data/pixmaps/kbd_input.png => icons/hicolor/16x16/actions/gajim-kbd_input.png (100%) rename data/pixmaps/mic_active.png => icons/hicolor/16x16/actions/gajim-mic_active.png (100%) rename data/pixmaps/mic_inactive.png => icons/hicolor/16x16/actions/gajim-mic_inactive.png (100%) rename data/pixmaps/muc_separator.png => icons/hicolor/16x16/actions/gajim-muc_separator.png (100%) rename data/pixmaps/receipt_missing.png => icons/hicolor/16x16/actions/gajim-receipt_missing.png (100%) rename data/pixmaps/security-high.png => icons/hicolor/16x16/actions/gajim-security_high.png (100%) rename data/pixmaps/security-low.png => icons/hicolor/16x16/actions/gajim-security_low.png (100%) rename data/pixmaps/agents/aim.png => icons/hicolor/32x32/categories/gajim-agent-aim.png (100%) rename data/pixmaps/agents/bytestreams.png => icons/hicolor/32x32/categories/gajim-agent-bytestreams.png (100%) rename data/pixmaps/agents/conference.png => icons/hicolor/32x32/categories/gajim-agent-conference.png (100%) rename data/pixmaps/agents/disc.png => icons/hicolor/32x32/categories/gajim-agent-disc.png (100%) rename data/pixmaps/agents/error.png => icons/hicolor/32x32/categories/gajim-agent-error.png (100%) rename data/pixmaps/agents/facebook.png => icons/hicolor/32x32/categories/gajim-agent-facebook.png (100%) rename data/pixmaps/agents/gadu-gadu.png => icons/hicolor/32x32/categories/gajim-agent-gadu-gadu.png (100%) rename data/pixmaps/agents/http-ws.png => icons/hicolor/32x32/categories/gajim-agent-http-ws.png (100%) rename data/pixmaps/agents/icq.png => icons/hicolor/32x32/categories/gajim-agent-icq.png (100%) rename data/pixmaps/agents/irc.png => icons/hicolor/32x32/categories/gajim-agent-irc.png (100%) rename data/pixmaps/agents/jabber.png => icons/hicolor/32x32/categories/gajim-agent-jabber.png (100%) rename data/pixmaps/agents/jud.png => icons/hicolor/32x32/categories/gajim-agent-jud.png (100%) rename data/pixmaps/agents/mail.png => icons/hicolor/32x32/categories/gajim-agent-mail.png (100%) rename data/pixmaps/agents/mrim.png => icons/hicolor/32x32/categories/gajim-agent-mrim.png (100%) rename data/pixmaps/agents/msn.png => icons/hicolor/32x32/categories/gajim-agent-msn.png (100%) rename data/pixmaps/agents/pubsub.png => icons/hicolor/32x32/categories/gajim-agent-pubsub.png (100%) rename data/pixmaps/agents/rss.png => icons/hicolor/32x32/categories/gajim-agent-rss.png (100%) rename data/pixmaps/agents/sip.png => icons/hicolor/32x32/categories/gajim-agent-sip.png (100%) rename data/pixmaps/agents/sms.png => icons/hicolor/32x32/categories/gajim-agent-sms.png (100%) rename data/pixmaps/agents/tv.png => icons/hicolor/32x32/categories/gajim-agent-tv.png (100%) rename data/pixmaps/agents/weather.png => icons/hicolor/32x32/categories/gajim-agent-weather.png (100%) rename data/pixmaps/agents/yahoo.png => icons/hicolor/32x32/categories/gajim-agent-yahoo.png (100%) rename data/pixmaps/events/chat_msg_recv.png => icons/hicolor/48x48/actions/gajim-chat_msg_recv.png (100%) rename data/pixmaps/events/connection_lost.png => icons/hicolor/48x48/actions/gajim-connection_lost.png (100%) rename data/pixmaps/events/ft_done.png => icons/hicolor/48x48/actions/gajim-ft_done.png (100%) rename data/pixmaps/events/ft_error.png => icons/hicolor/48x48/actions/gajim-ft_error.png (100%) rename data/pixmaps/events/ft_request.png => icons/hicolor/48x48/actions/gajim-ft_request.png (100%) rename data/pixmaps/events/ft_stopped.png => icons/hicolor/48x48/actions/gajim-ft_stopped.png (100%) rename data/pixmaps/events/gc_invitation.png => icons/hicolor/48x48/actions/gajim-gc_invitation.png (100%) rename data/pixmaps/events/new_email_recv.png => icons/hicolor/48x48/actions/gajim-new_email_recv.png (100%) rename data/pixmaps/events/priv_msg_recv.png => icons/hicolor/48x48/actions/gajim-priv_msg_recv.png (100%) rename data/pixmaps/security-high-big.png => icons/hicolor/48x48/actions/gajim-security_high.png (100%) rename data/pixmaps/security-low-big.png => icons/hicolor/48x48/actions/gajim-security_low.png (100%) rename data/pixmaps/events/single_msg_recv.png => icons/hicolor/48x48/actions/gajim-single_msg_recv.png (100%) rename data/pixmaps/events/subscription_request.png => icons/hicolor/48x48/actions/gajim-subscription_request.png (100%) rename data/pixmaps/events/unsubscribed.png => icons/hicolor/48x48/actions/gajim-unsubscribed.png (100%) rename {data/pixmaps => icons/hicolor/64x64/apps}/gajim.png (100%) rename {data/pixmaps => icons/hicolor/scalable/apps}/gajim.svg (100%) diff --git a/data/pixmaps/Makefile.am b/data/pixmaps/Makefile.am index ae122a971..a7eadc116 100644 --- a/data/pixmaps/Makefile.am +++ b/data/pixmaps/Makefile.am @@ -1,15 +1,5 @@ pixmapsdir = $(pkgdatadir)/data/pixmaps nobase_dist_pixmaps_DATA = \ - $(srcdir)/events/*.png \ - $(srcdir)/agents/*.png \ - $(srcdir)/*.png \ - $(srcdir)/gajim.svg \ $(srcdir)/gajim.ico -gajimpixmapdir = $(datadir)/pixmaps -gajimpixmap_DATA = \ - $(srcdir)/gajim.png \ - $(srcdir)/gajim.svg \ - $(srcdir)/gajim_about.png - MAINTAINERCLEANFILES = Makefile.in diff --git a/data/pixmaps/gajim.icns b/data/pixmaps/gajim.icns deleted file mode 100644 index 37516d5e80152b50a41bbf8885ac9ee0960585eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42918 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 @@ -2463,9 +2462,7 @@ class PopupNotificationWindow: # default image if not path_to_image: - path_to_image = os.path.abspath( - os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png')) # img to display + path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48) if event_type == _('Contact Signed In'): bg_color = 'limegreen' @@ -4540,43 +4537,37 @@ class ESessionInfoWindow: def update_info(self): labeltext = _('''Your chat session with %(jid)s is encrypted.\n\nThis session's Short Authentication String is %(sas)s.''') % {'jid': self.session.jid, 'sas': self.session.sas} - dir_ = os.path.join(gajim.DATA_DIR, 'pixmaps') if self.session.verified_identity: labeltext += '\n\n' + _('''You have already verified this contact's identity.''') - security_image = 'security-high-big.png' + security_image = 'gajim-security_high' if self.session.control: self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), True) + self.session.is_loggable(), True) verification_status = _('''Contact's identity verified''') self.window.set_title(verification_status) self.xml.get_widget('verification_status_label').set_markup( - '' + - verification_status + - '') + '%s' % verification_status) self.xml.get_widget('dialog-action_area1').set_no_show_all(True) self.button_label.set_text(_('Verify again...')) else: if self.session.control: self.session.control._show_lock_image(True, 'E2E', True, - self.session.is_loggable(), False) + self.session.is_loggable(), False) labeltext += '\n\n' + _('''To be certain that only the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''') - security_image = 'security-low-big.png' + security_image = 'gajim-security_low' verification_status = _('''Contact's identity NOT verified''') self.window.set_title(verification_status) self.xml.get_widget('verification_status_label').set_markup( - '' + - verification_status + - '') + '%s' % verification_status) self.button_label.set_text(_('Verify...')) - path = os.path.join(dir_, security_image) - filename = os.path.abspath(path) - self.security_image.set_from_file(filename) + path = gtkgui_helpers.get_icon_path(security_image, 32) + self.security_image.set_from_file(path) self.xml.get_widget('info_display').set_markup(labeltext) @@ -4622,13 +4613,13 @@ class GPGInfoWindow: verification_status = _('''Contact's identity NOT verified''') info = _('The contact\'s key (%s) does not match the key ' 'assigned in Gajim.') % keyID[:8] - image = 'security-low-big.png' + image = 'gajim-security_low' elif not keyID: # No key assigned nor a key is used by remote contact verification_status = _('No GPG key assigned') info = _('No GPG key is assigned to this contact. So you cannot ' 'encrypt messages.') - image = 'security-low-big.png' + image = 'gajim-security_low' else: error = gajim.connections[account].gpg.encrypt('test', [keyID])[1] if error: @@ -4636,21 +4627,19 @@ class GPGInfoWindow: info = _('GPG key is assigned to this contact, but you do not ' 'trust his key, so message cannot be encrypted. Use ' 'your GPG client to trust this key.') - image = 'security-low-big.png' + image = 'gajim-security_low' else: verification_status = _('''Contact's identity verified''') info = _('GPG Key is assigned to this contact, and you trust his ' 'key, so messages will be encrypted.') - image = 'security-high-big.png' + image = 'gajim-security_high' status_label.set_markup('%s' % \ verification_status) info_label.set_markup(info) - dir_ = os.path.join(gajim.DATA_DIR, 'pixmaps') - path = os.path.join(dir_, image) - filename = os.path.abspath(path) - security_image.set_from_file(filename) + path = gtkgui_helpers.get_icon_path(image, 32) + security_image.set_from_file(path) xml.signal_autoconnect(self) self.window.show_all() diff --git a/src/disco.py b/src/disco.py index da9021dd4..8e5bc1e92 100644 --- a/src/disco.py +++ b/src/disco.py @@ -73,44 +73,44 @@ def _gen_agent_type_info(): (0, 0): (None, None), # Jabber server - ('server', 'im'): (ToplevelAgentBrowser, 'jabber.png'), - ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber.png'), - ('hierarchy', 'branch'): (AgentBrowser, 'jabber.png'), + ('server', 'im'): (ToplevelAgentBrowser, 'jabber'), + ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber'), + ('hierarchy', 'branch'): (AgentBrowser, 'jabber'), # Services - ('conference', 'text'): (MucBrowser, 'conference.png'), - ('headline', 'rss'): (AgentBrowser, 'rss.png'), - ('headline', 'weather'): (False, 'weather.png'), - ('gateway', 'weather'): (False, 'weather.png'), - ('_jid', 'weather'): (False, 'weather.png'), - ('gateway', 'sip'): (False, 'sip.png'), - ('directory', 'user'): (None, 'jud.png'), - ('pubsub', 'generic'): (PubSubBrowser, 'pubsub.png'), - ('pubsub', 'service'): (PubSubBrowser, 'pubsub.png'), - ('proxy', 'bytestreams'): (None, 'bytestreams.png'), # Socks5 FT proxy - ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail.png'), + ('conference', 'text'): (MucBrowser, 'conference'), + ('headline', 'rss'): (AgentBrowser, 'rss'), + ('headline', 'weather'): (False, 'weather'), + ('gateway', 'weather'): (False, 'weather'), + ('_jid', 'weather'): (False, 'weather'), + ('gateway', 'sip'): (False, 'sip'), + ('directory', 'user'): (None, 'jud'), + ('pubsub', 'generic'): (PubSubBrowser, 'pubsub'), + ('pubsub', 'service'): (PubSubBrowser, 'pubsub'), + ('proxy', 'bytestreams'): (None, 'bytestreams'), # Socks5 FT proxy + ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail'), # Transports - ('conference', 'irc'): (ToplevelAgentBrowser, 'irc.png'), - ('_jid', 'irc'): (False, 'irc.png'), - ('gateway', 'aim'): (False, 'aim.png'), - ('_jid', 'aim'): (False, 'aim.png'), - ('gateway', 'gadu-gadu'): (False, 'gadu-gadu.png'), - ('_jid', 'gadugadu'): (False, 'gadu-gadu.png'), - ('gateway', 'http-ws'): (False, 'http-ws.png'), - ('gateway', 'icq'): (False, 'icq.png'), - ('_jid', 'icq'): (False, 'icq.png'), - ('gateway', 'msn'): (False, 'msn.png'), - ('_jid', 'msn'): (False, 'msn.png'), - ('gateway', 'sms'): (False, 'sms.png'), - ('_jid', 'sms'): (False, 'sms.png'), - ('gateway', 'smtp'): (False, 'mail.png'), - ('gateway', 'yahoo'): (False, 'yahoo.png'), - ('_jid', 'yahoo'): (False, 'yahoo.png'), - ('gateway', 'mrim'): (False, 'mrim.png'), - ('_jid', 'mrim'): (False, 'mrim.png'), - ('gateway', 'facebook'): (False, 'facebook.png'), - ('_jid', 'facebook'): (False, 'facebook.png'), + ('conference', 'irc'): (ToplevelAgentBrowser, 'irc'), + ('_jid', 'irc'): (False, 'irc'), + ('gateway', 'aim'): (False, 'aim'), + ('_jid', 'aim'): (False, 'aim'), + ('gateway', 'gadu-gadu'): (False, 'gadu_gadu'), + ('_jid', 'gadugadu'): (False, 'gadu_gadu'), + ('gateway', 'http-ws'): (False, 'http_ws'), + ('gateway', 'icq'): (False, 'icq'), + ('_jid', 'icq'): (False, 'icq'), + ('gateway', 'msn'): (False, 'msn'), + ('_jid', 'msn'): (False, 'msn'), + ('gateway', 'sms'): (False, 'sms'), + ('_jid', 'sms'): (False, 'sms'), + ('gateway', 'smtp'): (False, 'mail'), + ('gateway', 'yahoo'): (False, 'yahoo'), + ('_jid', 'yahoo'): (False, 'yahoo'), + ('gateway', 'mrim'): (False, 'mrim'), + ('_jid', 'mrim'): (False, 'mrim'), + ('gateway', 'facebook'): (False, 'facebook'), + ('_jid', 'facebook'): (False, 'facebook'), } # Category type to "human-readable" description string, and sort priority @@ -291,13 +291,12 @@ class ServicesCache: info = _agent_type_info[(0, 0)] filename = info[1] if not filename: # we don't have an image to show for this type - filename = 'jabber.png' + filename = 'jabber' # Use the cache if possible if filename in _icon_cache: return _icon_cache[filename] # Or load it - filepath = os.path.join(gajim.DATA_DIR, 'pixmaps', 'agents', filename) - pix = gtk.gdk.pixbuf_new_from_file(filepath) + pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-' + filename, size=32) # Store in cache _icon_cache[filename] = pix return pix diff --git a/src/gajim.py b/src/gajim.py index a2e58f4e7..d0ed38f20 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -322,8 +322,7 @@ def pid_alive(): return True if pid_alive(): - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png') - pix = gtk.gdk.pixbuf_new_from_file(path_to_file) + pix = gtkgui_helpers.get_icon_pixmap('gajim', 48) gtk.window_set_default_icon(pix) # set the icon to all newly opened wind pritext = _('Gajim is already running') sectext = _('Another instance of Gajim seems to be running\nRun anyway?') diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py index 1c90e0159..021fb3929 100644 --- a/src/gtkgui_helpers.py +++ b/src/gtkgui_helpers.py @@ -82,6 +82,15 @@ def get_icon_path(icon_name, size=16): except gobject.GError, e: log.error("Unable to find icon %s: %s" % (icon_name, str(e))) +def add_image_to_menuitem(menuitem, icon_name): + img = gtk.Image() + path_img = get_icon_path(icon_name) + img.set_from_file(path_img) + menuitem.set_image(img) + +def add_image_to_button(button, icon_name): + add_image_to_menuitem(button, icon_name) + GLADE_DIR = os.path.join(gajim.DATA_DIR, 'glade') def get_glade(file_name, root = None): file_path = os.path.join(GLADE_DIR, file_name) diff --git a/src/gui_interface.py b/src/gui_interface.py index 6f5f06e4f..1ba0d8f8a 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -199,9 +199,7 @@ class Interface: def handle_event_connection_lost(self, account, array): # ('CONNECTION_LOST', account, [title, text]) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'connection_lost.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48) notify.popup(_('Connection Failed'), account, account, 'connection_failed', path, array[0], array[1]) @@ -593,9 +591,7 @@ class Interface: self.add_event(account, jid, 'subscription_request', (text, nick)) if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'subscription_request.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48) event_type = _('Subscription request') notify.popup(event_type, jid, account, 'subscription_request', path, event_type, jid) @@ -657,9 +653,7 @@ class Interface: self.add_event(account, jid, 'unsubscribed', contact) if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'unsubscribed.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48) event_type = _('Unsubscribed') notify.popup(event_type, jid, account, 'unsubscribed', path, event_type, jid) @@ -1117,9 +1111,7 @@ class Interface: array[3], array[4])) if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'gc_invitation.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48) event_type = _('Groupchat Invitation') notify.popup(event_type, jid, account, 'gc-invitation', path, event_type, room_jid) @@ -1139,7 +1131,7 @@ class Interface: sectext += _('You are currently connected without your OpenPGP key.') dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) else: - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'warning.png') + path = gtkgui_helpers.get_icon_path('gajim-warning', 48) notify.popup('warning', account, account, 'warning', path, _('OpenGPG Passphrase Incorrect'), _('You are currently connected without your OpenPGP key.')) @@ -1276,8 +1268,7 @@ class Interface: self.add_event(account, jid, 'file-send-error', file_props) if helpers.allow_showing_notification(account): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) event_type = _('File Transfer Error') notify.popup(event_type, jid, account, 'file-send-error', path, event_type, file_props['name']) @@ -1287,8 +1278,7 @@ class Interface: gmail_new_messages = int(array[1]) gmail_messages_list = array[2] if gajim.config.get('notify_on_new_gmail_email'): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'new_email_recv.png') + path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48) title = _('New mail on %(gmail_mail_address)s') % \ {'gmail_mail_address': jid} text = i18n.ngettext('You have %d new mail conversation', @@ -1311,7 +1301,6 @@ class Interface: if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): helpers.play_sound('gmail_received') - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) notify.popup(_('New E-mail'), jid, account, 'gmail', path_to_image=path, title=title, text=text) @@ -1343,9 +1332,7 @@ class Interface: if helpers.allow_showing_notification(account): # check if we should be notified - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') - - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) event_type = _('File Transfer Error') notify.popup(event_type, jid, account, msg_type, path, title = event_type, text = file_props['name']) @@ -1374,11 +1361,9 @@ class Interface: self.add_event(account, jid, 'file-request', file_props) if helpers.allow_showing_notification(account): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'ft_request.png') + path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48) txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( account, jid) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) event_type = _('File Transfer Request') notify.popup(event_type, jid, account, 'file-request', path_to_image = path, title = event_type, text = txt) @@ -1449,11 +1434,11 @@ class Interface: if event_type == _('File Transfer Completed'): txt = _('You successfully received %(filename)s from %(name)s.')\ % {'filename': filename, 'name': name} - img = 'ft_done.png' + img_name = 'gajim-ft_done' else: # ft stopped txt = _('File transfer of %(filename)s from %(name)s stopped.')\ % {'filename': filename, 'name': name} - img = 'ft_stopped.png' + img_name = 'gajim-ft_stopped' else: receiver = file_props['receiver'] if hasattr(receiver, 'jid'): @@ -1466,23 +1451,23 @@ class Interface: if event_type == _('File Transfer Completed'): txt = _('You successfully sent %(filename)s to %(name)s.')\ % {'filename': filename, 'name': name} - img = 'ft_done.png' + img_name = 'gajim-ft_done' else: # ft stopped txt = _('File transfer of %(filename)s to %(name)s stopped.')\ % {'filename': filename, 'name': name} - img = 'ft_stopped.png' - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', img) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + img_name = 'gajim-ft_stopped' + path = gtkgui_helpers.get_icon_path(img_name, 48) else: txt = '' + path = '' if gajim.config.get('notify_on_file_complete') and \ (gajim.config.get('autopopupaway') or \ gajim.connections[account].connected in (2, 3)): # we want to be notified and we are online/chat or we don't mind # bugged when away/na/busy - notify.popup(event_type, jid, account, msg_type, path_to_image = path, - title = event_type, text = txt) + notify.popup(event_type, jid, account, msg_type, path_to_image=path, + title=event_type, text=txt) def handle_event_stanza_arrived(self, account, stanza): if account not in self.instances: @@ -1766,11 +1751,9 @@ class Interface: if helpers.allow_showing_notification(account): # TODO: we should use another pixmap ;-) - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'ft_request.png') txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid( account, peerjid) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48) event_type = _('Voice Chat Request') notify.popup(event_type, peerjid, account, 'jingle-incoming', path_to_image = path, title = event_type, text = txt) @@ -2782,8 +2765,8 @@ class Interface: if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'): status = status + '.png' elif status == 'online': - prefix = os.path.join(gajim.DATA_DIR, 'pixmaps') - status = 'gajim.png' + prefix = '' + status = gtkgui_helpers.get_icon_path('gajim', 32) path = os.path.join(prefix, status) try: obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn') @@ -3386,8 +3369,7 @@ class Interface: import statusicon self.systray = statusicon.StatusIcon() - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png') - pix = gtk.gdk.pixbuf_new_from_file(path_to_file) + pix = gtkgui_helpers.get_icon_pixmap('gajim', 32) # set the icon to all windows gtk.window_set_default_icon(pix) diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py index f18354175..5fd799a99 100644 --- a/src/gui_menu_builder.py +++ b/src/gui_menu_builder.py @@ -207,11 +207,7 @@ def get_contact_menu(contact, account, use_multiple_contacts=True, if not our_jid: # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - rename_menuitem.set_image(img) + gtkgui_helpers.add_image_to_menuitem(rename_menuitem, 'gajim-kbd_input') muc_icon = gtkgui_helpers.load_icon('muc_active') if muc_icon: diff --git a/src/history_manager.py b/src/history_manager.py index c75f120b2..13dd22a62 100644 --- a/src/history_manager.py +++ b/src/history_manager.py @@ -107,8 +107,7 @@ import sqlite3 as sqlite class HistoryManager: def __init__(self): - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png') - pix = gtk.gdk.pixbuf_new_from_file(path_to_file) + pix = gtkgui_helpers.get_icon_pixmap('gajim') gtk.window_set_default_icon(pix) # set the icon to all newly opened windows if not os.path.exists(LOG_DB_PATH): diff --git a/src/htmltextview.py b/src/htmltextview.py index 11b69e4b6..35a289ebb 100644 --- a/src/htmltextview.py +++ b/src/htmltextview.py @@ -941,12 +941,12 @@ if __name__ == '__main__': htmlview = ConversationTextview(None) - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') + path = gtkgui_helpers.get_icon_path('gajim-muc_separator') # use this for hr - htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - + htmlview.tv.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path) tooltip = tooltips.BaseTooltip() + def on_textview_motion_notify_event(widget, event): """ Change the cursor to a hand when we are over a mail or an url diff --git a/src/notify.py b/src/notify.py index 587c4e292..37a7fd267 100644 --- a/src/notify.py +++ b/src/notify.py @@ -234,16 +234,16 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): show_image = 'online.png' suffix = '_notif_size_colored' transport_name = gajim.get_transport_name_from_jid(jid) - img = None + img_path = None if transport_name: - img = os.path.join(helpers.get_transport_path(transport_name), + img_path = os.path.join(helpers.get_transport_path(transport_name), '48x48', show_image) - if not img or not os.path.isfile(img): + if not img_path or not os.path.isfile(img_path): iconset = gajim.config.get('iconset') - img = os.path.join(helpers.get_iconset_path(iconset), '48x48', + img_path = os.path.join(helpers.get_iconset_path(iconset), '48x48', show_image) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img, - jid = jid, suffix = suffix) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img_path, jid=jid, + suffix=suffix) if event == 'status_change': title = _('%(nick)s Changed Status') % \ {'nick': gajim.get_name_from_jid(account, jid)} @@ -273,16 +273,14 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): elif event == 'new_message': if message_type == 'normal': # single message event_type = _('New Single Message') - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'single_msg_recv.png') + img_name = 'gajim-single_msg_recv' title = _('New Single Message from %(nickname)s') % \ {'nickname': nickname} text = message elif message_type == 'pm': # private message event_type = _('New Private Message') room_name = gajim.get_nick_from_jid(jid) - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'priv_msg_recv.png') + img_name = 'gajim-priv_msg_recv' title = _('New Private Message from group chat %s') % room_name if message: text = _('%(nickname)s: %(message)s') % {'nickname': nickname, @@ -292,14 +290,13 @@ def notify(event, jid, account, parameters, advanced_notif_num=None): else: # chat message event_type = _('New Message') - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png') + img_name = 'gajim-chat_msg_recv' title = _('New Message from %(nickname)s') % \ {'nickname': nickname} text = message - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + img_path = gtkgui_helpers.get_icon_path(img_name, 48) popup(event_type, jid, account, message_type, - path_to_image=path, title=title, text=text) + path_to_image=img_path, title=title, text=text) if do_sound: snd_file = None @@ -342,9 +339,7 @@ def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None, """ # default image if not path_to_image: - path_to_image = os.path.abspath( - os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png')) # img to display + path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48) if gajim.HAVE_INDICATOR and event_type in (_('New Message'), _('New Single Message'), _('New Private Message')): @@ -529,9 +524,8 @@ class DesktopNotification: ntype = 'unsubscribed' else: # default failsafe values - self.path_to_image = os.path.abspath( - os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'chat_msg_recv.png')) # img to display + self.path_to_image = gtkgui_helpers.get_icon_path( + 'gajim-chat_msg_recv', 48) ntype = 'im' # Notification Type self.notif = dbus_support.get_notifications_interface(self) @@ -554,8 +548,7 @@ class DesktopNotification: notification_text = ('' \ '%(title)s
%(text)s') % {'title': self.title, 'text': self.text, 'image': self.path_to_image} - gajim_icon = os.path.abspath(os.path.join(gajim.DATA_DIR, 'pixmaps', - 'gajim.png')) + gajim_icon = gtkgui_helpers.get_icon_path('gajim', 48) self.notif.Notify( dbus.String(_('Gajim')), # app_name (string) dbus.UInt32(0), # replaces_id (uint) diff --git a/src/roster_window.py b/src/roster_window.py index 50249d642..9e56d3c97 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -4962,10 +4962,7 @@ class RosterWindow: sub_menu.append(item) item = gtk.ImageMenuItem(_('_Change Status Message')) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path) - item.set_image(img) + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') sub_menu.append(item) item.connect('activate', self.on_change_status_message_activate, account) @@ -5063,10 +5060,7 @@ class RosterWindow: sub_menu.append(item) item = gtk.ImageMenuItem(_('_Change Status Message')) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path) - item.set_image(img) + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') sub_menu.append(item) item.connect('activate', self.on_change_status_message_activate, account) @@ -5219,11 +5213,7 @@ class RosterWindow: # Rename rename_item = gtk.ImageMenuItem(_('Re_name')) # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - rename_item.set_image(img) + gtkgui_helpers.add_image_to_menuitem(rename_item, 'gajim-kbd_input') menu.append(rename_item) rename_item.connect('activate', self.on_rename, 'group', group, account) @@ -5481,11 +5471,7 @@ class RosterWindow: # Rename item = gtk.ImageMenuItem(_('_Rename')) # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - item.set_image(img) + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') manage_transport_submenu.append(item) item.connect('activate', self.on_rename, 'agent', jid, account) if gajim.account_is_disconnected(account): @@ -5813,7 +5799,7 @@ class RosterWindow: # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) liststore.append(['SEPARATOR', None, '', True]) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') + path = gtkgui_helpers.get_icon_path('gajim-kbd_input') img = gtk.Image() img.set_from_file(path) # sensitivity to False because by default we're offline diff --git a/src/statusicon.py b/src/statusicon.py index 556fb9fa1..33c3fa063 100644 --- a/src/statusicon.py +++ b/src/statusicon.py @@ -207,10 +207,7 @@ class StatusIcon: sub_menu.append(item) item = gtk.ImageMenuItem(_('_Change Status Message...')) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path) - item.set_image(img) + gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input') sub_menu.append(item) item.connect('activate', self.on_change_status_message_activate) From 33b9a8116c0f02ea54e928f41e4e1b6bee06937f Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 1 Dec 2009 16:44:31 +0100 Subject: [PATCH 12/15] install gajim.png/svg in $PREFIX/share/icons so that other apps can use it. Fixes #5332 --- icons/Makefile.am | 3 +++ 1 file changed, 3 insertions(+) diff --git a/icons/Makefile.am b/icons/Makefile.am index fb75f0ef3..e61275c37 100644 --- a/icons/Makefile.am +++ b/icons/Makefile.am @@ -1,4 +1,7 @@ iconsdir = $(pkgdatadir)/icons nobase_dist_icons_DATA = $(srcdir)/*/*/*/* +systemiconsdir = $(datadir)/icons +nobase_dist_systemicons_DATA = $(srcdir)/*/*/*/gajim.* + MAINTAINERCLEANFILES = Makefile.in From e7dd3e7d135d58681296feaddd6a6d16688efa30 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 1 Dec 2009 16:51:11 +0100 Subject: [PATCH 13/15] don't show in roster / notification that a contact unsubscribed us if we already popup up the dialog. Fixes #5470 --- src/gui_interface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui_interface.py b/src/gui_interface.py index 1ba0d8f8a..9f1ab9273 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -649,6 +649,7 @@ class Interface: if helpers.allow_popup_window(account) or not self.systray_enabled: self.show_unsubscribed_dialog(account, contact) + return self.add_event(account, jid, 'unsubscribed', contact) From 484e3970d3c7582810dda581aa8ff0e6ef8f9120 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 1 Dec 2009 17:41:43 +0100 Subject: [PATCH 14/15] [dm] define an exception we use. Fixes #5457 --- src/common/dbus_support.py | 2 +- src/common/exceptions.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py index 24d4525ac..4762b474d 100644 --- a/src/common/dbus_support.py +++ b/src/common/dbus_support.py @@ -63,7 +63,7 @@ class SystemBus: raise exceptions.DbusNotSupported if not self.present(): - raise exceptions.SystemBusNotPresent + raise exceptions.SystemBusNotPresent return self.system_bus def bus(self): diff --git a/src/common/exceptions.py b/src/common/exceptions.py index ddd928752..090255768 100644 --- a/src/common/exceptions.py +++ b/src/common/exceptions.py @@ -75,7 +75,20 @@ class SessionBusNotPresent(Exception): Exception.__init__(self) def __str__(self): - return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus') + return _('Session bus is not available.\nTry reading %(url)s') % \ + {'url': 'http://trac.gajim.org/wiki/GajimDBus'} + +class SystemBusNotPresent(Exception): + """ + This exception indicates that there is no session daemon + """ + + def __init__(self): + Exception.__init__(self) + + def __str__(self): + return _('System bus is not available.\nTry reading %(url)s') % \ + {'url': 'http://trac.gajim.org/wiki/GajimDBus'} class NegotiationError(Exception): """ From 592bacce4a8d835aa48ade5eb12b8f17604b2c57 Mon Sep 17 00:00:00 2001 From: Thibaut GIRKA Date: Tue, 1 Dec 2009 22:15:50 +0100 Subject: [PATCH 15/15] [Jingle] Fix make_bin_from_config, improve JingleSession.__parse_contents --- src/common/jingle_content.py | 6 ++++ src/common/jingle_rtp.py | 4 ++- src/common/jingle_session.py | 55 +++++++++++++++++++----------------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index 03c5895a2..0dd013660 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -23,6 +23,12 @@ def get_jingle_content(node): return contents[namespace](node) +class FailedApplication(Exception): + """ + Exception that should be raised when a content fails to setup. + """ + + class JingleContent(object): """ An abstraction of content in Jingle sessions diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py index 980e3c16a..2795e32ad 100644 --- a/src/common/jingle_rtp.py +++ b/src/common/jingle_rtp.py @@ -24,7 +24,7 @@ from glib import GError import gajim from jingle_transport import JingleTransportICEUDP -from jingle_content import contents, JingleContent +from jingle_content import contents, JingleContent, FailedApplication class JingleRTPContent(JingleContent): @@ -92,11 +92,13 @@ class JingleRTPContent(JingleContent): try: bin = gst.parse_bin_from_description(pipeline % gajim.config.get(config_key), 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\nError was:\n%s") % (text, error_str))) + raise FailedApplication def add_remote_candidates(self, candidates): JingleContent.add_remote_candidates(self, candidates) diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index f1129a79c..806207157 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -29,7 +29,7 @@ Handles Jingle sessions (XEP 0166) import gajim #Get rid of that? import xmpp from jingle_transport import get_jingle_transport -from jingle_content import get_jingle_content +from jingle_content import get_jingle_content, FailedApplication # FIXME: Move it to JingleSession.States? class JingleStates(object): @@ -394,8 +394,8 @@ class JingleSession(object): raise OutOfOrder parse_result = self.__parse_contents(jingle) - contents = parse_result[2] - rejected_contents = parse_result[3] + contents = parse_result[0] + rejected_contents = parse_result[1] for name, creator in rejected_contents: # TODO @@ -426,21 +426,13 @@ class JingleSession(object): # error. # Lets check what kind of jingle session does the peer want - contents_ok, transports_ok, contents, pouet = self.__parse_contents(jingle) + contents, contents_rejected, reason = self.__parse_contents(jingle) # If there's no content we understand... - if not contents_ok: + if not contents: # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate reason = xmpp.Node('reason') - reason.setTag('unsupported-applications') - self.__ack(stanza, jingle, error, action) - self._session_terminate(reason) - raise xmpp.NodeProcessed - - if not transports_ok: - # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate - reason = xmpp.Node('reason') - reason.setTag('unsupported-transports') + reason.setTag(reason) self.__ack(stanza, jingle, error, action) self._session_terminate(reason) raise xmpp.NodeProcessed @@ -485,26 +477,37 @@ class JingleSession(object): # TODO: Needs some reworking contents = [] contents_rejected = [] - contents_ok = False - transports_ok = False + 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: - contents_ok = True - if transport: - content = content_type(self, transport) - self.add_content(element['name'], - content, 'peer') - contents.append((content.media,)) - transports_ok = True - else: - contents_rejected.append((element['name'], 'peer')) + 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 FailedApplication: + reasons.add('failed-application') else: contents_rejected.append((element['name'], 'peer')) + failed.add('unsupported-applications') - return (contents_ok, transports_ok, contents, contents_rejected) + 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: