Philipp Hörist pushed to branch port-pyproject at gajim / gajim
Commits: d084fd30 by mesonium at 2022-12-30T16:59:08+01:00 feat: Audio Preview: Visualization: Add centerline and improve color palette
- Visually distinguish left / right audio channels - Use CSS defined colors
- - - - - 2f2b3ea1 by Philipp Hörist at 2022-12-31T09:50:18+01:00 change: Port to pyproject.toml
- - - - - d5992a37 by Philipp Hörist at 2022-12-31T09:50:18+01:00 new: Add custom PEP517 build backend
- - - - - f063c801 by Philipp Hörist at 2022-12-31T09:50:18+01:00 refactor: Load translations from package data directory
- - - - - 31bcc33b by Philipp Hörist at 2022-12-31T09:50:18+01:00 ci: Use `build` to build Gajim
- - - - - 5a17c496 by Philipp Hörist at 2022-12-31T09:50:18+01:00 Update README
- - - - -
13 changed files:
- .gitlab-ci.yml - MANIFEST.in - README.md - gajim/common/i18n.py - gajim/data/style/gajim.css - gajim/gtk/preview_audio.py - gajim/gtk/preview_audio_visualizer.py - + pep517build/__init__.py - + pep517build/backend.py - + pep517build/install_metadata.py - pyproject.toml - − setup.cfg - − setup.py
Changes:
===================================== .gitlab-ci.yml ===================================== @@ -15,8 +15,9 @@ test-other: stage: test script: - codespell gajim - - python3 setup.py build - - appstream-util validate build/data/org.gajim.Gajim.appdata.xml + - pip3 install build + - python3 -m build -w + - appstream-util validate dist/share/metainfo/org.gajim.Gajim.appdata.xml interruptible: true
test-pyright: @@ -68,7 +69,8 @@ build-linux: script: - apt-get update - apt-get install -y python3-nbxmpp-nightly - - python3 setup.py sdist + - pip3 install build + - python3 -m build -s - python3 .ci/debian_build.py "$(find dist/gajim-*)" 1
artifacts:
===================================== MANIFEST.in ===================================== @@ -1,4 +1,6 @@ include COPYING +include ChangeLog +recursive-include pep517build * recursive-include po * recursive-include data *.1 *.in -recursive-include test * +recursive-include test * \ No newline at end of file
===================================== README.md ===================================== @@ -13,7 +13,7 @@ ### Runtime Requirements - [precis_i18n](https://pypi.org/project/precis-i18n/) - [packaging](https://pypi.org/project/packaging/) - [Pillow](https://pypi.org/project/Pillow/) -- [setuptools](https://pypi.org/project/setuptools/) +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0) - [Gtk3](https://gitlab.com/gnome/gtk) (>=3.24.30) - [GLib](https://gitlab.com/gnome/glib) (>=2.60.0) - [GtkSourceView](https://gitlab.gnome.org/GNOME/gtksourceview) @@ -39,17 +39,60 @@ #### For Video and Audio Calls - [gst-plugins-ugly](https://gitlab.freedesktop.org/gstreamer/gst-plugins-ugly) - [gst-libav](https://gitlab.freedesktop.org/gstreamer/gst-libav)
-### Install Requirements +### Build Requirements
-- [setuptools](https://pypi.org/project/setuptools/) +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0) - [gettext](https://savannah.gnu.org/projects/gettext/)
+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. + +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. + +Execute the following inside the source directory + +#### Building the wheel and metadata files + +A wheel and additional metadata files are placed in the `dist` folder. + +##### Using `build` + +```bash +python -m build -w +``` + +##### Using `pip` + +```bash +pip wheel --no-deps --use-pep517 -w dist . +``` + +#### Installing + +```bash +pip install dist/name_of_wheel.whl +``` + +The wheel building step can be skipped with `pip` + +```bash +pip install . +``` + +in this case the metadata files are still placed into the `dist` directory. + +#### Installing metadata files + +On the Unix platform the metadata files need to be installed also. +There is a script to copy the files into the right places. +See `pep517build/install_metadata.py`. You may want to edit the script and +adjust the target folders depending on which distribution you use. + ### Running Tests
- `python -m unittest discover -s test` (no_gui directory) - `python -m unittest ./test/gtk/gui_file.py` (gtk directory)
-### Installation Procedure +### Packages and install instructions
#### Packages
@@ -68,15 +111,11 @@ #### Snapshots - [Daily Linux](https://www.gajim.org/downloads/snap/) - [Daily Windows](https://gajim.org/downloads/snap/win)
-#### Linux - - pip install . - #### Mac
see [Wiki](https://dev.gajim.org/gajim/gajim/-/wikis/help/Gajim-on-macOS)
-#### Developing +### Developing
To create a virutalenv you can execute
===================================== gajim/common/i18n.py ===================================== @@ -20,56 +20,19 @@
from __future__ import annotations
-from typing import Iterator +from typing import cast from typing import Optional
import os import sys import locale import gettext +import importlib.resources import unicodedata from pathlib import Path -from gi.repository import GLib - -DOMAIN = 'gajim' -_translation = gettext.NullTranslations() - - -def get_locale_dirs() -> Optional[list[Path]]: - if sys.platform == 'win32': - return - - path = gettext.find(DOMAIN) - if path is not None: - # gettext can find the location itself - # so we don’t need the localedir - return - - if Path('/app/share/run-as-flatpak').exists(): - # Check if we run as flatpak - return [Path('/app/share/')] - - data_dirs = [GLib.get_user_data_dir()] + GLib.get_system_data_dirs() - return [Path(dir_) for dir_ in data_dirs]
-def iter_locale_dirs() -> Iterator[Optional[Path]]: - locale_dirs = get_locale_dirs() - if locale_dirs is None: - yield None - return - - # gettext fallback - locale_dirs.append(Path(sys.base_prefix) / 'share') - - found_paths: list[Path] = [] - for path in locale_dirs: - locale_dir = path / 'locale' - if locale_dir in found_paths: - continue - found_paths.append(locale_dir) - if locale_dir.is_dir(): - yield locale_dir +DOMAIN = 'gajim'
def get_win32_default_lang() -> str: @@ -160,19 +123,16 @@ def ngettext(s_sing: str, # doesn't retain other region settings like LC_TIME os.environ['LANGUAGE'] = LANG
-# Search for the translation in all locale dirs -for dir_ in iter_locale_dirs(): - try: - _translation = gettext.translation(DOMAIN, dir_) - _ = _translation.gettext - if hasattr(locale, 'bindtextdomain'): - locale.bindtextdomain(DOMAIN, dir_) - except OSError: - continue - else: - break - -else: + +package_dir = cast(Path, importlib.resources.files('gajim')) +locale_dir = package_dir / 'data' / 'locale' + +try: + _translation = gettext.translation(DOMAIN, locale_dir) + _ = _translation.gettext + if hasattr(locale, 'bindtextdomain'): + locale.bindtextdomain(DOMAIN, locale_dir) +except OSError: + _translation = gettext.NullTranslations() _ = _translation.gettext print('No translations found for', LANG, file=sys.stderr) - print('Dirs searched: %s' % get_locale_dirs(), file=sys.stderr)
===================================== gajim/data/style/gajim.css ===================================== @@ -650,6 +650,17 @@ .preview-image-button:hover { box-shadow: none; }
+/* Audiovisualizer */ +#AudioVisualizer { + color: @theme_selected_bg_color; + background-color: @insensitive_fg_color; + border-color: @theme_unfocused_bg_color; +} + +#AudioVisualizer:selected { + color: lighter(@theme_selected_bg_color); +} + /* Conversation meta elements */ .conversation-meta { opacity: 0.6;
===================================== gajim/gtk/preview_audio.py ===================================== @@ -257,13 +257,11 @@ def _setup_audio_visualizer(self) -> None: return
width -= 2 * SEEK_BAR_PADDING - seek_bar_rgba = self._ui.seek_bar.get_style_context().get_color( - Gtk.StateFlags.LINK) self._audio_visualizer = AudioVisualizerWidget( width, height, SEEK_BAR_PADDING, - seek_bar_rgba) + )
self._ui.drawing_box.add(self._audio_visualizer)
@@ -272,8 +270,8 @@ def _update_timestamp_label(self) -> None: dur = self._state.duration
dur_str = format_duration(dur, dur) - ltr_char = u'\u202B' - pop_char = u'\u202C' + ltr_char = '\u202B' + pop_char = '\u202C'
if self._state.is_timestamp_positive: cur_str = f'{format_duration(cur, dur)}' @@ -445,9 +443,6 @@ def _on_bus_message(self, _bus: Gst.Bus, message: Gst.Message) -> None: if not self._pause_seek: self._ui.seek_bar.set_value(self._state.duration) self._audio_visualizer.draw_graph(1.0) - else: - self._audio_visualizer.draw_graph( - self._state.position / self._state.duration)
elif message.type == Gst.MessageType.STATE_CHANGED: is_paused = self._get_paused()
===================================== gajim/gtk/preview_audio_visualizer.py ===================================== @@ -28,7 +28,6 @@
from .util import rgba_to_float
- log = logging.getLogger('gajim.gui.preview_audio_visualizer')
@@ -37,37 +36,29 @@ def __init__(self, width: int, height: int, x_offset: int, - seek_bar_color: Gdk.RGBA ) -> None:
Gtk.DrawingArea.__init__(self)
+ self.set_name('AudioVisualizer') + self._update_colors() self._x_offset = x_offset + self._peak_width = 2 # in px self._width = width self._height = height - self._is_LTR = (self.get_direction() == Gtk.TextDirection.LTR) - self._amplitude_width = 2 + self._is_LTR = bool(self.get_direction() == Gtk.TextDirection.LTR)
self._num_samples = 0 self._samples: AudioSampleT = [] self._seek_position = -1.0 self._position = 0.0
- self._default_color = (0.6, 0.6, 0.6) # gray - self._progress_color = rgba_to_float(seek_bar_color) - - # Use a 50% lighter color to the seeking indicator - self._seek_color = tuple( - min(1.0, color * 1.5) for color in self._progress_color - ) - - self._surface: Optional[cairo.ImageSurface] = None - - self.set_size_request(self._width, self._height) - self.connect('draw', self._on_drawingarea_draw) self.connect('configure-event', self._on_drawingarea_changed) + self.connect('style-updated', self._on_style_updated)
+ self.set_size_request(self._width, self._height) + self._surface: Optional[cairo.ImageSurface] = None self._setup_surface()
def update_params(self, @@ -89,6 +80,45 @@ def draw_graph(self, position: float, seek_position: float = -1.0) -> None:
self.queue_draw()
+ def _on_drawingarea_draw(self, + _drawing_area: Gtk.DrawingArea, + ctx: cairo.Context[cairo.ImageSurface] + ) -> None: + + if self._num_samples != 0: + self._draw_surface(ctx) + + def _on_drawingarea_changed(self, + _drawing_area: Gtk.DrawingArea, + _event: Gdk.EventConfigure + ) -> None: + + self._is_LTR = bool(self.get_direction() == Gtk.TextDirection.LTR) + self._setup_surface() + self.queue_draw() + + def _on_style_updated(self, + _event: Gdk.EventConfigure + ) -> None: + + self._update_colors() + self.queue_draw() + + def _update_colors(self) -> None: + sc = self.get_style_context() + self._progress_color = rgba_to_float( + sc.get_color(Gtk.StateFlags.NORMAL) + ) + self._default_color = rgba_to_float( + sc.get_background_color(Gtk.StateFlags.NORMAL) + ) + self._outline_color = rgba_to_float( + sc.get_border_color(Gtk.StateFlags.NORMAL) + ) + self._seek_color = rgba_to_float( + sc.get_color(Gtk.StateFlags.SELECTED) + ) + def _setup_surface(self) -> None: if self._surface: self._surface.finish() @@ -103,9 +133,10 @@ def _setup_surface(self) -> None: self._draw_surface(ctx)
def _process_samples(self) -> None: - # Pick a subset of all samples and average over three samples - w = int(self._width / (self._amplitude_width * 2)) - n = math.floor(len(self._samples) / w) + 1 + # Create a subset by taking the average of three samples + # around every nth sample + num_divisions = int(self._width / (self._peak_width * 2)) + n = math.floor(len(self._samples) / num_divisions) + 1
if n >= 2: samples: AudioSampleT = [] @@ -120,9 +151,18 @@ def _process_samples(self) -> None:
# Normalize both channels using the same scale max_elem = max(max(samples)) - self._samples = [ - (val1 / max_elem, val2 / max_elem) for val1, val2 in samples - ] + min_elem = min(min(samples)) + delta = max_elem - min_elem + if delta > 0: + self._samples = [ + ( + (val1 - min_elem) / delta, + (val2 - min_elem) / delta + ) + for val1, val2 in samples + ] + else: + self._samples = samples
def _draw_surface(self, ctx: cairo.Context[cairo.ImageSurface]) -> None: # First draw the progress part from left @@ -130,7 +170,8 @@ def _draw_surface(self, ctx: cairo.Context[cairo.ImageSurface]) -> None: self._draw_rms_amplitudes(ctx, start=0, end=end, - color=self._progress_color) + color=self._progress_color + )
if self._seek_position >= 0: # 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: self._draw_rms_amplitudes(ctx, start=start, end=end, - color=self._seek_color) + color=self._seek_color + )
# Draw the default amplitudes for the rest of the timeline play_pos = min(round( @@ -164,7 +206,8 @@ def _draw_surface(self, ctx: cairo.Context[cairo.ImageSurface]) -> None: self._draw_rms_amplitudes(ctx, start=start_default, end=self._num_samples, - color=self._default_color) + color=self._default_color + )
def _draw_rms_amplitudes(self, ctx: cairo.Context[cairo.ImageSurface], @@ -172,84 +215,98 @@ def _draw_rms_amplitudes(self, end: int, color: tuple[float, float, float] ) -> None: - - ctx.set_source_rgb(*color) - - w = self._amplitude_width - - # determines the spacing between the amplitudes - o = (self._width - 2 * w) / self._num_samples - y = self._height / 2 - r = 1 # radius of the arcs on top and bottom of the amplitudes - s = self._height / 2 - r # amplitude scaling factor + peak_width = self._peak_width + radius = 1 # radius of the arcs on top and bottom of the amplitudes + gap = 0.25 # between upper and lower peak in pixels + scaling = self._height / 2 - radius - gap / 2 # peak scaling factor + ctx.set_line_width(1.0)
if self._is_LTR: - o = (self._width - 2 * w) / self._num_samples - x = self._x_offset + o * (start + 1) + # determines the spacing between the amplitudes + x_shift = (self._width - 2 * peak_width) / self._num_samples + x = self._x_offset + x_shift * (start + 1) else: - o = -(self._width - 2 * w) / self._num_samples - x = -o * (self._num_samples - start) + self._x_offset + x_shift = -(self._width - 2 * peak_width) / self._num_samples + x = -x_shift * (self._num_samples - start) + self._x_offset
for i in range(start, end): - # Ensure that no empty area is drawn, - # thus use a minimum sample value - if self._is_LTR: - sample1 = max(0.05, self._samples[i][0]) - sample2 = max(0.05, self._samples[i][1]) + sample1 = self._samples[i][0] + sample2 = self._samples[i][1] else: - sample1 = max(0.05, self._samples[i][1]) - sample2 = max(0.05, self._samples[i][0]) - - self._draw_rounded_rec2(ctx, x, y, w, sample1 * s, sample2 * s, r) + sample1 = self._samples[i][1] + sample2 = self._samples[i][0] + + self._draw_rounded_rec( + ctx, + x, + self._height / 2, + peak_width, + sample1 * scaling, + sample2 * scaling, + radius, + gap, + ) + + ctx.set_source_rgb(*self._outline_color) + ctx.stroke_preserve() + + ctx.set_source_rgb(*color) ctx.fill() - x += o - - def _on_drawingarea_draw(self, - _drawing_area: Gtk.DrawingArea, - ctx: cairo.Context[cairo.ImageSurface], - ) -> None: - - if self._num_samples != 0: - self._draw_surface(ctx) - - def _on_drawingarea_changed(self, - _drawing_area: Gtk.DrawingArea, - _event: Gdk.EventConfigure, - ) -> None: - - self._is_LTR = (self.get_direction() == Gtk.TextDirection.LTR) - self._setup_surface() - self.queue_draw() - - def _draw_rounded_rec2(self, - context: cairo.Context[cairo.ImageSurface], - x: float, - y: float, - w: float, - h1: float, - h2: float, - r: float - ) -> None: - - ''' - Draws a rectangle of width w and total height of h1+h2 - The top and bottom edges are curved to the outside - ''' - m = w / 2
+ x += x_shift + + def _draw_rounded_rec(self, + ctx: cairo.Context[cairo.ImageSurface], + x: float, + y: float, + width: float, + height1: float, + height2: float, + radius: float, + gap_size: float, + ) -> None: + + # Don't insert a gap if bars are too small + if height1 + height2 < 3: + gap_size = 0.0 + + # Set a minimum height to improve visibility for quasi silence + height1 = max(0.5, height1) + height2 = max(0.5, height2) + + # Draws a rectangle of width w and total height of h1+h2 + # The top and bottom edges are curved to the outside + m = width / 2 if not self._is_LTR: m = -m - w = -w - - context.curve_to(x, y + h1, - x + m, y + h1 + r, - x + w, y + h1) - - context.line_to(x + w, y - h2) - - context.curve_to(x + w, y - h2, - x + m, y - h2 - r, - x, y - h2) - - context.line_to(x, y + h1) + width = -width + + # A --- B --- C + # | | + # F --- E --- D + + # Up + # A -- B -- C + ctx.curve_to(x, y + height1, + x + m, y + height1 + radius, + x + width, y + height1) + # C -- D + ctx.line_to(x + width, y + gap_size) + # D -- F + ctx.line_to(x, y + gap_size) + # F -- A + ctx.line_to(x, y + height1) + + # Down + # C -- D + ctx.move_to(x + width, y - gap_size) + ctx.line_to(x + width, y - height2) + # D -- E -- F + ctx.curve_to(x + width, y - height2, + x + m, y - height2 - radius, + x, y - height2) + # F -- A + ctx.line_to(x, y - gap_size) + # A -- C + ctx.line_to(x + width, y - gap_size)
===================================== pep517build/__init__.py =====================================
===================================== pep517build/backend.py ===================================== @@ -0,0 +1,150 @@ +from __future__ import annotations + +from typing import Any + +import gzip +import logging +import shutil +import subprocess +import sys +from pathlib import Path + +import setuptools.build_meta as _orig + + +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') +log = logging.getLogger() + +DEFAULT_METADATA_PATH = Path('dist/metadata') +ALLOWED_CONFIG_SETTINGS = {'target'} +MAN_FILES = [ + Path('data/gajim.1'), + Path('data/gajim-remote.1') +] +META_FILES = [ + (Path('data/org.gajim.Gajim.desktop.in'), '--desktop'), + (Path('data/org.gajim.Gajim.appdata.xml.in'), '--xml') +] +ICONS = [ + Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg'), + Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg'), +] + + +def build_translations() -> None: + # Compile translation files and place them into "gajim/data/locale" + + source_dir = Path.cwd() + translation_dir = source_dir / 'po' + locale_dir = source_dir / 'gajim' / 'data' / 'locale' + + langs = sorted([lang.stem for lang in translation_dir.glob('*.po')]) + + for lang in langs: + po_file = source_dir / 'po' / f'{lang}.po' + mo_file = locale_dir / lang / 'LC_MESSAGES' / 'gajim.mo' + mo_file.parent.mkdir(parents=True, exist_ok=True) + + log.info('Compile %s >> %s', po_file, mo_file) + + subprocess.run(['msgfmt', + str(po_file), + '-o', + str(mo_file)], + check=True) + + +def build_man(target_path: Path) -> None: + # Build man files in target path + + for man_path in MAN_FILES: + data = man_path.read_bytes() + man_file_name = man_path.name + + man_out_path = target_path / f'{man_file_name}.gz' + log.info('Compress %s >> %s', man_file_name, man_out_path) + + with gzip.open(man_out_path, 'wb') as f_out: + f_out.write(data) + + + +def build_intl(target_path: Path) -> None: + # Merge translation files into desktop and metadata files + + for file_path, option in META_FILES: + out_path = target_path / file_path.name + out_path = out_path.with_suffix('') + + log.info('Compile %s >> %s', file_path, out_path) + + subprocess.run(['msgfmt', + option, + '-d', + 'po', + '--template', + str(file_path), + '-o', + str(out_path)], + check=True) + + +def build_app_icons(target_path: Path) -> None: + for file_path in ICONS: + out_path = target_path / file_path.name + + log.info('Copy %s >> %s', file_path, out_path) + shutil.copy2(file_path, out_path) + + +def _check_config_settings(config_settings: dict[str, str]) -> None: + settings = set(config_settings.keys()) - ALLOWED_CONFIG_SETTINGS + if settings: + raise ValueError('Unknown config setting %s' % settings) + + +def _get_target_path(config_settings: dict[str, str] | None) -> Path: + if config_settings is None: + path = DEFAULT_METADATA_PATH + else: + path = Path(config_settings.get('target', DEFAULT_METADATA_PATH)) + + path.mkdir(parents=True, exist_ok=True) + return path + + +def get_requires_for_build_sdist(*args: Any, **kwargs: Any) -> str: + return _orig.get_requires_for_build_sdist(*args, **kwargs) + +def build_sdist(*args: Any, **kwargs: Any) -> str: + return _orig.build_sdist(*args, **kwargs) + +def get_requires_for_build_wheel(*args: Any, **kwargs: Any) -> str: + return _orig.get_requires_for_build_wheel(*args, **kwargs) + +def prepare_metadata_for_build_wheel(*args: Any, **kwargs: Any) -> str: + return _orig.prepare_metadata_for_build_wheel(*args, **kwargs) + +def build_wheel(wheel_directory: str, + config_settings: dict[str, str] | None = None, + metadata_directory: str | None = None + ) -> str: + + if config_settings is not None: + _check_config_settings(config_settings) + target_path = _get_target_path(config_settings) + + build_translations() + + basename = _orig.build_wheel( + wheel_directory, + config_settings=config_settings, + metadata_directory=metadata_directory, + ) + + if sys.platform not in ('win32', 'darwin'): + build_man(target_path) + build_intl(target_path) + build_app_icons(target_path) + + return basename
===================================== pep517build/install_metadata.py ===================================== @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +from __future__ import annotations + +import argparse +import logging +import shutil +from pathlib import Path + +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') +log = logging.getLogger() + +DEFAULT_METADATA_PATH = Path('dist/metadata') + +FILES = { + 'gajim-remote.1.gz': 'share/man/man1', + 'gajim.1.gz': 'share/man/man1', + 'org.gajim.Gajim.desktop': 'share/applications', + 'org.gajim.Gajim-symbolic.svg': 'share/icons/hicolor/scalable/apps', + 'org.gajim.Gajim.svg': 'share/icons/hicolor/scalable/apps', + 'org.gajim.Gajim.appdata.xml': 'share/metainfo', +} + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Install metadata files') + parser.add_argument('prefix', type=Path) + parser.add_argument('--path', type=Path, default=DEFAULT_METADATA_PATH) + args = parser.parse_args() + + for file, path in FILES.items(): + src = args.path / file + dest_dir = args.prefix / path + log.info('Copy %s to %s', src, dest_dir) + if not dest_dir.exists(): + dest_dir.mkdir(parents=True) + shutil.copy(src, dest_dir / file)
===================================== pyproject.toml ===================================== @@ -1,15 +1,76 @@ [build-system] requires = [ - "setuptools >= 43.0.0", + "setuptools >= 65.0.0", ] -build-backend = "setuptools.build_meta" +build-backend = "backend" +backend-path = ["pep517build"] + +[project] +name = "gajim" +description = "A GTK XMPP Client" +readme = "README.md" +requires-python = ">=3.10" +license = {text = "GPL-3.0-or-later"} +authors = [ + {name = "Philipp Hörist", email = "philipp@hoerist.com"}, + {name = "Yann Leboulanger", email = "yann@leboulanger.org"}, +] +keywords = ["chat", "messaging", "im", "xmpp", "voip"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: X11 Applications :: GTK", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.10", + "Topic :: Communications :: Chat", +] +dependencies = [ + "css-parser", + "keyring", + "nbxmpp>=3.2.5,<4.0.0", + "packaging", + "pillow", + "precis-i18n>=1.0.0", + "cryptography>=3.4.8", + "pycairo>=1.16.0", + "PyGObject>=3.42.0", +] +dynamic = ["version"] + +[project.urls] +homepage = "https://gajim.org" +repository = "https://dev.gajim.org/gajim/gajim" + +[project.optional-dependencies] +dev = [ + "ruff", + "codespell[toml]", + "nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git", + "PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git", + "python-gitlab", + "sentry-sdk", +] + +[project.scripts] +gajim-remote = "gajim.gajim_remote:main" + +[project.gui-scripts] +gajim = "gajim.gajim:main"
+[tool.setuptools.packages.find] +include = ["gajim*"] + +[tool.setuptools.package-data] +gajim = ["py.typed"] +"gajim.data" = ["**/*"] + +[tool.setuptools.dynamic] +version = {attr = "gajim.__version__"}
[tool.codespell] skip = "*__pycache__*,gajim/gui,gajim/data/icons,gajim/data/sounds" ignore-words-list = "claus,pres,ser,trough"
- [tool.pyright] pythonVersion = "3.10" pythonPlatform = "All" @@ -45,6 +106,7 @@ include = [ "mac/*", "scripts/*", "win/*", + "pep517build/*", "gajim/common/application.py", "gajim/common/call_manager.py", "gajim/common/cert_store.py", @@ -79,7 +141,6 @@ include = [ "gajim/plugins/*", ]
- [tool.ruff] line-length = 80
@@ -162,7 +223,6 @@ target-version = "py310" "test/no_gui/test_styling.py" = ["RUF001", "E501"] "test/no_gui/test_regex.py" = ["RUF001"]
- [tool.ruff.mccabe] max-complexity = 15
===================================== setup.cfg deleted ===================================== @@ -1,58 +0,0 @@ -[metadata] -name = gajim -version = attr: gajim.__version__ -description = A GTK XMPP client -long_description = file: README.md -long_description_content_type = text/markdown -author = Philipp Hoerist, Yann Leboulanger -author_email = gajim-devel@gajim.org -url = https://gajim.org -license = GPL v3 -keywords = chat,messaging,im,xmpp,voip -classifiers = - Development Status :: 5 - Production/Stable - Environment :: X11 Applications :: GTK - License :: OSI Approved :: GNU General Public License v3 (GPLv3) - Operating System :: OS Independent - Programming Language :: Python :: 3.10 - Topic :: Communications :: Chat - -[options] -python_requires = >=3.10 -packages = find: -test_suite = test -install_requires = - css-parser - keyring - nbxmpp>=3.2.5,<4.0.0 - packaging - pillow - precis-i18n>=1.0.0 - cryptography>=3.4.8 - pycairo>=1.16.0 - PyGObject>=3.42.0 - -[options.extras_require] -dev = - ruff - codespell[toml] - nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git - PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git - python-gitlab - sentry-sdk - -[options.package_data] -gajim = - data/gui/*.ui - data/icons/hicolor/*/*/*.png - data/icons/hicolor/*/*/*.svg - data/other/* - data/sounds/*.wav - data/style/*.css - data/plugins/*/* - py.typed - -[options.packages.find] -exclude = - gajim.dev - test*
===================================== setup.py deleted ===================================== @@ -1,202 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -from typing import cast - -import logging -import os -import sys - -if sys.version_info < (3, 10): - sys.exit('Gajim needs Python 3.10+') - -import subprocess -from pathlib import Path - -from setuptools import setup -from setuptools.command.build_py import build_py as _build -from setuptools.command.install import install as _install - - -DataFilesT = list[tuple[str, list[str]]] - - -MAN_FILES = [ - 'gajim.1', - 'gajim-remote.1' -] -META_FILES = [ - ('data/org.gajim.Gajim.desktop', 'share/applications', '--desktop'), - ('data/org.gajim.Gajim.appdata.xml', 'share/metainfo', '--xml')] - - -TRANS_DIR = Path('po') -TRANS_TEMPLATE = TRANS_DIR / 'gajim.pot' -REPO_DIR = Path(__file__).resolve().parent -BUILD_DIR = REPO_DIR / 'build' - -ALL_LINGUAS = sorted([lang.stem for lang in TRANS_DIR.glob('*.po')]) - - -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') -log = logging.getLogger() - - -def newer(source: Path, target: Path) -> bool: - if not source.exists(): - raise ValueError('file "%s" does not exist' % source.resolve()) - if not target.exists(): - return True - - from stat import ST_MTIME - mtime1 = source.stat()[ST_MTIME] - mtime2 = target.stat()[ST_MTIME] - - return mtime1 > mtime2 - - -def build_translation() -> None: - for lang in ALL_LINGUAS: - po_file = TRANS_DIR / f'{lang}.po' - mo_file = BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo' - mo_dir = mo_file.parent - if not (mo_dir.is_dir() or mo_dir.is_symlink()): - mo_dir.mkdir(parents=True) - - if newer(po_file, mo_file): - subprocess.run(['msgfmt', - str(po_file), - '-o', - str(mo_file)], - cwd=REPO_DIR, - check=True) - - log.info('Compiling %s >> %s', po_file, mo_file) - - -def install_trans(data_files: DataFilesT) -> None: - for lang in ALL_LINGUAS: - mo_file = str(BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo') - target = f'share/locale/{lang}/LC_MESSAGES' - data_files.append((target, [mo_file])) - - -def build_man() -> None: - ''' - Compress Gajim manual files - ''' - newdir = BUILD_DIR / 'man' - if not (newdir.is_dir() or newdir.is_symlink()): - newdir.mkdir() - - for man in MAN_FILES: - filename = Path('data') / man - man_file_gz = newdir / (man + '.gz') - if man_file_gz.exists(): - if newer(filename, man_file_gz): - man_file_gz.unlink() - else: - continue - - import gzip - # Binary io, so open is OK - with open(filename, 'rb') as f_in,\ - gzip.open(man_file_gz, 'wb') as f_out: - f_out.writelines(f_in) - log.info('Compiling %s >> %s', filename, man_file_gz) - - -def install_man(data_files: DataFilesT) -> None: - man_dir = BUILD_DIR / 'man' - target = 'share/man/man1' - - for man in MAN_FILES: - man_file_gz = str(man_dir / (man + '.gz')) - data_files.append((target, [man_file_gz])) - - -def build_intl() -> None: - ''' - Merge translation files into desktop and mime files - ''' - base = BUILD_DIR - - for filename, _, option in META_FILES: - newfile = base / filename - newdir = newfile.parent - if not (newdir.is_dir() or newdir.is_symlink()): - newdir.mkdir() - merge(Path(filename + '.in'), newfile, option) - - -def install_intl(data_files: DataFilesT) -> None: - for filename, target, _ in META_FILES: - data_files.append((target, [str(BUILD_DIR / filename)])) - - -def merge(in_file: Path, - out_file: Path, - option: str, - po_dir: str = 'po') -> None: - ''' - Run the msgfmt command. - ''' - if in_file.exists(): - cmd = (('msgfmt %(opt)s -d %(po_dir)s --template %(in_file)s ' - '-o %(out_file)s') % - {'opt': option, - 'po_dir': po_dir, - 'in_file': in_file, - 'out_file': out_file}) - if os.system(cmd) != 0: - msg = ('ERROR: %s was not merged into the translation files!\n' % - out_file) - raise SystemExit(msg) - log.info('Compiling %s >> %s', in_file, out_file) - - -class Build(_build): - def run(self): - build_translation() - if sys.platform != 'win32': - build_man() - build_intl() - _build.run(self) - - -class Install(_install): - def run(self): - data_files = cast(DataFilesT, self.distribution.data_files) # pyright: ignore # noqa: E501 - install_trans(data_files) - if sys.platform != 'win32': - install_man(data_files) - install_intl(data_files) - _install.run(self) # pyright: ignore - - -# only install subdirectories of data -data_files_app_icon = [ - ('share/icons/hicolor/scalable/apps', - ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg']), - ('share/icons/hicolor/scalable/apps', - ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg']) -] - -data_files: DataFilesT = data_files_app_icon - -setup( - cmdclass={ - 'build_py': Build, - 'install': Install, - }, - entry_points={ - 'console_scripts': [ - 'gajim-remote = gajim.gajim_remote:main', - ], - 'gui_scripts': [ - 'gajim = gajim.gajim:main', - ] - }, - data_files=data_files -)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/a20fbea14179078a637011b687eee783...