diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index e8ff3cdf5..724d8afe5 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -20,7 +20,7 @@ jobs:
run: |
python -m pip install --upgrade pip
sudo apt-get update
- sudo apt install -y meson gettext itstool libgirepository1.0-dev gir1.2-gtk-4.0 libgtksourceview-5-dev
+ sudo apt install -y meson gettext itstool libgirepository1.0-dev gir1.2-gtk-4.0 libgtksourceview-5-dev libportal-dev
pip install --user -e git+https://github.com/getting-things-gnome/liblarch.git#egg=liblarch
pip install --user pytest pycairo PyGObject caldav lxml
- name: Build and install GTG
diff --git a/GTG/__init__.py b/GTG/__init__.py
index c05612362..44b2a5f76 100644
--- a/GTG/__init__.py
+++ b/GTG/__init__.py
@@ -24,3 +24,4 @@ def gi_version_requires():
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
gi.require_version('GtkSource', '5')
+ gi.require_version('Xdp', '1.0')
diff --git a/GTG/core/info.py.in b/GTG/core/info.py.in
index 7b82ce63d..63c90b672 100644
--- a/GTG/core/info.py.in
+++ b/GTG/core/info.py.in
@@ -30,41 +30,60 @@ from gettext import gettext as _
# These variables get used by main_window.py to set the About dialog's metadata.
# Translator credits are set dynamically in main_window.py, not hardcoded.
NAME = "Getting Things GNOME!"
+AUTHORS = _("GTG contributors")
+COPYRIGHT = _(f"Copyright © 2008-%d {AUTHORS}") \
+ % date.today().year
SHORT_DESCRIPTION = _("""A personal productivity tool for GNOME,
inspired by the GTD methodology.""") # A manual line break looks better in the About dialog.
URL = "https://wiki.gnome.org/Apps/GTG"
-TRANSLATE_URL = "https://github.com/getting-things-gnome/gtg/"
+CHAT_URL = "https://matrix.to/#/#gtg:gnome.org"
+SOURCE_CODE_URL = "https://github.com/getting-things-gnome/gtg/"
+OPENHUB_URL = "https://www.openhub.net/p/gtg/contributors"
REPORT_BUG_URL = "https://github.com/getting-things-gnome/gtg/issues/"
EMAIL = "gtg-contributors@lists.launchpad.net"
VERSION = '@VCS_TAG@'
-AUTHORS_MAINTAINERS = """
-• Diego Garcia Gangl
-• Jean-François Fortin Tam
-"""
+AUTHORS_MAINTAINERS = [
+ "Diego Garcia Gangl",
+ "Jean-François Fortin Tam",
+]
+
# Per-release stats generated as per the "release process and checklist.md" file.
# Including contributors with 2 or more commits.
-# No need to add extra line breaks between commas, Python/GTK handles them.
-# Don't indend lines inside a multi-line string, or it'll show in the About dialog.
-AUTHORS_RELEASE_CONTRIBUTORS = """
-• "Neui"
-• Mohieddine Drissi
-• "odoood"
-• Diego Garcia Gangl
-• Jean-François Fortin Tam
-• Jacob Anderson
-• Raidro Manchester
-• Daniel Koć
-• François Schmidts
-• Sebastian Grabowski
-• Fridolin Weisser
-• Tommy Priest
-• Laurent Combe
-• Smitty
-• Tiziana Sellitto
-• "unsupported-transceiver"
-"""
+AUTHORS_RELEASE_CONTRIBUTORS = [
+ "Neui",
+ "Mohieddine Drissi",
+ "“odoood”",
+ "Diego Garcia Gangl",
+ "Jean-François Fortin Tam",
+ "Jacob Anderson",
+ "Raidro Manchester",
+ "Daniel Koć",
+ "François Schmidts",
+ "Sebastian Grabowski",
+ "Fridolin Weisser",
+ "Tommy Priest",
+ "Laurent Combe",
+ "Smitty",
+ "Tiziana Sellitto",
+ "“unsupported-transceiver”",
+]
+
+ARTISTS = [
+ "Diego Garcia Gangl (2021 logo)",
+ "Tobias Bernard (2021 logo)",
+ "Kalle Persson (2009 logo)",
+ "Bertrand Rousseau (UX)",
+ "Jean-François Fortin Tam (UX)"
+]
+
+DOCUMENTERS = [
+ "Danielle Vansia",
+ "Radina Matic",
+ "Jean-François Fortin Tam"
+]
-ARTISTS = ["Diego Garcia Gangl (2021 logo)", "Tobias Bernard (2021 logo)", "Kalle Persson (2009 logo)", "Bertrand Rousseau (UX)", "Jean-François Fortin Tam (UX)"]
+AUTHORS_MAINTAINERS.sort()
+AUTHORS_RELEASE_CONTRIBUTORS.sort()
ARTISTS.sort()
-DOCUMENTERS = [ "Danielle Vansia", "Radina Matic", "Jean-François Fortin Tam"]
+DOCUMENTERS.sort()
diff --git a/GTG/core/meson.build b/GTG/core/meson.build
index fc7a65c95..9a4828345 100644
--- a/GTG/core/meson.build
+++ b/GTG/core/meson.build
@@ -48,6 +48,7 @@ gtg_core_sources = [
'datastore.py',
'filters.py',
'sorters.py',
+ 'system_info.py',
]
gtg_core_plugin_sources = [
diff --git a/GTG/core/system_info.py b/GTG/core/system_info.py
new file mode 100644
index 000000000..de66cf3f4
--- /dev/null
+++ b/GTG/core/system_info.py
@@ -0,0 +1,117 @@
+# system_info.py: Collect system information
+# Copyright (C) 2024 GTG Contributors
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see .
+
+
+from datetime import date
+import gi
+import os
+import platform
+import importlib
+
+from GTG.core import info
+
+from gi.repository import Gdk, Gtk, GObject, GLib, Xdp
+
+
+class SystemInfo:
+ def get_system_info(self, report: bool = False) -> str:
+ """
+ Get system information based on their
+ availability and installed version.
+ """
+ self.report = report
+
+ sys_info = ""
+ sys_info += self.__format_info("GTG", info.VERSION)
+
+ if Xdp.Portal.running_under_flatpak():
+ sys_info += self.__format_info("Flatpak", self.__get_flatpak_version())
+ else:
+ sys_info += self.__format_info("Flatpak", "False")
+
+ sys_info += self.__format_info("Snap", Xdp.Portal.running_under_snap())
+ sys_info += self.__format_info("Display Name", Gdk.Display.get_default().get_name())
+ sys_info += self.__format_info("Desktop", os.environ.get("XDG_CURRENT_DESKTOP"))
+
+ sys_info += "\n"
+ sys_info += self.__format_info("lxml", self.__get_python_module("lxml"))
+ sys_info += self.__format_info("caldav", self.__get_python_module("caldav"))
+ sys_info += self.__format_info("liblarch", self.__get_python_module("liblarch"))
+ sys_info += self.__format_info("Cheetah3", self.__get_python_module("Cheetah"))
+ sys_info += self.__format_info("dbus-python", self.__get_python_module("dbus"))
+ sys_info += self.__format_info("pdflatex", self.__get_python_module("pdflatex"))
+ sys_info += self.__format_info("pypdftk", self.__get_python_module("pdftk"))
+
+ # Only display OS info when user isn't running as Flatpak/Snap
+ if not Xdp.Portal.running_under_sandbox():
+ sys_info += "\n"
+ sys_info += self.__format_info("OS", GLib.get_os_info("PRETTY_NAME"))
+
+ sys_info += self.__format_info(
+ "Python", f"{platform.python_implementation()} {platform.python_version()}"
+ )
+
+ sys_info += self.__format_info("GLib", self.__version_to_string(GLib.glib_version))
+ sys_info += self.__format_info("PyGLib", self.__version_to_string(GLib.pyglib_version))
+
+ sys_info += self.__format_info(
+ "PyGObject", self.__version_to_string(GObject.pygobject_version)
+ )
+
+ sys_info += self.__format_info("GTK", self.__version_to_string(self.__get_gtk_version()))
+
+ return sys_info
+
+
+ def __version_to_string(self, version: tuple) -> str:
+ """
+ Convert version tuple (major, micro, minor)
+ version to string (major.micro.minor).
+ """
+ return ".".join(map(str, version))
+
+
+ def __format_info(self, lib: str, getter) -> str:
+ """
+ Pretty-format library and availability
+ """
+ if self.report:
+ return f"**{lib}:** {getter}\n"
+ else:
+ return f"{lib}: {getter}\n"
+
+
+ def __get_flatpak_version(self) -> str:
+ """Get Flatpak version."""
+ with open("/.flatpak-info") as flatpak_info:
+ for line in flatpak_info:
+ if line.startswith("flatpak-version"):
+ flatpak_version = line.split("=")[1].strip()
+ return flatpak_version
+
+
+ def __get_gtk_version(self) -> str:
+ """Get GTK version."""
+ return (
+ Gtk.get_major_version(),
+ Gtk.get_micro_version(),
+ Gtk.get_minor_version(),
+ )
+
+
+ def __get_python_module(self, module: str) -> bool:
+ """Check if Python module is installed."""
+ return bool(importlib.util.find_spec(module))
diff --git a/GTG/gtk/browser/main_window.py b/GTG/gtk/browser/main_window.py
index 19ef4aa0c..5bb567057 100644
--- a/GTG/gtk/browser/main_window.py
+++ b/GTG/gtk/browser/main_window.py
@@ -25,8 +25,11 @@
from typing import Optional
from gi.repository import GObject, Gtk, Gdk, Gio, GLib
+from webbrowser import open as openurl
+from textwrap import dedent
from GTG.core import info
+from GTG.core.system_info import SystemInfo
from GTG.backends.backend_signals import BackendSignals
from GTG.core.dirs import ICONS_DIR
from GTG.core.search import parse_search_query, InvalidQuery
@@ -319,51 +322,75 @@ def _init_about_dialog(self):
"""
Show the about dialog
"""
- # These lines should be in info.py, but due to their dynamic nature
- # there'd be no way to show them translated in Gtk's About dialog:
-
- translated_copyright = _("Copyright © 2008-%d the GTG contributors.") \
- % datetime.date.today().year
-
- ohstats_url = 'OpenHub'
- ghstats_url = \
- 'GitHub'
-
- UNITED_AUTHORS_OF_GTGETTON = [
- # GTK prefixes the first line with "Created by ",
- # but we can't split the string because it would cause trouble for some languages.
- _("GTG was made by many contributors around the world."),
- _("The GTG project is maintained/administered by:"),
- info.AUTHORS_MAINTAINERS,
- _("This release was brought to you by the efforts of these people:"),
- info.AUTHORS_RELEASE_CONTRIBUTORS,
- _("Many others contributed to GTG over the years.\n" \
- "You can see them on {OH_stats} and {GH_stats}.").format(
- OH_stats=ohstats_url, GH_stats=ghstats_url),
- "\n"]
+
+ # Create `GtkButton`s and add to size group
+ def create_uri_button(string=None, uri=None):
+ btn = Gtk.Button.new_with_mnemonic(string)
+ btn.connect("clicked", lambda _: openurl(uri))
+ btn.set_tooltip_text(uri)
+ size_group.add_widget(btn)
+ return btn
+
+ ohstats_url = f'OpenHub'
+ ghstats_url = 'GitHub'
+
+ UNITED_AUTHORS_OF_GTGETTON = dedent(
+ _(
+ """\
+ Many others contributed to GTG over the years.
+ You can find them on {OH_stats} and {GH_stats}."""
+ ).format(OH_stats=ohstats_url, GH_stats=ghstats_url)
+ )
self.about.set_transient_for(self)
+ self.about.set_modal(True)
self.about.set_program_name(info.NAME)
- self.about.set_website(info.URL)
self.about.set_logo_icon_name(self.app.props.application_id)
- self.about.set_website_label(_("GTG website"))
self.about.set_version(info.VERSION)
# This line translated in info.py works, as it has no strings replacements
self.about.set_comments(_(info.SHORT_DESCRIPTION))
- self.about.set_copyright(translated_copyright)
+ self.about.set_copyright(info.COPYRIGHT)
self.about.set_license_type(Gtk.License.GPL_3_0)
- self.about.set_authors(UNITED_AUTHORS_OF_GTGETTON)
+ self.about.set_authors([info.AUTHORS])
self.about.set_artists(info.ARTISTS)
self.about.set_documenters(info.DOCUMENTERS)
+ self.about.set_system_information(SystemInfo().get_system_info())
+
+ self.about.add_credit_section(
+ _("Maintained/Administered by"), info.AUTHORS_MAINTAINERS
+ )
+
+ authors = info.AUTHORS_RELEASE_CONTRIBUTORS
+ authors.append(UNITED_AUTHORS_OF_GTGETTON)
+
+ self.about.add_credit_section(
+ _("Contributed by"), info.AUTHORS_RELEASE_CONTRIBUTORS
+ )
+
# Translators for a particular language should put their names here.
- # Please keep the width at 80 chars max, as GTK3's About dialog won't wrap text.
+ # Please keep the width at 80 chars max, as GTK4's About dialog won't wrap text.
# GtkAboutDialog will detect if “translator-credits” is untranslated and auto-hide the tab.
self.about.set_translator_credits(_("translator-credits"))
+ # Retrieve the `GtkBox` within GtkDialog,
+ # and create `GtkSizeGroup` to group buttons
+ about_box = self.about.get_first_child()
+ size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
+
+ # Create new `GtkBox` to add button links
+ uri_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6)
+ uri_box.set_halign(Gtk.Align.CENTER)
+ uri_box.append(create_uri_button(_("_Website"), info.URL))
+ uri_box.append(create_uri_button(_("_Dev Chatroom"), info.CHAT_URL))
+ uri_box.append(create_uri_button(_("_GitHub"), info.SOURCE_CODE_URL))
+
+ about_box.append(uri_box)
+
+
def _init_signal_connections(self):
"""
connects signals on UI elements
diff --git a/GTG/gtk/errorhandler.py b/GTG/gtk/errorhandler.py
index 3d837cd9f..1ee73ec13 100644
--- a/GTG/gtk/errorhandler.py
+++ b/GTG/gtk/errorhandler.py
@@ -12,6 +12,7 @@
import configparser
from GTG.core import info
+from GTG.core.system_info import SystemInfo
log = logging.getLogger(__name__)
@@ -115,60 +116,11 @@ def _update_additional_info(self):
if self._context_info is not None:
text = text + "**Context:** " + self._context_info + "\n\n"
text = text + "```python-traceback\n" + body + "```\n\n"
- text = text + _collect_versions()
+ text = text + SystemInfo().get_system_info(report=True)
self._additional_info.get_buffer().set_text(text)
-def _collect_versions() -> str:
- """
- Collect version information of various components,
- and lay it out in markdown format to facilitate bug reports.
- """
- def t2v(version_tuple) -> str:
- """Version tuple to a string."""
- return '.'.join(map(str, version_tuple))
- python_version = sys.version.replace('\n', ' ')
- gtk_version = (Gtk.get_major_version(),
- Gtk.get_minor_version(),
- Gtk.get_micro_version())
- versions = f"""**Software versions:**
-* {info.NAME} {info.VERSION}
-* {platform.python_implementation()} {python_version}
-* GTK {t2v(gtk_version)}, GLib {t2v(GLib.glib_version)}
-* PyGLib {t2v(GLib.pyglib_version)}, PyGObject {t2v(GObject.pygobject_version)}
-* {platform.platform()}"""
-
- flatpak_info = _collect_flatpak_info()
- if flatpak_info is not None:
- versions += f"""
-* Flatpak {flatpak_info.get('Instance.flatpak-version')}
-* Flatpak runtime: {flatpak_info.get('Application.runtime')} ({flatpak_info.get('Instance.runtime-commit')})
-* Flatpak app: {flatpak_info.get('Application.name')} ({flatpak_info.get('Instance.app-commit')})"""
-
- return versions
-
-
-def _collect_flatpak_info() -> Optional[dict]:
- """
- Collect all kinds of information easily available when running inside
- the flatpak environment.
- Returns None if not running in a flatpak.
- """
- try:
- fconfig = configparser.ConfigParser(interpolation=None)
- if fconfig.read('/.flatpak-info') == []:
- return None
- d = {}
- for section in fconfig.sections():
- for option in fconfig[section]:
- d[f'{section}.{option}'] = fconfig[section][option]
- return d
- except Exception as e: # Just in case
- log.exception("Exception occurred while trying to collcet flatpak info")
- return None
-
-
def _format_exception(exception: Exception) -> str:
"""Format an exception the python way, as a string."""
return "".join(traceback.format_exception(type(exception),
diff --git a/build-aux/org.gnome.GTG.Devel.json b/build-aux/org.gnome.GTG.Devel.json
index 3fd261253..80545853f 100644
--- a/build-aux/org.gnome.GTG.Devel.json
+++ b/build-aux/org.gnome.GTG.Devel.json
@@ -45,6 +45,24 @@
"mkdir ${FLATPAK_DEST}/texlive"
]
},
+ {
+ "name": "libportal",
+ "buildsystem": "meson",
+ "config-opts": [
+ "-Dtests=false",
+ "-Dbackend-gtk3=disabled",
+ "-Dbackend-gtk4=enabled",
+ "-Dbackend-qt5=disabled",
+ "-Ddocs=false"
+ ],
+ "sources": [
+ {
+ "type": "archive",
+ "url": "https://github.com/flatpak/libportal/releases/download/0.7.1/libportal-0.7.1.tar.xz",
+ "sha256": "297b90b263fad22190a26b8c7e8ea938fe6b18fb936265e588927179920d3805"
+ }
+ ]
+ },
{
"name": "gtg",
"config-opts" : [