Philipp Hörist pushed to branch chat_markers at gajim / gajim
Commits: b8116f40 by Yann Leboulanger at 2019-10-03T21:00:04Z fix S5B to IBB fallback. Fixes #9692, #9784
- - - - - 0c3ed72e by Philipp Hörist at 2019-10-04T21:08:25Z Adapt to nbxmpp carbon data changes
- - - - - 58df7d42 by Philipp Hörist at 2019-10-04T21:08:25Z Check if Soup is available at startup
- - - - - 48cf6366 by Philipp Hörist at 2019-10-04T21:08:25Z Update README.md
Add Soup to runtime requirements
- - - - - 8409f5e7 by Philipp Hörist at 2019-10-05T12:08:07Z Plugins: Fix registering modules on enabling account
- - - - - 13acb5af by Philipp Hörist at 2019-10-05T22:00:16Z MAM: Simplify _parse_chat_attrs()
This became more simple due to nbxmpp changes
- - - - - 8cde7100 by Yann Leboulanger at 2019-10-05T22:09:47Z start implementing XEP-0333, chat markers
- - - - - 596c52d9 by Yann Leboulanger at 2019-10-05T22:09:47Z fix error
- - - - - cb88e8da by Philipp Hörist at 2019-10-05T22:09:47Z Remove chat marker from modules
- - - - - ccb249c1 by Yann Leboulanger at 2019-10-05T22:09:47Z add missing chat_markers module
- - - - - cd504fd3 by Yann Leboulanger at 2019-10-05T22:09:47Z Revert "Remove chat marker from modules"
This reverts commit f5377d98af23483244d71196a8e5cfab96111118.
- - - - - e6a9ef53 by Philipp Hörist at 2019-10-05T22:09:47Z Adapt to nbxmpp changes
- Move displayed event handler from roster to gui interface - Rename msg_id to message_id
- - - - - d6ea7500 by Philipp Hörist at 2019-10-05T22:09:47Z remove not needed change
- - - - - 1fd9ffca by Philipp Hörist at 2019-10-05T22:09:47Z Fix some errors
- - - - - 8914a8af by Philipp Hörist at 2019-10-05T22:09:47Z Remove breakpoint
- - - - - 1e37e384 by Philipp Hörist at 2019-10-06T12:56:22Z Improve code
- - - - -
16 changed files:
- README.md - gajim/chat_control_base.py - gajim/common/app.py - gajim/common/connection.py - gajim/common/events.py - gajim/common/jingle_content.py - gajim/common/jingle_ft.py - gajim/common/jingle_session.py - gajim/common/modules/__init__.py - + gajim/common/modules/chat_markers.py - gajim/common/modules/mam.py - gajim/common/modules/message.py - gajim/gajim.py - gajim/groupchat_control.py - gajim/gui_interface.py - gajim/plugins/pluginmanager.py
Changes:
===================================== README.md ===================================== @@ -12,6 +12,7 @@ - python3-css-parser - python3-keyring - python3-precis-i18n +- gir1.2-soup-2.4
### Optional Runtime Requirements
===================================== gajim/chat_control_base.py ===================================== @@ -322,6 +322,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self.received_history_pos = 0 self.orig_msg = None
+ # For XEP-0333 + self.last_msg_id = None + self.set_emoticon_popover()
# Attach speller @@ -1022,6 +1025,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): if restored: return
+ if message_id: + self.last_msg_id = message_id + if kind == 'incoming': if not self.type_id == message_control.TYPE_GC or \ app.config.notify_for_muc(jid) or \ @@ -1065,6 +1071,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): event_type.type_, self.contact.jid)
event = event_type(text, subject, self, msg_log_id, + message_id=message_id, show_in_roster=show_in_roster, show_in_systray=show_in_systray) app.events.add_event(self.account, full_jid, event) @@ -1220,6 +1227,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): types=type_): # There were events to remove self.redraw_after_event_removed(jid) + # XEP-0333 Send <displayed> tag. + con.get_module('ChatMarkers').send_displayed_marker( + self.get_full_jid(), self.last_msg_id, + self.type_id == message_control.TYPE_GC) + self.last_msg_id = None # send chatstate inactive to the one we're leaving # and active to the one we visit if self.msg_textview.has_text(): @@ -1262,8 +1274,14 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): # we are at the end if not app.events.remove_events( self.account, jid, types=types_list): + con = app.connections[self.account] # There were events to remove self.redraw_after_event_removed(jid) + # XEP-0333 Send <displayed> tag. + con.get_module('ChatMarkers').send_displayed_marker( + self.get_full_jid(), self.last_msg_id, + self.type_id == message_control.TYPE_GC) + self.last_msg_id = None
def _on_scrollbar_button_release(self, scrollbar, event): if event.get_button()[1] != 1: @@ -1328,6 +1346,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): We just removed a 'printed_*' event, redraw contact in roster or gc_roster and titles in roster and msg_win """ + if not self.parent_win: # minimized groupchat + return self.parent_win.redraw_tab(self) self.parent_win.show_title() # TODO : get the contact and check get_show_in_roster()
===================================== gajim/common/app.py ===================================== @@ -152,6 +152,7 @@ gajim_common_features = [ nbxmpp.NS_BYTESTREAM, nbxmpp.NS_FILE, nbxmpp.NS_MUC, + nbxmpp.NS_CHATMARKERS, nbxmpp.NS_COMMANDS, nbxmpp.NS_DISCO_INFO, nbxmpp.NS_LAST,
===================================== gajim/common/connection.py ===================================== @@ -308,6 +308,9 @@ class CommonConnection: obj.session.last_send = time.time() msg_iq.setThread(obj.session.thread_id)
+ if obj.message: + msg_iq.setMarkable() + self._push_stanza_message_outgoing(obj, msg_iq)
def _push_stanza_message_outgoing(self, obj, msg_iq): @@ -1637,6 +1640,7 @@ class Connection(CommonConnection, ConnectionHandlers): msg_iq.setID(obj.stanza_id) if obj.message: msg_iq.setOriginID(obj.stanza_id) + msg_iq.setMarkable()
if obj.correct_id: msg_iq.setTag('replace', attrs={'id': obj.correct_id},
===================================== gajim/common/events.py ===================================== @@ -95,13 +95,14 @@ class PmEvent(ChatEvent): class PrintedChatEvent(Event): type_ = 'printed_chat' def __init__(self, message, subject, control, msg_log_id, time_=None, - show_in_roster=False, show_in_systray=True): + message_id=None, show_in_roster=False, show_in_systray=True): Event.__init__(self, time_, show_in_roster=show_in_roster, show_in_systray=show_in_systray) self.message = message self.subject = subject self.control = control self.msg_log_id = msg_log_id + self.message_id = message_id
class PrintedGcMsgEvent(PrintedChatEvent): type_ = 'printed_gc_msg'
===================================== gajim/common/jingle_content.py ===================================== @@ -88,7 +88,7 @@ class JingleContent: 'session-terminate': [], 'transport-info': [self.__on_transport_info], 'transport-replace': [self.__on_transport_replace], - 'transport-accept': [], + 'transport-accept': [self.__on_transport_replace], 'transport-reject': [], 'iq-result': [], 'iq-error': [],
===================================== gajim/common/jingle_ft.py ===================================== @@ -312,7 +312,7 @@ class JingleFileTransfer(JingleContent): def __on_iq_result(self, stanza, content, error, action): log.info("__on_iq_result")
- if self.state == State.NOT_STARTED: + if self.state in (State.NOT_STARTED, State.CAND_RECEIVED): self.__state_changed(State.INITIALIZED) elif self.state == State.CAND_SENT_AND_RECEIVED: if not self.nominated_cand['our-cand'] and \
===================================== gajim/common/jingle_session.py ===================================== @@ -140,7 +140,9 @@ class JingleSession: 'transport-info': [self.__ack, self.__broadcast], 'transport-replace': [self.__ack, self.__broadcast, self.__on_transport_replace], #TODO - 'transport-accept': [self.__ack], #TODO + 'transport-accept': [self.__ack, self.__on_session_accept, + self.__on_content_accept, + self.__broadcast], 'transport-reject': [self.__ack], #TODO 'iq-result': [self.__broadcast], 'iq-error': [self.__on_error], @@ -244,8 +246,9 @@ class JingleSession: Currently used for transport replacement ''' content = self.contents[(creator, name)] - transport.set_sid(content.transport.sid) - transport.set_file_props(content.transport.file_props) + file_props = content.transport.file_props + file_props.transport_sid = transport.sid + transport.set_file_props(file_props) content.transport = transport # The content will have to be resend now that it is modified content.sent = False @@ -410,12 +413,13 @@ class JingleSession: # Anyway, content's transport is not modifiable yet pass elif transport_ns == nbxmpp.NS_JINGLE_IBB: - transport = JingleTransportIBB() + transport = JingleTransportIBB(node=content.getTag( + 'transport')) self.modify_content(creator, name, transport) self.state = JingleStates.PENDING self.contents[(creator, name)].state = State.TRANSPORT_REPLACE self.__ack(stanza, jingle, error, action) - self.__session_accept() + self.__transport_accept(transport) else: stanza, jingle = self.__make_jingle('transport-reject') content = jingle.setTag('content', attrs={'creator': creator, @@ -834,3 +838,12 @@ class JingleSession: conn=self.connection, jingle_session=self, media=media)) + + def __transport_accept(self, transport): + assert self.state != JingleStates.ENDED + stanza, jingle = self.__make_jingle('transport-accept') + self.__append_contents(jingle) + self.__broadcast(stanza, jingle, None, 'transport-accept') + self.connection.connection.send(stanza) + self.collect_iq_id(stanza.getID()) + self.state = JingleStates.ACTIVE
===================================== gajim/common/modules/__init__.py ===================================== @@ -40,6 +40,7 @@ MODULES = [ 'bookmarks', 'caps', 'carbons', + 'chat_markers', 'chatstates', 'delimiter', 'discovery',
===================================== gajim/common/modules/chat_markers.py ===================================== @@ -0,0 +1,103 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +# Chat Markers (XEP-0333) + +import nbxmpp +from nbxmpp.structs import StanzaHandler + +from gajim.common import app +from gajim.common.nec import NetworkEvent +from gajim.common.modules.base import BaseModule + + +class ChatMarkers(BaseModule): + + _nbxmpp_extends = 'ChatMarkers' + + def __init__(self, con): + BaseModule.__init__(self, con) + + self.handlers = [ + StanzaHandler(name='message', + callback=self._process_read_state_sync, + ns=nbxmpp.NS_CHATMARKERS, + priority=47), + StanzaHandler(name='message', + callback=self._process_chat_marker, + ns=nbxmpp.NS_CHATMARKERS, + priority=48), + ] + + def _process_read_state_sync(self, _con, _stanza, properties): + if not properties.is_marker: + return + + if not properties.marker.is_displayed: + return + + if properties.type.is_error: + return + + if properties.is_mam_message: + return + + if properties.type.is_groupchat: + manager = self._con.get_module('MUC').get_manager() + muc_data = manager.get(properties.muc_jid) + if muc_data is None: + return + + if properties.muc_nickname != muc_data.nick: + return + + elif not properties.is_carbon_message or not properties.carbon.is_sent: + return + + self._log.info('Read state sync: %s %s', + properties.jid, + properties.marker.id) + + app.nec.push_outgoing_event( + NetworkEvent('read-state-sync', + account=self._account, + jid=properties.jid, + properties=properties, + type=properties.type, + is_muc_pm=properties.is_muc_pm, + marker_id=properties.marker.id)) + + raise nbxmpp.NodeProcessed + + def _process_chat_marker(self, _con, _stanza, properties): + if not properties.is_marker: + return + # TODO: Implement showing displayed state in ConversationsTextview + raise nbxmpp.NodeProcessed + + def send_marker(self, jid, marker, id_, is_gc): + if is_gc: + message = nbxmpp.Message(to=jid, typ='groupchat') + else: + message = nbxmpp.Message(to=jid, typ='chat') + message.setTag(marker, namespace=nbxmpp.NS_CHATMARKERS, + attrs={'id': id_}) + self._nbxmpp().send(message) + + def send_displayed_marker(self, jid, id_, is_gc): + self.send_marker(jid, 'displayed', id_, is_gc) + + +def get_instance(*args, **kwargs): + return ChatMarkers(*args, **kwargs), 'ChatMarkers'
===================================== gajim/common/modules/mam.py ===================================== @@ -174,7 +174,7 @@ class MAM(BaseModule): if groupchat: event_attrs.update(self._parse_gc_attrs(properties)) else: - event_attrs.update(self._parse_chat_attrs(stanza, properties)) + event_attrs.update(self._parse_chat_attrs(properties))
stanza_id, message_id = self._get_unique_id(properties)
@@ -243,17 +243,13 @@ class MAM(BaseModule): 'real_jid': real_jid, 'kind': KindConstant.GC_MSG}
- def _parse_chat_attrs(self, stanza, properties): - frm = properties.jid - to = stanza.getTo() - if frm.bareMatch(self._con.get_own_jid()): - with_ = to + def _parse_chat_attrs(self, properties): + if properties.from_.bareMatch(self._con.get_own_jid()): kind = KindConstant.CHAT_MSG_SENT else: - with_ = frm kind = KindConstant.CHAT_MSG_RECV
- return {'with_': with_, + return {'with_': properties.jid, 'nick': None, 'kind': kind}
===================================== gajim/common/modules/message.py ===================================== @@ -69,9 +69,7 @@ class Message(BaseModule): stanza=stanza, account=self._account))
- forwarded = properties.carbon_type is not None - sent = properties.carbon_type == 'sent' - if sent: + if properties.is_carbon_message and properties.carbon.is_sent: # Ugly, we treat the from attr as the remote jid, # to make that work with sent carbons we have to do this. # TODO: Check where in Gajim and plugins we depend on that behavior @@ -157,8 +155,8 @@ class Message(BaseModule): 'id_': properties.id, 'encrypted': False, 'additional_data': additional_data, - 'forwarded': forwarded, - 'sent': sent, + 'forwarded': properties.is_carbon_message, + 'sent': properties.is_carbon_message and properties.carbon.is_sent, 'fjid': fjid, 'jid': jid, 'resource': resource,
===================================== gajim/gajim.py ===================================== @@ -67,12 +67,16 @@ def _init_gui(gui):
def _init_gtk(): import gi - gi.require_version('GLib', '2.0') - gi.require_version('Gio', '2.0') - gi.require_version('Gtk', '3.0') - gi.require_version('Gdk', '3.0') - gi.require_version('GObject', '2.0') - gi.require_version('Pango', '1.0') + try: + gi.require_version('GLib', '2.0') + gi.require_version('Gio', '2.0') + gi.require_version('Gtk', '3.0') + gi.require_version('Gdk', '3.0') + gi.require_version('GObject', '2.0') + gi.require_version('Pango', '1.0') + gi.require_version('Soup', '2.4') + except ValueError as error: + sys.exit('Missing dependency: %s' % error)
from gi.repository import Gtk gtk_ver = '%s.%s.%s' % (Gtk.get_major_version(),
===================================== gajim/groupchat_control.py ===================================== @@ -847,7 +847,8 @@ class GroupchatControl(ChatControlBase):
def on_private_message(self, nick, sent, msg, tim, xhtml, session, msg_log_id=None, encrypted=False, - displaymarking=None): + displaymarking=None, message_id=None): + # Do we have a queue? fjid = self.room_jid + '/' + nick
@@ -862,7 +863,9 @@ class GroupchatControl(ChatControlBase): session=session, form_node=None, displaymarking=displaymarking, - sent_forwarded=sent) + sent_forwarded=sent, + message_id=message_id) + app.events.add_event(self.account, fjid, event)
autopopup = app.config.get('autopopup') @@ -1106,7 +1109,8 @@ class GroupchatControl(ChatControlBase): self.on_private_message( nick, obj.sent, obj.msgtxt, obj.timestamp, obj.xhtml, self.session, msg_log_id=obj.msg_log_id, - encrypted=obj.encrypted, displaymarking=obj.displaymarking) + encrypted=obj.encrypted, displaymarking=obj.displaymarking, + message_id=obj.message_id)
def _nec_ping(self, obj): if self.contact.jid != obj.contact.room_jid:
===================================== gajim/gui_interface.py ===================================== @@ -285,6 +285,39 @@ class Interface: if ctrl and ctrl.session and len(obj.contact_list) > 1: ctrl.remove_session(ctrl.session)
+ def handle_event_read_state_sync(self, event): + if event.type.is_groupchat: + control = self.get_groupchat_control(event.account, + event.jid.getBare()) + if control is None: + log.warning('Groupchat control not found') + return + + jid = event.jid.getBare() + types = ['printed_gc_msg', 'printed_marked_gc_msg'] + + else: + types = ['chat', 'pm', 'printed_chat', 'printed_pm'] + jid = event.jid.getBare() + if event.is_muc_pm: + jid = event.jid + + control = app.interface.msg_win_mgr.get_control(jid, event.account) + + # Compare with control.last_msg_id. + events_ = app.events.get_events(event.account, jid, types) + if not events_: + log.warning('No Events') + return + + if events_[-1].message_id != event.marker_id: + return + + if not app.events.remove_events(event.account, jid, types=types): + # There were events to remove + if control is not None: + control.redraw_after_event_removed(event.jid) + def handle_event_msgerror(self, obj): #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[session])) account = obj.conn.name @@ -1173,6 +1206,7 @@ class Interface: 'unsubscribed-presence-received': [ self.handle_event_unsubscribed_presence], 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict], + 'read-state-sync': [self.handle_event_read_state_sync], }
def register_core_handlers(self): @@ -1449,6 +1483,12 @@ class Interface: ### Methods for opening new messages controls ################################################################################
+ def get_groupchat_control(self, account, room_jid): + control = self.minimized_controls[account].get(room_jid) + if control is None: + control = self.msg_win_mgr.get_gc_control(room_jid, account) + return control + def show_groupchat(self, account, room_jid): minimized_control = self.minimized_controls[account].get(room_jid) if minimized_control is not None:
===================================== gajim/plugins/pluginmanager.py ===================================== @@ -553,10 +553,10 @@ class PluginManager(metaclass=Singleton): ''' for plugin in self.plugins: if not plugin.active: - return + continue
if not hasattr(plugin, 'modules'): - return + continue
for module in plugin.modules: instance, name = module.get_instance(con)
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/263c5f2cc46d593b7263badf1789439d85...