Philipp Hörist pushed to branch port-pyproject at gajim / gajim

Commits:

13 changed files:

Changes:

  • .gitlab-ci.yml
    ... ... @@ -15,8 +15,9 @@ test-other:
    15 15
       stage: test
    
    16 16
       script:
    
    17 17
         - codespell gajim
    
    18
    -    - python3 setup.py build
    
    19
    -    - appstream-util validate build/data/org.gajim.Gajim.appdata.xml
    
    18
    +    - pip3 install build
    
    19
    +    - python3 -m build -w
    
    20
    +    - appstream-util validate dist/share/metainfo/org.gajim.Gajim.appdata.xml
    
    20 21
       interruptible: true
    
    21 22
     
    
    22 23
     test-pyright:
    
    ... ... @@ -68,7 +69,8 @@ build-linux:
    68 69
       script:
    
    69 70
         - apt-get update
    
    70 71
         - apt-get install -y python3-nbxmpp-nightly
    
    71
    -    - python3 setup.py sdist
    
    72
    +    - pip3 install build
    
    73
    +    - python3 -m build -s
    
    72 74
         - python3 .ci/debian_build.py "$(find dist/gajim-*)" 1
    
    73 75
     
    
    74 76
       artifacts:
    

  • MANIFEST.in
    1 1
     include COPYING
    
    2
    +include ChangeLog
    
    3
    +recursive-include pep517build *
    
    2 4
     recursive-include po *
    
    3 5
     recursive-include data *.1 *.in
    
    4
    -recursive-include test *
    6
    +recursive-include test *
    \ No newline at end of file

  • README.md
    ... ... @@ -13,7 +13,7 @@ ### Runtime Requirements
    13 13
     - [precis_i18n](https://pypi.org/project/precis-i18n/)
    
    14 14
     - [packaging](https://pypi.org/project/packaging/)
    
    15 15
     - [Pillow](https://pypi.org/project/Pillow/)
    
    16
    -- [setuptools](https://pypi.org/project/setuptools/)
    
    16
    +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0)
    
    17 17
     - [Gtk3](https://gitlab.com/gnome/gtk) (>=3.24.30)
    
    18 18
     - [GLib](https://gitlab.com/gnome/glib) (>=2.60.0)
    
    19 19
     - [GtkSourceView](https://gitlab.gnome.org/GNOME/gtksourceview)
    
    ... ... @@ -39,17 +39,60 @@ #### For Video and Audio Calls
    39 39
     - [gst-plugins-ugly](https://gitlab.freedesktop.org/gstreamer/gst-plugins-ugly)
    
    40 40
     - [gst-libav](https://gitlab.freedesktop.org/gstreamer/gst-libav)
    
    41 41
     
    
    42
    -### Install Requirements
    
    42
    +### Build Requirements
    
    43 43
     
    
    44
    -- [setuptools](https://pypi.org/project/setuptools/)
    
    44
    +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0)
    
    45 45
     - [gettext](https://savannah.gnu.org/projects/gettext/)
    
    46 46
     
    
    47
    +To build Gajim a PEP517 build frontend like pip (https://pip.pypa.io/en/stable/) or build (https://pypa-build.readthedocs.io/en/stable/) must be used.
    
    48
    +
    
    49
    +The build frontend takes care of installing all python build requirements. Beware `gettext` is not a python library and cannot be installed by the build frontend.
    
    50
    +
    
    51
    +Execute the following inside the source directory
    
    52
    +
    
    53
    +#### Building the wheel and metadata files
    
    54
    +
    
    55
    +A wheel and additional metadata files are placed in the `dist` folder.
    
    56
    +
    
    57
    +##### Using `build`
    
    58
    +
    
    59
    +```bash
    
    60
    +python -m build -w
    
    61
    +```
    
    62
    +
    
    63
    +##### Using `pip`
    
    64
    +
    
    65
    +```bash
    
    66
    +pip wheel --no-deps --use-pep517 -w dist .
    
    67
    +```
    
    68
    +
    
    69
    +#### Installing
    
    70
    +
    
    71
    +```bash
    
    72
    +pip install dist/name_of_wheel.whl
    
    73
    +```
    
    74
    +
    
    75
    +The wheel building step can be skipped with `pip`
    
    76
    +
    
    77
    +```bash
    
    78
    +pip install .
    
    79
    +```
    
    80
    +
    
    81
    +in this case the metadata files are still placed into the `dist` directory.
    
    82
    +
    
    83
    +#### Installing metadata files
    
    84
    +
    
    85
    +On the Unix platform the metadata files need to be installed also.
    
    86
    +There is a script to copy the files into the right places.
    
    87
    +See `pep517build/install_metadata.py`. You may want to edit the script and
    
    88
    +adjust the target folders depending on which distribution you use.
    
    89
    +
    
    47 90
     ### Running Tests
    
    48 91
     
    
    49 92
     - `python -m unittest discover -s test` (no_gui directory)
    
    50 93
     - `python -m unittest ./test/gtk/gui_file.py` (gtk directory)
    
    51 94
     
    
    52
    -### Installation Procedure
    
    95
    +### Packages and install instructions
    
    53 96
     
    
    54 97
     #### Packages
    
    55 98
     
    
    ... ... @@ -68,15 +111,11 @@ #### Snapshots
    68 111
     - [Daily Linux](https://www.gajim.org/downloads/snap/)
    
    69 112
     - [Daily Windows](https://gajim.org/downloads/snap/win)
    
    70 113
     
    
    71
    -#### Linux
    
    72
    -
    
    73
    -    pip install .
    
    74
    -
    
    75 114
     #### Mac
    
    76 115
     
    
    77 116
     see [Wiki](https://dev.gajim.org/gajim/gajim/-/wikis/help/Gajim-on-macOS)
    
    78 117
     
    
    79
    -#### Developing
    
    118
    +### Developing
    
    80 119
     
    
    81 120
     To create a virutalenv you can execute
    
    82 121
     
    

  • gajim/common/i18n.py
    ... ... @@ -20,56 +20,19 @@
    20 20
     
    
    21 21
     from __future__ import annotations
    
    22 22
     
    
    23
    -from typing import Iterator
    
    23
    +from typing import cast
    
    24 24
     from typing import Optional
    
    25 25
     
    
    26 26
     import os
    
    27 27
     import sys
    
    28 28
     import locale
    
    29 29
     import gettext
    
    30
    +import importlib.resources
    
    30 31
     import unicodedata
    
    31 32
     from pathlib import Path
    
    32
    -from gi.repository import GLib
    
    33
    -
    
    34
    -DOMAIN = 'gajim'
    
    35
    -_translation = gettext.NullTranslations()
    
    36
    -
    
    37
    -
    
    38
    -def get_locale_dirs() -> Optional[list[Path]]:
    
    39
    -    if sys.platform == 'win32':
    
    40
    -        return
    
    41
    -
    
    42
    -    path = gettext.find(DOMAIN)
    
    43
    -    if path is not None:
    
    44
    -        # gettext can find the location itself
    
    45
    -        # so we don’t need the localedir
    
    46
    -        return
    
    47
    -
    
    48
    -    if Path('/app/share/run-as-flatpak').exists():
    
    49
    -        # Check if we run as flatpak
    
    50
    -        return [Path('/app/share/')]
    
    51
    -
    
    52
    -    data_dirs = [GLib.get_user_data_dir()] + GLib.get_system_data_dirs()
    
    53
    -    return [Path(dir_) for dir_ in data_dirs]
    
    54 33
     
    
    55 34
     
    
    56
    -def iter_locale_dirs() -> Iterator[Optional[Path]]:
    
    57
    -    locale_dirs = get_locale_dirs()
    
    58
    -    if locale_dirs is None:
    
    59
    -        yield None
    
    60
    -        return
    
    61
    -
    
    62
    -    # gettext fallback
    
    63
    -    locale_dirs.append(Path(sys.base_prefix) / 'share')
    
    64
    -
    
    65
    -    found_paths: list[Path] = []
    
    66
    -    for path in locale_dirs:
    
    67
    -        locale_dir = path / 'locale'
    
    68
    -        if locale_dir in found_paths:
    
    69
    -            continue
    
    70
    -        found_paths.append(locale_dir)
    
    71
    -        if locale_dir.is_dir():
    
    72
    -            yield locale_dir
    
    35
    +DOMAIN = 'gajim'
    
    73 36
     
    
    74 37
     
    
    75 38
     def get_win32_default_lang() -> str:
    
    ... ... @@ -160,19 +123,16 @@ def ngettext(s_sing: str,
    160 123
         # doesn't retain other region settings like LC_TIME
    
    161 124
         os.environ['LANGUAGE'] = LANG
    
    162 125
     
    
    163
    -# Search for the translation in all locale dirs
    
    164
    -for dir_ in iter_locale_dirs():
    
    165
    -    try:
    
    166
    -        _translation = gettext.translation(DOMAIN, dir_)
    
    167
    -        _ = _translation.gettext
    
    168
    -        if hasattr(locale, 'bindtextdomain'):
    
    169
    -            locale.bindtextdomain(DOMAIN, dir_)
    
    170
    -    except OSError:
    
    171
    -        continue
    
    172
    -    else:
    
    173
    -        break
    
    174
    -
    
    175
    -else:
    
    126
    +
    
    127
    +package_dir = cast(Path, importlib.resources.files('gajim'))
    
    128
    +locale_dir = package_dir / 'data' / 'locale'
    
    129
    +
    
    130
    +try:
    
    131
    +    _translation = gettext.translation(DOMAIN, locale_dir)
    
    132
    +    _ = _translation.gettext
    
    133
    +    if hasattr(locale, 'bindtextdomain'):
    
    134
    +        locale.bindtextdomain(DOMAIN, locale_dir)
    
    135
    +except OSError:
    
    136
    +    _translation = gettext.NullTranslations()
    
    176 137
         _ = _translation.gettext
    
    177 138
         print('No translations found for', LANG, file=sys.stderr)
    178
    -    print('Dirs searched: %s' % get_locale_dirs(), file=sys.stderr)

  • gajim/data/style/gajim.css
    ... ... @@ -650,6 +650,17 @@ .preview-image-button:hover {
    650 650
         box-shadow: none;
    
    651 651
     }
    
    652 652
     
    
    653
    +/* Audiovisualizer */
    
    654
    +#AudioVisualizer {
    
    655
    +    color: @theme_selected_bg_color;
    
    656
    +    background-color: @insensitive_fg_color;
    
    657
    +    border-color: @theme_unfocused_bg_color;
    
    658
    +}
    
    659
    +
    
    660
    +#AudioVisualizer:selected {
    
    661
    +    color: lighter(@theme_selected_bg_color);
    
    662
    +}
    
    663
    +
    
    653 664
     /* Conversation meta elements */
    
    654 665
     .conversation-meta {
    
    655 666
         opacity: 0.6;
    

  • gajim/gtk/preview_audio.py
    ... ... @@ -257,13 +257,11 @@ def _setup_audio_visualizer(self) -> None:
    257 257
                 return
    
    258 258
     
    
    259 259
             width -= 2 * SEEK_BAR_PADDING
    
    260
    -        seek_bar_rgba = self._ui.seek_bar.get_style_context().get_color(
    
    261
    -            Gtk.StateFlags.LINK)
    
    262 260
             self._audio_visualizer = AudioVisualizerWidget(
    
    263 261
                 width,
    
    264 262
                 height,
    
    265 263
                 SEEK_BAR_PADDING,
    
    266
    -            seek_bar_rgba)
    
    264
    +        )
    
    267 265
     
    
    268 266
             self._ui.drawing_box.add(self._audio_visualizer)
    
    269 267
     
    
    ... ... @@ -272,8 +270,8 @@ def _update_timestamp_label(self) -> None:
    272 270
             dur = self._state.duration
    
    273 271
     
    
    274 272
             dur_str = format_duration(dur, dur)
    
    275
    -        ltr_char = u'\u202B'
    
    276
    -        pop_char = u'\u202C'
    
    273
    +        ltr_char = '\u202B'
    
    274
    +        pop_char = '\u202C'
    
    277 275
     
    
    278 276
             if self._state.is_timestamp_positive:
    
    279 277
                 cur_str = f'{format_duration(cur, dur)}'
    
    ... ... @@ -445,9 +443,6 @@ def _on_bus_message(self, _bus: Gst.Bus, message: Gst.Message) -> None:
    445 443
                 if not self._pause_seek:
    
    446 444
                     self._ui.seek_bar.set_value(self._state.duration)
    
    447 445
                     self._audio_visualizer.draw_graph(1.0)
    
    448
    -            else:
    
    449
    -                self._audio_visualizer.draw_graph(
    
    450
    -                    self._state.position / self._state.duration)
    
    451 446
     
    
    452 447
             elif message.type == Gst.MessageType.STATE_CHANGED:
    
    453 448
                 is_paused = self._get_paused()
    

  • gajim/gtk/preview_audio_visualizer.py
    ... ... @@ -28,7 +28,6 @@
    28 28
     
    
    29 29
     from .util import rgba_to_float
    
    30 30
     
    
    31
    -
    
    32 31
     log = logging.getLogger('gajim.gui.preview_audio_visualizer')
    
    33 32
     
    
    34 33
     
    
    ... ... @@ -37,37 +36,29 @@ def __init__(self,
    37 36
                      width: int,
    
    38 37
                      height: int,
    
    39 38
                      x_offset: int,
    
    40
    -                 seek_bar_color: Gdk.RGBA
    
    41 39
                      ) -> None:
    
    42 40
     
    
    43 41
             Gtk.DrawingArea.__init__(self)
    
    44 42
     
    
    43
    +        self.set_name('AudioVisualizer')
    
    44
    +        self._update_colors()
    
    45 45
             self._x_offset = x_offset
    
    46
    +        self._peak_width = 2  # in px
    
    46 47
             self._width = width
    
    47 48
             self._height = height
    
    48
    -        self._is_LTR = (self.get_direction() == Gtk.TextDirection.LTR)
    
    49
    -        self._amplitude_width = 2
    
    49
    +        self._is_LTR = bool(self.get_direction() == Gtk.TextDirection.LTR)
    
    50 50
     
    
    51 51
             self._num_samples = 0
    
    52 52
             self._samples: AudioSampleT = []
    
    53 53
             self._seek_position = -1.0
    
    54 54
             self._position = 0.0
    
    55 55
     
    
    56
    -        self._default_color = (0.6, 0.6, 0.6)  # gray
    
    57
    -        self._progress_color = rgba_to_float(seek_bar_color)
    
    58
    -
    
    59
    -        # Use a 50% lighter color to the seeking indicator
    
    60
    -        self._seek_color = tuple(
    
    61
    -            min(1.0, color * 1.5) for color in self._progress_color
    
    62
    -        )
    
    63
    -
    
    64
    -        self._surface: Optional[cairo.ImageSurface] = None
    
    65
    -
    
    66
    -        self.set_size_request(self._width, self._height)
    
    67
    -
    
    68 56
             self.connect('draw', self._on_drawingarea_draw)
    
    69 57
             self.connect('configure-event', self._on_drawingarea_changed)
    
    58
    +        self.connect('style-updated', self._on_style_updated)
    
    70 59
     
    
    60
    +        self.set_size_request(self._width, self._height)
    
    61
    +        self._surface: Optional[cairo.ImageSurface] = None
    
    71 62
             self._setup_surface()
    
    72 63
     
    
    73 64
         def update_params(self,
    
    ... ... @@ -89,6 +80,45 @@ def draw_graph(self, position: float, seek_position: float = -1.0) -> None:
    89 80
     
    
    90 81
             self.queue_draw()
    
    91 82
     
    
    83
    +    def _on_drawingarea_draw(self,
    
    84
    +                             _drawing_area: Gtk.DrawingArea,
    
    85
    +                             ctx: cairo.Context[cairo.ImageSurface]
    
    86
    +                             ) -> None:
    
    87
    +
    
    88
    +        if self._num_samples != 0:
    
    89
    +            self._draw_surface(ctx)
    
    90
    +
    
    91
    +    def _on_drawingarea_changed(self,
    
    92
    +                                _drawing_area: Gtk.DrawingArea,
    
    93
    +                                _event: Gdk.EventConfigure
    
    94
    +                                ) -> None:
    
    95
    +
    
    96
    +        self._is_LTR = bool(self.get_direction() == Gtk.TextDirection.LTR)
    
    97
    +        self._setup_surface()
    
    98
    +        self.queue_draw()
    
    99
    +
    
    100
    +    def _on_style_updated(self,
    
    101
    +                          _event: Gdk.EventConfigure
    
    102
    +                          ) -> None:
    
    103
    +
    
    104
    +        self._update_colors()
    
    105
    +        self.queue_draw()
    
    106
    +
    
    107
    +    def _update_colors(self) -> None:
    
    108
    +        sc = self.get_style_context()
    
    109
    +        self._progress_color = rgba_to_float(
    
    110
    +            sc.get_color(Gtk.StateFlags.NORMAL)
    
    111
    +        )
    
    112
    +        self._default_color = rgba_to_float(
    
    113
    +            sc.get_background_color(Gtk.StateFlags.NORMAL)
    
    114
    +        )
    
    115
    +        self._outline_color = rgba_to_float(
    
    116
    +            sc.get_border_color(Gtk.StateFlags.NORMAL)
    
    117
    +        )
    
    118
    +        self._seek_color = rgba_to_float(
    
    119
    +            sc.get_color(Gtk.StateFlags.SELECTED)
    
    120
    +        )
    
    121
    +
    
    92 122
         def _setup_surface(self) -> None:
    
    93 123
             if self._surface:
    
    94 124
                 self._surface.finish()
    
    ... ... @@ -103,9 +133,10 @@ def _setup_surface(self) -> None:
    103 133
                 self._draw_surface(ctx)
    
    104 134
     
    
    105 135
         def _process_samples(self) -> None:
    
    106
    -        # Pick a subset of all samples and average over three samples
    
    107
    -        w = int(self._width / (self._amplitude_width * 2))
    
    108
    -        n = math.floor(len(self._samples) / w) + 1
    
    136
    +        # Create a subset by taking the average of three samples
    
    137
    +        # around every nth sample
    
    138
    +        num_divisions = int(self._width / (self._peak_width * 2))
    
    139
    +        n = math.floor(len(self._samples) / num_divisions) + 1
    
    109 140
     
    
    110 141
             if n >= 2:
    
    111 142
                 samples: AudioSampleT = []
    
    ... ... @@ -120,9 +151,18 @@ def _process_samples(self) -> None:
    120 151
     
    
    121 152
             # Normalize both channels using the same scale
    
    122 153
             max_elem = max(max(samples))
    
    123
    -        self._samples = [
    
    124
    -            (val1 / max_elem, val2 / max_elem) for val1, val2 in samples
    
    125
    -        ]
    
    154
    +        min_elem = min(min(samples))
    
    155
    +        delta = max_elem - min_elem
    
    156
    +        if delta > 0:
    
    157
    +            self._samples = [
    
    158
    +                (
    
    159
    +                    (val1 - min_elem) / delta,
    
    160
    +                    (val2 - min_elem) / delta
    
    161
    +                )
    
    162
    +                for val1, val2 in samples
    
    163
    +            ]
    
    164
    +        else:
    
    165
    +            self._samples = samples
    
    126 166
     
    
    127 167
         def _draw_surface(self, ctx: cairo.Context[cairo.ImageSurface]) -> None:
    
    128 168
             # First draw the progress part from left
    
    ... ... @@ -130,7 +170,8 @@ def _draw_surface(self, ctx: cairo.Context[cairo.ImageSurface]) -> None:
    130 170
             self._draw_rms_amplitudes(ctx,
    
    131 171
                                       start=0,
    
    132 172
                                       end=end,
    
    133
    -                                  color=self._progress_color)
    
    173
    +                                  color=self._progress_color
    
    174
    +                                  )
    
    134 175
     
    
    135 176
             if self._seek_position >= 0:
    
    136 177
                 # If state is PLAYING and the user seeks, determine whether the
    
    ... ... @@ -152,7 +193,8 @@ def _draw_surface(self, ctx: cairo.Context[cairo.ImageSurface]) -> None:
    152 193
                 self._draw_rms_amplitudes(ctx,
    
    153 194
                                           start=start,
    
    154 195
                                           end=end,
    
    155
    -                                      color=self._seek_color)
    
    196
    +                                      color=self._seek_color
    
    197
    +                                      )
    
    156 198
     
    
    157 199
             # Draw the default amplitudes for the rest of the timeline
    
    158 200
             play_pos = min(round(
    
    ... ... @@ -164,7 +206,8 @@ def _draw_surface(self, ctx: cairo.Context[cairo.ImageSurface]) -> None:
    164 206
             self._draw_rms_amplitudes(ctx,
    
    165 207
                                       start=start_default,
    
    166 208
                                       end=self._num_samples,
    
    167
    -                                  color=self._default_color)
    
    209
    +                                  color=self._default_color
    
    210
    +                                  )
    
    168 211
     
    
    169 212
         def _draw_rms_amplitudes(self,
    
    170 213
                                  ctx: cairo.Context[cairo.ImageSurface],
    
    ... ... @@ -172,84 +215,98 @@ def _draw_rms_amplitudes(self,
    172 215
                                  end: int,
    
    173 216
                                  color: tuple[float, float, float]
    
    174 217
                                  ) -> None:
    
    175
    -
    
    176
    -        ctx.set_source_rgb(*color)
    
    177
    -
    
    178
    -        w = self._amplitude_width
    
    179
    -
    
    180
    -        # determines the spacing between the amplitudes
    
    181
    -        o = (self._width - 2 * w) / self._num_samples
    
    182
    -        y = self._height / 2
    
    183
    -        r = 1  # radius of the arcs on top and bottom of the amplitudes
    
    184
    -        s = self._height / 2 - r  # amplitude scaling factor
    
    218
    +        peak_width = self._peak_width
    
    219
    +        radius = 1  # radius of the arcs on top and bottom of the amplitudes
    
    220
    +        gap = 0.25  # between upper and lower peak in pixels
    
    221
    +        scaling = self._height / 2 - radius - gap / 2  # peak scaling factor
    
    222
    +        ctx.set_line_width(1.0)
    
    185 223
     
    
    186 224
             if self._is_LTR:
    
    187
    -            o = (self._width - 2 * w) / self._num_samples
    
    188
    -            x = self._x_offset + o * (start + 1)
    
    225
    +            # determines the spacing between the amplitudes
    
    226
    +            x_shift = (self._width - 2 * peak_width) / self._num_samples
    
    227
    +            x = self._x_offset + x_shift * (start + 1)
    
    189 228
             else:
    
    190
    -            o = -(self._width - 2 * w) / self._num_samples
    
    191
    -            x = -o * (self._num_samples - start) + self._x_offset
    
    229
    +            x_shift = -(self._width - 2 * peak_width) / self._num_samples
    
    230
    +            x = -x_shift * (self._num_samples - start) + self._x_offset
    
    192 231
     
    
    193 232
             for i in range(start, end):
    
    194
    -            # Ensure that no empty area is drawn,
    
    195
    -            # thus use a minimum sample value
    
    196
    -
    
    197 233
                 if self._is_LTR:
    
    198
    -                sample1 = max(0.05, self._samples[i][0])
    
    199
    -                sample2 = max(0.05, self._samples[i][1])
    
    234
    +                sample1 = self._samples[i][0]
    
    235
    +                sample2 = self._samples[i][1]
    
    200 236
                 else:
    
    201
    -                sample1 = max(0.05, self._samples[i][1])
    
    202
    -                sample2 = max(0.05, self._samples[i][0])
    
    203
    -
    
    204
    -            self._draw_rounded_rec2(ctx, x, y, w, sample1 * s, sample2 * s, r)
    
    237
    +                sample1 = self._samples[i][1]
    
    238
    +                sample2 = self._samples[i][0]
    
    239
    +
    
    240
    +            self._draw_rounded_rec(
    
    241
    +                ctx,
    
    242
    +                x,
    
    243
    +                self._height / 2,
    
    244
    +                peak_width,
    
    245
    +                sample1 * scaling,
    
    246
    +                sample2 * scaling,
    
    247
    +                radius,
    
    248
    +                gap,
    
    249
    +            )
    
    250
    +
    
    251
    +            ctx.set_source_rgb(*self._outline_color)
    
    252
    +            ctx.stroke_preserve()
    
    253
    +
    
    254
    +            ctx.set_source_rgb(*color)
    
    205 255
                 ctx.fill()
    
    206
    -            x += o
    
    207
    -
    
    208
    -    def _on_drawingarea_draw(self,
    
    209
    -                             _drawing_area: Gtk.DrawingArea,
    
    210
    -                             ctx: cairo.Context[cairo.ImageSurface],
    
    211
    -                             ) -> None:
    
    212
    -
    
    213
    -        if self._num_samples != 0:
    
    214
    -            self._draw_surface(ctx)
    
    215
    -
    
    216
    -    def _on_drawingarea_changed(self,
    
    217
    -                                _drawing_area: Gtk.DrawingArea,
    
    218
    -                                _event: Gdk.EventConfigure,
    
    219
    -                                ) -> None:
    
    220
    -
    
    221
    -        self._is_LTR = (self.get_direction() == Gtk.TextDirection.LTR)
    
    222
    -        self._setup_surface()
    
    223
    -        self.queue_draw()
    
    224
    -
    
    225
    -    def _draw_rounded_rec2(self,
    
    226
    -                           context: cairo.Context[cairo.ImageSurface],
    
    227
    -                           x: float,
    
    228
    -                           y: float,
    
    229
    -                           w: float,
    
    230
    -                           h1: float,
    
    231
    -                           h2: float,
    
    232
    -                           r: float
    
    233
    -                           ) -> None:
    
    234
    -
    
    235
    -        '''
    
    236
    -        Draws a rectangle of width w and total height of h1+h2
    
    237
    -        The top and bottom edges are curved to the outside
    
    238
    -        '''
    
    239
    -        m = w / 2
    
    240 256
     
    
    257
    +            x += x_shift
    
    258
    +
    
    259
    +    def _draw_rounded_rec(self,
    
    260
    +                          ctx: cairo.Context[cairo.ImageSurface],
    
    261
    +                          x: float,
    
    262
    +                          y: float,
    
    263
    +                          width: float,
    
    264
    +                          height1: float,
    
    265
    +                          height2: float,
    
    266
    +                          radius: float,
    
    267
    +                          gap_size: float,
    
    268
    +                          ) -> None:
    
    269
    +
    
    270
    +        # Don't insert a gap if bars are too small
    
    271
    +        if height1 + height2 < 3:
    
    272
    +            gap_size = 0.0
    
    273
    +
    
    274
    +        # Set a minimum height to improve visibility for quasi silence
    
    275
    +        height1 = max(0.5, height1)
    
    276
    +        height2 = max(0.5, height2)
    
    277
    +
    
    278
    +        # Draws a rectangle of width w and total height of h1+h2
    
    279
    +        # The top and bottom edges are curved to the outside
    
    280
    +        m = width / 2
    
    241 281
             if not self._is_LTR:
    
    242 282
                 m = -m
    
    243
    -            w = -w
    
    244
    -
    
    245
    -        context.curve_to(x, y + h1,
    
    246
    -                         x + m, y + h1 + r,
    
    247
    -                         x + w, y + h1)
    
    248
    -
    
    249
    -        context.line_to(x + w, y - h2)
    
    250
    -
    
    251
    -        context.curve_to(x + w, y - h2,
    
    252
    -                         x + m, y - h2 - r,
    
    253
    -                         x, y - h2)
    
    254
    -
    
    255
    -        context.line_to(x, y + h1)
    283
    +            width = -width
    
    284
    +
    
    285
    +        # A --- B --- C
    
    286
    +        # |           |
    
    287
    +        # F --- E --- D
    
    288
    +
    
    289
    +        # Up
    
    290
    +        # A -- B -- C
    
    291
    +        ctx.curve_to(x, y + height1,
    
    292
    +                     x + m, y + height1 + radius,
    
    293
    +                     x + width, y + height1)
    
    294
    +        # C -- D
    
    295
    +        ctx.line_to(x + width, y + gap_size)
    
    296
    +        # D -- F
    
    297
    +        ctx.line_to(x, y + gap_size)
    
    298
    +        # F -- A
    
    299
    +        ctx.line_to(x, y + height1)
    
    300
    +
    
    301
    +        # Down
    
    302
    +        # C -- D
    
    303
    +        ctx.move_to(x + width, y - gap_size)
    
    304
    +        ctx.line_to(x + width, y - height2)
    
    305
    +        # D -- E -- F
    
    306
    +        ctx.curve_to(x + width, y - height2,
    
    307
    +                     x + m, y - height2 - radius,
    
    308
    +                     x, y - height2)
    
    309
    +        # F -- A
    
    310
    +        ctx.line_to(x, y - gap_size)
    
    311
    +        # A -- C
    
    312
    +        ctx.line_to(x + width, y - gap_size)

  • pep517build/__init__.py

  • pep517build/backend.py
    1
    +from __future__ import annotations
    
    2
    +
    
    3
    +from typing import Any
    
    4
    +
    
    5
    +import gzip
    
    6
    +import logging
    
    7
    +import shutil
    
    8
    +import subprocess
    
    9
    +import sys
    
    10
    +from pathlib import Path
    
    11
    +
    
    12
    +import setuptools.build_meta as _orig
    
    13
    +
    
    14
    +
    
    15
    +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    16
    +log = logging.getLogger()
    
    17
    +
    
    18
    +DEFAULT_METADATA_PATH = Path('dist/metadata')
    
    19
    +ALLOWED_CONFIG_SETTINGS = {'target'}
    
    20
    +MAN_FILES = [
    
    21
    +    Path('data/gajim.1'),
    
    22
    +    Path('data/gajim-remote.1')
    
    23
    +]
    
    24
    +META_FILES = [
    
    25
    +    (Path('data/org.gajim.Gajim.desktop.in'), '--desktop'),
    
    26
    +    (Path('data/org.gajim.Gajim.appdata.xml.in'), '--xml')
    
    27
    +]
    
    28
    +ICONS = [
    
    29
    +    Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg'),
    
    30
    +    Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg'),
    
    31
    +]
    
    32
    +
    
    33
    +
    
    34
    +def build_translations() -> None:
    
    35
    +    # Compile translation files and place them into "gajim/data/locale"
    
    36
    +
    
    37
    +    source_dir = Path.cwd()
    
    38
    +    translation_dir = source_dir / 'po'
    
    39
    +    locale_dir = source_dir / 'gajim' / 'data' / 'locale'
    
    40
    +
    
    41
    +    langs = sorted([lang.stem for lang in translation_dir.glob('*.po')])
    
    42
    +
    
    43
    +    for lang in langs:
    
    44
    +        po_file = source_dir / 'po' / f'{lang}.po'
    
    45
    +        mo_file = locale_dir / lang / 'LC_MESSAGES' / 'gajim.mo'
    
    46
    +        mo_file.parent.mkdir(parents=True, exist_ok=True)
    
    47
    +
    
    48
    +        log.info('Compile %s >> %s', po_file, mo_file)
    
    49
    +
    
    50
    +        subprocess.run(['msgfmt',
    
    51
    +                        str(po_file),
    
    52
    +                        '-o',
    
    53
    +                        str(mo_file)],
    
    54
    +                       check=True)
    
    55
    +
    
    56
    +
    
    57
    +def build_man(target_path: Path) -> None:
    
    58
    +    # Build man files in target path
    
    59
    +
    
    60
    +    for man_path in MAN_FILES:
    
    61
    +        data = man_path.read_bytes()
    
    62
    +        man_file_name = man_path.name
    
    63
    +
    
    64
    +        man_out_path = target_path / f'{man_file_name}.gz'
    
    65
    +        log.info('Compress %s >> %s', man_file_name, man_out_path)
    
    66
    +
    
    67
    +        with gzip.open(man_out_path, 'wb') as f_out:
    
    68
    +            f_out.write(data)
    
    69
    +
    
    70
    +
    
    71
    +
    
    72
    +def build_intl(target_path: Path) -> None:
    
    73
    +    # Merge translation files into desktop and metadata files
    
    74
    +
    
    75
    +    for file_path, option in META_FILES:
    
    76
    +        out_path = target_path / file_path.name
    
    77
    +        out_path = out_path.with_suffix('')
    
    78
    +
    
    79
    +        log.info('Compile %s >> %s', file_path, out_path)
    
    80
    +
    
    81
    +        subprocess.run(['msgfmt',
    
    82
    +                        option,
    
    83
    +                        '-d',
    
    84
    +                        'po',
    
    85
    +                        '--template',
    
    86
    +                        str(file_path),
    
    87
    +                        '-o',
    
    88
    +                        str(out_path)],
    
    89
    +                        check=True)
    
    90
    +
    
    91
    +
    
    92
    +def build_app_icons(target_path: Path) -> None:
    
    93
    +    for file_path in ICONS:
    
    94
    +        out_path = target_path / file_path.name
    
    95
    +
    
    96
    +        log.info('Copy %s >> %s', file_path, out_path)
    
    97
    +        shutil.copy2(file_path, out_path)
    
    98
    +
    
    99
    +
    
    100
    +def _check_config_settings(config_settings: dict[str, str]) -> None:
    
    101
    +    settings = set(config_settings.keys()) - ALLOWED_CONFIG_SETTINGS
    
    102
    +    if settings:
    
    103
    +        raise ValueError('Unknown config setting %s' % settings)
    
    104
    +
    
    105
    +
    
    106
    +def _get_target_path(config_settings: dict[str, str] | None) -> Path:
    
    107
    +    if config_settings is None:
    
    108
    +        path = DEFAULT_METADATA_PATH
    
    109
    +    else:
    
    110
    +        path = Path(config_settings.get('target', DEFAULT_METADATA_PATH))
    
    111
    +
    
    112
    +    path.mkdir(parents=True, exist_ok=True)
    
    113
    +    return path
    
    114
    +
    
    115
    +
    
    116
    +def get_requires_for_build_sdist(*args: Any, **kwargs: Any) -> str:
    
    117
    +    return _orig.get_requires_for_build_sdist(*args, **kwargs)
    
    118
    +
    
    119
    +def build_sdist(*args: Any, **kwargs: Any) -> str:
    
    120
    +    return _orig.build_sdist(*args, **kwargs)
    
    121
    +
    
    122
    +def get_requires_for_build_wheel(*args: Any, **kwargs: Any) -> str:
    
    123
    +    return _orig.get_requires_for_build_wheel(*args, **kwargs)
    
    124
    +
    
    125
    +def prepare_metadata_for_build_wheel(*args: Any, **kwargs: Any) -> str:
    
    126
    +    return _orig.prepare_metadata_for_build_wheel(*args, **kwargs)
    
    127
    +
    
    128
    +def build_wheel(wheel_directory: str,
    
    129
    +                config_settings: dict[str, str] | None = None,
    
    130
    +                metadata_directory: str | None = None
    
    131
    +                ) -> str:
    
    132
    +
    
    133
    +    if config_settings is not None:
    
    134
    +        _check_config_settings(config_settings)
    
    135
    +    target_path = _get_target_path(config_settings)
    
    136
    +
    
    137
    +    build_translations()
    
    138
    +
    
    139
    +    basename = _orig.build_wheel(
    
    140
    +        wheel_directory,
    
    141
    +        config_settings=config_settings,
    
    142
    +        metadata_directory=metadata_directory,
    
    143
    +    )
    
    144
    +
    
    145
    +    if sys.platform not in ('win32', 'darwin'):
    
    146
    +        build_man(target_path)
    
    147
    +        build_intl(target_path)
    
    148
    +        build_app_icons(target_path)
    
    149
    +
    
    150
    +    return basename

  • pep517build/install_metadata.py
    1
    +#!/usr/bin/env python
    
    2
    +
    
    3
    +from __future__ import annotations
    
    4
    +
    
    5
    +import argparse
    
    6
    +import logging
    
    7
    +import shutil
    
    8
    +from pathlib import Path
    
    9
    +
    
    10
    +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    11
    +log = logging.getLogger()
    
    12
    +
    
    13
    +DEFAULT_METADATA_PATH = Path('dist/metadata')
    
    14
    +
    
    15
    +FILES = {
    
    16
    +    'gajim-remote.1.gz': 'share/man/man1',
    
    17
    +    'gajim.1.gz': 'share/man/man1',
    
    18
    +    'org.gajim.Gajim.desktop': 'share/applications',
    
    19
    +    'org.gajim.Gajim-symbolic.svg': 'share/icons/hicolor/scalable/apps',
    
    20
    +    'org.gajim.Gajim.svg': 'share/icons/hicolor/scalable/apps',
    
    21
    +    'org.gajim.Gajim.appdata.xml': 'share/metainfo',
    
    22
    +}
    
    23
    +
    
    24
    +if __name__ == '__main__':
    
    25
    +    parser = argparse.ArgumentParser(description='Install metadata files')
    
    26
    +    parser.add_argument('prefix', type=Path)
    
    27
    +    parser.add_argument('--path', type=Path, default=DEFAULT_METADATA_PATH)
    
    28
    +    args = parser.parse_args()
    
    29
    +
    
    30
    +    for file, path in FILES.items():
    
    31
    +        src = args.path / file
    
    32
    +        dest_dir = args.prefix / path
    
    33
    +        log.info('Copy %s to %s', src, dest_dir)
    
    34
    +        if not dest_dir.exists():
    
    35
    +            dest_dir.mkdir(parents=True)
    
    36
    +        shutil.copy(src, dest_dir / file)

  • pyproject.toml
    1 1
     [build-system]
    
    2 2
     requires = [
    
    3
    -  "setuptools >= 43.0.0",
    
    3
    +  "setuptools >= 65.0.0",
    
    4 4
     ]
    
    5
    -build-backend = "setuptools.build_meta"
    
    5
    +build-backend = "backend"
    
    6
    +backend-path = ["pep517build"]
    
    7
    +
    
    8
    +[project]
    
    9
    +name = "gajim"
    
    10
    +description = "A GTK XMPP Client"
    
    11
    +readme = "README.md"
    
    12
    +requires-python = ">=3.10"
    
    13
    +license = {text = "GPL-3.0-or-later"}
    
    14
    +authors = [
    
    15
    +    {name = "Philipp Hörist", email = "philipp@hoerist.com"},
    
    16
    +    {name = "Yann Leboulanger", email = "yann@leboulanger.org"},
    
    17
    +]
    
    18
    +keywords = ["chat", "messaging", "im", "xmpp", "voip"]
    
    19
    +classifiers = [
    
    20
    +  "Development Status :: 5 - Production/Stable",
    
    21
    +  "Environment :: X11 Applications :: GTK",
    
    22
    +  "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
    
    23
    +  "Operating System :: OS Independent",
    
    24
    +  "Programming Language :: Python :: 3.10",
    
    25
    +  "Topic :: Communications :: Chat",
    
    26
    +]
    
    27
    +dependencies = [
    
    28
    +  "css-parser",
    
    29
    +  "keyring",
    
    30
    +  "nbxmpp>=3.2.5,<4.0.0",
    
    31
    +  "packaging",
    
    32
    +  "pillow",
    
    33
    +  "precis-i18n>=1.0.0",
    
    34
    +  "cryptography>=3.4.8",
    
    35
    +  "pycairo>=1.16.0",
    
    36
    +  "PyGObject>=3.42.0",
    
    37
    +]
    
    38
    +dynamic = ["version"]
    
    39
    +
    
    40
    +[project.urls]
    
    41
    +homepage = "https://gajim.org"
    
    42
    +repository = "https://dev.gajim.org/gajim/gajim"
    
    43
    +
    
    44
    +[project.optional-dependencies]
    
    45
    +dev = [
    
    46
    +  "ruff",
    
    47
    +  "codespell[toml]",
    
    48
    +  "nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git",
    
    49
    +  "PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git",
    
    50
    +  "python-gitlab",
    
    51
    +  "sentry-sdk",
    
    52
    +]
    
    53
    +
    
    54
    +[project.scripts]
    
    55
    +gajim-remote = "gajim.gajim_remote:main"
    
    56
    +
    
    57
    +[project.gui-scripts]
    
    58
    +gajim = "gajim.gajim:main"
    
    6 59
     
    
    60
    +[tool.setuptools.packages.find]
    
    61
    +include = ["gajim*"]
    
    62
    +
    
    63
    +[tool.setuptools.package-data]
    
    64
    +gajim = ["py.typed"]
    
    65
    +"gajim.data" = ["**/*"]
    
    66
    +
    
    67
    +[tool.setuptools.dynamic]
    
    68
    +version = {attr = "gajim.__version__"}
    
    7 69
     
    
    8 70
     [tool.codespell]
    
    9 71
     skip = "*__pycache__*,gajim/gui,gajim/data/icons,gajim/data/sounds"
    
    10 72
     ignore-words-list = "claus,pres,ser,trough"
    
    11 73
     
    
    12
    -
    
    13 74
     [tool.pyright]
    
    14 75
     pythonVersion = "3.10"
    
    15 76
     pythonPlatform = "All"
    
    ... ... @@ -45,6 +106,7 @@ include = [
    45 106
         "mac/*",
    
    46 107
         "scripts/*",
    
    47 108
         "win/*",
    
    109
    +    "pep517build/*",
    
    48 110
         "gajim/common/application.py",
    
    49 111
         "gajim/common/call_manager.py",
    
    50 112
         "gajim/common/cert_store.py",
    
    ... ... @@ -79,7 +141,6 @@ include = [
    79 141
         "gajim/plugins/*",
    
    80 142
     ]
    
    81 143
     
    
    82
    -
    
    83 144
     [tool.ruff]
    
    84 145
     line-length = 80
    
    85 146
     
    
    ... ... @@ -162,7 +223,6 @@ target-version = "py310"
    162 223
     "test/no_gui/test_styling.py" = ["RUF001", "E501"]
    
    163 224
     "test/no_gui/test_regex.py" = ["RUF001"]
    
    164 225
     
    
    165
    -
    
    166 226
     [tool.ruff.mccabe]
    
    167 227
     max-complexity = 15
    
    168 228
     
    

  • setup.cfg deleted
    1
    -[metadata]
    
    2
    -name = gajim
    
    3
    -version = attr: gajim.__version__
    
    4
    -description = A GTK XMPP client
    
    5
    -long_description = file: README.md
    
    6
    -long_description_content_type = text/markdown
    
    7
    -author = Philipp Hoerist, Yann Leboulanger
    
    8
    -author_email = gajim-devel@gajim.org
    
    9
    -url = https://gajim.org
    
    10
    -license = GPL v3
    
    11
    -keywords = chat,messaging,im,xmpp,voip
    
    12
    -classifiers =
    
    13
    -    Development Status :: 5 - Production/Stable
    
    14
    -    Environment :: X11 Applications :: GTK
    
    15
    -    License :: OSI Approved :: GNU General Public License v3 (GPLv3)
    
    16
    -    Operating System :: OS Independent
    
    17
    -    Programming Language :: Python :: 3.10
    
    18
    -    Topic :: Communications :: Chat
    
    19
    -
    
    20
    -[options]
    
    21
    -python_requires = >=3.10
    
    22
    -packages = find:
    
    23
    -test_suite = test
    
    24
    -install_requires =
    
    25
    -    css-parser
    
    26
    -    keyring
    
    27
    -    nbxmpp>=3.2.5,<4.0.0
    
    28
    -    packaging
    
    29
    -    pillow
    
    30
    -    precis-i18n>=1.0.0
    
    31
    -    cryptography>=3.4.8
    
    32
    -    pycairo>=1.16.0
    
    33
    -    PyGObject>=3.42.0
    
    34
    -
    
    35
    -[options.extras_require]
    
    36
    -dev =
    
    37
    -    ruff
    
    38
    -    codespell[toml]
    
    39
    -    nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git
    
    40
    -    PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git
    
    41
    -    python-gitlab
    
    42
    -    sentry-sdk
    
    43
    -
    
    44
    -[options.package_data]
    
    45
    -gajim =
    
    46
    -    data/gui/*.ui
    
    47
    -    data/icons/hicolor/*/*/*.png
    
    48
    -    data/icons/hicolor/*/*/*.svg
    
    49
    -    data/other/*
    
    50
    -    data/sounds/*.wav
    
    51
    -    data/style/*.css
    
    52
    -    data/plugins/*/*
    
    53
    -    py.typed
    
    54
    -
    
    55
    -[options.packages.find]
    
    56
    -exclude =
    
    57
    -    gajim.dev
    
    58
    -    test*

  • setup.py deleted
    1
    -#!/usr/bin/env python3
    
    2
    -
    
    3
    -from __future__ import annotations
    
    4
    -
    
    5
    -from typing import cast
    
    6
    -
    
    7
    -import logging
    
    8
    -import os
    
    9
    -import sys
    
    10
    -
    
    11
    -if sys.version_info < (3, 10):
    
    12
    -    sys.exit('Gajim needs Python 3.10+')
    
    13
    -
    
    14
    -import subprocess
    
    15
    -from pathlib import Path
    
    16
    -
    
    17
    -from setuptools import setup
    
    18
    -from setuptools.command.build_py import build_py as _build
    
    19
    -from setuptools.command.install import install as _install
    
    20
    -
    
    21
    -
    
    22
    -DataFilesT = list[tuple[str, list[str]]]
    
    23
    -
    
    24
    -
    
    25
    -MAN_FILES = [
    
    26
    -    'gajim.1',
    
    27
    -    'gajim-remote.1'
    
    28
    -]
    
    29
    -META_FILES = [
    
    30
    -    ('data/org.gajim.Gajim.desktop', 'share/applications', '--desktop'),
    
    31
    -    ('data/org.gajim.Gajim.appdata.xml', 'share/metainfo', '--xml')]
    
    32
    -
    
    33
    -
    
    34
    -TRANS_DIR = Path('po')
    
    35
    -TRANS_TEMPLATE = TRANS_DIR / 'gajim.pot'
    
    36
    -REPO_DIR = Path(__file__).resolve().parent
    
    37
    -BUILD_DIR = REPO_DIR / 'build'
    
    38
    -
    
    39
    -ALL_LINGUAS = sorted([lang.stem for lang in TRANS_DIR.glob('*.po')])
    
    40
    -
    
    41
    -
    
    42
    -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    43
    -log = logging.getLogger()
    
    44
    -
    
    45
    -
    
    46
    -def newer(source: Path, target: Path) -> bool:
    
    47
    -    if not source.exists():
    
    48
    -        raise ValueError('file "%s" does not exist' % source.resolve())
    
    49
    -    if not target.exists():
    
    50
    -        return True
    
    51
    -
    
    52
    -    from stat import ST_MTIME
    
    53
    -    mtime1 = source.stat()[ST_MTIME]
    
    54
    -    mtime2 = target.stat()[ST_MTIME]
    
    55
    -
    
    56
    -    return mtime1 > mtime2
    
    57
    -
    
    58
    -
    
    59
    -def build_translation() -> None:
    
    60
    -    for lang in ALL_LINGUAS:
    
    61
    -        po_file = TRANS_DIR / f'{lang}.po'
    
    62
    -        mo_file = BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo'
    
    63
    -        mo_dir = mo_file.parent
    
    64
    -        if not (mo_dir.is_dir() or mo_dir.is_symlink()):
    
    65
    -            mo_dir.mkdir(parents=True)
    
    66
    -
    
    67
    -        if newer(po_file, mo_file):
    
    68
    -            subprocess.run(['msgfmt',
    
    69
    -                            str(po_file),
    
    70
    -                            '-o',
    
    71
    -                            str(mo_file)],
    
    72
    -                           cwd=REPO_DIR,
    
    73
    -                           check=True)
    
    74
    -
    
    75
    -            log.info('Compiling %s >> %s', po_file, mo_file)
    
    76
    -
    
    77
    -
    
    78
    -def install_trans(data_files: DataFilesT) -> None:
    
    79
    -    for lang in ALL_LINGUAS:
    
    80
    -        mo_file = str(BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo')
    
    81
    -        target = f'share/locale/{lang}/LC_MESSAGES'
    
    82
    -        data_files.append((target, [mo_file]))
    
    83
    -
    
    84
    -
    
    85
    -def build_man() -> None:
    
    86
    -    '''
    
    87
    -    Compress Gajim manual files
    
    88
    -    '''
    
    89
    -    newdir = BUILD_DIR / 'man'
    
    90
    -    if not (newdir.is_dir() or newdir.is_symlink()):
    
    91
    -        newdir.mkdir()
    
    92
    -
    
    93
    -    for man in MAN_FILES:
    
    94
    -        filename = Path('data') / man
    
    95
    -        man_file_gz = newdir / (man + '.gz')
    
    96
    -        if man_file_gz.exists():
    
    97
    -            if newer(filename, man_file_gz):
    
    98
    -                man_file_gz.unlink()
    
    99
    -            else:
    
    100
    -                continue
    
    101
    -
    
    102
    -        import gzip
    
    103
    -        # Binary io, so open is OK
    
    104
    -        with open(filename, 'rb') as f_in,\
    
    105
    -                gzip.open(man_file_gz, 'wb') as f_out:
    
    106
    -            f_out.writelines(f_in)
    
    107
    -            log.info('Compiling %s >> %s', filename, man_file_gz)
    
    108
    -
    
    109
    -
    
    110
    -def install_man(data_files: DataFilesT) -> None:
    
    111
    -    man_dir = BUILD_DIR / 'man'
    
    112
    -    target = 'share/man/man1'
    
    113
    -
    
    114
    -    for man in MAN_FILES:
    
    115
    -        man_file_gz = str(man_dir / (man + '.gz'))
    
    116
    -        data_files.append((target, [man_file_gz]))
    
    117
    -
    
    118
    -
    
    119
    -def build_intl() -> None:
    
    120
    -    '''
    
    121
    -    Merge translation files into desktop and mime files
    
    122
    -    '''
    
    123
    -    base = BUILD_DIR
    
    124
    -
    
    125
    -    for filename, _, option in META_FILES:
    
    126
    -        newfile = base / filename
    
    127
    -        newdir = newfile.parent
    
    128
    -        if not (newdir.is_dir() or newdir.is_symlink()):
    
    129
    -            newdir.mkdir()
    
    130
    -        merge(Path(filename + '.in'), newfile, option)
    
    131
    -
    
    132
    -
    
    133
    -def install_intl(data_files: DataFilesT) -> None:
    
    134
    -    for filename, target, _ in META_FILES:
    
    135
    -        data_files.append((target, [str(BUILD_DIR / filename)]))
    
    136
    -
    
    137
    -
    
    138
    -def merge(in_file: Path,
    
    139
    -          out_file: Path,
    
    140
    -          option: str,
    
    141
    -          po_dir: str = 'po') -> None:
    
    142
    -    '''
    
    143
    -    Run the msgfmt command.
    
    144
    -    '''
    
    145
    -    if in_file.exists():
    
    146
    -        cmd = (('msgfmt %(opt)s -d %(po_dir)s --template %(in_file)s '
    
    147
    -                '-o %(out_file)s') %
    
    148
    -               {'opt': option,
    
    149
    -                'po_dir': po_dir,
    
    150
    -                'in_file': in_file,
    
    151
    -                'out_file': out_file})
    
    152
    -        if os.system(cmd) != 0:
    
    153
    -            msg = ('ERROR: %s was not merged into the translation files!\n' %
    
    154
    -                   out_file)
    
    155
    -            raise SystemExit(msg)
    
    156
    -        log.info('Compiling %s >> %s', in_file, out_file)
    
    157
    -
    
    158
    -
    
    159
    -class Build(_build):
    
    160
    -    def run(self):
    
    161
    -        build_translation()
    
    162
    -        if sys.platform != 'win32':
    
    163
    -            build_man()
    
    164
    -            build_intl()
    
    165
    -        _build.run(self)
    
    166
    -
    
    167
    -
    
    168
    -class Install(_install):
    
    169
    -    def run(self):
    
    170
    -        data_files = cast(DataFilesT, self.distribution.data_files)  # pyright: ignore  # noqa: E501
    
    171
    -        install_trans(data_files)
    
    172
    -        if sys.platform != 'win32':
    
    173
    -            install_man(data_files)
    
    174
    -            install_intl(data_files)
    
    175
    -        _install.run(self)  # pyright: ignore
    
    176
    -
    
    177
    -
    
    178
    -# only install subdirectories of data
    
    179
    -data_files_app_icon = [
    
    180
    -    ('share/icons/hicolor/scalable/apps',
    
    181
    -     ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg']),
    
    182
    -    ('share/icons/hicolor/scalable/apps',
    
    183
    -     ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg'])
    
    184
    -]
    
    185
    -
    
    186
    -data_files: DataFilesT = data_files_app_icon
    
    187
    -
    
    188
    -setup(
    
    189
    -    cmdclass={
    
    190
    -        'build_py': Build,
    
    191
    -        'install': Install,
    
    192
    -    },
    
    193
    -    entry_points={
    
    194
    -        'console_scripts': [
    
    195
    -            'gajim-remote = gajim.gajim_remote:main',
    
    196
    -        ],
    
    197
    -        'gui_scripts': [
    
    198
    -            'gajim = gajim.gajim:main',
    
    199
    -        ]
    
    200
    -    },
    
    201
    -    data_files=data_files
    
    202
    -)