Philipp Hörist pushed to branch chat_markers at gajim / gajim
Commits:
-
d5d99c66
by lovetox at 2020-11-03T18:00:05+01:00
-
ee0cf350
by André Apitzsch at 2020-11-06T18:31:56+01:00
-
7439bbd9
by André Apitzsch at 2020-11-07T12:38:36+01:00
-
70b6371a
by André Apitzsch at 2020-11-07T12:39:04+01:00
-
9980d079
by lovetox at 2020-11-07T13:22:48+01:00
-
229d84f3
by André Apitzsch at 2020-11-07T14:54:54+01:00
-
9d949958
by André Apitzsch at 2020-11-07T15:01:03+01:00
-
26a8b374
by André Apitzsch at 2020-11-07T15:07:36+01:00
-
0e9aed6a
by André Apitzsch at 2020-11-07T15:55:20+01:00
-
680a5314
by Yann Leboulanger at 2020-11-07T16:29:47+01:00
-
6006dbf1
by wurstsalat at 2020-11-07T16:29:47+01:00
-
de1acdc0
by wurstsalat at 2020-11-07T16:29:47+01:00
26 changed files:
- gajim/__init__.py
- gajim/chat_control.py
- gajim/chat_control_base.py
- gajim/common/configpaths.py
- gajim/common/const.py
- gajim/common/dbus/music_track.py
- gajim/common/events.py
- gajim/common/modules/__init__.py
- + gajim/common/modules/chat_markers.py
- gajim/common/modules/httpupload.py
- gajim/common/modules/message.py
- gajim/common/setting_values.py
- gajim/common/settings.py
- gajim/common/structs.py
- gajim/conversation_textview.py
- gajim/data/style/gajim.css
- gajim/gajim.py
- gajim/groupchat_control.py
- gajim/gtk/accounts.py
- gajim/gtk/groupchat_settings.py
- gajim/gui_interface.py
- gajim/gui_menu_builder.py
- setup.cfg
- win/README.md
- win/_base.sh
- win/misc/gajim-portable.nsi
Changes:
| 1 |
-import os
|
|
| 2 | 1 |
import subprocess
|
| 2 |
+import sys
|
|
| 3 |
+from pathlib import Path
|
|
| 3 | 4 |
|
| 4 | 5 |
__version__ = "1.2.91"
|
| 5 | 6 |
|
| 6 |
-IS_FLATPAK = False
|
|
| 7 |
-if os.path.exists('/app/share/run-as-flatpak'):
|
|
| 8 |
- IS_FLATPAK = True
|
|
| 7 |
+IS_FLATPAK = Path('/app/share/run-as-flatpak').exists()
|
|
| 9 | 8 |
|
| 10 |
-IS_PORTABLE = False
|
|
| 11 |
-portable_path = os.path.join(os.getcwd(), 'is_portable')
|
|
| 12 |
-if os.path.exists(portable_path):
|
|
| 13 |
- IS_PORTABLE = True
|
|
| 9 |
+portable_path = Path(sys.executable).parent / 'is_portable'
|
|
| 10 |
+IS_PORTABLE = portable_path.exists()
|
|
| 14 | 11 |
|
| 15 | 12 |
try:
|
| 16 | 13 |
p = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
|
| ... | ... | @@ -123,7 +123,7 @@ def __init__(self, parent_win, contact, acct, session, resource=None): |
| 123 | 123 |
|
| 124 | 124 |
# Menu for the HeaderBar
|
| 125 | 125 |
self.control_menu = gui_menu_builder.get_singlechat_menu(
|
| 126 |
- self.control_id, self.account, self.contact.jid)
|
|
| 126 |
+ self.control_id, self.account, self.contact.jid, self._type)
|
|
| 127 | 127 |
|
| 128 | 128 |
# Settings menu
|
| 129 | 129 |
self.xml.settings_menu.set_menu_model(self.control_menu)
|
| ... | ... | @@ -228,6 +228,7 @@ def __init__(self, parent_win, contact, acct, session, resource=None): |
| 228 | 228 |
('mam-decrypted-message-received', ged.GUI1, self._on_mam_decrypted_message_received),
|
| 229 | 229 |
('decrypted-message-received', ged.GUI1, self._on_decrypted_message_received),
|
| 230 | 230 |
('receipt-received', ged.GUI1, self._receipt_received),
|
| 231 |
+ ('displayed-received', ged.GUI1, self._displayed_received),
|
|
| 231 | 232 |
('message-error', ged.GUI1, self._on_message_error),
|
| 232 | 233 |
('zeroconf-error', ged.GUI1, self._on_zeroconf_error),
|
| 233 | 234 |
])
|
| ... | ... | @@ -271,6 +272,15 @@ def add_actions(self): |
| 271 | 272 |
act.connect('change-state', self._on_send_chatstate)
|
| 272 | 273 |
self.parent_win.window.add_action(act)
|
| 273 | 274 |
|
| 275 |
+ marker = self.contact.settings.get('send_marker')
|
|
| 276 |
+ |
|
| 277 |
+ act = Gio.SimpleAction.new_stateful(
|
|
| 278 |
+ f'send-marker-{self.control_id}',
|
|
| 279 |
+ None,
|
|
| 280 |
+ GLib.Variant.new_boolean(marker))
|
|
| 281 |
+ act.connect('change-state', self._on_send_marker)
|
|
| 282 |
+ self.parent_win.window.add_action(act)
|
|
| 283 |
+ |
|
| 274 | 284 |
def update_actions(self):
|
| 275 | 285 |
win = self.parent_win.window
|
| 276 | 286 |
online = app.account_is_connected(self.account)
|
| ... | ... | @@ -338,6 +348,12 @@ def update_actions(self): |
| 338 | 348 |
tooltip_text = _('No File Transfer available')
|
| 339 | 349 |
self.xml.sendfile_button.set_tooltip_text(tooltip_text)
|
| 340 | 350 |
|
| 351 |
+ # Chat markers
|
|
| 352 |
+ state = GLib.Variant.new_boolean(
|
|
| 353 |
+ self.contact.settings.get('send_marker'))
|
|
| 354 |
+ win.lookup_action(
|
|
| 355 |
+ f'send-marker-{self.control_id}').change_state(state)
|
|
| 356 |
+ |
|
| 341 | 357 |
# Convert to GC
|
| 342 | 358 |
if app.settings.get_account_setting(self.account, 'is_zeroconf'):
|
| 343 | 359 |
win.lookup_action(
|
| ... | ... | @@ -363,6 +379,7 @@ def remove_actions(self): |
| 363 | 379 |
'information-',
|
| 364 | 380 |
'start-call-',
|
| 365 | 381 |
'send-chatstate-',
|
| 382 |
+ 'send-marker-',
|
|
| 366 | 383 |
]
|
| 367 | 384 |
for action in actions:
|
| 368 | 385 |
self.parent_win.window.remove_action(f'{action}{self.control_id}')
|
| ... | ... | @@ -435,6 +452,10 @@ def _on_send_chatstate(self, action, param): |
| 435 | 452 |
action.set_state(param)
|
| 436 | 453 |
self.contact.settings.set('send_chatstate', param.get_string())
|
| 437 | 454 |
|
| 455 |
+ def _on_send_marker(self, action, param):
|
|
| 456 |
+ action.set_state(param)
|
|
| 457 |
+ self.contact.settings.set('send_marker', param.get_boolean())
|
|
| 458 |
+ |
|
| 438 | 459 |
def subscribe_events(self):
|
| 439 | 460 |
"""
|
| 440 | 461 |
Register listeners to the events class
|
| ... | ... | @@ -643,6 +664,10 @@ def _on_message_sent(self, event): |
| 643 | 664 |
def _receipt_received(self, event):
|
| 644 | 665 |
self.conv_textview.show_receipt(event.receipt_id)
|
| 645 | 666 |
|
| 667 |
+ @event_filter(['account', 'jid'])
|
|
| 668 |
+ def _displayed_received(self, event):
|
|
| 669 |
+ self.conv_textview.show_displayed(event.marker_id)
|
|
| 670 |
+ |
|
| 646 | 671 |
@event_filter(['account', 'jid'])
|
| 647 | 672 |
def _on_zeroconf_error(self, event):
|
| 648 | 673 |
self.add_status_message(event.message)
|
| ... | ... | @@ -1385,6 +1410,14 @@ def read_queue(self): |
| 1385 | 1410 |
self.set_session(event.session)
|
| 1386 | 1411 |
if message_ids:
|
| 1387 | 1412 |
app.storage.archive.set_read_messages(message_ids)
|
| 1413 |
+ |
|
| 1414 |
+ # XEP-0333 Send <displayed> marker
|
|
| 1415 |
+ con = app.connections[self.account]
|
|
| 1416 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1417 |
+ self.contact,
|
|
| 1418 |
+ self.last_msg_id,
|
|
| 1419 |
+ self._type)
|
|
| 1420 |
+ self.last_msg_id = None
|
|
| 1388 | 1421 |
app.events.remove_events(self.account,
|
| 1389 | 1422 |
jid_with_resource,
|
| 1390 | 1423 |
types=[str(self._type)])
|
| ... | ... | @@ -218,6 +218,9 @@ def __init__(self, parent_win, widget_name, contact, acct, |
| 218 | 218 |
self.received_history_pos = 0
|
| 219 | 219 |
self.orig_msg = None
|
| 220 | 220 |
|
| 221 |
+ # For XEP-0333
|
|
| 222 |
+ self.last_msg_id = None
|
|
| 223 |
+ |
|
| 221 | 224 |
self.correcting = False
|
| 222 | 225 |
self.last_sent_msg = None
|
| 223 | 226 |
|
| ... | ... | @@ -1116,6 +1119,7 @@ def add_message(self, |
| 1116 | 1119 |
displaymarking=None,
|
| 1117 | 1120 |
msg_log_id=None,
|
| 1118 | 1121 |
message_id=None,
|
| 1122 |
+ stanza_id=None,
|
|
| 1119 | 1123 |
correct_id=None,
|
| 1120 | 1124 |
additional_data=None,
|
| 1121 | 1125 |
marker=None,
|
| ... | ... | @@ -1159,6 +1163,12 @@ def add_message(self, |
| 1159 | 1163 |
if restored:
|
| 1160 | 1164 |
return
|
| 1161 | 1165 |
|
| 1166 |
+ if message_id:
|
|
| 1167 |
+ if self._type.is_groupchat:
|
|
| 1168 |
+ self.last_msg_id = stanza_id or message_id
|
|
| 1169 |
+ else:
|
|
| 1170 |
+ self.last_msg_id = message_id
|
|
| 1171 |
+ |
|
| 1162 | 1172 |
if kind == 'incoming':
|
| 1163 | 1173 |
if (not self._type.is_groupchat or
|
| 1164 | 1174 |
self.contact.can_notify() or
|
| ... | ... | @@ -1203,7 +1213,10 @@ def add_message(self, |
| 1203 | 1213 |
|
| 1204 | 1214 |
event = event_type(text,
|
| 1205 | 1215 |
subject,
|
| 1206 |
- self, msg_log_id,
|
|
| 1216 |
+ self,
|
|
| 1217 |
+ msg_log_id,
|
|
| 1218 |
+ message_id=message_id,
|
|
| 1219 |
+ stanza_id=stanza_id,
|
|
| 1207 | 1220 |
show_in_roster=show_in_roster,
|
| 1208 | 1221 |
show_in_systray=show_in_systray)
|
| 1209 | 1222 |
app.events.add_event(self.account, full_jid, event)
|
| ... | ... | @@ -1355,7 +1368,7 @@ def set_control_active(self, state): |
| 1355 | 1368 |
jid = self.contact.jid
|
| 1356 | 1369 |
if self.conv_textview.autoscroll:
|
| 1357 | 1370 |
# we are at the end
|
| 1358 |
- type_ = ['printed_%s' % self._type]
|
|
| 1371 |
+ type_ = [f'printed_{self._type}']
|
|
| 1359 | 1372 |
if self._type.is_groupchat:
|
| 1360 | 1373 |
type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
|
| 1361 | 1374 |
if not app.events.remove_events(self.account,
|
| ... | ... | @@ -1363,6 +1376,12 @@ def set_control_active(self, state): |
| 1363 | 1376 |
types=type_):
|
| 1364 | 1377 |
# There were events to remove
|
| 1365 | 1378 |
self.redraw_after_event_removed(jid)
|
| 1379 |
+ # XEP-0333 Send <displayed> marker
|
|
| 1380 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1381 |
+ self.contact,
|
|
| 1382 |
+ self.last_msg_id,
|
|
| 1383 |
+ self._type)
|
|
| 1384 |
+ self.last_msg_id = None
|
|
| 1366 | 1385 |
# send chatstate inactive to the one we're leaving
|
| 1367 | 1386 |
# and active to the one we visit
|
| 1368 | 1387 |
if self.msg_textview.has_text():
|
| ... | ... | @@ -1391,20 +1410,27 @@ def _on_edge_reached(self, _scrolledwindow, pos): |
| 1391 | 1410 |
types_list = []
|
| 1392 | 1411 |
if self._type.is_groupchat:
|
| 1393 | 1412 |
types_list = ['printed_gc_msg', 'gc_msg', 'printed_marked_gc_msg']
|
| 1394 |
- else: # Not a GC
|
|
| 1395 |
- types_list = ['printed_%s' % self._type, str(self._type)]
|
|
| 1413 |
+ else:
|
|
| 1414 |
+ types_list = [f'printed_{self._type}', str(self._type)]
|
|
| 1396 | 1415 |
|
| 1397 | 1416 |
if not app.events.get_events(self.account, jid, types_list):
|
| 1398 | 1417 |
return
|
| 1399 | 1418 |
if not self.parent_win:
|
| 1400 | 1419 |
return
|
| 1401 |
- if self.parent_win.get_active_control() == self and \
|
|
| 1402 |
- self.parent_win.window.is_active():
|
|
| 1420 |
+ if (self.parent_win.get_active_control() == self and
|
|
| 1421 |
+ self.parent_win.window.is_active()):
|
|
| 1403 | 1422 |
# we are at the end
|
| 1404 | 1423 |
if not app.events.remove_events(
|
| 1405 | 1424 |
self.account, jid, types=types_list):
|
| 1406 | 1425 |
# There were events to remove
|
| 1407 | 1426 |
self.redraw_after_event_removed(jid)
|
| 1427 |
+ # XEP-0333 Send <displayed> tag
|
|
| 1428 |
+ con = app.connections[self.account]
|
|
| 1429 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1430 |
+ self.contact,
|
|
| 1431 |
+ self.last_msg_id,
|
|
| 1432 |
+ self._type)
|
|
| 1433 |
+ self.last_msg_id = None
|
|
| 1408 | 1434 |
|
| 1409 | 1435 |
def _on_scrollbar_button_release(self, scrollbar, event):
|
| 1410 | 1436 |
if event.get_button()[1] != 1:
|
| ... | ... | @@ -1469,6 +1495,8 @@ def redraw_after_event_removed(self, jid): |
| 1469 | 1495 |
We just removed a 'printed_*' event, redraw contact in roster or
|
| 1470 | 1496 |
gc_roster and titles in roster and msg_win
|
| 1471 | 1497 |
"""
|
| 1498 |
+ if not self.parent_win: # minimized groupchat
|
|
| 1499 |
+ return
|
|
| 1472 | 1500 |
self.parent_win.redraw_tab(self)
|
| 1473 | 1501 |
self.parent_win.show_title()
|
| 1474 | 1502 |
# TODO : get the contact and check get_show_in_roster()
|
| ... | ... | @@ -105,13 +105,14 @@ def __init__(self) -> None: |
| 105 | 105 |
self.custom_config_root = None # type: Optional[Path]
|
| 106 | 106 |
|
| 107 | 107 |
if os.name == 'nt':
|
| 108 |
- try:
|
|
| 108 |
+ if gajim.IS_PORTABLE:
|
|
| 109 |
+ application_path = Path(sys.executable).parent
|
|
| 110 |
+ self.config_root = self.cache_root = self.data_root = \
|
|
| 111 |
+ application_path.parent / 'UserData'
|
|
| 112 |
+ else:
|
|
| 109 | 113 |
# Documents and Settings\[User Name]\Application Data\Gajim
|
| 110 | 114 |
self.config_root = self.cache_root = self.data_root = \
|
| 111 | 115 |
Path(os.environ['appdata']) / 'Gajim'
|
| 112 |
- except KeyError:
|
|
| 113 |
- # win9x, in cwd
|
|
| 114 |
- self.config_root = self.cache_root = self.data_root = Path('.')
|
|
| 115 | 116 |
else:
|
| 116 | 117 |
self.config_root = Path(GLib.get_user_config_dir()) / 'gajim'
|
| 117 | 118 |
self.cache_root = Path(GLib.get_user_cache_dir()) / 'gajim'
|
| ... | ... | @@ -970,6 +970,7 @@ def is_error(self): |
| 970 | 970 |
Namespace.SECLABEL,
|
| 971 | 971 |
Namespace.CONFERENCE,
|
| 972 | 972 |
Namespace.CORRECT,
|
| 973 |
+ Namespace.CHATMARKERS,
|
|
| 973 | 974 |
Namespace.EME,
|
| 974 | 975 |
Namespace.XHTML_IM,
|
| 975 | 976 |
Namespace.HASHES_2,
|
| ... | ... | @@ -93,9 +93,7 @@ def start(self): |
| 93 | 93 |
self._add_player(name)
|
| 94 | 94 |
|
| 95 | 95 |
for name in list(self.players):
|
| 96 |
- info = self._get_playing_track(name)
|
|
| 97 |
- if info is not None:
|
|
| 98 |
- self._emit(info)
|
|
| 96 |
+ self._get_playing_track(name)
|
|
| 99 | 97 |
|
| 100 | 98 |
def stop(self):
|
| 101 | 99 |
for name in list(self.players):
|
| ... | ... | @@ -154,10 +152,7 @@ def _signal_received(self, |
| 154 | 152 |
'''Signal handler for PropertiesChanged event'''
|
| 155 | 153 |
|
| 156 | 154 |
log.info('Signal received: %s - %s', interface_name, parameters)
|
| 157 |
- |
|
| 158 |
- info = self._get_playing_track(user_data[0])
|
|
| 159 |
- |
|
| 160 |
- self._emit(info)
|
|
| 155 |
+ self._get_playing_track(user_data[0])
|
|
| 161 | 156 |
|
| 162 | 157 |
@staticmethod
|
| 163 | 158 |
def _get_music_info(properties):
|
| ... | ... | @@ -189,18 +184,23 @@ def _get_playing_track(self, name): |
| 189 | 184 |
'org.freedesktop.DBus.Properties',
|
| 190 | 185 |
None)
|
| 191 | 186 |
|
| 192 |
- try:
|
|
| 193 |
- result = proxy.call_sync(
|
|
| 194 |
- "GetAll",
|
|
| 195 |
- GLib.Variant('(s)', ('org.mpris.MediaPlayer2.Player',)),
|
|
| 196 |
- Gio.DBusCallFlags.NONE,
|
|
| 197 |
- -1,
|
|
| 198 |
- None)
|
|
| 199 |
- except GLib.Error as error:
|
|
| 200 |
- log.debug("Could not enable music listener: %s", error.message)
|
|
| 201 |
- return None
|
|
| 187 |
+ def proxy_call_finished(proxy, res):
|
|
| 188 |
+ try:
|
|
| 189 |
+ result = proxy.call_finish(res)
|
|
| 190 |
+ except GLib.Error as error:
|
|
| 191 |
+ log.debug("Could not enable music listener: %s", error.message)
|
|
| 192 |
+ return
|
|
| 193 |
+ |
|
| 194 |
+ info = self._get_music_info(result[0])
|
|
| 195 |
+ if info is not None:
|
|
| 196 |
+ self._emit(info)
|
|
| 202 | 197 |
|
| 203 |
- return self._get_music_info(result[0])
|
|
| 198 |
+ proxy.call("GetAll",
|
|
| 199 |
+ GLib.Variant('(s)', ('org.mpris.MediaPlayer2.Player',)),
|
|
| 200 |
+ Gio.DBusCallFlags.NONE,
|
|
| 201 |
+ -1,
|
|
| 202 |
+ None,
|
|
| 203 |
+ proxy_call_finished)
|
|
| 204 | 204 |
|
| 205 | 205 |
|
| 206 | 206 |
def enable():
|
| ... | ... | @@ -91,13 +91,16 @@ class PmEvent(ChatEvent): |
| 91 | 91 |
class PrintedChatEvent(Event):
|
| 92 | 92 |
type_ = 'printed_chat'
|
| 93 | 93 |
def __init__(self, message, subject, control, msg_log_id, time_=None,
|
| 94 |
- show_in_roster=False, show_in_systray=True):
|
|
| 94 |
+ message_id=None, stanza_id=None, show_in_roster=False,
|
|
| 95 |
+ show_in_systray=True):
|
|
| 95 | 96 |
Event.__init__(self, time_, show_in_roster=show_in_roster,
|
| 96 |
- show_in_systray=show_in_systray)
|
|
| 97 |
+ show_in_systray=show_in_systray)
|
|
| 97 | 98 |
self.message = message
|
| 98 | 99 |
self.subject = subject
|
| 99 | 100 |
self.control = control
|
| 100 | 101 |
self.msg_log_id = msg_log_id
|
| 102 |
+ self.message_id = message_id
|
|
| 103 |
+ self.stanza_id = stanza_id
|
|
| 101 | 104 |
|
| 102 | 105 |
class PrintedGcMsgEvent(PrintedChatEvent):
|
| 103 | 106 |
type_ = 'printed_gc_msg'
|
| ... | ... | @@ -40,6 +40,7 @@ |
| 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 |
+from nbxmpp.namespaces import Namespace
|
|
| 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 |
+from gajim.common.structs import OutgoingMessage
|
|
| 24 |
+ |
|
| 25 |
+ |
|
| 26 |
+class ChatMarkers(BaseModule):
|
|
| 27 |
+ |
|
| 28 |
+ _nbxmpp_extends = 'ChatMarkers'
|
|
| 29 |
+ |
|
| 30 |
+ def __init__(self, con):
|
|
| 31 |
+ BaseModule.__init__(self, con)
|
|
| 32 |
+ |
|
| 33 |
+ self.handlers = [
|
|
| 34 |
+ StanzaHandler(name='message',
|
|
| 35 |
+ callback=self._process_chat_marker,
|
|
| 36 |
+ ns=Namespace.CHATMARKERS,
|
|
| 37 |
+ priority=47),
|
|
| 38 |
+ ]
|
|
| 39 |
+ |
|
| 40 |
+ def _process_chat_marker(self, _con, _stanza, properties):
|
|
| 41 |
+ if not properties.is_marker or not properties.marker.is_displayed:
|
|
| 42 |
+ return
|
|
| 43 |
+ |
|
| 44 |
+ if properties.type.is_error:
|
|
| 45 |
+ return
|
|
| 46 |
+ |
|
| 47 |
+ if properties.type.is_groupchat:
|
|
| 48 |
+ manager = self._con.get_module('MUC').get_manager()
|
|
| 49 |
+ muc_data = manager.get(properties.muc_jid)
|
|
| 50 |
+ if muc_data is None:
|
|
| 51 |
+ return
|
|
| 52 |
+ |
|
| 53 |
+ if properties.muc_nickname != muc_data.nick:
|
|
| 54 |
+ return
|
|
| 55 |
+ |
|
| 56 |
+ self._raise_event('read-state-sync', properties)
|
|
| 57 |
+ return
|
|
| 58 |
+ |
|
| 59 |
+ if properties.is_carbon_message and properties.carbon.is_sent:
|
|
| 60 |
+ self._raise_event('read-state-sync', properties)
|
|
| 61 |
+ return
|
|
| 62 |
+ |
|
| 63 |
+ if properties.is_mam_message:
|
|
| 64 |
+ if properties.from_.bareMatch(self._con.get_own_jid()):
|
|
| 65 |
+ return
|
|
| 66 |
+ |
|
| 67 |
+ self._raise_event('displayed-received', properties)
|
|
| 68 |
+ |
|
| 69 |
+ def _raise_event(self, name, properties):
|
|
| 70 |
+ self._log.info('%s: %s %s',
|
|
| 71 |
+ name,
|
|
| 72 |
+ properties.jid,
|
|
| 73 |
+ properties.marker.id)
|
|
| 74 |
+ |
|
| 75 |
+ jid = properties.jid
|
|
| 76 |
+ if not properties.is_muc_pm and not properties.type.is_groupchat:
|
|
| 77 |
+ jid = properties.jid.bare
|
|
| 78 |
+ |
|
| 79 |
+ app.nec.push_outgoing_event(
|
|
| 80 |
+ NetworkEvent(name,
|
|
| 81 |
+ account=self._account,
|
|
| 82 |
+ jid=jid,
|
|
| 83 |
+ properties=properties,
|
|
| 84 |
+ type=properties.type,
|
|
| 85 |
+ is_muc_pm=properties.is_muc_pm,
|
|
| 86 |
+ marker_id=properties.marker.id))
|
|
| 87 |
+ |
|
| 88 |
+ def _send_marker(self, contact, marker, id_, type_):
|
|
| 89 |
+ jid = contact.jid
|
|
| 90 |
+ if contact.is_pm_contact:
|
|
| 91 |
+ jid = app.get_jid_without_resource(contact.jid)
|
|
| 92 |
+ |
|
| 93 |
+ if type_ in ('gc', 'pm'):
|
|
| 94 |
+ disco_info = app.storage.cache.get_last_disco_info(jid)
|
|
| 95 |
+ |
|
| 96 |
+ context = 'public'
|
|
| 97 |
+ if disco_info is not None and disco_info.muc_is_members_only:
|
|
| 98 |
+ context = 'private'
|
|
| 99 |
+ |
|
| 100 |
+ if not app.settings.get_group_chat_setting(
|
|
| 101 |
+ self._account, jid, 'send_marker', context=context):
|
|
| 102 |
+ return
|
|
| 103 |
+ else:
|
|
| 104 |
+ if not app.settings.get_contact_setting(
|
|
| 105 |
+ self._account, jid, 'send_marker'):
|
|
| 106 |
+ return
|
|
| 107 |
+ |
|
| 108 |
+ typ = 'groupchat' if type_ == 'gc' else 'chat'
|
|
| 109 |
+ message = OutgoingMessage(account=self._account,
|
|
| 110 |
+ contact=contact,
|
|
| 111 |
+ message=None,
|
|
| 112 |
+ type_=typ,
|
|
| 113 |
+ marker=(marker, id_),
|
|
| 114 |
+ play_sound=False)
|
|
| 115 |
+ self._con.send_message(message)
|
|
| 116 |
+ self._log.info('Send %s: %s', marker, contact.jid)
|
|
| 117 |
+ |
|
| 118 |
+ def send_displayed_marker(self, contact, id_, type_):
|
|
| 119 |
+ self._send_marker(contact, 'displayed', id_, str(type_))
|
|
| 120 |
+ |
|
| 121 |
+ |
|
| 122 |
+def get_instance(*args, **kwargs):
|
|
| 123 |
+ return ChatMarkers(*args, **kwargs), 'ChatMarkers'
|
| ... | ... | @@ -217,7 +217,7 @@ def _upload_file(self, transfer): |
| 217 | 217 |
|
| 218 | 218 |
message.props.request_headers.set_content_type(transfer.mime, None)
|
| 219 | 219 |
message.props.request_headers.set_content_length(transfer.size)
|
| 220 |
- for name, value in transfer.headers:
|
|
| 220 |
+ for name, value in transfer.headers.items():
|
|
| 221 | 221 |
message.props.request_headers.append(name, value)
|
| 222 | 222 |
|
| 223 | 223 |
message.connect('wrote-headers', self._on_wrote_headers, transfer)
|
| ... | ... | @@ -350,6 +350,13 @@ def build_message_stanza(self, message): |
| 350 | 350 |
stanza.setTag('no-store',
|
| 351 | 351 |
namespace=Namespace.MSG_HINTS)
|
| 352 | 352 |
|
| 353 |
+ # XEP-0333
|
|
| 354 |
+ if message.message:
|
|
| 355 |
+ stanza.setMarkable()
|
|
| 356 |
+ if message.marker:
|
|
| 357 |
+ marker, id_ = message.marker
|
|
| 358 |
+ stanza.setMarker(marker, id_)
|
|
| 359 |
+ |
|
| 353 | 360 |
# Add other nodes
|
| 354 | 361 |
if message.nodes is not None:
|
| 355 | 362 |
for node in message.nodes:
|
| ... | ... | @@ -255,12 +255,16 @@ class _ACCOUNT_DEFAULT: |
| 255 | 255 |
'filetransfer_preference': 'httpupload',
|
| 256 | 256 |
'send_chatstate_default': 'composing_only',
|
| 257 | 257 |
'gc_send_chatstate_default': 'composing_only',
|
| 258 |
+ 'send_marker_default': True,
|
|
| 259 |
+ 'gc_send_marker_private_default': True,
|
|
| 260 |
+ 'gc_send_marker_public_default': False,
|
|
| 258 | 261 |
'chat_history_max_age': -1,
|
| 259 | 262 |
},
|
| 260 | 263 |
|
| 261 | 264 |
'contact': {
|
| 262 | 265 |
'speller_language': '',
|
| 263 | 266 |
'send_chatstate': HAS_ACCOUNT_DEFAULT,
|
| 267 |
+ 'send_marker': HAS_ACCOUNT_DEFAULT,
|
|
| 264 | 268 |
'encryption': '',
|
| 265 | 269 |
},
|
| 266 | 270 |
|
| ... | ... | @@ -272,6 +276,7 @@ class _ACCOUNT_DEFAULT: |
| 272 | 276 |
'minimize_on_autojoin': True,
|
| 273 | 277 |
'minimize_on_close': True,
|
| 274 | 278 |
'send_chatstate': HAS_ACCOUNT_DEFAULT,
|
| 279 |
+ 'send_marker': HAS_ACCOUNT_DEFAULT,
|
|
| 275 | 280 |
'encryption': '',
|
| 276 | 281 |
'sync_threshold': HAS_APP_DEFAULT,
|
| 277 | 282 |
},
|
| ... | ... | @@ -678,11 +678,13 @@ def set_group_chat_setting(self, |
| 678 | 678 |
|
| 679 | 679 |
def set_group_chat_settings(self,
|
| 680 | 680 |
setting: str,
|
| 681 |
- value: SETTING_TYPE) -> None:
|
|
| 681 |
+ value: SETTING_TYPE,
|
|
| 682 |
+ context: str = None) -> None:
|
|
| 682 | 683 |
|
| 683 | 684 |
for account in self._account_settings:
|
| 684 | 685 |
for jid in self._account_settings[account]['group_chat']:
|
| 685 |
- self.set_group_chat_setting(account, jid, setting, value)
|
|
| 686 |
+ self.set_group_chat_setting(
|
|
| 687 |
+ account, jid, setting, value, context)
|
|
| 686 | 688 |
|
| 687 | 689 |
def get_contact_setting(self,
|
| 688 | 690 |
account: str,
|
| ... | ... | @@ -55,6 +55,7 @@ def __init__(self, |
| 55 | 55 |
type_,
|
| 56 | 56 |
subject=None,
|
| 57 | 57 |
chatstate=None,
|
| 58 |
+ marker=None,
|
|
| 58 | 59 |
resource=None,
|
| 59 | 60 |
user_nick=None,
|
| 60 | 61 |
label=None,
|
| ... | ... | @@ -69,7 +70,7 @@ def __init__(self, |
| 69 | 70 |
if type_ not in ('chat', 'groupchat', 'normal', 'headline'):
|
| 70 | 71 |
raise ValueError('Unknown message type: %s' % type_)
|
| 71 | 72 |
|
| 72 |
- if not message and chatstate is None:
|
|
| 73 |
+ if not message and chatstate is None and marker is None:
|
|
| 73 | 74 |
raise ValueError('Trying to send message without content')
|
| 74 | 75 |
|
| 75 | 76 |
self.account = account
|
| ... | ... | @@ -91,6 +92,7 @@ def __init__(self, |
| 91 | 92 |
|
| 92 | 93 |
self.subject = subject
|
| 93 | 94 |
self.chatstate = chatstate
|
| 95 |
+ self.marker = marker
|
|
| 94 | 96 |
self.resource = resource
|
| 95 | 97 |
self.user_nick = user_nick
|
| 96 | 98 |
self.label = label
|
| ... | ... | @@ -38,6 +38,7 @@ |
| 38 | 38 |
from gajim.common import helpers
|
| 39 | 39 |
from gajim.common import i18n
|
| 40 | 40 |
from gajim.common.i18n import _
|
| 41 |
+from gajim.common.i18n import Q_
|
|
| 41 | 42 |
from gajim.common.helpers import AdditionalDataDict
|
| 42 | 43 |
from gajim.common.const import StyleAttr
|
| 43 | 44 |
from gajim.common.const import Trust
|
| ... | ... | @@ -311,6 +312,12 @@ def show_receipt(self, id_): |
| 311 | 312 |
return
|
| 312 | 313 |
line.set_receipt()
|
| 313 | 314 |
|
| 315 |
+ def show_displayed(self, id_):
|
|
| 316 |
+ line = self._get_message_line(id_)
|
|
| 317 |
+ if line is None:
|
|
| 318 |
+ return
|
|
| 319 |
+ line.set_displayed()
|
|
| 320 |
+ |
|
| 314 | 321 |
def show_error(self, id_, error):
|
| 315 | 322 |
line = self._get_message_line(id_)
|
| 316 | 323 |
if line is None:
|
| ... | ... | @@ -1194,16 +1201,27 @@ def __init__(self, id_, timestamp, message_icons, start_mark): |
| 1194 | 1201 |
self.timestamp = timestamp
|
| 1195 | 1202 |
self.start_mark = start_mark
|
| 1196 | 1203 |
self._has_receipt = False
|
| 1204 |
+ self._has_displayed = False
|
|
| 1197 | 1205 |
self._message_icons = message_icons
|
| 1198 | 1206 |
|
| 1199 | 1207 |
@property
|
| 1200 | 1208 |
def has_receipt(self):
|
| 1201 | 1209 |
return self._has_receipt
|
| 1202 | 1210 |
|
| 1211 |
+ @property
|
|
| 1212 |
+ def has_displayed(self):
|
|
| 1213 |
+ return self._has_displayed
|
|
| 1214 |
+ |
|
| 1203 | 1215 |
def set_receipt(self):
|
| 1204 | 1216 |
self._has_receipt = True
|
| 1217 |
+ if self._has_displayed:
|
|
| 1218 |
+ return
|
|
| 1205 | 1219 |
self._message_icons.set_receipt_icon_visible(True)
|
| 1206 | 1220 |
|
| 1221 |
+ def set_displayed(self):
|
|
| 1222 |
+ self._has_displayed = True
|
|
| 1223 |
+ self._message_icons.set_displayed_icon_visible(True)
|
|
| 1224 |
+ |
|
| 1207 | 1225 |
def set_correction(self, tooltip):
|
| 1208 | 1226 |
self._message_icons.set_correction_icon_visible(True)
|
| 1209 | 1227 |
self._message_icons.set_correction_tooltip(tooltip)
|
| ... | ... | @@ -1222,12 +1240,12 @@ def __init__(self): |
| 1222 | 1240 |
'document-edit-symbolic', Gtk.IconSize.MENU)
|
| 1223 | 1241 |
self._correction_image.set_no_show_all(True)
|
| 1224 | 1242 |
|
| 1225 |
- self._receipt_image = Gtk.Image.new_from_icon_name(
|
|
| 1243 |
+ self._marker_image = Gtk.Image.new_from_icon_name(
|
|
| 1226 | 1244 |
'emblem-ok-symbolic', Gtk.IconSize.MENU)
|
| 1227 |
- self._receipt_image.get_style_context().add_class(
|
|
| 1245 |
+ self._marker_image.get_style_context().add_class(
|
|
| 1228 | 1246 |
'receipt-received-color')
|
| 1229 |
- self._receipt_image.set_tooltip_text(_('Received'))
|
|
| 1230 |
- self._receipt_image.set_no_show_all(True)
|
|
| 1247 |
+ self._marker_image.set_tooltip_text(_('Received'))
|
|
| 1248 |
+ self._marker_image.set_no_show_all(True)
|
|
| 1231 | 1249 |
|
| 1232 | 1250 |
self._error_image = Gtk.Image.new_from_icon_name(
|
| 1233 | 1251 |
'dialog-warning-symbolic', Gtk.IconSize.MENU)
|
| ... | ... | @@ -1235,14 +1253,22 @@ def __init__(self): |
| 1235 | 1253 |
self._error_image.set_no_show_all(True)
|
| 1236 | 1254 |
|
| 1237 | 1255 |
self.add(self._correction_image)
|
| 1238 |
- self.add(self._receipt_image)
|
|
| 1256 |
+ self.add(self._marker_image)
|
|
| 1239 | 1257 |
self.add(self._error_image)
|
| 1240 | 1258 |
self.show_all()
|
| 1241 | 1259 |
|
| 1242 | 1260 |
def set_receipt_icon_visible(self, visible):
|
| 1243 | 1261 |
if not app.settings.get('positive_184_ack'):
|
| 1244 | 1262 |
return
|
| 1245 |
- self._receipt_image.set_visible(visible)
|
|
| 1263 |
+ self._marker_image.set_visible(visible)
|
|
| 1264 |
+ |
|
| 1265 |
+ def set_displayed_icon_visible(self, visible):
|
|
| 1266 |
+ self._marker_image.set_visible(visible)
|
|
| 1267 |
+ self._marker_image.get_style_context().remove_class(
|
|
| 1268 |
+ 'receipt-received-color')
|
|
| 1269 |
+ self._marker_image.get_style_context().add_class(
|
|
| 1270 |
+ 'displayed-received-color')
|
|
| 1271 |
+ self._marker_image.set_tooltip_text(Q_('?Message state:Read'))
|
|
| 1246 | 1272 |
|
| 1247 | 1273 |
def set_correction_icon_visible(self, visible):
|
| 1248 | 1274 |
self._correction_image.set_visible(visible)
|
| ... | ... | @@ -295,6 +295,7 @@ button.flat.link { padding: 0; border: 0; } |
| 295 | 295 |
|
| 296 | 296 |
/*Receipts*/
|
| 297 | 297 |
.receipt-received-color { color: rgb(75, 181, 67); }
|
| 298 |
+.displayed-received-color { color: rgb(0, 141, 242); }
|
|
| 298 | 299 |
|
| 299 | 300 |
/*Dataforms*/
|
| 300 | 301 |
.field-fixed { font-size: 16px; font-weight: bold; padding-top:5px;}
|
| ... | ... | @@ -26,7 +26,7 @@ |
| 26 | 26 |
|
| 27 | 27 |
from gajim.common import i18n
|
| 28 | 28 |
|
| 29 |
-_MIN_NBXMPP_VER = '1.91.0'
|
|
| 29 |
+_MIN_NBXMPP_VER = '1.99.0'
|
|
| 30 | 30 |
_MIN_GTK_VER = '3.22.27'
|
| 31 | 31 |
_MIN_CAIRO_VER = '1.16.0'
|
| 32 | 32 |
_MIN_PYGOBJECT_VER = '3.32.0'
|
| ... | ... | @@ -658,6 +658,17 @@ def on_groupchat_maximize(self): |
| 658 | 658 |
self.update_actions()
|
| 659 | 659 |
self.set_lock_image()
|
| 660 | 660 |
self.draw_banner_text()
|
| 661 |
+ type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
|
|
| 662 |
+ if not app.events.remove_events(self.account,
|
|
| 663 |
+ self.get_full_jid(),
|
|
| 664 |
+ types=type_):
|
|
| 665 |
+ # XEP-0333 Send <displayed> marker
|
|
| 666 |
+ con = app.connections[self.account]
|
|
| 667 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 668 |
+ self.contact,
|
|
| 669 |
+ self.last_msg_id,
|
|
| 670 |
+ self._type)
|
|
| 671 |
+ self.last_msg_id = None
|
|
| 661 | 672 |
|
| 662 | 673 |
def _on_roster_row_activated(self, _roster, nick):
|
| 663 | 674 |
self._start_private_message(nick)
|
| ... | ... | @@ -796,17 +807,21 @@ def _on_gc_message_received(self, event): |
| 796 | 807 |
else:
|
| 797 | 808 |
if event.properties.muc_nickname == self.nick:
|
| 798 | 809 |
self.last_sent_txt = event.msgtxt
|
| 810 |
+ stanza_id = None
|
|
| 811 |
+ if event.properties.stanza_id:
|
|
| 812 |
+ stanza_id = event.properties.stanza_id.id
|
|
| 799 | 813 |
self.add_message(event.msgtxt,
|
| 800 | 814 |
contact=event.properties.muc_nickname,
|
| 801 | 815 |
tim=event.properties.timestamp,
|
| 802 | 816 |
displaymarking=event.displaymarking,
|
| 803 | 817 |
correct_id=event.correct_id,
|
| 804 | 818 |
message_id=event.properties.id,
|
| 819 |
+ stanza_id=stanza_id,
|
|
| 805 | 820 |
additional_data=event.additional_data)
|
| 806 | 821 |
event.needs_highlight = self.needs_visual_notification(event.msgtxt)
|
| 807 | 822 |
|
| 808 | 823 |
def on_private_message(self, nick, sent, msg, tim, session, additional_data,
|
| 809 |
- msg_log_id=None, displaymarking=None):
|
|
| 824 |
+ message_id, msg_log_id=None, displaymarking=None):
|
|
| 810 | 825 |
# Do we have a queue?
|
| 811 | 826 |
fjid = self.room_jid + '/' + nick
|
| 812 | 827 |
|
| ... | ... | @@ -819,7 +834,9 @@ def on_private_message(self, nick, sent, msg, tim, session, additional_data, |
| 819 | 834 |
session=session,
|
| 820 | 835 |
displaymarking=displaymarking,
|
| 821 | 836 |
sent_forwarded=sent,
|
| 822 |
- additional_data=additional_data)
|
|
| 837 |
+ additional_data=additional_data,
|
|
| 838 |
+ message_id=message_id)
|
|
| 839 |
+ |
|
| 823 | 840 |
app.events.add_event(self.account, fjid, event)
|
| 824 | 841 |
|
| 825 | 842 |
if allow_popup_window(self.account):
|
| ... | ... | @@ -837,7 +854,7 @@ def on_private_message(self, nick, sent, msg, tim, session, additional_data, |
| 837 | 854 |
|
| 838 | 855 |
def add_message(self, text, contact='', tim=None,
|
| 839 | 856 |
displaymarking=None, correct_id=None, message_id=None,
|
| 840 |
- additional_data=None):
|
|
| 857 |
+ stanza_id=None, additional_data=None):
|
|
| 841 | 858 |
"""
|
| 842 | 859 |
Add message to the ConversationsTextview
|
| 843 | 860 |
|
| ... | ... | @@ -887,6 +904,7 @@ def add_message(self, text, contact='', tim=None, |
| 887 | 904 |
displaymarking=displaymarking,
|
| 888 | 905 |
correct_id=correct_id,
|
| 889 | 906 |
message_id=message_id,
|
| 907 |
+ stanza_id=stanza_id,
|
|
| 890 | 908 |
additional_data=additional_data)
|
| 891 | 909 |
|
| 892 | 910 |
def get_nb_unread(self):
|
| ... | ... | @@ -1057,6 +1075,7 @@ def _on_decrypted_message_received(self, event): |
| 1057 | 1075 |
event.properties.timestamp,
|
| 1058 | 1076 |
self.session,
|
| 1059 | 1077 |
event.additional_data,
|
| 1078 |
+ event.properties.id,
|
|
| 1060 | 1079 |
msg_log_id=event.msg_log_id,
|
| 1061 | 1080 |
displaymarking=event.displaymarking)
|
| 1062 | 1081 |
|
| ... | ... | @@ -720,6 +720,19 @@ def __init__(self, account): |
| 720 | 720 |
'button-style': 'destructive-action',
|
| 721 | 721 |
'button-callback': self._reset_gc_send_chatstate}),
|
| 722 | 722 |
|
| 723 |
+ Setting(SettingKind.SWITCH,
|
|
| 724 |
+ _('Send Read Markers'),
|
|
| 725 |
+ SettingType.VALUE,
|
|
| 726 |
+ app.settings.get_account_setting(
|
|
| 727 |
+ account, 'send_marker_default'),
|
|
| 728 |
+ callback=self._send_read_marker,
|
|
| 729 |
+ desc=_('Default for chats and private group chats'),
|
|
| 730 |
+ props={'button-text': _('Reset'),
|
|
| 731 |
+ 'button-tooltip': _('Reset all chats to the '
|
|
| 732 |
+ 'current default value'),
|
|
| 733 |
+ 'button-style': 'destructive-action',
|
|
| 734 |
+ 'button-callback': self._reset_send_read_marker}),
|
|
| 735 |
+ |
|
| 723 | 736 |
Setting(SettingKind.POPOVER,
|
| 724 | 737 |
_('Keep Chat History'),
|
| 725 | 738 |
SettingType.ACCOUNT_CONFIG,
|
| ... | ... | @@ -739,6 +752,20 @@ def _reset_gc_send_chatstate(button): |
| 739 | 752 |
button.set_sensitive(False)
|
| 740 | 753 |
app.settings.set_group_chat_settings('send_chatstate', None)
|
| 741 | 754 |
|
| 755 |
+ def _send_read_marker(self, state, _data):
|
|
| 756 |
+ app.settings.set_account_setting(
|
|
| 757 |
+ self._account, 'send_marker_default', state)
|
|
| 758 |
+ app.settings.set_account_setting(
|
|
| 759 |
+ self._account, 'gc_send_marker_private_default', state)
|
|
| 760 |
+ |
|
| 761 |
+ def _reset_send_read_marker(self, button):
|
|
| 762 |
+ button.set_sensitive(False)
|
|
| 763 |
+ app.settings.set_contact_settings('send_marker', None)
|
|
| 764 |
+ app.settings.set_group_chat_settings(
|
|
| 765 |
+ 'send_marker', None, context='private')
|
|
| 766 |
+ for ctrl in app.interface.msg_win_mgr.get_controls(acct=self._account):
|
|
| 767 |
+ ctrl.update_actions()
|
|
| 768 |
+ |
|
| 742 | 769 |
|
| 743 | 770 |
class ConnectionPage(GenericSettingPage):
|
| 744 | 771 |
|
| ... | ... | @@ -71,6 +71,13 @@ def __init__(self, account, jid, context): |
| 71 | 71 |
'send_chatstate',
|
| 72 | 72 |
props={'entries': chat_state}),
|
| 73 | 73 |
|
| 74 |
+ Setting(SettingKind.SWITCH,
|
|
| 75 |
+ _('Send Chat Markers'),
|
|
| 76 |
+ SettingType.GROUP_CHAT,
|
|
| 77 |
+ 'send_marker',
|
|
| 78 |
+ context=context,
|
|
| 79 |
+ desc=_('Let others know if you read up to this point')),
|
|
| 80 |
+ |
|
| 74 | 81 |
Setting(SettingKind.POPOVER,
|
| 75 | 82 |
_('Sync Threshold'),
|
| 76 | 83 |
SettingType.GROUP_CHAT,
|
| ... | ... | @@ -242,6 +242,43 @@ def handle_event_presence(self, obj): |
| 242 | 242 |
if ctrl and ctrl.session and len(obj.contact_list) > 1:
|
| 243 | 243 |
ctrl.remove_session(ctrl.session)
|
| 244 | 244 |
|
| 245 |
+ @staticmethod
|
|
| 246 |
+ def handle_event_read_state_sync(event):
|
|
| 247 |
+ if event.type.is_groupchat:
|
|
| 248 |
+ control = app.get_groupchat_control(
|
|
| 249 |
+ event.account, event.jid.bare)
|
|
| 250 |
+ if control is None:
|
|
| 251 |
+ log.warning('Groupchat control not found')
|
|
| 252 |
+ return
|
|
| 253 |
+ |
|
| 254 |
+ jid = event.jid.bare
|
|
| 255 |
+ types = ['printed_gc_msg', 'printed_marked_gc_msg']
|
|
| 256 |
+ |
|
| 257 |
+ else:
|
|
| 258 |
+ types = ['chat', 'pm', 'printed_chat', 'printed_pm']
|
|
| 259 |
+ jid = event.jid
|
|
| 260 |
+ |
|
| 261 |
+ control = app.interface.msg_win_mgr.get_control(jid, event.account)
|
|
| 262 |
+ |
|
| 263 |
+ # Compare with control.last_msg_id.
|
|
| 264 |
+ events_ = app.events.get_events(event.account, jid, types)
|
|
| 265 |
+ if not events_:
|
|
| 266 |
+ log.warning('No Events')
|
|
| 267 |
+ return
|
|
| 268 |
+ |
|
| 269 |
+ if event.type.is_groupchat:
|
|
| 270 |
+ id_ = events_[-1].stanza_id or events_[-1].message_id
|
|
| 271 |
+ else:
|
|
| 272 |
+ id_ = events_[-1].message_id
|
|
| 273 |
+ |
|
| 274 |
+ if id_ != event.marker_id:
|
|
| 275 |
+ return
|
|
| 276 |
+ |
|
| 277 |
+ if not app.events.remove_events(event.account, jid, types=types):
|
|
| 278 |
+ # There were events to remove
|
|
| 279 |
+ if control is not None:
|
|
| 280 |
+ control.redraw_after_event_removed(event.jid)
|
|
| 281 |
+ |
|
| 245 | 282 |
@staticmethod
|
| 246 | 283 |
def handle_event_msgsent(obj):
|
| 247 | 284 |
if not obj.play_sound:
|
| ... | ... | @@ -1060,6 +1097,7 @@ def create_core_handlers_list(self): |
| 1060 | 1097 |
'unsubscribed-presence-received': [
|
| 1061 | 1098 |
self.handle_event_unsubscribed_presence],
|
| 1062 | 1099 |
'zeroconf-name-conflict': [self.handle_event_zc_name_conflict],
|
| 1100 |
+ 'read-state-sync': [self.handle_event_read_state_sync],
|
|
| 1063 | 1101 |
}
|
| 1064 | 1102 |
|
| 1065 | 1103 |
def register_core_handlers(self):
|
| ... | ... | @@ -511,12 +511,13 @@ def get_transport_menu(contact, account): |
| 511 | 511 |
return menu
|
| 512 | 512 |
|
| 513 | 513 |
|
| 514 |
-def get_singlechat_menu(control_id, account, jid):
|
|
| 514 |
+def get_singlechat_menu(control_id, account, jid, type_):
|
|
| 515 | 515 |
singlechat_menu = [
|
| 516 | 516 |
(_('Send File'), [
|
| 517 | 517 |
('win.send-file-httpupload-', _('Upload File…')),
|
| 518 | 518 |
('win.send-file-jingle-', _('Send File Directly…')),
|
| 519 | 519 |
]),
|
| 520 |
+ ('win.send-marker-', _('Send Read Markers')),
|
|
| 520 | 521 |
(_('Send Chatstate'), ['chatstate']),
|
| 521 | 522 |
('win.invite-contacts-', _('Invite Contacts…')),
|
| 522 | 523 |
('win.add-to-roster-', _('Add to Contact List…')),
|
| ... | ... | @@ -545,6 +546,9 @@ def build_menu(preset): |
| 545 | 546 |
for item in preset:
|
| 546 | 547 |
if isinstance(item[1], str):
|
| 547 | 548 |
action_name, label = item
|
| 549 |
+ if action_name == 'win.send-marker-' and type_ == 'pm':
|
|
| 550 |
+ continue
|
|
| 551 |
+ |
|
| 548 | 552 |
if action_name == 'app.browse-history':
|
| 549 | 553 |
menuitem = Gio.MenuItem.new(label, action_name)
|
| 550 | 554 |
dict_ = {'account': GLib.Variant('s', account),
|
| ... | ... | @@ -22,7 +22,7 @@ test_suite = test |
| 22 | 22 |
install_requires =
|
| 23 | 23 |
css-parser
|
| 24 | 24 |
keyring
|
| 25 |
- nbxmpp>=1.91.0
|
|
| 25 |
+ nbxmpp>=1.99.0
|
|
| 26 | 26 |
packaging
|
| 27 | 27 |
precis-i18n>=1.0.0
|
| 28 | 28 |
pyOpenSSL>=16.2
|
| 1 | 1 |
# Windows Installer
|
| 2 | 2 |
|
| 3 |
-We use [msys2](http://www.msys2.org/) for creating the Windows installer
|
|
| 3 |
+We use [msys2](https://www.msys2.org/) for creating the Windows installer
|
|
| 4 | 4 |
and development on Windows.
|
| 5 | 5 |
|
| 6 | 6 |
|
| 7 |
-### Development
|
|
| 8 |
------------
|
|
| 7 |
+## Development
|
|
| 9 | 8 |
|
| 10 |
-Download [msys2](http://www.msys2.org/) (msys2-x86_64-xxx.exe) and follow the install instructions on the [msys2](http://www.msys2.org/) startpage (**Important!**)
|
|
| 9 |
+Download [msys2](https://www.msys2.org/) (msys2-x86_64-xxx.exe) and follow the install instructions on the [msys2](https://www.msys2.org/) startpage (**Important!**)
|
|
| 11 | 10 |
|
| 12 | 11 |
* Fork the master branch on dev.gajim.org
|
| 13 |
-* Execute ``C:\msys64\msys2_shell.cmd -mingw64``
|
|
| 14 |
-* Run ``pacman -S git`` to install git
|
|
| 15 |
-* Run ``git clone https://dev.gajim.org/USERNAME/gajim.git``
|
|
| 16 |
-* Run ``cd gajim/win`` to end up where this README exists.
|
|
| 17 |
-* Execute ``./dev_env.sh`` to install all the needed dependencies.
|
|
| 18 |
-* Now go to the git root dir ``cd ..``
|
|
| 19 |
-* Launch Gajim ``./launch.py``
|
|
| 12 |
+* Execute `C:\msys64\msys2_shell.cmd -mingw64`
|
|
| 13 |
+* Run `pacman -S git` to install git
|
|
| 14 |
+* Run `git clone https://dev.gajim.org/USERNAME/gajim.git`
|
|
| 15 |
+* Run `cd gajim/win` to end up where this README exists.
|
|
| 16 |
+* Execute `./dev_env.sh` to install all the needed dependencies.
|
|
| 17 |
+* Now go to the git root dir `cd ..`
|
|
| 18 |
+* Launch Gajim `./launch.py`
|
|
| 20 | 19 |
|
| 21 | 20 |
|
| 22 |
-### Build Gajim / Creating an Installer
|
|
| 23 |
----------------------
|
|
| 21 |
+## Build Gajim / Create an Installer
|
|
| 24 | 22 |
|
| 25 |
-Follow the steps in the Development section, but instead of ``./dev_env.sh`` execute ``./build.sh``.
|
|
| 26 |
- |
|
| 27 |
-You can supply a git tag ``./build.sh [git-tag]``.
|
|
| 28 |
- |
|
| 29 |
-Or dont supply it, then current HEAD is build.
|
|
| 23 |
+Follow the steps in the Development section, but instead of `./dev_env.sh` execute `./build.sh i686` or `./build.sh x86_64`.
|
|
| 30 | 24 |
|
| 31 | 25 |
Both the installer and the portable installer should appear in ``C:\msys64\home\USER\gajim\win\_build_root``.
|
| 32 | 26 |
|
| ... | ... | @@ -72,12 +72,6 @@ function create_root { |
| 72 | 72 |
build_pacman --noconfirm -S base
|
| 73 | 73 |
}
|
| 74 | 74 |
|
| 75 |
-function download_install_dep () {
|
|
| 76 |
- curl -o "${BUILD_ROOT}"/mingw-w64-"${ARCH}"-"$1"-any.pkg.tar.xz \
|
|
| 77 |
- http://repo.msys2.org/mingw/"${ARCH}"/mingw-w64-"${ARCH}"-"$1"-any.pkg.tar.xz
|
|
| 78 |
- build_pacman --noconfirm -U "${BUILD_ROOT}"/mingw-w64-"${ARCH}"-"$1"-any.pkg.tar.xz
|
|
| 79 |
-}
|
|
| 80 |
- |
|
| 81 | 75 |
function install_deps {
|
| 82 | 76 |
build_pacman --noconfirm -S mingw-w64-"${ARCH}"-"${PYTHON_ID}" \
|
| 83 | 77 |
mingw-w64-"${ARCH}"-"${PYTHON_ID}"-gobject \
|
| ... | ... | @@ -105,9 +105,9 @@ Section "Gajim" SecGajim |
| 105 | 105 |
|
| 106 | 106 |
SetOutPath "$INSTDIR\bin"
|
| 107 | 107 |
CreateShortCut "$INSTDIR\Gajim-Portable.lnk" "$INSTDIR\bin\Gajim.exe" \
|
| 108 |
- "-c ..\UserData" "" "" SW_SHOWNORMAL "" "Gajim Portable"
|
|
| 108 |
+ "" "" "" SW_SHOWNORMAL "" "Gajim Portable"
|
|
| 109 | 109 |
CreateShortCut "$INSTDIR\Gajim-Portable-Debug.lnk" "$INSTDIR\bin\Gajim-Debug.exe" \
|
| 110 |
- "-c ..\UserData" "" "" SW_SHOWNORMAL "" "Gajim Portable Debug"
|
|
| 110 |
+ "" "" "" SW_SHOWNORMAL "" "Gajim Portable Debug"
|
|
| 111 | 111 |
FileOpen $0 "is_portable" w
|
| 112 | 112 |
FileClose $0
|
| 113 | 113 |
|