diff --git a/src/chat_control.py b/src/chat_control.py index 6613f6b8a..d93adc6d4 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -312,6 +312,7 @@ class ChatControlBase(MessageControl): spell.set_language(lang) except (gobject.GError, RuntimeError): dialogs.AspellDictError(lang) + def on_msg_textview_populate_popup(self, textview, menu): '''we override the default context menu and we prepend an option to switch languages''' def _on_select_dictionary(widget, lang): @@ -594,21 +595,19 @@ class ChatControlBase(MessageControl): return True return False - def send_message(self, message, keyID = '', type_ = 'chat', chatstate = None, - msg_id = None, composing_xep = None, resource = None, - process_command = True, xhtml = None): + def send_message(self, message, keyID='', type_='chat', chatstate=None, + msg_id=None, composing_xep=None, resource=None, process_command=True, + xhtml=None, callback=None, callback_args=[]): '''Send the given message to the active tab. Doesn't return None if error ''' if not message or message == '\n': return None - ret = None - if not process_command or not self._process_command(message): - ret = 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) + 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) @@ -620,8 +619,6 @@ class ChatControlBase(MessageControl): message_buffer = self.msg_textview.get_buffer() message_buffer.set_text('') # clear message buffer (and tv of course) - return ret - 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) @@ -1802,11 +1799,7 @@ class ChatControl(ChatControlBase): gobject.source_remove(self.possible_inactive_timeout_id) self._schedule_activity_timers() - id_ = ChatControlBase.send_message(self, message, keyID, - type_ = 'chat', chatstate = chatstate_to_send, - composing_xep = composing_xep, - process_command = process_command, xhtml = xhtml) - if id_: + def _on_sent(id_, contact, message, encrypted, xhtml): # XXX: Once we have fallback to disco, remove notexistant check if gajim.capscache.is_supported(contact, NS_RECEIPTS) \ and not gajim.capscache.is_supported(contact, @@ -1820,6 +1813,11 @@ class ChatControl(ChatControlBase): encrypted = encrypted, xep0184_id = xep0184_id, xhtml = xhtml) + ChatControlBase.send_message(self, message, keyID, type_='chat', + chatstate=chatstate_to_send, composing_xep=composing_xep, + process_command=process_command, xhtml=xhtml, callback=_on_sent, + callback_args=[contact, message, encrypted, xhtml]) + 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? diff --git a/src/common/connection.py b/src/common/connection.py index cd7ed005c..6c2ed9eca 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -1135,7 +1135,7 @@ class Connection(ConnectionHandlers): 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): + original_message=None, delayed=None, callback=None, callback_args=[]): if not self.connection or self.connected < 2: return 1 try: @@ -1151,7 +1151,7 @@ class Connection(ConnectionHandlers): 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 2 + return fjid = jid if resource: fjid += '/' + resource @@ -1168,30 +1168,61 @@ class Connection(ConnectionHandlers): elif keyID.endswith('MISMATCH'): error = _('The contact\'s key (%s) does not match the key assigned in Gajim.' % keyID[:8]) else: - #encrypt - msgenc, error = self.gpg.encrypt(msg, [keyID]) - if msgenc and not error: - msgtxt = '[This message is *encrypted* (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 + ')' - else: - # Encryption failed, do not send message - tim = localtime() - self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) - return 3 + def encrypt_thread(msg, keyID): + # encrypt message. This function returns (msgenc, error) + return self.gpg.encrypt(msg, [keyID]) + gajim.thread_interface(encrypt_thread, [msg, keyID], + self._on_message_encrypted, [type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, subject, + chatstate, composing_xep, forward_from, delayed, session, + form_node, user_nick, keyID, callback, callback_args]) + return + + self._on_message_encrypted(self, ('', error), type_, msg, msgtxt, + original_message, fjid, resource, jid, xhtml, subject, chatstate, + composing_xep, forward_from, delayed, session, form_node, user_nick, + keyID, callback, callback_args) + + self._on_continue_message(type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, + forward_from, delayed, session, form_node, user_nick, callback, + callback_args) + + def _on_message_encrypted(self, output, type_, msg, msgtxt, original_message, + fjid, resource, jid, xhtml, subject, chatstate, composing_xep, forward_from, + delayed, session, form_node, user_nick, keyID, callback, callback_args): + msgenc, error = output + + if msgenc and not error: + msgtxt = '[This message is *encrypted* (See :XEP:`27`]' + lang = os.getenv('LANG') + if lang is not None and lang != 'en': # we're not english + # one in locale and one en + msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ + ' (' + msgtxt + ')' + self._on_continue_message(type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, + composing_xep, forward_from, delayed, session, form_node, user_nick, + callback, callback_args) + return + # Encryption failed, do not send message + tim = localtime() + self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) + + def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, + resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, + forward_from, delayed, session, form_node, user_nick, callback, + callback_args): if type_ == 'chat': - msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type_, - xhtml = xhtml) + 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) + 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) + 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) @@ -1206,11 +1237,9 @@ class Connection(ConnectionHandlers): # 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) + contact = gajim.contacts.get_contact(self.name, jid, resource) else: - contact = gajim.contacts. \ - get_contact_with_highest_priority(self.name, + contact = gajim.contacts.get_contact_with_highest_priority(self.name, jid) # chatstates - if peer supports xep85 or xep22, send chatstates @@ -1226,16 +1255,13 @@ class Connection(ConnectionHandlers): not gajim.capscache.is_supported(contact, 'notexistant')): # XEP-0085 - msg_iq.setTag(chatstate, - namespace = common.xmpp.NS_CHATSTATES) + 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) + chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT) if chatstate is 'composing' or msgtxt: - chatstate_node.addChild( - name = 'composing') + chatstate_node.addChild(name='composing') if forward_from: addresses = msg_iq.addChild('addresses', @@ -1255,8 +1281,7 @@ class Connection(ConnectionHandlers): if msgtxt and gajim.config.get_per('accounts', self.name, 'request_receipt') and gajim.capscache.is_supported(contact, common.xmpp.NS_RECEIPTS): - msg_iq.setTag('request', - namespace=common.xmpp.NS_RECEIPTS) + msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS) if session: # XEP-0201 @@ -1294,7 +1319,8 @@ class Connection(ConnectionHandlers): common.logger.LOG_DB_PATH self.dispatch('MSGSENT', (jid, msg, keyID)) - return msg_id + if callback: + callback(msg_id, *callback_args) def send_stanza(self, stanza): ''' send a stanza untouched ''' diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index a04ac525e..6648831f5 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1772,6 +1772,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, 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) @@ -1853,10 +1854,22 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, 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 = 'xep27' + def decrypt_thread(encmsg, keyID): + decmsg = self.gpg.decrypt(encmsg, keyID) + # \x00 chars are not allowed in C (so in GTK) + msgtxt = 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': diff --git a/src/common/gajim.py b/src/common/gajim.py index 89a72be32..3eac84495 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -63,6 +63,7 @@ If you start gajim from svn: 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 config = config.Config() version = config.get('version') connections = {} # 'account name': 'account (connection.Connection) instance' diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py index b683d3888..fe126d4c2 100644 --- a/src/common/xmpp/dispatcher_nb.py +++ b/src/common/xmpp/dispatcher_nb.py @@ -134,7 +134,10 @@ class XMPPDispatcher(PlugIn): self._owner.lastErrNode = None self._owner.lastErr = None self._owner.lastErrCode = None - self.StreamInit() + if hasattr(self._owner, 'StreamInit'): + self._owner.StreamInit() + else: + self.StreamInit() def plugout(self): ''' Prepares instance to be destructed. ''' diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py index 77f72e75e..a643454fa 100644 --- a/src/common/zeroconf/client_zeroconf.py +++ b/src/common/zeroconf/client_zeroconf.py @@ -155,11 +155,16 @@ class P2PClient(IdleObject): if on_not_ok: on_not_ok('Connection to host could not be established.') return - id = stanza.getThread() + 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) + self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, + thread_id)) else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [id] + self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, + thread_id)] def add_stanza(self, stanza, is_message=False): if self.Connection: @@ -170,24 +175,32 @@ class P2PClient(IdleObject): self.stanzaqueue.append((stanza, is_message)) if is_message: - 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) + self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, + thread_id)) else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [id] + self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, + thread_id)] return True def on_message_sent(self, connection_id): - self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0) + 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() - if self.on_ok: - self.on_ok() def StreamInit(self): ''' Send an initial stream header. ''' @@ -393,8 +406,9 @@ class P2PConnection(IdleObject, PlugIn): 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 in ids[self.fd]: - self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to, id)) + for (id_, thread_id) in ids[self.fd]: + self._owner.Dispatcher.Event('', DATA_ERROR, (self.client.to, + thread_id)) ids[self.fd] = [] self.pollend() @@ -679,8 +693,7 @@ class ClientZeroconf: stanza.setID(id_) if conn.add_stanza(stanza, is_message): if on_ok: - on_ok() - return id_ + on_ok(id_) if item['address'] in self.ip_to_hash: hash_ = self.ip_to_hash[item['address']] @@ -690,14 +703,11 @@ class ClientZeroconf: stanza.setID(id_) if conn.add_stanza(stanza, is_message): if on_ok: - on_ok() - return id_ + on_ok(id_) # otherwise open new connection stanza.setID('zero') P2PClient(None, item['address'], item['port'], self, [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok) - return 'zero' - # vim: se ts=3: diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index ceb322617..b7728c45d 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -364,7 +364,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): 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): + original_message=None, delayed=None, callback=None, callback_args=[]): fjid = jid if msg and not xhtml and gajim.config.get( @@ -402,7 +402,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): # Encryption failed, do not send message tim = time.localtime() self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) - return 3 + return if type_ == 'chat': msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, @@ -458,7 +458,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): if session.enable_encryption: msg_iq = session.encrypt_stanza(msg_iq) - def on_send_ok(): + def on_send_ok(id): no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') ji = gajim.get_jid_without_resource(jid) if session.is_loggable() and self.name not in no_log_for and\ @@ -473,9 +473,12 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): else: kind = 'single_msg_sent' gajim.logger.write(kind, jid, log_msg) - + self.dispatch('MSGSENT', (jid, msg, keyID)) + if callback: + callback(id, *callback_args) + def on_send_not_ok(reason): reason += ' ' + _('Your message could not be sent.') self.dispatch('MSGERROR', [jid, -1, reason, None, None, session]) @@ -484,8 +487,6 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): if ret == -1: # Contact Offline self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your message could not be sent.'), None, None, session]) - return ret - return ret def send_stanza(self, stanza): diff --git a/src/gajim.py b/src/gajim.py index 740971177..6ec553dc0 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -423,6 +423,7 @@ parser = optparser.OptionsParser(config_filename) import roster_window import profile_window import config +from threading import Thread class PassphraseRequest: @@ -482,6 +483,22 @@ class PassphraseRequest: cancel_handler=_cancel) self.dialog_created = True + +class ThreadInterface: + def __init__(self, func, func_args, callback, callback_args): + '''Call a function in a thread + + :param func: the function to call in the thread + :param func_args: list or arguments for this function + :param callback: callback to call once function is finished + :param callback_args: list of arguments for this callback + ''' + 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() + class Interface: ################################################################################ @@ -3071,6 +3088,7 @@ class Interface: 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': {}, diff --git a/src/message_control.py b/src/message_control.py index b27a1e9a2..ab580a1ae 100644 --- a/src/message_control.py +++ b/src/message_control.py @@ -158,9 +158,9 @@ class MessageControl: 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): + 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 @@ -182,11 +182,10 @@ class MessageControl: self.set_session(sess) # Send and update history - return 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) + 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: