Philipp Hörist pushed to branch newcontrol at gajim / gajim

Commits:

17 changed files:

Changes:

  • README.md
    ... ... @@ -2,46 +2,45 @@ # Welcome to Gajim
    2 2
     
    
    3 3
     ### Runtime Requirements
    
    4 4
     
    
    5
    -- python3.9 or higher
    
    6
    -- python3-gi
    
    7
    -- python3-gi-cairo
    
    8
    -- python3-nbxmpp (>=2.99.0)
    
    9
    -- python3-openssl (>=16.2)
    
    10
    -- python3-css-parser
    
    11
    -- python3-keyring
    
    12
    -- python3-precis-i18n
    
    13
    -- python3-packaging
    
    14
    -- python3-pil
    
    15
    -- python3-setuptools
    
    16
    -- Gtk3 (>=3.22)
    
    17
    -- GLib (>=2.60.0)
    
    18
    -- gir1.2-gtksource-4
    
    19
    -- gir1.2-soup-2.4
    
    5
    +- [Python](https://www.python.org/) (>=3.9)
    
    6
    +- [PyGObject](https://pypi.org/project/PyGObject/)
    
    7
    +- [pycairo](https://pypi.org/project/pycairo/)
    
    8
    +- [nbxmpp](https://pypi.org/project/nbxmpp/) (>=3.0.0)
    
    9
    +- [pyOpenSSL](https://pypi.org/project/pyOpenSSL/) (>=16.2)
    
    10
    +- [css-parser](https://pypi.org/project/css-parser/)
    
    11
    +- [keyring](https://pypi.org/project/keyring/)
    
    12
    +- [precis_i18n](https://pypi.org/project/precis-i18n/)
    
    13
    +- [packaging](https://pypi.org/project/packaging/)
    
    14
    +- [Pillow](https://pypi.org/project/Pillow/)
    
    15
    +- [setuptools](https://pypi.org/project/setuptools/)
    
    16
    +- [Gtk3](https://gitlab.com/gnome/gtk) (>=3.22)
    
    17
    +- [GLib](https://gitlab.com/gnome/glib) (>=2.60.0)
    
    18
    +- [GtkSourceView](https://gitlab.gnome.org/GNOME/gtksourceview)
    
    19
    +- [libsoup](https://gitlab.gnome.org/GNOME/libsoup/)
    
    20 20
     
    
    21 21
     ### Optional Runtime Requirements
    
    22 22
     
    
    23
    -- python3-sentry-sdk for Sentry error reporting to dev.gajim.org (users decide whether to send reports or not)
    
    24
    -- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
    
    25
    -- gir1.2-secret-1 for GNOME Keyring or KDE support as password storage
    
    26 23
     - D-Bus running to have gajim-remote working
    
    27
    -- gir1.2-gupnpigd-1.0 for better NAT traversing
    
    28
    -- gir1.2-networkmanager-1.0 for network lose detection
    
    29
    -- gir1.2-geoclue-2.0 for sharing your location
    
    30
    -- gir1.2-gsound-1.0 for sound on Linux
    
    24
    +- [sentry-sdk](https://pypi.org/project/sentry-sdk/) for Sentry error reporting to dev.gajim.org (users decide whether to send reports or not)
    
    25
    +- [gspell](https://gitlab.gnome.org/GNOME/gspell) and hunspell-LANG where lang is your locale eg. en, fr etc
    
    26
    +- [libsecret](https://gitlab.gnome.org/GNOME/libsecret/) for GNOME Keyring or KDE support as password storage
    
    27
    +- [GUPnP-IGD](https://gitlab.gnome.org/GNOME/gupnp) for better NAT traversing
    
    28
    +- [NetworkManager](https://gitlab.freedesktop.org/NetworkManager/NetworkManager) for network lose detection
    
    29
    +- [GeoClue](https://gitlab.freedesktop.org/geoclue/geoclue) for sharing your location
    
    30
    +- [GSound](https://gitlab.gnome.org/GNOME/gsound) for sound on Linux
    
    31 31
     
    
    32 32
     #### For Video and Audio Calls
    
    33 33
     
    
    34
    -- gir1.2-farstream-0.2 for video calls
    
    35
    -- gir1.2-gstreamer-1.0 for video and audio calls
    
    36
    -- gir1.2-gst-plugins-base-1.0 for video and audio calls
    
    37
    -- gstreamer1.0-plugins-ugly for video and audio calls
    
    38
    -- gstreamer1.0-libav for video and audio calls
    
    39
    -- gstreamer1.0-gtk3 
    
    34
    +- [Farstream](https://gitlab.freedesktop.org/farstream/farstream)
    
    35
    +- [GStreamer](https://gitlab.freedesktop.org/gstreamer/gstreamer)
    
    36
    +- [gst-plugins-base](https://gitlab.freedesktop.org/gstreamer/gst-plugins-base)
    
    37
    +- [gst-plugins-ugly](https://gitlab.freedesktop.org/gstreamer/gst-plugins-ugly)
    
    38
    +- [gst-libav](https://gitlab.freedesktop.org/gstreamer/gst-libav)
    
    40 39
     
    
    41 40
     ### Compile-time Requirements
    
    42 41
     
    
    43
    -- python3-setuptools
    
    44
    -- gettext
    
    42
    +- [setuptools](https://pypi.org/project/setuptools/)
    
    43
    +- [gettext](https://savannah.gnu.org/projects/gettext/)
    
    45 44
     
    
    46 45
     ### Running Tests
    
    47 46
     
    

  • gajim/common/helpers.py
    ... ... @@ -385,36 +385,6 @@ def sanitize_filename(filename: str) -> str:
    385 385
         return filename
    
    386 386
     
    
    387 387
     
    
    388
    -def reduce_chars_newlines(text: str, max_chars: int = 0,
    
    389
    -                          max_lines: int = 0) -> str:
    
    390
    -    """
    
    391
    -    Cut the chars after 'max_chars' on each line and show only the first
    
    392
    -    'max_lines'
    
    393
    -
    
    394
    -    If any of the params is not present (None or 0) the action on it is not
    
    395
    -    performed
    
    396
    -    """
    
    397
    -    def _cut_if_long(string_: str) -> str:
    
    398
    -        if len(string_) > max_chars:
    
    399
    -            string_ = string_[:max_chars - 3] + '…'
    
    400
    -        return string_
    
    401
    -
    
    402
    -    if max_lines == 0:
    
    403
    -        lines = text.split('\n')
    
    404
    -    else:
    
    405
    -        lines = text.split('\n', max_lines)[:max_lines]
    
    406
    -    if max_chars > 0:
    
    407
    -        if lines:
    
    408
    -            lines = [_cut_if_long(e) for e in lines]
    
    409
    -    if lines:
    
    410
    -        reduced_text = '\n'.join(lines)
    
    411
    -        if reduced_text != text:
    
    412
    -            reduced_text += '…'
    
    413
    -    else:
    
    414
    -        reduced_text = ''
    
    415
    -    return reduced_text
    
    416
    -
    
    417
    -
    
    418 388
     def get_contact_dict_for_account(account: str) -> dict[str, types.BareContact]:
    
    419 389
         """
    
    420 390
         Creates a dict of jid -> contact with all contacts of account
    

  • gajim/gajim.py
    ... ... @@ -30,7 +30,7 @@
    30 30
     from gajim.common import i18n
    
    31 31
     
    
    32 32
     
    
    33
    -_MIN_NBXMPP_VER = '3.0.0.dev3'
    
    33
    +_MIN_NBXMPP_VER = '3.0.0'
    
    34 34
     _MIN_GTK_VER = '3.22.27'
    
    35 35
     _MIN_CAIRO_VER = '1.16.0'
    
    36 36
     _MIN_PYGOBJECT_VER = '3.32.0'
    

  • gajim/gtk/account_page.py
    ... ... @@ -75,7 +75,7 @@ def __init__(self, account: str) -> None:
    75 75
     
    
    76 76
             roster_menu = Gio.Menu()
    
    77 77
             for action, label in ROSTER_MENU_DICT.items():
    
    78
    -            roster_menu.append(label, f'win.{action}-{account}')
    
    78
    +            roster_menu.append(label, f'win.{action}')
    
    79 79
             self._ui.roster_menu_button.set_menu_model(roster_menu)
    
    80 80
     
    
    81 81
             self._ui.connect_signals(self)
    

  • gajim/gtk/application.py
    ... ... @@ -524,6 +524,8 @@ def _on_feature_discovered(self, event: events.FeatureDiscovered) -> None:
    524 524
             elif event.feature == Namespace.BLOCKING:
    
    525 525
                 action = '%s-blocking' % event.account
    
    526 526
                 self.set_action_state(action, True)
    
    527
    +            action = '%s-block-contact' % event.account
    
    528
    +            self.set_action_state(action, True)
    
    527 529
     
    
    528 530
         # Action Callbacks
    
    529 531
     
    

  • gajim/gtk/chat_action_processor.py
    ... ... @@ -210,7 +210,7 @@ def _check_for_emoji(self, start_iter: Gtk.TextIter) -> bool:
    210 210
                 start, _end = search
    
    211 211
                 colon_offset = start.get_offset()
    
    212 212
                 before_colon = self._buf.get_iter_at_offset(colon_offset - 1)
    
    213
    -            if before_colon.get_char() != ' ':
    
    213
    +            if before_colon.get_char() not in (' ', '\n'):
    
    214 214
                     # We want to show the menu only if text begins with a colon,
    
    215 215
                     # or if a colon follows on a space. This avoids showing the
    
    216 216
                     # menu within normal sentences containing colons.
    

  • gajim/gtk/const.py
    ... ... @@ -212,11 +212,17 @@ def __str__(self):
    212 212
         ('set-encryption', 's', 'disabled', False),
    
    213 213
         ('send-chatstate', 's', 'disabled', False),
    
    214 214
         ('send-marker', 'b', False, False),
    
    215
    +    ('sort-by-show', 'b', 'app-setting:showoffline', True),
    
    216
    +    ('show-offline', 'b', 'app-setting:sort_by_show_in_roster', True),
    
    215 217
     ]
    
    216 218
     
    
    217 219
     
    
    218 220
     ACCOUNT_ACTIONS = [
    
    219 221
         ('add-contact', 'as'),
    
    222
    +    ('block-contact', 's'),
    
    223
    +    ('remove-contact', 's'),
    
    224
    +    ('execute-command', 's'),
    
    225
    +    ('modify-gateway', 's'),
    
    220 226
         ('archive', 's'),
    
    221 227
         ('blocking', 's'),
    
    222 228
         ('bookmarks', 's'),
    
    ... ... @@ -242,6 +248,9 @@ def __str__(self):
    242 248
     
    
    243 249
     ONLINE_ACCOUNT_ACTIONS = {
    
    244 250
         'add-contact',
    
    251
    +    'remove-contact',
    
    252
    +    'execute-command',
    
    253
    +    'modify-gateway',
    
    245 254
         'bookmarks',
    
    246 255
         'import-contacts',
    
    247 256
         'open-chat',
    
    ... ... @@ -256,4 +265,5 @@ def __str__(self):
    256 265
     FEATURE_ACCOUNT_ACTIONS = {
    
    257 266
         'archive',
    
    258 267
         'blocking',
    
    268
    +    'block-contact',
    
    259 269
     }

  • gajim/gtk/conversation/code_widget.py
    ... ... @@ -100,6 +100,7 @@ def __init__(self) -> None:
    100 100
             self.set_top_margin(2)
    
    101 101
             self.set_bottom_margin(2)
    
    102 102
             self.set_monospace(True)
    
    103
    +        self.get_buffer().set_highlight_matching_brackets(False)
    
    103 104
     
    
    104 105
             self._source_manager = GtkSource.LanguageManager.get_default()
    
    105 106
             self._style_scheme_manager = GtkSource.StyleSchemeManager.get_default()
    

  • gajim/gtk/conversation/rows/message.py
    ... ... @@ -18,6 +18,7 @@
    18 18
     
    
    19 19
     from datetime import datetime
    
    20 20
     from datetime import timedelta
    
    21
    +import textwrap
    
    21 22
     
    
    22 23
     from gi.repository import Gdk
    
    23 24
     from gi.repository import GdkPixbuf
    
    ... ... @@ -40,7 +41,6 @@
    40 41
     from gajim.common.helpers import get_group_chat_nick
    
    41 42
     from gajim.common.helpers import get_muc_context
    
    42 43
     from gajim.common.helpers import message_needs_highlight
    
    43
    -from gajim.common.helpers import reduce_chars_newlines
    
    44 44
     from gajim.common.helpers import to_user_string
    
    45 45
     from gajim.common.i18n import _
    
    46 46
     from gajim.common.i18n import Q_
    
    ... ... @@ -187,8 +187,10 @@ def __init__(self,
    187 187
                 if correction_original is not None:
    
    188 188
                     self._original_text = correction_original
    
    189 189
                     self._message_icons.set_correction_icon_visible(True)
    
    190
    -                original_text = reduce_chars_newlines(
    
    191
    -                    correction_original, max_chars=150, max_lines=10)
    
    190
    +                original_text = textwrap.fill(correction_original,
    
    191
    +                                              width=150,
    
    192
    +                                              max_lines=10,
    
    193
    +                                              placeholder='…')
    
    192 194
                     self._message_icons.set_correction_tooltip(
    
    193 195
                         _('Message corrected. Original message:'
    
    194 196
                           '\n%s') % original_text)
    
    ... ... @@ -425,8 +427,10 @@ def set_correction(self, text: str, nickname: Optional[str]) -> None:
    425 427
             self._message_icons.set_receipt_icon_visible(False)
    
    426 428
             self._message_icons.set_correction_icon_visible(True)
    
    427 429
     
    
    428
    -        original_text = reduce_chars_newlines(
    
    429
    -            self._original_text, max_chars=150, max_lines=10)
    
    430
    +        original_text = textwrap.fill(self._original_text,
    
    431
    +                                      width=150,
    
    432
    +                                      max_lines=10,
    
    433
    +                                      placeholder='…')
    
    430 434
             self._message_icons.set_correction_tooltip(
    
    431 435
                 _('Message corrected. Original message:\n%s') % original_text)
    
    432 436
     
    

  • gajim/gtk/groupchat_roster.py
    ... ... @@ -378,7 +378,6 @@ def _show_contact_menu(self, nick: str, rect: Gdk.Rectangle) -> None:
    378 378
             assert self_contact is not None
    
    379 379
             contact = self._group_chat_contact.get_resource(nick)
    
    380 380
             menu = get_groupchat_roster_menu(self._account,
    
    381
    -                                         self._control_id,
    
    382 381
                                              self_contact,
    
    383 382
                                              contact)
    
    384 383
     
    

  • gajim/gtk/main.py
    ... ... @@ -335,6 +335,11 @@ def _add_actions2(self) -> None:
    335 335
                 self.add_action(act)
    
    336 336
     
    
    337 337
             for action, variant_type, default, enabled in MAIN_WIN_STATEFUL_ACTIONS:
    
    338
    +
    
    339
    +            if isinstance(default, str) and default.startswith('app-setting:'):
    
    340
    +                setting = default.removeprefix('app-setting:')
    
    341
    +                default = app.settings.get(setting)
    
    342
    +
    
    338 343
                 action = Gio.SimpleAction.new_stateful(
    
    339 344
                     action,
    
    340 345
                     GLib.VariantType(variant_type),
    

  • gajim/gtk/menus.py
    ... ... @@ -21,6 +21,7 @@
    21 21
     from typing import Optional
    
    22 22
     from typing import Union
    
    23 23
     
    
    24
    +import textwrap
    
    24 25
     from urllib.parse import quote
    
    25 26
     
    
    26 27
     from gi.repository import Gtk
    
    ... ... @@ -34,7 +35,6 @@
    34 35
     from gajim.common.helpers import is_affiliation_change_allowed
    
    35 36
     from gajim.common.helpers import is_role_change_allowed
    
    36 37
     from gajim.common.helpers import jid_is_blocked
    
    37
    -from gajim.common.helpers import reduce_chars_newlines
    
    38 38
     from gajim.common.i18n import _
    
    39 39
     from gajim.common.i18n import get_short_lang_code
    
    40 40
     from gajim.common.const import URIType
    
    ... ... @@ -77,7 +77,7 @@ def get_singlechat_menu(contact: types.BareContact) -> GajimMenu:
    77 77
     
    
    78 78
         menu = GajimMenu()
    
    79 79
         menu.add_item(_('Details'), 'win.show-contact-info')
    
    80
    -    menu.add_item(_('Block Contact…'), f'win.block-contact-{account}')
    
    80
    +    menu.add_item(_('Block Contact…'), f'app.{account}-block-contact')
    
    81 81
     
    
    82 82
         submenu = get_send_file_submenu()
    
    83 83
         menu.append_submenu(_('Send File'), submenu)
    
    ... ... @@ -207,7 +207,9 @@ def get_encryption_menu() -> GajimMenu:
    207 207
     def get_conv_action_context_menu(account: str,
    
    208 208
                                      selected_text: str
    
    209 209
                                      ) -> Gtk.MenuItem:
    
    210
    -    selected_text_short = reduce_chars_newlines(selected_text, 10, 1)
    
    210
    +    selected_text_short = textwrap.shorten(selected_text,
    
    211
    +                                           width=10,
    
    212
    +                                           placeholder='…')
    
    211 213
     
    
    212 214
         action_menu_item = Gtk.MenuItem.new_with_mnemonic(
    
    213 215
             _('_Actions for "%s"') % selected_text_short)
    
    ... ... @@ -356,15 +358,15 @@ def get_roster_menu(account: str, jid: str, gateway: bool = False) -> GajimMenu:
    356 358
         value = f'"{jid}"'
    
    357 359
     
    
    358 360
         menuitems: MenuItemListT = [
    
    359
    -        (_('Details'), f'win.contact-info-{account}', value),
    
    360
    -        (_('Execute Command…'), f'win.execute-command-{account}', value),
    
    361
    -        (block_label, f'win.block-contact-{account}', value),
    
    362
    -        (_('Remove…'), f'win.remove-contact-{account}', value),
    
    361
    +        (_('Details'), f'app.{account}-contact-info', value),
    
    362
    +        (_('Execute Command…'), f'app.{account}-execute-command', value),
    
    363
    +        (block_label, f'app.{account}-block-contact', value),
    
    364
    +        (_('Remove…'), f'app.{account}-remove-contact', value),
    
    363 365
         ]
    
    364 366
     
    
    365 367
         if gateway:
    
    366 368
             menuitems.insert(
    
    367
    -            1, (_('Modify Gateway…'), f'win.modify-gateway-{account}', value))
    
    369
    +            1, (_('Modify Gateway…'), f'app.{account}-modify-gateway', value))
    
    368 370
     
    
    369 371
         return GajimMenu.from_list(menuitems)
    
    370 372
     
    

  • gajim/gtk/notification_manager.py
    ... ... @@ -378,7 +378,7 @@ def __init__(self, account: str, jid: str) -> None:
    378 378
             remove_button.set_valign(Gtk.Align.CENTER)
    
    379 379
             remove_button.set_tooltip_text('Remove from contact list')
    
    380 380
             remove_button.set_action_name(
    
    381
    -            f'win.remove-contact-{self._account}')
    
    381
    +            f'win.{self._account}-remove-contact')
    
    382 382
             remove_button.set_action_target_value(GLib.Variant('s', str(self.jid)))
    
    383 383
             self.grid.attach(remove_button, 3, 1, 1, 2)
    
    384 384
     
    

  • gajim/gtk/roster.py
    ... ... @@ -116,8 +116,6 @@ def __init__(self, account: str) -> None:
    116 116
             self._ui.connect_signals(self)
    
    117 117
     
    
    118 118
             self.register_events([
    
    119
    -            ('account-connected', ged.CORE, self._on_account_state),
    
    120
    -            ('account-disconnected', ged.CORE, self._on_account_state),
    
    121 119
                 ('roster-received', ged.CORE, self._on_roster_received),
    
    122 120
                 ('theme-update', ged.GUI2, self._on_theme_update),
    
    123 121
                 ('roster-push', ged.GUI2, self._on_roster_push),
    
    ... ... @@ -134,73 +132,36 @@ def __init__(self, account: str) -> None:
    134 132
             self._filter_enabled = False
    
    135 133
             self._filter_string = ''
    
    136 134
     
    
    137
    -        self._add_actions()
    
    135
    +        self._connect_actions()
    
    138 136
             self._initial_draw()
    
    139 137
     
    
    140
    -    def _add_actions(self):
    
    141
    -        actions = [
    
    142
    -            ('contact-info', self._on_contact_info),
    
    143
    -            ('modify-gateway', self._on_modify_gateway),
    
    144
    -            ('execute-command', self._on_execute_command),
    
    145
    -            ('block-contact', self._on_block_contact),
    
    146
    -            ('remove-contact', self._on_remove_contact),
    
    138
    +    def _connect_actions(self):
    
    139
    +        app_actions = [
    
    140
    +            (f'{self._account}-modify-gateway', self._on_modify_gateway),
    
    141
    +            (f'{self._account}-execute-command', self._on_execute_command),
    
    142
    +            (f'{self._account}-block-contact', self._on_block_contact),
    
    143
    +            (f'{self._account}-remove-contact', self._on_remove_contact),
    
    147 144
             ]
    
    148
    -        for action in actions:
    
    149
    -            action_name, func = action
    
    150
    -            act = Gio.SimpleAction.new(
    
    151
    -                f'{action_name}-{self._account}', GLib.VariantType.new('s'))
    
    152
    -            act.connect('activate', func)
    
    153
    -            app.window.add_action(act)
    
    154
    -
    
    155
    -        action = Gio.SimpleAction.new_stateful(
    
    156
    -            f'show-offline-{self._account}',
    
    157
    -            None,
    
    158
    -            GLib.Variant.new_boolean(app.settings.get('showoffline')))
    
    159
    -        action.connect('change-state', self._on_show_offline)
    
    160
    -        app.window.add_action(action)
    
    161
    -
    
    162
    -        action = Gio.SimpleAction.new_stateful(
    
    163
    -            f'sort-by-show-{self._account}',
    
    164
    -            None,
    
    165
    -            GLib.Variant.new_boolean(
    
    166
    -                app.settings.get('sort_by_show_in_roster')))
    
    167
    -        action.connect('change-state', self._on_sort_by_show)
    
    168
    -        app.window.add_action(action)
    
    169
    -
    
    170
    -    def update_actions(self):
    
    171
    -        online = app.account_is_connected(self._account)
    
    172
    -        blocking_support = self._client.get_module('Blocking').supported
    
    173
    -
    
    174
    -        actions = [('contact-info', online),
    
    175
    -                   ('modify-gateway', online),
    
    176
    -                   ('execute-command', online),
    
    177
    -                   ('block-contact', online and blocking_support),
    
    178
    -                   ('remove-contact', online)]
    
    179
    -
    
    180
    -        for action, enabled in actions:
    
    181
    -            act = app.window.lookup_action(f'{action}-{self._account}')
    
    182
    -            assert act is not None
    
    183
    -            act.set_enabled(enabled)
    
    184
    -
    
    185
    -    def _remove_actions(self):
    
    186
    -        actions = [
    
    187
    -            'contact-info',
    
    188
    -            'modify-gateway',
    
    189
    -            'execute-command',
    
    190
    -            'block-contact',
    
    191
    -            'remove-contact',
    
    192
    -            'show-offline',
    
    193
    -            'sort-by-show',
    
    145
    +
    
    146
    +        win_actions = [
    
    147
    +            ('show-offline', self._on_show_offline),
    
    148
    +            ('sort-by-show', self._on_sort_by_show),
    
    194 149
             ]
    
    195
    -        for action in actions:
    
    196
    -            app.window.remove_action(f'{action}-{self._account}')
    
    150
    +
    
    151
    +        for action in app_actions:
    
    152
    +            action_name, func = action
    
    153
    +            action = app.app.lookup_action(action_name)
    
    154
    +            assert action is not None
    
    155
    +            action.connect('activate', func)
    
    156
    +
    
    157
    +        for action in win_actions:
    
    158
    +            action_name, func = action
    
    159
    +            action = app.window.get_action(action_name)
    
    160
    +            action.connect('change-state', func)
    
    197 161
     
    
    198 162
         def _get_contact(self, jid: str) -> types.BareContact:
    
    199 163
             return self._client.get_module('Contacts').get_contact(jid)
    
    200 164
     
    
    201
    -    def _on_account_state(self, _event: ApplicationEvent) -> None:
    
    202
    -        self.update_actions()
    
    203
    -
    
    204 165
         def _on_theme_update(self, _event: ApplicationEvent) -> None:
    
    205 166
             self.redraw()
    
    206 167
     
    
    ... ... @@ -785,7 +746,6 @@ def process_event(self, event: ApplicationEvent) -> None:
    785 746
                 return
    
    786 747
     
    
    787 748
         def _on_destroy(self, _roster: Roster) -> None:
    
    788
    -        self._remove_actions()
    
    789 749
             self._contact_refs.clear()
    
    790 750
             self._group_refs.clear()
    
    791 751
             self._unset_model()
    

  • gajim/gtk/status_icon.py
    ... ... @@ -80,8 +80,7 @@ def _can_use_libindicator() -> bool:
    80 80
                     app.is_installed('AYATANA_APPINDICATOR'))
    
    81 81
     
    
    82 82
         def _on_setting_changed(self, *args: Any) -> None:
    
    83
    -        count = app.window.get_total_unread_count()
    
    84
    -        self._backend.update_state(count=count)
    
    83
    +        self._backend.update_state()
    
    85 84
     
    
    86 85
         def connect_unread_widget(self, widget: Gtk.Widget, signal: str) -> None:
    
    87 86
             widget.connect(signal, self._on_unread_count_changed)
    
    ... ... @@ -89,8 +88,7 @@ def connect_unread_widget(self, widget: Gtk.Widget, signal: str) -> None:
    89 88
         def _on_unread_count_changed(self, *args: Any) -> None:
    
    90 89
             if not app.settings.get('trayicon_notification_on_events'):
    
    91 90
                 return
    
    92
    -        count = app.window.get_total_unread_count()
    
    93
    -        self._backend.update_state(count=count)
    
    91
    +        self._backend.update_state()
    
    94 92
     
    
    95 93
         def is_visible(self) -> bool:
    
    96 94
             return self._backend.is_visible()
    
    ... ... @@ -100,7 +98,7 @@ def shutdown(self) -> None:
    100 98
     
    
    101 99
     
    
    102 100
     class NoneBackend:
    
    103
    -    def update_state(self, count: int = 0) -> None:
    
    101
    +    def update_state(self, init: bool = False) -> None:
    
    104 102
             pass
    
    105 103
     
    
    106 104
         def is_visible(self) -> bool:
    
    ... ... @@ -133,7 +131,7 @@ def __init__(self) -> None:
    133 131
                 client.connect_signal('state-changed',
    
    134 132
                                       self._on_client_state_changed)
    
    135 133
     
    
    136
    -    def update_state(self, count: int = 0) -> None:
    
    134
    +    def update_state(self, init: bool = False) -> None:
    
    137 135
             raise NotImplementedError
    
    138 136
     
    
    139 137
         def is_visible(self) -> bool:
    
    ... ... @@ -188,9 +186,6 @@ def _on_sounds_mute(widget: Gtk.CheckMenuItem) -> None:
    188 186
             app.settings.set('sounds_on', not widget.get_active())
    
    189 187
     
    
    190 188
         def _on_toggle_window(self, _widget: Gtk.MenuItem) -> None:
    
    191
    -        self._on_activate()
    
    192
    -
    
    193
    -    def _on_activate(self, *args: Any) -> None:
    
    194 189
             if app.window.is_minimized():
    
    195 190
                 app.window.unminimize()
    
    196 191
             elif app.window.is_withdrawn():
    
    ... ... @@ -223,9 +218,9 @@ def __init__(self) -> None:
    223 218
             self._status_icon.connect('popup-menu', self._on_popup_menu)
    
    224 219
             self._status_icon.connect('size-changed', self._on_size_changed)
    
    225 220
     
    
    226
    -        self.update_state()
    
    221
    +        self.update_state(init=True)
    
    227 222
     
    
    228
    -    def update_state(self, count: int = 0) -> None:
    
    223
    +    def update_state(self, init: bool = False) -> None:
    
    229 224
             if self._shutdown:
    
    230 225
                 # Shutdown in progress, don't update icon
    
    231 226
                 return
    
    ... ... @@ -236,7 +231,7 @@ def update_state(self, count: int = 0) -> None:
    236 231
     
    
    237 232
             self._status_icon.set_visible(True)
    
    238 233
     
    
    239
    -        if count > 0:
    
    234
    +        if not init and app.window.get_total_unread_count():
    
    240 235
                 self._status_icon.set_from_icon_name('mail-message-new')
    
    241 236
                 return
    
    242 237
     
    
    ... ... @@ -258,17 +253,22 @@ def _on_size_changed(self,
    258 253
             self.update_state()
    
    259 254
     
    
    260 255
         def _on_popup_menu(self,
    
    261
    -                       _status_icon: Gtk.StatusIcon,
    
    256
    +                       status_icon: Gtk.StatusIcon,
    
    262 257
                            button: int,
    
    263 258
                            activate_time: int) -> None:
    
    264
    -        if button == 1:
    
    265
    -            self._on_activate()
    
    266
    -        elif button == 2:
    
    267
    -            self._on_activate()
    
    259
    +
    
    260
    +        if button in (1, 2):
    
    261
    +            self._on_activate(status_icon)
    
    268 262
             elif button == 3:
    
    269
    -            self._build_menu(activate_time)
    
    263
    +            self._build_menu(button, activate_time)
    
    264
    +
    
    265
    +    def _on_activate(self, _status_icon: Gtk.StatusIcon) -> None:
    
    266
    +        if app.window.has_toplevel_focus():
    
    267
    +            app.window.hide()
    
    268
    +        else:
    
    269
    +            app.window.show()
    
    270 270
     
    
    271
    -    def _build_menu(self, event_time: int) -> None:
    
    271
    +    def _build_menu(self, button: int, event_time: int) -> None:
    
    272 272
             for menu in self._popup_menus:
    
    273 273
                 menu.destroy()
    
    274 274
     
    
    ... ... @@ -289,7 +289,7 @@ def _build_menu(self, event_time: int) -> None:
    289 289
     
    
    290 290
             self._ui.systray_context_menu.show_all()
    
    291 291
             self._ui.systray_context_menu.popup(
    
    292
    -            None, None, None, None, 0, event_time)
    
    292
    +            None, None, None, None, button, event_time)
    
    293 293
     
    
    294 294
     
    
    295 295
     class AppIndicator(GtkMenuBackend):
    
    ... ... @@ -306,16 +306,16 @@ def __init__(self) -> None:
    306 306
             self._status_icon.set_secondary_activate_target(
    
    307 307
                 self._ui.toggle_window_menuitem)
    
    308 308
     
    
    309
    -        self.update_state()
    
    309
    +        self.update_state(init=True)
    
    310 310
     
    
    311
    -    def update_state(self, count: int = 0) -> None:
    
    311
    +    def update_state(self, init: bool = False) -> None:
    
    312 312
             if not app.settings.get('show_trayicon'):
    
    313 313
                 self._status_icon.set_status(appindicator.IndicatorStatus.PASSIVE)
    
    314 314
                 return
    
    315 315
     
    
    316 316
             self._status_icon.set_status(appindicator.IndicatorStatus.ACTIVE)
    
    317 317
     
    
    318
    -        if count > 0:
    
    318
    +        if not init and app.window.get_total_unread_count():
    
    319 319
                 icon_name = 'mail-message-new'
    
    320 320
                 self._status_icon.set_icon_full(icon_name, _('Pending Event'))
    
    321 321
                 return
    

  • gajim/gtk/tooltips.py
    ... ... @@ -206,6 +206,8 @@ def _add_resources(self, contact: types.BareContact) -> None:
    206 206
                 show_image.set_halign(Gtk.Align.START)
    
    207 207
                 show_image.set_valign(Gtk.Align.CENTER)
    
    208 208
     
    
    209
    +            show_string = helpers.get_uf_show(resource.show.value)
    
    210
    +
    
    209 211
                 assert resource.jid.resource is not None
    
    210 212
                 resource_string = GLib.markup_escape_text(resource.jid.resource)
    
    211 213
                 resource_label = Gtk.Label()
    
    ... ... @@ -213,7 +215,7 @@ def _add_resources(self, contact: types.BareContact) -> None:
    213 215
                 resource_label.set_xalign(0)
    
    214 216
                 resource_label.set_ellipsize(Pango.EllipsizeMode.END)
    
    215 217
                 resource_label.set_max_width_chars(30)
    
    216
    -            resource_label.set_text(resource_string)
    
    218
    +            resource_label.set_text(f'{show_string} ({resource_string})')
    
    217 219
     
    
    218 220
                 base_box = Gtk.Box(spacing=6)
    
    219 221
                 base_box.add(show_image)
    

  • pyrightconfig.json
    ... ... @@ -41,6 +41,7 @@
    41 41
             "gajim/common/sound.py",
    
    42 42
             "gajim/common/storage/",
    
    43 43
             "gajim/common/task_manager.py",
    
    44
    +        "gajim/gtk/conversation",
    
    44 45
             "gajim/gtk/about.py",
    
    45 46
             "gajim/gtk/account_page.py",
    
    46 47
             "gajim/gtk/account_side_bar.py",
    
    ... ... @@ -65,9 +66,8 @@
    65 66
             "gajim/gtk/control_stack.py",
    
    66 67
             "gajim/gtk/const.py",
    
    67 68
             "gajim/gtk/contact_info.py",
    
    68
    -        "gajim/gtk/contacts_flowbox.py",
    
    69 69
             "gajim/gtk/contact_settings.py",
    
    70
    -        "gajim/gtk/conversation",
    
    70
    +        "gajim/gtk/contacts_flowbox.py",
    
    71 71
             "gajim/gtk/dialogs.py",
    
    72 72
             "gajim/gtk/emoji_data_gtk.py",
    
    73 73
             "gajim/gtk/exception.py",
    
    ... ... @@ -79,11 +79,13 @@
    79 79
             "gajim/gtk/groupchat_details.py",
    
    80 80
             "gajim/gtk/groupchat_invitation.py",
    
    81 81
             "gajim/gtk/groupchat_inviter.py",
    
    82
    -        "gajim/gtk/groupchat_nick.py",
    
    82
    +        "gajim/gtk/groupchat_join.py",
    
    83 83
             "gajim/gtk/groupchat_nick_completion.py",
    
    84
    +        "gajim/gtk/groupchat_nick.py",
    
    84 85
             "gajim/gtk/groupchat_outcasts.py",
    
    85 86
             "gajim/gtk/groupchat_settings.py",
    
    86 87
             "gajim/gtk/groupchat_state.py",
    
    88
    +        "gajim/gtk/gstreamer.py",
    
    87 89
             "gajim/gtk/history_export.py",
    
    88 90
             "gajim/gtk/history_sync.py",
    
    89 91
             "gajim/gtk/main_stack.py",