Philipp Hörist pushed to branch chat_markers at gajim / gajim
Commits:
-
b8116f40
by Yann Leboulanger at 2019-10-03T21:00:04Z
-
0c3ed72e
by Philipp Hörist at 2019-10-04T21:08:25Z
-
58df7d42
by Philipp Hörist at 2019-10-04T21:08:25Z
-
48cf6366
by Philipp Hörist at 2019-10-04T21:08:25Z
-
8409f5e7
by Philipp Hörist at 2019-10-05T12:08:07Z
-
13acb5af
by Philipp Hörist at 2019-10-05T22:00:16Z
-
8cde7100
by Yann Leboulanger at 2019-10-05T22:09:47Z
-
596c52d9
by Yann Leboulanger at 2019-10-05T22:09:47Z
-
cb88e8da
by Philipp Hörist at 2019-10-05T22:09:47Z
-
ccb249c1
by Yann Leboulanger at 2019-10-05T22:09:47Z
-
cd504fd3
by Yann Leboulanger at 2019-10-05T22:09:47Z
-
e6a9ef53
by Philipp Hörist at 2019-10-05T22:09:47Z
-
d6ea7500
by Philipp Hörist at 2019-10-05T22:09:47Z
-
1fd9ffca
by Philipp Hörist at 2019-10-05T22:09:47Z
-
8914a8af
by Philipp Hörist at 2019-10-05T22:09:47Z
-
1e37e384
by Philipp Hörist at 2019-10-06T12:56:22Z
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:
| ... | ... | @@ -12,6 +12,7 @@ |
| 12 | 12 |
- python3-css-parser
|
| 13 | 13 |
- python3-keyring
|
| 14 | 14 |
- python3-precis-i18n
|
| 15 |
+- gir1.2-soup-2.4
|
|
| 15 | 16 |
|
| 16 | 17 |
### Optional Runtime Requirements
|
| 17 | 18 |
|
| ... | ... | @@ -322,6 +322,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): |
| 322 | 322 |
self.received_history_pos = 0
|
| 323 | 323 |
self.orig_msg = None
|
| 324 | 324 |
|
| 325 |
+ # For XEP-0333
|
|
| 326 |
+ self.last_msg_id = None
|
|
| 327 |
+ |
|
| 325 | 328 |
self.set_emoticon_popover()
|
| 326 | 329 |
|
| 327 | 330 |
# Attach speller
|
| ... | ... | @@ -1022,6 +1025,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): |
| 1022 | 1025 |
if restored:
|
| 1023 | 1026 |
return
|
| 1024 | 1027 |
|
| 1028 |
+ if message_id:
|
|
| 1029 |
+ self.last_msg_id = message_id
|
|
| 1030 |
+ |
|
| 1025 | 1031 |
if kind == 'incoming':
|
| 1026 | 1032 |
if not self.type_id == message_control.TYPE_GC or \
|
| 1027 | 1033 |
app.config.notify_for_muc(jid) or \
|
| ... | ... | @@ -1065,6 +1071,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): |
| 1065 | 1071 |
event_type.type_, self.contact.jid)
|
| 1066 | 1072 |
|
| 1067 | 1073 |
event = event_type(text, subject, self, msg_log_id,
|
| 1074 |
+ message_id=message_id,
|
|
| 1068 | 1075 |
show_in_roster=show_in_roster,
|
| 1069 | 1076 |
show_in_systray=show_in_systray)
|
| 1070 | 1077 |
app.events.add_event(self.account, full_jid, event)
|
| ... | ... | @@ -1220,6 +1227,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): |
| 1220 | 1227 |
types=type_):
|
| 1221 | 1228 |
# There were events to remove
|
| 1222 | 1229 |
self.redraw_after_event_removed(jid)
|
| 1230 |
+ # XEP-0333 Send <displayed> tag.
|
|
| 1231 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1232 |
+ self.get_full_jid(), self.last_msg_id,
|
|
| 1233 |
+ self.type_id == message_control.TYPE_GC)
|
|
| 1234 |
+ self.last_msg_id = None
|
|
| 1223 | 1235 |
# send chatstate inactive to the one we're leaving
|
| 1224 | 1236 |
# and active to the one we visit
|
| 1225 | 1237 |
if self.msg_textview.has_text():
|
| ... | ... | @@ -1262,8 +1274,14 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): |
| 1262 | 1274 |
# we are at the end
|
| 1263 | 1275 |
if not app.events.remove_events(
|
| 1264 | 1276 |
self.account, jid, types=types_list):
|
| 1277 |
+ con = app.connections[self.account]
|
|
| 1265 | 1278 |
# There were events to remove
|
| 1266 | 1279 |
self.redraw_after_event_removed(jid)
|
| 1280 |
+ # XEP-0333 Send <displayed> tag.
|
|
| 1281 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1282 |
+ self.get_full_jid(), self.last_msg_id,
|
|
| 1283 |
+ self.type_id == message_control.TYPE_GC)
|
|
| 1284 |
+ self.last_msg_id = None
|
|
| 1267 | 1285 |
|
| 1268 | 1286 |
def _on_scrollbar_button_release(self, scrollbar, event):
|
| 1269 | 1287 |
if event.get_button()[1] != 1:
|
| ... | ... | @@ -1328,6 +1346,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): |
| 1328 | 1346 |
We just removed a 'printed_*' event, redraw contact in roster or
|
| 1329 | 1347 |
gc_roster and titles in roster and msg_win
|
| 1330 | 1348 |
"""
|
| 1349 |
+ if not self.parent_win: # minimized groupchat
|
|
| 1350 |
+ return
|
|
| 1331 | 1351 |
self.parent_win.redraw_tab(self)
|
| 1332 | 1352 |
self.parent_win.show_title()
|
| 1333 | 1353 |
# TODO : get the contact and check get_show_in_roster()
|
| ... | ... | @@ -152,6 +152,7 @@ gajim_common_features = [ |
| 152 | 152 |
nbxmpp.NS_BYTESTREAM,
|
| 153 | 153 |
nbxmpp.NS_FILE,
|
| 154 | 154 |
nbxmpp.NS_MUC,
|
| 155 |
+ nbxmpp.NS_CHATMARKERS,
|
|
| 155 | 156 |
nbxmpp.NS_COMMANDS,
|
| 156 | 157 |
nbxmpp.NS_DISCO_INFO,
|
| 157 | 158 |
nbxmpp.NS_LAST,
|
| ... | ... | @@ -308,6 +308,9 @@ class CommonConnection: |
| 308 | 308 |
obj.session.last_send = time.time()
|
| 309 | 309 |
msg_iq.setThread(obj.session.thread_id)
|
| 310 | 310 |
|
| 311 |
+ if obj.message:
|
|
| 312 |
+ msg_iq.setMarkable()
|
|
| 313 |
+ |
|
| 311 | 314 |
self._push_stanza_message_outgoing(obj, msg_iq)
|
| 312 | 315 |
|
| 313 | 316 |
def _push_stanza_message_outgoing(self, obj, msg_iq):
|
| ... | ... | @@ -1637,6 +1640,7 @@ class Connection(CommonConnection, ConnectionHandlers): |
| 1637 | 1640 |
msg_iq.setID(obj.stanza_id)
|
| 1638 | 1641 |
if obj.message:
|
| 1639 | 1642 |
msg_iq.setOriginID(obj.stanza_id)
|
| 1643 |
+ msg_iq.setMarkable()
|
|
| 1640 | 1644 |
|
| 1641 | 1645 |
if obj.correct_id:
|
| 1642 | 1646 |
msg_iq.setTag('replace', attrs={'id': obj.correct_id},
|
| ... | ... | @@ -95,13 +95,14 @@ class PmEvent(ChatEvent): |
| 95 | 95 |
class PrintedChatEvent(Event):
|
| 96 | 96 |
type_ = 'printed_chat'
|
| 97 | 97 |
def __init__(self, message, subject, control, msg_log_id, time_=None,
|
| 98 |
- show_in_roster=False, show_in_systray=True):
|
|
| 98 |
+ message_id=None, show_in_roster=False, show_in_systray=True):
|
|
| 99 | 99 |
Event.__init__(self, time_, show_in_roster=show_in_roster,
|
| 100 | 100 |
show_in_systray=show_in_systray)
|
| 101 | 101 |
self.message = message
|
| 102 | 102 |
self.subject = subject
|
| 103 | 103 |
self.control = control
|
| 104 | 104 |
self.msg_log_id = msg_log_id
|
| 105 |
+ self.message_id = message_id
|
|
| 105 | 106 |
|
| 106 | 107 |
class PrintedGcMsgEvent(PrintedChatEvent):
|
| 107 | 108 |
type_ = 'printed_gc_msg'
|
| ... | ... | @@ -88,7 +88,7 @@ class JingleContent: |
| 88 | 88 |
'session-terminate': [],
|
| 89 | 89 |
'transport-info': [self.__on_transport_info],
|
| 90 | 90 |
'transport-replace': [self.__on_transport_replace],
|
| 91 |
- 'transport-accept': [],
|
|
| 91 |
+ 'transport-accept': [self.__on_transport_replace],
|
|
| 92 | 92 |
'transport-reject': [],
|
| 93 | 93 |
'iq-result': [],
|
| 94 | 94 |
'iq-error': [],
|
| ... | ... | @@ -312,7 +312,7 @@ class JingleFileTransfer(JingleContent): |
| 312 | 312 |
def __on_iq_result(self, stanza, content, error, action):
|
| 313 | 313 |
log.info("__on_iq_result")
|
| 314 | 314 |
|
| 315 |
- if self.state == State.NOT_STARTED:
|
|
| 315 |
+ if self.state in (State.NOT_STARTED, State.CAND_RECEIVED):
|
|
| 316 | 316 |
self.__state_changed(State.INITIALIZED)
|
| 317 | 317 |
elif self.state == State.CAND_SENT_AND_RECEIVED:
|
| 318 | 318 |
if not self.nominated_cand['our-cand'] and \
|
| ... | ... | @@ -140,7 +140,9 @@ class JingleSession: |
| 140 | 140 |
'transport-info': [self.__ack, self.__broadcast],
|
| 141 | 141 |
'transport-replace': [self.__ack, self.__broadcast,
|
| 142 | 142 |
self.__on_transport_replace], #TODO
|
| 143 |
- 'transport-accept': [self.__ack], #TODO
|
|
| 143 |
+ 'transport-accept': [self.__ack, self.__on_session_accept,
|
|
| 144 |
+ self.__on_content_accept,
|
|
| 145 |
+ self.__broadcast],
|
|
| 144 | 146 |
'transport-reject': [self.__ack], #TODO
|
| 145 | 147 |
'iq-result': [self.__broadcast],
|
| 146 | 148 |
'iq-error': [self.__on_error],
|
| ... | ... | @@ -244,8 +246,9 @@ class JingleSession: |
| 244 | 246 |
Currently used for transport replacement
|
| 245 | 247 |
'''
|
| 246 | 248 |
content = self.contents[(creator, name)]
|
| 247 |
- transport.set_sid(content.transport.sid)
|
|
| 248 |
- transport.set_file_props(content.transport.file_props)
|
|
| 249 |
+ file_props = content.transport.file_props
|
|
| 250 |
+ file_props.transport_sid = transport.sid
|
|
| 251 |
+ transport.set_file_props(file_props)
|
|
| 249 | 252 |
content.transport = transport
|
| 250 | 253 |
# The content will have to be resend now that it is modified
|
| 251 | 254 |
content.sent = False
|
| ... | ... | @@ -410,12 +413,13 @@ class JingleSession: |
| 410 | 413 |
# Anyway, content's transport is not modifiable yet
|
| 411 | 414 |
pass
|
| 412 | 415 |
elif transport_ns == nbxmpp.NS_JINGLE_IBB:
|
| 413 |
- transport = JingleTransportIBB()
|
|
| 416 |
+ transport = JingleTransportIBB(node=content.getTag(
|
|
| 417 |
+ 'transport'))
|
|
| 414 | 418 |
self.modify_content(creator, name, transport)
|
| 415 | 419 |
self.state = JingleStates.PENDING
|
| 416 | 420 |
self.contents[(creator, name)].state = State.TRANSPORT_REPLACE
|
| 417 | 421 |
self.__ack(stanza, jingle, error, action)
|
| 418 |
- self.__session_accept()
|
|
| 422 |
+ self.__transport_accept(transport)
|
|
| 419 | 423 |
else:
|
| 420 | 424 |
stanza, jingle = self.__make_jingle('transport-reject')
|
| 421 | 425 |
content = jingle.setTag('content', attrs={'creator': creator,
|
| ... | ... | @@ -834,3 +838,12 @@ class JingleSession: |
| 834 | 838 |
conn=self.connection,
|
| 835 | 839 |
jingle_session=self,
|
| 836 | 840 |
media=media))
|
| 841 |
+ |
|
| 842 |
+ def __transport_accept(self, transport):
|
|
| 843 |
+ assert self.state != JingleStates.ENDED
|
|
| 844 |
+ stanza, jingle = self.__make_jingle('transport-accept')
|
|
| 845 |
+ self.__append_contents(jingle)
|
|
| 846 |
+ self.__broadcast(stanza, jingle, None, 'transport-accept')
|
|
| 847 |
+ self.connection.connection.send(stanza)
|
|
| 848 |
+ self.collect_iq_id(stanza.getID())
|
|
| 849 |
+ self.state = JingleStates.ACTIVE
|
| ... | ... | @@ -40,6 +40,7 @@ MODULES = [ |
| 40 | 40 |
'bookmarks',
|
| 41 | 41 |
'caps',
|
| 42 | 42 |
'carbons',
|
| 43 |
+ 'chat_markers',
|
|
| 43 | 44 |
'chatstates',
|
| 44 | 45 |
'delimiter',
|
| 45 | 46 |
'discovery',
|
| 1 |
+# This file is part of Gajim.
|
|
| 2 |
+#
|
|
| 3 |
+# Gajim is free software; you can redistribute it and/or modify
|
|
| 4 |
+# it under the terms of the GNU General Public License as published
|
|
| 5 |
+# by the Free Software Foundation; version 3 only.
|
|
| 6 |
+#
|
|
| 7 |
+# Gajim is distributed in the hope that it will be useful,
|
|
| 8 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 9 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 10 |
+# GNU General Public License for more details.
|
|
| 11 |
+#
|
|
| 12 |
+# You should have received a copy of the GNU General Public License
|
|
| 13 |
+# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 14 |
+ |
|
| 15 |
+# Chat Markers (XEP-0333)
|
|
| 16 |
+ |
|
| 17 |
+import nbxmpp
|
|
| 18 |
+from nbxmpp.structs import StanzaHandler
|
|
| 19 |
+ |
|
| 20 |
+from gajim.common import app
|
|
| 21 |
+from gajim.common.nec import NetworkEvent
|
|
| 22 |
+from gajim.common.modules.base import BaseModule
|
|
| 23 |
+ |
|
| 24 |
+ |
|
| 25 |
+class ChatMarkers(BaseModule):
|
|
| 26 |
+ |
|
| 27 |
+ _nbxmpp_extends = 'ChatMarkers'
|
|
| 28 |
+ |
|
| 29 |
+ def __init__(self, con):
|
|
| 30 |
+ BaseModule.__init__(self, con)
|
|
| 31 |
+ |
|
| 32 |
+ self.handlers = [
|
|
| 33 |
+ StanzaHandler(name='message',
|
|
| 34 |
+ callback=self._process_read_state_sync,
|
|
| 35 |
+ ns=nbxmpp.NS_CHATMARKERS,
|
|
| 36 |
+ priority=47),
|
|
| 37 |
+ StanzaHandler(name='message',
|
|
| 38 |
+ callback=self._process_chat_marker,
|
|
| 39 |
+ ns=nbxmpp.NS_CHATMARKERS,
|
|
| 40 |
+ priority=48),
|
|
| 41 |
+ ]
|
|
| 42 |
+ |
|
| 43 |
+ def _process_read_state_sync(self, _con, _stanza, properties):
|
|
| 44 |
+ if not properties.is_marker:
|
|
| 45 |
+ return
|
|
| 46 |
+ |
|
| 47 |
+ if not properties.marker.is_displayed:
|
|
| 48 |
+ return
|
|
| 49 |
+ |
|
| 50 |
+ if properties.type.is_error:
|
|
| 51 |
+ return
|
|
| 52 |
+ |
|
| 53 |
+ if properties.is_mam_message:
|
|
| 54 |
+ return
|
|
| 55 |
+ |
|
| 56 |
+ if properties.type.is_groupchat:
|
|
| 57 |
+ manager = self._con.get_module('MUC').get_manager()
|
|
| 58 |
+ muc_data = manager.get(properties.muc_jid)
|
|
| 59 |
+ if muc_data is None:
|
|
| 60 |
+ return
|
|
| 61 |
+ |
|
| 62 |
+ if properties.muc_nickname != muc_data.nick:
|
|
| 63 |
+ return
|
|
| 64 |
+ |
|
| 65 |
+ elif not properties.is_carbon_message or not properties.carbon.is_sent:
|
|
| 66 |
+ return
|
|
| 67 |
+ |
|
| 68 |
+ self._log.info('Read state sync: %s %s',
|
|
| 69 |
+ properties.jid,
|
|
| 70 |
+ properties.marker.id)
|
|
| 71 |
+ |
|
| 72 |
+ app.nec.push_outgoing_event(
|
|
| 73 |
+ NetworkEvent('read-state-sync',
|
|
| 74 |
+ account=self._account,
|
|
| 75 |
+ jid=properties.jid,
|
|
| 76 |
+ properties=properties,
|
|
| 77 |
+ type=properties.type,
|
|
| 78 |
+ is_muc_pm=properties.is_muc_pm,
|
|
| 79 |
+ marker_id=properties.marker.id))
|
|
| 80 |
+ |
|
| 81 |
+ raise nbxmpp.NodeProcessed
|
|
| 82 |
+ |
|
| 83 |
+ def _process_chat_marker(self, _con, _stanza, properties):
|
|
| 84 |
+ if not properties.is_marker:
|
|
| 85 |
+ return
|
|
| 86 |
+ # TODO: Implement showing displayed state in ConversationsTextview
|
|
| 87 |
+ raise nbxmpp.NodeProcessed
|
|
| 88 |
+ |
|
| 89 |
+ def send_marker(self, jid, marker, id_, is_gc):
|
|
| 90 |
+ if is_gc:
|
|
| 91 |
+ message = nbxmpp.Message(to=jid, typ='groupchat')
|
|
| 92 |
+ else:
|
|
| 93 |
+ message = nbxmpp.Message(to=jid, typ='chat')
|
|
| 94 |
+ message.setTag(marker, namespace=nbxmpp.NS_CHATMARKERS,
|
|
| 95 |
+ attrs={'id': id_})
|
|
| 96 |
+ self._nbxmpp().send(message)
|
|
| 97 |
+ |
|
| 98 |
+ def send_displayed_marker(self, jid, id_, is_gc):
|
|
| 99 |
+ self.send_marker(jid, 'displayed', id_, is_gc)
|
|
| 100 |
+ |
|
| 101 |
+ |
|
| 102 |
+def get_instance(*args, **kwargs):
|
|
| 103 |
+ return ChatMarkers(*args, **kwargs), 'ChatMarkers'
|
| ... | ... | @@ -174,7 +174,7 @@ class MAM(BaseModule): |
| 174 | 174 |
if groupchat:
|
| 175 | 175 |
event_attrs.update(self._parse_gc_attrs(properties))
|
| 176 | 176 |
else:
|
| 177 |
- event_attrs.update(self._parse_chat_attrs(stanza, properties))
|
|
| 177 |
+ event_attrs.update(self._parse_chat_attrs(properties))
|
|
| 178 | 178 |
|
| 179 | 179 |
stanza_id, message_id = self._get_unique_id(properties)
|
| 180 | 180 |
|
| ... | ... | @@ -243,17 +243,13 @@ class MAM(BaseModule): |
| 243 | 243 |
'real_jid': real_jid,
|
| 244 | 244 |
'kind': KindConstant.GC_MSG}
|
| 245 | 245 |
|
| 246 |
- def _parse_chat_attrs(self, stanza, properties):
|
|
| 247 |
- frm = properties.jid
|
|
| 248 |
- to = stanza.getTo()
|
|
| 249 |
- if frm.bareMatch(self._con.get_own_jid()):
|
|
| 250 |
- with_ = to
|
|
| 246 |
+ def _parse_chat_attrs(self, properties):
|
|
| 247 |
+ if properties.from_.bareMatch(self._con.get_own_jid()):
|
|
| 251 | 248 |
kind = KindConstant.CHAT_MSG_SENT
|
| 252 | 249 |
else:
|
| 253 |
- with_ = frm
|
|
| 254 | 250 |
kind = KindConstant.CHAT_MSG_RECV
|
| 255 | 251 |
|
| 256 |
- return {'with_': with_,
|
|
| 252 |
+ return {'with_': properties.jid,
|
|
| 257 | 253 |
'nick': None,
|
| 258 | 254 |
'kind': kind}
|
| 259 | 255 |
|
| ... | ... | @@ -69,9 +69,7 @@ class Message(BaseModule): |
| 69 | 69 |
stanza=stanza,
|
| 70 | 70 |
account=self._account))
|
| 71 | 71 |
|
| 72 |
- forwarded = properties.carbon_type is not None
|
|
| 73 |
- sent = properties.carbon_type == 'sent'
|
|
| 74 |
- if sent:
|
|
| 72 |
+ if properties.is_carbon_message and properties.carbon.is_sent:
|
|
| 75 | 73 |
# Ugly, we treat the from attr as the remote jid,
|
| 76 | 74 |
# to make that work with sent carbons we have to do this.
|
| 77 | 75 |
# TODO: Check where in Gajim and plugins we depend on that behavior
|
| ... | ... | @@ -157,8 +155,8 @@ class Message(BaseModule): |
| 157 | 155 |
'id_': properties.id,
|
| 158 | 156 |
'encrypted': False,
|
| 159 | 157 |
'additional_data': additional_data,
|
| 160 |
- 'forwarded': forwarded,
|
|
| 161 |
- 'sent': sent,
|
|
| 158 |
+ 'forwarded': properties.is_carbon_message,
|
|
| 159 |
+ 'sent': properties.is_carbon_message and properties.carbon.is_sent,
|
|
| 162 | 160 |
'fjid': fjid,
|
| 163 | 161 |
'jid': jid,
|
| 164 | 162 |
'resource': resource,
|
| ... | ... | @@ -67,12 +67,16 @@ def _init_gui(gui): |
| 67 | 67 |
|
| 68 | 68 |
def _init_gtk():
|
| 69 | 69 |
import gi
|
| 70 |
- gi.require_version('GLib', '2.0')
|
|
| 71 |
- gi.require_version('Gio', '2.0')
|
|
| 72 |
- gi.require_version('Gtk', '3.0')
|
|
| 73 |
- gi.require_version('Gdk', '3.0')
|
|
| 74 |
- gi.require_version('GObject', '2.0')
|
|
| 75 |
- gi.require_version('Pango', '1.0')
|
|
| 70 |
+ try:
|
|
| 71 |
+ gi.require_version('GLib', '2.0')
|
|
| 72 |
+ gi.require_version('Gio', '2.0')
|
|
| 73 |
+ gi.require_version('Gtk', '3.0')
|
|
| 74 |
+ gi.require_version('Gdk', '3.0')
|
|
| 75 |
+ gi.require_version('GObject', '2.0')
|
|
| 76 |
+ gi.require_version('Pango', '1.0')
|
|
| 77 |
+ gi.require_version('Soup', '2.4')
|
|
| 78 |
+ except ValueError as error:
|
|
| 79 |
+ sys.exit('Missing dependency: %s' % error)
|
|
| 76 | 80 |
|
| 77 | 81 |
from gi.repository import Gtk
|
| 78 | 82 |
gtk_ver = '%s.%s.%s' % (Gtk.get_major_version(),
|
| ... | ... | @@ -847,7 +847,8 @@ class GroupchatControl(ChatControlBase): |
| 847 | 847 |
|
| 848 | 848 |
def on_private_message(self, nick, sent, msg, tim, xhtml, session,
|
| 849 | 849 |
msg_log_id=None, encrypted=False,
|
| 850 |
- displaymarking=None):
|
|
| 850 |
+ displaymarking=None, message_id=None):
|
|
| 851 |
+ |
|
| 851 | 852 |
# Do we have a queue?
|
| 852 | 853 |
fjid = self.room_jid + '/' + nick
|
| 853 | 854 |
|
| ... | ... | @@ -862,7 +863,9 @@ class GroupchatControl(ChatControlBase): |
| 862 | 863 |
session=session,
|
| 863 | 864 |
form_node=None,
|
| 864 | 865 |
displaymarking=displaymarking,
|
| 865 |
- sent_forwarded=sent)
|
|
| 866 |
+ sent_forwarded=sent,
|
|
| 867 |
+ message_id=message_id)
|
|
| 868 |
+ |
|
| 866 | 869 |
app.events.add_event(self.account, fjid, event)
|
| 867 | 870 |
|
| 868 | 871 |
autopopup = app.config.get('autopopup')
|
| ... | ... | @@ -1106,7 +1109,8 @@ class GroupchatControl(ChatControlBase): |
| 1106 | 1109 |
self.on_private_message(
|
| 1107 | 1110 |
nick, obj.sent, obj.msgtxt, obj.timestamp,
|
| 1108 | 1111 |
obj.xhtml, self.session, msg_log_id=obj.msg_log_id,
|
| 1109 |
- encrypted=obj.encrypted, displaymarking=obj.displaymarking)
|
|
| 1112 |
+ encrypted=obj.encrypted, displaymarking=obj.displaymarking,
|
|
| 1113 |
+ message_id=obj.message_id)
|
|
| 1110 | 1114 |
|
| 1111 | 1115 |
def _nec_ping(self, obj):
|
| 1112 | 1116 |
if self.contact.jid != obj.contact.room_jid:
|
| ... | ... | @@ -285,6 +285,39 @@ class Interface: |
| 285 | 285 |
if ctrl and ctrl.session and len(obj.contact_list) > 1:
|
| 286 | 286 |
ctrl.remove_session(ctrl.session)
|
| 287 | 287 |
|
| 288 |
+ def handle_event_read_state_sync(self, event):
|
|
| 289 |
+ if event.type.is_groupchat:
|
|
| 290 |
+ control = self.get_groupchat_control(event.account,
|
|
| 291 |
+ event.jid.getBare())
|
|
| 292 |
+ if control is None:
|
|
| 293 |
+ log.warning('Groupchat control not found')
|
|
| 294 |
+ return
|
|
| 295 |
+ |
|
| 296 |
+ jid = event.jid.getBare()
|
|
| 297 |
+ types = ['printed_gc_msg', 'printed_marked_gc_msg']
|
|
| 298 |
+ |
|
| 299 |
+ else:
|
|
| 300 |
+ types = ['chat', 'pm', 'printed_chat', 'printed_pm']
|
|
| 301 |
+ jid = event.jid.getBare()
|
|
| 302 |
+ if event.is_muc_pm:
|
|
| 303 |
+ jid = event.jid
|
|
| 304 |
+ |
|
| 305 |
+ control = app.interface.msg_win_mgr.get_control(jid, event.account)
|
|
| 306 |
+ |
|
| 307 |
+ # Compare with control.last_msg_id.
|
|
| 308 |
+ events_ = app.events.get_events(event.account, jid, types)
|
|
| 309 |
+ if not events_:
|
|
| 310 |
+ log.warning('No Events')
|
|
| 311 |
+ return
|
|
| 312 |
+ |
|
| 313 |
+ if events_[-1].message_id != event.marker_id:
|
|
| 314 |
+ return
|
|
| 315 |
+ |
|
| 316 |
+ if not app.events.remove_events(event.account, jid, types=types):
|
|
| 317 |
+ # There were events to remove
|
|
| 318 |
+ if control is not None:
|
|
| 319 |
+ control.redraw_after_event_removed(event.jid)
|
|
| 320 |
+ |
|
| 288 | 321 |
def handle_event_msgerror(self, obj):
|
| 289 | 322 |
#'MSGERROR' (account, (jid, error_code, error_msg, msg, time[session]))
|
| 290 | 323 |
account = obj.conn.name
|
| ... | ... | @@ -1173,6 +1206,7 @@ class Interface: |
| 1173 | 1206 |
'unsubscribed-presence-received': [
|
| 1174 | 1207 |
self.handle_event_unsubscribed_presence],
|
| 1175 | 1208 |
'zeroconf-name-conflict': [self.handle_event_zc_name_conflict],
|
| 1209 |
+ 'read-state-sync': [self.handle_event_read_state_sync],
|
|
| 1176 | 1210 |
}
|
| 1177 | 1211 |
|
| 1178 | 1212 |
def register_core_handlers(self):
|
| ... | ... | @@ -1449,6 +1483,12 @@ class Interface: |
| 1449 | 1483 |
### Methods for opening new messages controls
|
| 1450 | 1484 |
################################################################################
|
| 1451 | 1485 |
|
| 1486 |
+ def get_groupchat_control(self, account, room_jid):
|
|
| 1487 |
+ control = self.minimized_controls[account].get(room_jid)
|
|
| 1488 |
+ if control is None:
|
|
| 1489 |
+ control = self.msg_win_mgr.get_gc_control(room_jid, account)
|
|
| 1490 |
+ return control
|
|
| 1491 |
+ |
|
| 1452 | 1492 |
def show_groupchat(self, account, room_jid):
|
| 1453 | 1493 |
minimized_control = self.minimized_controls[account].get(room_jid)
|
| 1454 | 1494 |
if minimized_control is not None:
|
| ... | ... | @@ -553,10 +553,10 @@ class PluginManager(metaclass=Singleton): |
| 553 | 553 |
'''
|
| 554 | 554 |
for plugin in self.plugins:
|
| 555 | 555 |
if not plugin.active:
|
| 556 |
- return
|
|
| 556 |
+ continue
|
|
| 557 | 557 |
|
| 558 | 558 |
if not hasattr(plugin, 'modules'):
|
| 559 |
- return
|
|
| 559 |
+ continue
|
|
| 560 | 560 |
|
| 561 | 561 |
for module in plugin.modules:
|
| 562 | 562 |
instance, name = module.get_instance(con)
|