From fc0a385027caae73f4a7bafdfba3652a7af2205f Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 14:24:59 +0100 Subject: [PATCH 01/10] doc (components): document the common component settings: - Move the `ComponentSettings` autodoc from "Simplified Component Configuration" to a new "Common Component Settings" section. This was confusing when most of time only `jid` and `secret` are needed. - Document the config section naming (dashes replaced by underscores) and how to run a component in an isolated instance (systemd, `li -i`). - Fix the `[components.X]` section names (`[component.X]`). --- doc/components.rst | 53 ++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/doc/components.rst b/doc/components.rst index 4611faf4..dc3d99a8 100644 --- a/doc/components.rst +++ b/doc/components.rst @@ -1,8 +1,8 @@ .. _components: -=================== -Libervia Components -=================== +========== +Components +========== Libervia can act as an XMPP server component, which can be seen as a generic plugin for XMPP servers. @@ -38,23 +38,20 @@ OpenFire Prosody https://prosody.im/doc/components -Tigase - https://docs.tigase.net/en/latest/Tigase_Administration/Components/_Components.html#external-component-configuration +.. Tigase documentation is down (2026-11-03, the whole docs.tigase.net site is 404), + uncomment once it's back: +.. Tigase +.. https://docs.tigase.net/en/latest/Tigase_Administration/Components/_Components.html#external-component-configuration Simplified Component Configuration ================================== Most user will want to use this simplified configuration. To set up a component, you -simply need to create a configuration section with ``[component.]`` where -you'll set the ``jid`` and ``secret`` options. Everything will be automatically configured -on backend startup, and the component will start automatically. - -settings --------- - -In ``[component.]``: - -.. autopydantic_settings:: libervia.backend.tools.config.ComponentSettings +simply need to create a configuration section named after the entry point, with dashes +replaced by underscores (e.g. the ``email-gateway`` entry point uses the +``[component.email_gateway]`` section), where you'll set the ``jid`` and ``secret`` +options. Everything will be automatically configured on backend startup, and the +component will start automatically. example ------- @@ -69,6 +66,10 @@ find that it's ``pubsub``, then you simply put in your configuration (usually jid = "pubsub.example.org" secret = "your_shared_secret" +To run the component in an isolated backend instance (e.g. as a dedicated systemd +service), see :ref:`installation_system`. To work with such an instance from the CLI, +use the ``-i/--isolation`` option (see :ref:`li_isolation`). + Manual Component Configuration ============================== @@ -108,6 +109,14 @@ wants to connect it immediately, she can use:: $ li profile connect -cp ap-gateway +Common Component Settings +========================= + +Besides component specific options (see the component sections below), every +component supports these common options in its configuration section: + +.. autopydantic_settings:: libervia.backend.tools.config.ComponentSettings + Available Components ==================== @@ -139,7 +148,7 @@ entities (JIDs). Permissions can be set through Ad-Hoc Commands. Configuration ~~~~~~~~~~~~~ -All options are to be set in ``[components.file_sharing]`` section. +All options are to be set in ``[component.file_sharing]`` section. ``http_upload_port`` port to use for HTTP File Upload @@ -216,7 +225,13 @@ documentation to find how this must be done. Configuration ~~~~~~~~~~~~~ -All options are to be set in ``[components.ap_gateway]`` section. +All options are to be set in ``[component.ap_gateway]`` section: + +.. autopydantic_settings:: libervia.backend.plugins.plugin_comp_ap_gateway.settings.APGatewaySettings + +By default, the HTTP server uses HTTPS, thus a TLS certificate is required, either in the +``tls`` option or in the global ``[tls]`` section. If an HTTPS proxy already handles TLS, +use ``http_connection_type = "http"`` instead. .. _ap-actor-from-xmpp: @@ -745,7 +760,7 @@ any kind of extra informations) while AP only indicates if one will attend or no event. -.. _Mobilizon: https://joinmobilizon.org/ +.. _Mobilizon: https://mobilizon.org/ Using the Component (for developers) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -812,6 +827,8 @@ All options are to be set in the ``[component.conferences]`` section. .. _Galène project: https://galene.org/ +.. _email-gateway: + Email Gateway ------------- From 0c955f6abd5a696efaa06223b5651753a98ec5b0 Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 18:22:57 +0100 Subject: [PATCH 02/10] doc (apps): fix the apps documentation: The page was a messy reference, with several fields missing or incomplete. It is now completed and easier to read. --- doc/apps.rst | 139 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 87 insertions(+), 52 deletions(-) diff --git a/doc/apps.rst b/doc/apps.rst index 4aa89dd8..007ea6cc 100644 --- a/doc/apps.rst +++ b/doc/apps.rst @@ -1,21 +1,27 @@ .. _libervia-app-config: -================================= -Libervia App Configuration (YAML) -================================= +Libervia Apps +============= -The Libervia application uses a YAML configuration file to define various aspects of the -application's behavior, environment, and settings. +Libervia Apps are third party software, embedded and integrated in Libervia, usually as +containers (Podman, Docker). -The file will be parsed by `PyYAML`_ which is a full-featured YAML implementation. Check -its documentation for details. +Each app is defined by a YAML configuration file, which describes how to run it and which +settings are exposed. App files are named ``libervia_app_.yaml`` and are discovered +from the app manager plugin directories. -Below is the documentation explaining the structure and functionality of each field in the -YAML file. +Below is the documentation of each field of the YAML file. -================= +----------------- Root-Level Fields -================= +----------------- + +- ``name``: Human readable name of the application. The file name (without the + ``libervia_app_`` prefix) is used when not specified. + +- ``single_instance``: When ``True`` (default), only one instance of the application can + be started at a time, and its data are kept between restarts. When ``False``, each start + creates a new temporary instance directory, removed when the backend is unloaded. - ``type``: Specifies the type of the application. See :ref:`libervia-app_type`. @@ -27,16 +33,18 @@ Root-Level Fields - ``expose``: Specifies configurations exposed to frontends or administrators. See :ref:`libervia-app_expose`. -================== +------------------ Detailed Sections -=================== +------------------ .. _libervia-app_type: type ^^^^ -Currently, the only supported type is ``docker-compose``. +Currently, the only supported type is ``docker-compose``. The container engine is chosen +with the ``engine`` plugin setting: ``auto`` (default, Podman is tried first), ``docker`` +or ``podman``. .. _libervia-app_prepare: @@ -46,7 +54,7 @@ prepare The ``prepare`` section specifies things like git repositories to be cloned before running the application. Example -------- +~~~~~~~ Cloning the repository at `https://example.org/some/repository.git` to prepare the app: .. code-block:: yaml @@ -65,7 +73,7 @@ The YAML key is the name of the file to be created, and the content specified wi populate that file. Example -------- +~~~~~~~ Creating a file named `settings-override.py` with the content `USE_X_FORWARDED_HOST = True`: .. code-block:: yaml @@ -86,7 +94,7 @@ creates a ``docker-compose.override.yml`` file that will be merged with the defa `_. Example -------- +~~~~~~~ Overriding the `ports` for the `example_app` service to expose port `8080`: .. code-block:: yaml @@ -102,9 +110,31 @@ Overriding the `ports` for the `example_app` service to expose port `8080`: Exposing Data ^^^^^^^^^^^^^ -The `expose` section specifies the configurations that are exposed to users or frontends. -See :ref:`libervia-app_param` for a reference of some exposed data. Ports and generated -passwords are exposed in dicts, respectively at the ``ports`` and ``passwords`` key. +The ``expose`` section specifies the data exposed to users, frontends or administrators. + +The following keys are used by the Web frontend: + +url_prefix + The internal URL where the app will be served. +web_label + Label which will be used in the Web frontend. +web_url_path + Public facing URL path which will be used in the Web frontend to access the app. +web_external + If True, the Web frontend will open the app in a separated page instead of embedding + it. +front_url + Whole URL to access the app, useful when it has its own domain or subdomain. The + ``https`` scheme is added if not specified. + +Ports and generated passwords are exposed in dicts, respectively at the ``ports`` and +``passwords`` keys. Each entry of ``ports`` maps a name to the compose ``service`` and +``private`` (container) port, it is resolved to the exposed ``host:port`` when the app is +running. Each entry of ``passwords`` maps a name to the list of keys leading to the value +to expose in the application data. + +``url_prefix`` and ``front_url`` can also be specified as a list of keys leading to the +value in the application data. .. _libervia-app_yaml-tags: @@ -116,32 +146,49 @@ The following YAML tags can be used in the Libervia App configuration file: .. _libervia-app_conf: !libervia_conf -++++++++++++++ +^^^^^^^^^^^^^^ -Get a value from Libervia configuration. A list is expected with either: +Get a value from the Libervia configuration. It takes a list of 1 to 4 items: -- `name` of a config parameter -- `section` and `name` of a config parameter -- `section`, `name`, and `default` value of a config parameter -- `section`, `name`, `default` value, and `filter` (either `first` or `not`) +- ``[NAME]``: the ``NAME`` option of the ``[common]`` section +- ``[SECTION, NAME]``: the ``NAME`` option of the section named ``SECTION`` +- ``[SECTION, NAME, DEFAULT]``: use ``DEFAULT`` if the option is not set +- ``[SECTION, NAME, DEFAULT, FILTER]``: apply a filter to the value (see below) -Filter options: +The ``application.`` section refers to the configuration section of the +```` application (e.g. ``application.peertube``). -- `first`: get the first item of the value -- `not`: get the opposite value (to be used with booleans) +Available filters: -Example -------- -Getting a value from Libervia configuration: +- ``first``: get the first item of the value +- ``not``: get the opposite value (to be used with booleans) + +Examples +~~~~~~~~ +Getting a value from an application configuration section: .. code-block:: yaml - !libervia_conf [section, name, default] + !libervia_conf [application.peertube, hostname] + +Getting the SMTP server from the ``[email]`` section (an empty default is +used when the option is not set): + +.. code-block:: yaml + + !libervia_conf [email, server, ""] + +Getting the first email of the ``[common]`` section admin list (using the +``first`` filter): + +.. code-block:: yaml + + !libervia_conf [common, email_admins_list, "", first] .. _libervia-app_generate_pwd: !libervia_generate_pwd -+++++++++++++++++++++ +^^^^^^^^^^^^^^^^^^^^^^ Generate a password and store it in persistent data. If the password has already been generated previously, it is reused. @@ -152,7 +199,7 @@ Arguments: - `size` (int): Size of the password to generate. Default to 30 Example -------- +~~~~~~~ Generating a password named ``some_password`` with a size of 32 characters: .. code-block:: yaml @@ -162,35 +209,23 @@ Generating a password named ``some_password`` with a size of 32 characters: .. _libervia-app_param: !libervia_param -++++++++++++++ +^^^^^^^^^^^^^^^ Get a parameter specified when starting the application. The value can be either the name of the parameter to get, or a list as `[name, default_value]`. - - Available parameters: url_prefix - The internal URL where the app will be served. -web_label - Label which will be used in Web frontend. -web_url_path - Public facing URL path which will be used in the web frontend to access the app. -web_external - If True, the web frontend will open the app in a separated page instead of embedding - it. -front_url - Whole URL to access the app. It is specified when the app has its own domain or - subdomain. + The internal URL where the app will be served, it is given by the Web frontend. +front_url_path + The URL path which will be used in the Web frontend to access the app. Example -------- +~~~~~~~ Getting a parameter: .. code-block:: yaml !libervia_param [url_prefix, /some_app] -.. _PyYAML: https://pyyaml.org/ - From b51b9421038f0f07935b5b20f8338d5cd770993e Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 20:37:11 +0100 Subject: [PATCH 03/10] doc (how-tos/email gateway): update the email gateway how-to: pdate the gateway how-to to follow changes in configuration: - configure the component with the simplifiedi configuration (`[component.email_gateway]`) - remove the "supported features" section (redundant with email gateway documentation in `components.rst`) --- doc/howto_backup.rst | 2 +- doc/howto_email_gateway.rst | 134 +++++++++++++----------------------- 2 files changed, 47 insertions(+), 89 deletions(-) diff --git a/doc/howto_backup.rst b/doc/howto_backup.rst index 22bc4392..192aeb03 100644 --- a/doc/howto_backup.rst +++ b/doc/howto_backup.rst @@ -20,7 +20,7 @@ A backup is made of data *types*, included or excluded with ``-i/--include`` and - ``private``: private storage: PEP-like client data, OMEMO crypto keys, PGP trust state - ``roster``: contact lists (from the roster cache in the database, kept up to date on every connection) -- ``pep``: client PEP nodes and items (nick, avatar metadata, ...) +- ``pep``: client PEP nodes and items (nick, avatar metadata, …) - ``gpg``: GPG home per profile (public and secret keys) - ``files``: shared files (the blobs of ``~/.sat/files/``) - ``db``: full copy of the database (not portable, SQLite only) diff --git a/doc/howto_email_gateway.rst b/doc/howto_email_gateway.rst index d0c21eab..19e43539 100644 --- a/doc/howto_email_gateway.rst +++ b/doc/howto_email_gateway.rst @@ -4,18 +4,23 @@ How-To: Email Gateway ===================== -This document explains how to configure and use the Libervia Email Gateway component. **Note that the Libervia backend must be installed first** (see :ref:`installation`). +This document explains how to configure and use the Libervia Email Gateway component. -The Email Gateway component allows users to send and receive emails through XMPP clients and convert mailing list to XMPP forums. It connects to IMAP/SMTP servers using credentials provided by users during registration. +The Email Gateway component allows users to send and receive emails through XMPP clients +and connect mailing lists to XMPP forums. It connects to IMAP/SMTP servers using +credentials provided by users during registration. + +For the supported features and other details, refer to the :ref:`email-gateway` section +of the components documentation. Prerequisites ------------- Before configuring the Email Gateway, ensure you have: -1. Libervia backend installed and running (see :ref:`installation`) -2. An XMPP server configured with component support (such as Prosody, Ejabberd, Openfire, - Tigase, MongooseIM, etc). +1. Libervia backend installed and working (see :ref:`installation`) +2. An XMPP server configured with component support (such as Prosody, Ejabberd, + Openfire, Tigase, MongooseIM, etc). Configuration Steps ------------------- @@ -30,7 +35,7 @@ Configuration Steps -- Add to /etc/prosody/prosody.cfg.lua Component "email.example.org" - component_secret = "your_unique_secret" + component_secret = "secret" *For Ejabberd:* @@ -46,30 +51,26 @@ Configuration Steps module: ejabberd_service hosts: "email.example.org": - password: "your_unique_secret" + password: "secret" - **Important:** Replace `email.example.org` with your actual domain and `your_unique_secret` with a secure random string (you may use `openssl rand -hex 16` to generate). + Of course replace ``email.example.org`` with your actual domain and ``secret`` with a + secure random string (you may use ``openssl rand -hex 16`` to generate one). -2. **Create Libervia Profile for Email Gateway** +2. **Configure the Email Gateway Component in Libervia** - Use the Libervia CLI to create the gateway profile: + Add the following section to your Libervia configuration file (usually + ``libervia.toml``), using the same JID and shared secret as the ones configured on + your XMPP server: - .. code-block:: bash + .. code-block:: toml - $ li profile create email-gateway \ - -j email.example.org \ - -p "" \ - -x "your_unique_secret" \ - -C email-gateway \ - -A + [component.email_gateway] + jid = "email.example.org" + secret = "secret" - This creates a profile named `email-gateway` with: - - - JID: `email.example.org` - - No profile password (`-p ""`), to avoid password prompts during Libervia backend startup. - - XMPP server shared secret (`-x "your_unique_secret"`) - - Component entry point (`-C email-gateway`) - - Auto-connect enabled (`-A`) + The component profile is then created automatically on backend startup, and the + component starts automatically. See :ref:`components` for details about the + components configuration. 3. **User Registration** @@ -81,7 +82,7 @@ Configuration Steps .. code-block:: bash - $ li gateway register -cp your_user_profile email.example.org + $ li gateway register -c -p your_user_profile email.example.org **Registration Fields** @@ -117,55 +118,16 @@ Configuration Steps SMTP Password Password for SMTP authentication - **Important Notes** + .. note:: - - Ensure your IMAP/SMTP servers are accessible from the Libervia backend - - The IMAP server must support the IDLE extension for real-time notifications + - Ensure your IMAP/SMTP servers are accessible from the Libervia backend + - The IMAP server must support the IDLE extension for real-time notifications Configuration ------------- -The Email Gateway uses user-specific registration data - **no additional configuration is needed** in Libervia's main config file. All settings are stored per-user during registration. - -Supported Features ------------------- - -- **Normal Email Conversion**: Standard email messages are converted to XMPP chat - messages. Each email appears as a regular message in the user's XMPP client. - -- **Mailing List Handling**: Mailing list emails are converted to PubSub blog nodes - (microblogging) using XEP-0277: - - * Each mailing list becomes a separate pubsub node - * Each email in the list becomes a blog item - * Replies to specific topics appear as comments to the corresponding item - * Title tags within square brackets (e.g., `[tag]`) are converted to blog categories - * Only the recipient JID associated with the mailing list can access the pubsub node by - default. - -- **Extended Addressing Support**: Properly handles email addressing metadata (To, CC, - BCC) via XEP‑0033, converting it into XMPP addressing information for correct delivery. - This allows multi‑party discussions without relying on group chat (XEP‑0045 MUC), which - can be tricky to implement in email‑based communication. - -- **Header Conversion**: Converts important email headers to XMPP metadata, such as: - - * Keywords → XMPP "Keywords" header (XEP-0131) - * Importance → XMPP "Urgency" header (XEP-0131) - -- **Message Cleaning**: Automatically detects noisy elements in mailing list emails - (quoted replies, signatures) and tags them with a CSS class named "noise" so XMPP - clients can selectively hide or visually distinguish these parts of the message. - -- **Attachment Management**: Allows users to retrieve and delete email attachments via - PubSub File Sharing (XEP-0498). - - .. note:: - Attachments cannot yet be sent with messages (this - functionality is in development). - -- **End-to-End Encryption**: Supports Gateway Relayed Encryption (not yet an official XEP) - for encrypted email messages. Requires XMPP clients that implement this specification. +Besides the component configuration described above, no other configuration is needed: +the gateway uses user-specific data provided during registration. Troubleshooting --------------- @@ -178,37 +140,33 @@ Troubleshooting - Solution * - XMPP component connection failed - Verify the shared secret matches on **both** Libervia and the XMPP server. - The component JID must *exactly* match the JID configured on the XMPP server (e.g. - “email.example.org” in Prosody must match the JID you set when creating the - Libervia profile). + The component JID must *exactly* match the JID configured on the XMPP server + (e.g. "email.example.org" in Prosody must match the JID you set in the Libervia + configuration). * - User registration fails - - * Check the IMAP/SMTP credentials and make sure the servers are reachable from the + - * Check the IMAP credentials and make sure the IMAP server is reachable from the Libervia host. * Verify that the IMAP server supports the **IDLE** extension (required for - real‑time notifications). - * If you use self‑signed certificates, add them to the system trust store. + real-time notifications). + * If you use self-signed certificates, add them to the system trust store. * - Email not received - - * Confirm the user’s email credentials (you can re-register to check). + - * Confirm the user's email credentials (you can re-register to check). * Check Libervia Backend logs to ensure that the IMAP connection succeeded. - * Verify that messages are arriving in the user’s INBOX. - * - “DBus not launched” error - - Follow :ref:`installation_dbus` instructions for headless servers – the - Libervia backend relies on D‑Bus to run. + * Verify that messages are arriving in the user's INBOX. + * - Email not sent + - * Check the SMTP credentials and make sure the SMTP server is reachable from the + Libervia host. + * Check the Libervia backend logs for the sending error. * - Attachments not appearing - The Email Gateway can *retrieve* attachments, but does not yet deliver them with outgoing messages (feature work in progress). * - Encrypted emails not working - - “Gateway Relayed Encryption” is a work‑in‑progress extension (not yet an official XEP). - Use a client that implements this specification. + - "Gateway Relayed Encryption" is a work-in-progress extension (not yet an official + XEP). Use a client that implements this specification. * - Mailing lists not converted - - Verify that the incoming e‑mail contains a **valid “List‑ID”** header. The value + - Verify that the incoming e-mail contains a **valid "List-ID"** header. The value must match the format expected for PubSub node creation. .. note:: The Email Gateway is under active development. Some features may have limitations. - -See Also --------- - -- For Libervia components overview: :ref:`components` From 40fef895ba1e04c162aad493dc428b993a54d5ba Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 20:57:37 +0100 Subject: [PATCH 04/10] doc (cli): add isolation (`-i/--isolation`) documentation --- doc/libervia-cli/index.rst | 47 +++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/doc/libervia-cli/index.rst b/doc/libervia-cli/index.rst index fe3ea519..0da081aa 100644 --- a/doc/libervia-cli/index.rst +++ b/doc/libervia-cli/index.rst @@ -37,37 +37,29 @@ After the commands come the options. For instance if you want to send a message, get the available options with ``--help`` as explained above:: $ li message send --help - usage: li message send [-h] [-p PROFILE] [--pwd PASSWORD] [-c] [-l LANG] [-s] - [-n] [-S SUBJECT] [-L SUBJECT_LANG] + usage: li message send [-h] [-p PROFILE] [--pwd PASSWORD] [-c] [-t {chat,error,groupchat,headline,normal,auto}] - [-e ALGORITHM] [--encrypt-noreplace] [-x | -r] + [-e ALGORITHM] [--encrypt-noreplace] […] jid positional arguments: jid the destination jid - optional arguments: + options: -h, --help show this help message and exit - -p PROFILE, --profile PROFILE + -p, --profile PROFILE Use PROFILE profile key (default: @DEFAULT@) --pwd PASSWORD Password used to connect profile, if necessary -c, --connect Connect the profile before doing anything else - -l LANG, --lang LANG language of the message - -s, --separate separate xmpp messages: send one message per line - instead of one message alone. - -n, --new-line add a new line at the beginning of the input (usefull - for ascii art ;)) - -S SUBJECT, --subject SUBJECT - subject of the message - -L SUBJECT_LANG, --subject_lang SUBJECT_LANG - language of subject - -t {chat,error,groupchat,headline,normal,auto}, --type {chat,error,groupchat,headline,normal,auto} + -t, --type {chat,error,groupchat,headline,normal,auto} type of the message - -e ALGORITHM, --encrypt ALGORITHM + -e, --encrypt ALGORITHM encrypt message using given algorithm - --encrypt-noreplace don't replace encryption algorithm if an other one is + --encrypt-noreplace don't replace encryption algorithm if another one is already used - -x, --xhtml XHTML body + […] + +The output above has been shortened: use ``--help`` to see the full list of options. If you want to send a message to, say, ``pierre@example.net``, and encrypt it with OMEMO, just do the following:: @@ -77,6 +69,25 @@ just do the following:: (note that with OMEMO, you need to have previously validated fingerprint of your contact for this to work). +.. _li_isolation: + +Isolation +--------- + +Several Libervia instances can run side by side, each with its own data, this is called +isolation (see :ref:`configuration_isolation` for details). The instance to work with is +selected with the ``-i/--isolation NAME`` option, which must be used before the command: + +.. code-block:: console + + $ li -i pubsub profile list + +.. note:: + + If the ``LIBERVIA_ISOLATION`` environment variable is already set, this option is not + needed, unless you want to work with another instance (the option takes priority over + the environment variable). + The different commands are explained in dedicated sections. .. toctree:: From 441e9cec52023283d3783efe6c01dd1fcdfd3195 Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 20:57:59 +0100 Subject: [PATCH 05/10] doc: minors updates --- doc/libervia-cli/backup.rst | 2 +- doc/libervia-cli/common_arguments.rst | 2 +- doc/libervia-cli/gateway.rst | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/libervia-cli/backup.rst b/doc/libervia-cli/backup.rst index 50cb5945..11313b50 100644 --- a/doc/libervia-cli/backup.rst +++ b/doc/libervia-cli/backup.rst @@ -11,7 +11,7 @@ Libervia-specific sections, so a backup remains readable by future Libervia vers A backup is made of data *types*, selected with ``-i/--include`` and ``-e/--exclude`` (repeatable): ``params``, ``private``, ``roster``, ``pep``, ``gpg``, ``files`` and ``db`` (full copy of the database, included by default on export). A backup is complete by -default (passwords, OMEMO keys, GPG secret keys...), so the archive should be kept +default (passwords, OMEMO keys, GPG secret keys…), so the archive should be kept private: it is written with mode 0600, and ``--encrypt`` can be used for untrusted storage. diff --git a/doc/libervia-cli/common_arguments.rst b/doc/libervia-cli/common_arguments.rst index 38fdca40..1c9fcc30 100644 --- a/doc/libervia-cli/common_arguments.rst +++ b/doc/libervia-cli/common_arguments.rst @@ -38,7 +38,7 @@ The common arguments for profile are: history of your shell. If the profile password is needed and not specified, it will be prompted (with echo disabled). If you are on a shared machine or if anybody can access your shell history at some point, you should either connect first your profile with an - other frontend (Primitivus for instance), or avoid the ``--pwd`` option and use the + other frontend (Libervia TUI for instance), or avoid the ``--pwd`` option and use the prompt instead. .. _pubsub_common: diff --git a/doc/libervia-cli/gateway.rst b/doc/libervia-cli/gateway.rst index 31799ed2..a8417a69 100644 --- a/doc/libervia-cli/gateway.rst +++ b/doc/libervia-cli/gateway.rst @@ -1,6 +1,6 @@ -========================== +=========================== gateway: Gateway management -========================== +=========================== ``gateway`` command allows you to list, register, and unregister from gateways (usually used to access other networks like ActivityPub or Email) on an XMPP server. From 0805f77f45da088fd16c8e538dec16c71c710c21 Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 20:59:54 +0100 Subject: [PATCH 06/10] doc (overview, libervia-tui): rename "Primitivus" to "Libervia TUI": The TUI documentation was still using the legacy name. The reference in the overview is also commented out, as the TUI is temporarily broken. --- doc/libervia-tui/index.rst | 26 +++++++++++++------------- doc/overview.rst | 25 +++++++++++++------------ 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/doc/libervia-tui/index.rst b/doc/libervia-tui/index.rst index d4c77a5e..132734a5 100644 --- a/doc/libervia-tui/index.rst +++ b/doc/libervia-tui/index.rst @@ -1,26 +1,26 @@ -.. _primitivus-documentation: +.. _libervia-tui-documentation: -========== -Primitivus -========== +============ +Libervia TUI +============ -``Primitivus`` is the TUI (Terminal User Interface) frontend of Libervia +``Libervia TUI`` is the TUI (Terminal User Interface) frontend of Libervia Overview ======== -``Primitivus`` is a text based frontend. It is specially adapted for systems without +``Libervia TUI`` is a text based frontend. It is specially adapted for systems without graphical environments (e.g. servers), for low bandwidth remote shells (e.g. ssh) or for people who like straightforward interfaces without distracting images or animations. -For the moment, Primitivus implements one 2 one chat, group chat (also called *MUC* for +For the moment, Libervia TUI implements one 2 one chat, group chat (also called *MUC* for Multi-Users Chat), and some related features (end 2 end encryption, bookmarks, gateways interaction, file sending, etc.). Usage ===== -Primitivus is modal (vi-like), one can switch from one mode to another +Libervia TUI is modal (vi-like), one can switch from one mode to another in the same way as in vi/vim: - From any mode, press ``[Esc]`` to switch to normal mode. @@ -28,7 +28,7 @@ in the same way as in vi/vim: - From normal mode, press ``i`` to switch to insert mode – the one you use to write messages to your contacts. -Primitivus can be handled either with the mouse, in a very intuitive way, or with the +Libervia TUI can be handled either with the mouse, in a very intuitive way, or with the keyboard. Below, the keyboard shortcuts are explained. Keyboard handling @@ -46,8 +46,8 @@ Main keys ``[Tab]`` and/or ``CTRL + up/down arrows`` change focus -``CTRL + X`` - quit Primitivus +``CTRL + X`` + quit Libervia TUI ``CTRL + F, 1`` or ``CTRL + F, M`` focus on the menu ``CTRL + F, 2`` or ``CTRL + F, B`` @@ -128,12 +128,12 @@ The following commands must be typed in command mode (type ``[Esc]`` to switch t mode, then the ``:`` at the beginning of the command will switch to command mode). ``:quit`` - quit Primitivus + quit Libervia TUI ``:messages`` display log messages (see also the logging configuration) .. - FIXME: :presence and :status are currently disabled in Primitivus + FIXME: :presence and :status are currently disabled in Libervia TUI ``:presence [status]`` set your presence status. Invoked without argument, a pop-up will allow you to choose your presence status; otherwise, you can use the following arguments: - ``online`` diff --git a/doc/overview.rst b/doc/overview.rst index 7c082a57..ab4ce36a 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -113,24 +113,25 @@ The `official Libervia website`_ is made with Libervia web framework. .. _Jinja2: http://jinja.pocoo.org/ .. _official Libervia website: https://salut-a-toi.org -Libervia TUI (Primitivus) -------------------------- +Libervia TUI +------------ -Libervia TUI (alias ``Primitivus``) is the Terminal User Interface. In other words, it -works in console and is intended for people at ease with it. Its text only interface has -several advantages: you get rid of many distracting things (like images), it works without -graphical environment installed (which is often the case on servers) and it works on -distant shell (like `ssh`_) while staying gentle with your bandwidth. +Libervia TUI is the Terminal User Interface. In other words, it works in console and is +intended for people at ease with it. Its text only interface has several advantages: you +get rid of many distracting things (like images), it works without graphical environment +installed (which is often the case on servers) and it works on distant shell (like `ssh`_) +while staying gentle with your bandwidth. -Primitivus is shipped with the backend, so it should be always available once Libervia is -installed (but some distributions may provide it separately). +Libervia TUI is shipped with the backend, so it should be always available once Libervia +is installed (but some distributions may provide it separately). -You can check :doc:`Libervia TUI documentation ` for more details. +.. The TUI is temporarily on hold and broken, its documentation is not +.. included in the build for the moment. .. _ssh: https://en.wikipedia.org/wiki/Secure_Shell -Libervia CLI (jp) ------------------ +Libervia CLI +------------ Libervia CLI (alias ``li`` or ``jp``) is the Command Line Interface. It's a powerful tool which allows to do nearly everything you can do with other frontends. From 1003cd2c6db236b8a02d4675e756a85ff07b0ec0 Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 21:01:23 +0100 Subject: [PATCH 07/10] doc (conf.py): update project name and exclude the TUI documentation --- doc/conf.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 9224fa1e..d8ff1d9b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -60,6 +60,9 @@ rst_prolog = """ # ones. extensions = ["sphinx.ext.autodoc", "docstring", "sphinxcontrib.autodoc_pydantic"] +# do not show pydantic field constraints (custom validators are not meaningful here) +autodoc_pydantic_field_show_constraints = False + # Add any paths that contain templates here, relative to this directory. templates_path = [".templates"] @@ -82,7 +85,8 @@ language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [".build", "Thumbs.db", ".DS_Store"] +# the TUI is temporarily on hold and broken, its documentation is excluded +exclude_patterns = [".build", "Thumbs.db", ".DS_Store", "libervia-tui"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None @@ -120,7 +124,7 @@ html_static_path = [".static"] # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = "SalutToidoc" +htmlhelp_basename = "LiberviaBackenddoc" # -- Options for LaTeX output ------------------------------------------------ @@ -144,7 +148,13 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "SalutToi.tex", "Libervia Documentation", "Libervia team", "manual"), + ( + master_doc, + "LiberviaBackend.tex", + "Libervia Backend Documentation", + "Libervia team", + "manual", + ), ] @@ -163,10 +173,10 @@ man_pages = [(master_doc, "saluttoi", "Libervia Documentation", [author], 1)] texinfo_documents = [ ( master_doc, - "SalutToi", - "Libervia Documentation", + "LiberviaBackend", + "Libervia Backend Documentation", author, - "SalutToi", + "LiberviaBackend", "One line description of project.", "Miscellaneous", ), From e8b3948ad0a854eb72f0092cee65b020fc7e6342 Mon Sep 17 00:00:00 2001 From: Goffi Date: Tue, 3 Nov 2026 23:06:54 +0100 Subject: [PATCH 08/10] plugin misc account: fix `send_email` calls + admin email flag: The `send_email` calls in `send_emails` passed the plugin `EmailSettings` as the `config` argument, raising `AttributeError` on account creation and leading to the profile deletion. They now pass the main config via a new `email_settings`. `credentials_xmpp_connect` takes a new `send_email` flag to let frontends skip the admin notification email (notably useful in web local mode). --- .../backend/plugins/plugin_misc_account.py | 18 +++++++++++++----- libervia/backend/tools/common/email.py | 19 +++++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/libervia/backend/plugins/plugin_misc_account.py b/libervia/backend/plugins/plugin_misc_account.py index d6d904dd..6d944ad2 100644 --- a/libervia/backend/plugins/plugin_misc_account.py +++ b/libervia/backend/plugins/plugin_misc_account.py @@ -145,7 +145,7 @@ class MiscAccount(object): host.bridge.add_method( "credentials_xmpp_connect", ".plugin", - in_sign="ss", + in_sign="ssb", out_sign="b", method=self.credentials_xmpp_connect, async_=True, @@ -293,11 +293,11 @@ class MiscAccount(object): # there is no email when an existing XMPP account is used body = f"New account created on {domain}: {profile} [{email or ''}]" d_admin = sat_email.send_email( - self.get_email_conf(), self.host.memory.config, admins_emails, subject, body, + email_settings=self.get_email_conf(), ) admins_emails_txt = ", ".join(["<" + addr + ">" for addr in admins_emails]) @@ -326,7 +326,13 @@ class MiscAccount(object): # XXX: this will not fail when the email address doesn't exist # FIXME: check email reception to validate email given by the user # FIXME: delete the profile if the email could not been sent? - d_user = sat_email.send_email(self.get_email_conf, [email], subject, body) + d_user = sat_email.send_email( + self.host.memory.config, + [email], + subject, + body, + email_settings=self.get_email_conf(), + ) d_user.addCallbacks( lambda __: log.debug( "Account creation confirmation sent to <{}>".format(email) @@ -689,12 +695,13 @@ class MiscAccount(object): d.addCallbacks(deleted, errback) return d - def credentials_xmpp_connect(self, jid_s, password): + def credentials_xmpp_connect(self, jid_s, password, send_email=True): """Create and connect a new SàT profile using the given XMPP credentials. Re-use given JID and XMPP password for the profile name and profile password. @param jid_s (unicode): JID @param password (unicode): XMPP password + @param send_email (bool): send the administrator notification email @return Deferred(bool) @raise exceptions.PasswordError, exceptions.ConflictError """ @@ -716,7 +723,8 @@ class MiscAccount(object): ) def connected(result): - self.send_emails(None, profile=jid_s) + if send_email: + self.send_emails(None, profile=jid_s) return result def remove_profile( diff --git a/libervia/backend/tools/common/email.py b/libervia/backend/tools/common/email.py index 2a7be733..cfad14f0 100644 --- a/libervia/backend/tools/common/email.py +++ b/libervia/backend/tools/common/email.py @@ -34,6 +34,7 @@ def send_email( subject: str = "", body: str = "", from_email: str | None = None, + email_settings: tools_config.EmailSettings | None = None, ) -> defer.Deferred: """Send an email using Libervia configuration @@ -43,12 +44,14 @@ def send_email( @param subject: subject of the message @param body: body of the message @param from_email: address of the sender + @param email_settings: ``EmailSettings`` to use, default to ``config.email`` @return:Same as smtp.sendmail. """ if isinstance(to_emails, str): to_emails = to_emails.split() - email_host = config.email.server - email_from = from_email or config.email.from_ + settings = email_settings or config.email + email_host = settings.server + email_from = from_email or settings.from_ # we suppose that email domain and XMPP domain are identical domain = config.common.xmpp_domain @@ -60,12 +63,12 @@ def send_email( if email_from is None: email_from = "no_reply@" + domain - email_sender_domain = config.email.sender_domain or domain - email_port = config.email.port - email_username = config.email.username - email_password = config.email.password - email_auth = config.email.auth - email_starttls = config.email.starttls + email_sender_domain = settings.sender_domain or domain + email_port = settings.port + email_username = settings.username + email_password = settings.password + email_auth = settings.auth + email_starttls = settings.starttls msg = MIMEText(body, "plain", "UTF-8") msg["Subject"] = subject From d3dc59b1410b2a6c6054aaec866aaf102799ebc1 Mon Sep 17 00:00:00 2001 From: Goffi Date: Wed, 4 Nov 2026 17:45:14 +0100 Subject: [PATCH 09/10] quick_frontend: Q&D adapter to use legacy callback/errback with modern bridges --- .../frontends/quick_frontend/quick_app.py | 7 +- .../frontends/quick_frontend/quick_bridge.py | 118 ++++++++++++++++++ .../quick_frontend/quick_profile_manager.py | 13 +- 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 libervia/frontends/quick_frontend/quick_bridge.py diff --git a/libervia/frontends/quick_frontend/quick_app.py b/libervia/frontends/quick_frontend/quick_app.py index 88cc9e72..418b9afc 100644 --- a/libervia/frontends/quick_frontend/quick_app.py +++ b/libervia/frontends/quick_frontend/quick_app.py @@ -349,7 +349,12 @@ class QuickApp: log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_)) def on_bridge_connected(self): - self.bridge.ready_get(self.on_backend_ready) + d = self.bridge.ready_get() + d.addErrback(self._ready_get_eb) + d.addCallback(lambda __: self.on_backend_ready()) + + def _ready_get_eb(self, failure_): + log.error("Can't check backend readiness: {msg}".format(msg=failure_)) def _bridge_cb(self): self.register_signal("connected") diff --git a/libervia/frontends/quick_frontend/quick_bridge.py b/libervia/frontends/quick_frontend/quick_bridge.py new file mode 100644 index 00000000..53999743 --- /dev/null +++ b/libervia/frontends/quick_frontend/quick_bridge.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +# Libervia: an XMPP client +# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Adapte the legacy callback/errback bridge API to bridges""" + +import asyncio +import inspect +from typing import Any, Callable + +from twisted.internet import defer +from twisted.python.failure import Failure + +from libervia.backend.core.log import getLogger + +log = getLogger(__name__) + + +class QuickBridgeAdapter: + """Adapt legacy callback/errback to bridges + + @param bridge: bridge instance + """ + + def __init__(self, bridge: Any) -> None: + self._bridge = bridge + + def __getattr__(self, name: str) -> Any: + attr = getattr(self._bridge, name) + if not callable(attr): + return attr + + def wrapper(*args: Any, **kwargs: Any) -> Any: + callback = kwargs.pop("callback", None) + errback = kwargs.pop("errback", None) + result = attr(*args, **kwargs) + if inspect.iscoroutine(result): + result = asyncio.ensure_future(result) + if isinstance(result, asyncio.Future): + # async bridges (e.g. AIOBridge) return asyncio futures + result = defer.Deferred.fromFuture(result) + if (callback is None and errback is None) or not hasattr( + result, "addCallback" + ): + return result + if callback is not None: + result.addCallback(callback) + if errback is not None: + result.addErrback(errback) + return result + + return wrapper + + def register_signal(self, name: str, handler: Callable, iface: str = "core") -> None: + """Register a signal handler, adapting sync handlers to async bridges + + @param name: signal name + @param handler: signal handler, sync or async + @param iface: bridge interface ("core" or "plugin") + """ + if not inspect.iscoroutinefunction(handler): + + async def async_handler(*args: Any, + _handler: Callable = handler) -> None: + _handler(*args) + + handler = async_handler + + try: + self._bridge.register_signal(name, handler, iface) + except AttributeError: + log.warning(f"Signal {name} is not available on the bridge") + + def bridge_connect(self, callback: Callable | None = None, + errback: Callable | None = None) -> None: + """Connect to the bridge + + @param callback: called when the bridge is connected + @param errback: called with a twisted Failure on connection error + """ + + async def _connect() -> None: + try: + await self._bridge.bridge_connect() + except Exception as e: + if errback is not None: + errback(Failure(e)) + else: + if callback is not None: + callback() + + try: + asyncio.get_running_loop() + except RuntimeError: + # the event loop is not running yet (frontends usually call connect_bridge() + # before starting their loop) + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.call_soon(asyncio.ensure_future, _connect()) + else: + asyncio.ensure_future(_connect()) diff --git a/libervia/frontends/quick_frontend/quick_profile_manager.py b/libervia/frontends/quick_frontend/quick_profile_manager.py index 880ee6eb..90631e23 100644 --- a/libervia/frontends/quick_frontend/quick_profile_manager.py +++ b/libervia/frontends/quick_frontend/quick_profile_manager.py @@ -233,9 +233,16 @@ class QuickProfileManager(object): def refill_profiles(self): """Rebuild the list of profiles""" - profiles = self.host.bridge.profiles_list_get() - profiles.sort() - self.set_profiles(profiles) + self.host.bridge.profiles_list_get( + callback=self._profiles_loaded, + errback=self._profiles_list_eb, + ) + + def _profiles_loaded(self, profiles): + self.set_profiles(sorted(profiles)) + + def _profiles_list_eb(self, failure_): + log.error("Can't get profiles list: {msg}".format(msg=failure_)) ## Method which must be implemented by frontends ## From e5ed4e3d7c72f01d84cd2c8af4bc8d93e52e8aeb Mon Sep 17 00:00:00 2001 From: Goffi Date: Wed, 4 Nov 2026 17:59:56 +0100 Subject: [PATCH 10/10] tui: Q&D adaptation to new code: use the new `quick_bridge` adapter and do few changes to make TUI work with new backend. TUI will probably be rewritten at some point, this is just an update to keep it working. `urwid` has been updated to have a version working with Python 3.14. --- libervia/tui/base.py | 95 ++++++++++++++++++++++----------- libervia/tui/game_tarot.py | 7 ++- libervia/tui/notify.py | 5 +- libervia/tui/profile_manager.py | 6 +-- libervia/tui/progress.py | 37 +++++++------ libervia/tui/status.py | 58 +++++++++++--------- pyproject.toml | 2 +- 7 files changed, 134 insertions(+), 76 deletions(-) diff --git a/libervia/tui/base.py b/libervia/tui/base.py index 7b3a4daa..2f2a7724 100644 --- a/libervia/tui/base.py +++ b/libervia/tui/base.py @@ -28,9 +28,10 @@ from libervia.backend.core import log as logging log = logging.getLogger(__name__) from libervia.backend.tools import config as tools_config import urwid -from urwid.util import is_wide_char +from urwid import is_wide_char from urwid_satext import sat_widgets from libervia.frontends.quick_frontend.quick_app import QuickApp +from libervia.frontends.quick_frontend import quick_bridge from libervia.frontends.quick_frontend import quick_utils from libervia.frontends.quick_frontend import quick_chat from libervia.tui.profile_manager import ProfileManager @@ -45,6 +46,7 @@ from libervia.tui.keys import action_key_map as a_key from libervia.frontends.tools.misc import InputHistory from libervia.backend.tools.common import dynamic_import from libervia.frontends.tools import jid +import asyncio import signal import sys @@ -67,10 +69,19 @@ class TUISettings(tools_config.FrontendSettings, key="tui"): ## bridge handling # we get bridge name from conf and initialise the right class accordingly main_config = tools_config.parse_main_conf(ignore_cache=True) -bridge_name = next(iter(main_config.common.bridges)).type +bridge_name = next(iter(main_config.bridges)).type if "dbus" not in bridge_name: print("only D-Bus bridge is currently supported") sys.exit(3) +# install the asyncio twisted reactor +try: + asyncio.get_event_loop() +except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) +from twisted.internet import asyncioreactor + +asyncioreactor.install() class EditBar(sat_widgets.ModalEdit): @@ -338,9 +349,7 @@ class LiberviaTUIApp(QuickApp, InputHistory): AVATARS_HANDLER = False def __init__(self): - bridge_module = dynamic_import.import_bridge( - bridge_name, "libervia.frontends.bridge" - ) + bridge_module = dynamic_import.import_bridge(bridge_name) if bridge_module is None: log.error("Can't import {} bridge".format(bridge_name)) sys.exit(3) @@ -348,16 +357,18 @@ class LiberviaTUIApp(QuickApp, InputHistory): log.debug("Loading {} bridge".format(bridge_name)) QuickApp.__init__( self, - bridge_factory=bridge_module.bridge, + # AIOBridge is the only bridge compatible with the asyncio loop + bridge_factory=lambda: quick_bridge.QuickBridgeAdapter( + bridge_module.AIOBridge() + ), xmlui=xmlui, check_options=quick_utils.check_options, connect_bridge=False, ) self.tui_settings = main_config.get_settings(TUISettings) ## main loop setup ## - event_loop = ( - urwid.GLibEventLoop if "dbus" in bridge_name else urwid.TwistedEventLoop - ) + # the new D-Bus bridge is twisted based (txdbus) + event_loop = urwid.TwistedEventLoop self.loop = urwid.MainLoop( urwid.SolidFill(), C.PALETTE, @@ -373,6 +384,10 @@ class LiberviaTUIApp(QuickApp, InputHistory): def on_bridge_connected(self): ##misc setup## + self._version = None + self.bridge.version_get( + callback=self._version_get_cb, errback=lambda __: None + ) self._visible_widgets = set() self.notif_bar = sat_widgets.NotificationBar() urwid.connect_signal(self.notif_bar, "change", self.on_notification) @@ -400,6 +415,9 @@ class LiberviaTUIApp(QuickApp, InputHistory): self.loop.widget = self.main_widget = ProfileManager(self) self.post_init() + def _version_get_cb(self, version): + self._version = version + @property def visible_widgets(self): return self._visible_widgets @@ -579,7 +597,7 @@ class LiberviaTUIApp(QuickApp, InputHistory): self.__saved_overlay = None elif ( - input_ == a_key["DEBUG"] and ".dev0" in self.bridge.version_get() + input_ == a_key["DEBUG"] and ".dev0" in (self._version or "") ): # Debug only for dev versions self.debug() elif input_ == a_key["CONTACTS_HIDE"]: # user wants to (un)hide the contact lists @@ -614,6 +632,9 @@ class LiberviaTUIApp(QuickApp, InputHistory): def add_menus(self, menu, type_filter, menu_data=None): """Add cached menus to instance + + The menus are retrieved asynchronously from the bridge. + @param menu: sat_widgets.Menu instance @param type_filter: menu type like is sat.core.sat_main.import_menu @param menu_data: data to send with these menus @@ -623,16 +644,24 @@ class LiberviaTUIApp(QuickApp, InputHistory): def add_menu_cb(callback_id): self.action_launch(callback_id, menu_data, profile=self.current_profile) - for id_, type_, path, path_i18n, extra in self.bridge.menus_get( - "", C.NO_SECURITY_LIMIT - ): # TODO: manage extra - if type_ != type_filter: - continue - if len(path) != 2: - raise NotImplementedError("Menu with a path != 2 are not implemented yet") - menu.add_menu( - path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_) - ) + def menus_get_cb(menus): + for id_, type_, path, path_i18n, extra in menus: # TODO: manage extra + if type_ != type_filter: + continue + if len(path) != 2: + raise NotImplementedError( + "Menu with a path != 2 are not implemented yet" + ) + menu.add_menu( + path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_) + ) + + def menus_get_eb(failure): + log.error("Can't get menus: {}".format(failure)) + + self.bridge.menus_get( + "", C.NO_SECURITY_LIMIT, callback=menus_get_cb, errback=menus_get_eb + ) def _build_menu_roller(self): menu = sat_widgets.Menu(self.loop) @@ -1016,19 +1045,25 @@ class LiberviaTUIApp(QuickApp, InputHistory): def on_join_room_request(self, menu): """User wants to join a MUC room""" - pop_up_widget = sat_widgets.InputDialog( - _("Entering a MUC room"), - _("Please enter MUC's JID"), - default_txt=self.bridge.muc_get_default_service(), - ok_cb=self.on_join_room, + + def show_join_dialog(default_service): + pop_up_widget = sat_widgets.InputDialog( + _("Entering a MUC room"), + _("Please enter MUC's JID"), + default_txt=default_service, + ok_cb=self.on_join_room, + ) + pop_up_widget.set_callback( + "cancel", lambda dummy: self.remove_pop_up(pop_up_widget) + ) + self.show_pop_up(pop_up_widget) + + self.bridge.muc_get_default_service( + callback=show_join_dialog, errback=lambda __: show_join_dialog("") ) - pop_up_widget.set_callback( - "cancel", lambda dummy: self.remove_pop_up(pop_up_widget) - ) - self.show_pop_up(pop_up_widget) def on_about_request(self, menu): - self.alert(_("About"), C.APP_NAME + " v" + self.bridge.version_get()) + self.alert(_("About"), C.APP_NAME + " v" + (self._version or "")) # MISC CALLBACKS# diff --git a/libervia/tui/game_tarot.py b/libervia/tui/game_tarot.py index c303ff61..82d4c1f9 100644 --- a/libervia/tui/game_tarot.py +++ b/libervia/tui/game_tarot.py @@ -174,10 +174,13 @@ class Card(TarotCard): return CardDisplayer(self) -class Table(urwid.FlowWidget): +class Table(urwid.Widget): """Represent the cards currently on the table""" + _sizing = frozenset([urwid.Sizing.FLOW]) + def __init__(self): + super().__init__() self.top = self.left = self.bottom = self.right = None def put_card(self, location, card): @@ -313,7 +316,7 @@ class TarotGame(QuickTarotGame, urwid.WidgetWrap): if not winners and not loosers: title = _("Draw game") else: - title = _("You win \o/") if self.player_nick in winners else _("You loose :(") + title = _("You win \\o/") if self.player_nick in winners else _("You loose :(") form = xmlui.create( self.parent.host, xml_data, diff --git a/libervia/tui/notify.py b/libervia/tui/notify.py index f34b7c17..096ac031 100644 --- a/libervia/tui/notify.py +++ b/libervia/tui/notify.py @@ -17,7 +17,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import dbus +try: + import dbus +except ImportError: + dbus = None class Notify(object): diff --git a/libervia/tui/profile_manager.py b/libervia/tui/profile_manager.py index 46ab7411..7d4cac61 100644 --- a/libervia/tui/profile_manager.py +++ b/libervia/tui/profile_manager.py @@ -37,11 +37,11 @@ class ProfileManager(QuickProfileManager, urwid.WidgetWrap): self.pass_wid = sat_widgets.Password(_("Password:"), align="center") style = ["no_first_select"] - profiles = host.bridge.profiles_list_get() - profiles.sort() self.list_profile = sat_widgets.List( - profiles, style=style, align="center", on_change=self.on_profile_change + [], style=style, align="center", on_change=self.on_profile_change ) + # the profiles list is retrieved asynchronously + self.refill_profiles() # new & delete buttons buttons = [ diff --git a/libervia/tui/progress.py b/libervia/tui/progress.py index 9614d8dd..88ef0c3a 100644 --- a/libervia/tui/progress.py +++ b/libervia/tui/progress.py @@ -54,24 +54,31 @@ class Progress(urwid.WidgetWrap, quick_widgets.QuickWidget): def progress_cb(self, loop, data): progress_id, message, profile = data - data = self.host.bridge.progress_get(progress_id, profile) - pbar = self.progress_dict[(progress_id, profile)]["progress"] - if data: - if self.progress_dict[(progress_id, profile)]["state"] == "init": - # first answer, we must construct the bar - self.progress_dict[(progress_id, profile)]["state"] = "progress" - pbar.done = float(data["size"]) - pbar.set_completion(float(data["position"])) - self.update_not_bar() - else: - if self.progress_dict[(progress_id, profile)]["state"] == "progress": - self.progress_dict[(progress_id, profile)]["state"] = "done" - pbar.set_completion(pbar.done) - self.update_not_bar() + def progress_get_cb(data): + entry = self.progress_dict.get((progress_id, profile)) + if entry is None: + # the bar has been removed in the meantime return + pbar = entry["progress"] + if data: + if entry["state"] == "init": + # first answer, we must construct the bar + entry["state"] = "progress" + pbar.done = float(data["size"]) - loop.set_alarm_in(0.2, self.progress_cb, (progress_id, message, profile)) + pbar.set_completion(float(data["position"])) + self.update_not_bar() + else: + if entry["state"] == "progress": + entry["state"] = "done" + pbar.set_completion(pbar.done) + self.update_not_bar() + return + + loop.set_alarm_in(0.2, self.progress_cb, (progress_id, message, profile)) + + self.host.bridge.progress_get(progress_id, profile, callback=progress_get_cb) def _remove_bar(self, progress_id, profile): wid = self.progress_dict[(progress_id, profile)]["full"] diff --git a/libervia/tui/status.py b/libervia/tui/status.py index 2aafca1d..94228701 100644 --- a/libervia/tui/status.py +++ b/libervia/tui/status.py @@ -45,34 +45,44 @@ class StatusBar(urwid.Columns): urwid.connect_signal(self.status, "click", self.on_status_click) def on_presence_click(self, sender=None): - if not self.host.bridge.is_connected( - self.host.current_profile - ): # FIXME: manage multi-profiles - return - options = [commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE] - list_widget = sat_widgets.GenericList( - options=options, - option_type=sat_widgets.ClickableText, - on_click=self.on_change, + def show_presence_popup(connected): + if not connected: + return + options = [ + commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE + ] + list_widget = sat_widgets.GenericList( + options=options, + option_type=sat_widgets.ClickableText, + on_click=self.on_change, + ) + decorated = sat_widgets.LabelLine( + list_widget, sat_widgets.SurroundedText(_("Set your presence")) + ) + self.host.show_pop_up(decorated) + + # FIXME: manage multi-profiles + self.host.bridge.is_connected( + self.host.current_profile, callback=show_presence_popup ) - decorated = sat_widgets.LabelLine( - list_widget, sat_widgets.SurroundedText(_("Set your presence")) - ) - self.host.show_pop_up(decorated) def on_status_click(self, sender=None): - if not self.host.bridge.is_connected( - self.host.current_profile - ): # FIXME: manage multi-profiles - return - pop_up_widget = sat_widgets.InputDialog( - _("Set your status"), - _("New status"), - default_txt=self.status.get_text(), - cancel_cb=lambda _: self.host.remove_pop_up(), - ok_cb=self.on_change, + def show_status_popup(connected): + if not connected: + return + pop_up_widget = sat_widgets.InputDialog( + _("Set your status"), + _("New status"), + default_txt=self.status.get_text(), + cancel_cb=lambda _: self.host.remove_pop_up(), + ok_cb=self.on_change, + ) + self.host.show_pop_up(pop_up_widget) + + # FIXME: manage multi-profiles + self.host.bridge.is_connected( + self.host.current_profile, callback=show_status_popup ) - self.host.show_pop_up(pop_up_widget) def on_change(self, sender=None, user_data=None): new_value = user_data.get_text() diff --git a/pyproject.toml b/pyproject.toml index b9474811..4fa94510 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ dependencies = [ "twisted[tls] >= 22.10", "twomemo >= 1.0.0, < 2", "txdbus", - "urwid >= 1.2.0, < 3", + "urwid >= 4.2.5, < 5", "urwid-satext", "xmlschema < 4.3.1", "tx-xmpp>=0.15",