Compare commits

...
Author SHA1 Message Date
Goffi
e5ed4e3d7c 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.
2026-11-04 18:11:04 +01:00
Goffi
d3dc59b141 quick_frontend: Q&D adapter to use legacy callback/errback with modern bridges 2026-11-04 17:45:14 +01:00
Goffi
e8b3948ad0 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).
2026-11-04 09:47:04 +01:00
Goffi
1003cd2c6d doc (conf.py): update project name and exclude the TUI documentation 2026-11-04 09:47:04 +01:00
Goffi
0805f77f45 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.
2026-11-04 09:47:04 +01:00
Goffi
441e9cec52 doc: minors updates 2026-11-04 09:47:04 +01:00
Goffi
40fef895ba doc (cli): add isolation (-i/--isolation) documentation 2026-11-04 09:47:04 +01:00
Goffi
b51b942103 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`)
2026-11-04 09:47:04 +01:00
Goffi
0c955f6abd 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.
2026-11-04 09:47:04 +01:00
Goffi
fc0a385027 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]`).
2026-11-04 09:47:04 +01:00
23 changed files with 537 additions and 306 deletions

View file

@ -1,21 +1,27 @@
.. _libervia-app-config: .. _libervia-app-config:
================================= Libervia Apps
Libervia App Configuration (YAML) =============
=================================
The Libervia application uses a YAML configuration file to define various aspects of the Libervia Apps are third party software, embedded and integrated in Libervia, usually as
application's behavior, environment, and settings. containers (Podman, Docker).
The file will be parsed by `PyYAML`_ which is a full-featured YAML implementation. Check Each app is defined by a YAML configuration file, which describes how to run it and which
its documentation for details. settings are exposed. App files are named ``libervia_app_<name>.yaml`` and are discovered
from the app manager plugin directories.
Below is the documentation explaining the structure and functionality of each field in the Below is the documentation of each field of the YAML file.
YAML file.
================= -----------------
Root-Level Fields 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`. - ``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`. - ``expose``: Specifies configurations exposed to frontends or administrators. See :ref:`libervia-app_expose`.
================== ------------------
Detailed Sections Detailed Sections
=================== ------------------
.. _libervia-app_type: .. _libervia-app_type:
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: .. _libervia-app_prepare:
@ -46,7 +54,7 @@ prepare
The ``prepare`` section specifies things like git repositories to be cloned before running the application. The ``prepare`` section specifies things like git repositories to be cloned before running the application.
Example Example
------- ~~~~~~~
Cloning the repository at `https://example.org/some/repository.git` to prepare the app: Cloning the repository at `https://example.org/some/repository.git` to prepare the app:
.. code-block:: yaml .. 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. populate that file.
Example Example
------- ~~~~~~~
Creating a file named `settings-override.py` with the content `USE_X_FORWARDED_HOST = True`: Creating a file named `settings-override.py` with the content `USE_X_FORWARDED_HOST = True`:
.. code-block:: yaml .. code-block:: yaml
@ -86,7 +94,7 @@ creates a ``docker-compose.override.yml`` file that will be merged with the defa
<https://docs.docker.com/compose/reference/#specifying-multiple-compose-files>`_. <https://docs.docker.com/compose/reference/#specifying-multiple-compose-files>`_.
Example Example
------- ~~~~~~~
Overriding the `ports` for the `example_app` service to expose port `8080`: Overriding the `ports` for the `example_app` service to expose port `8080`:
.. code-block:: yaml .. code-block:: yaml
@ -102,9 +110,31 @@ Overriding the `ports` for the `example_app` service to expose port `8080`:
Exposing Data Exposing Data
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
The `expose` section specifies the configurations that are exposed to users or frontends. The ``expose`` section specifies the data exposed to users, frontends or administrators.
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 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: .. _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-app_conf:
!libervia_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 - ``[NAME]``: the ``NAME`` option of the ``[common]`` section
- `section` and `name` of a config parameter - ``[SECTION, NAME]``: the ``NAME`` option of the section named ``SECTION``
- `section`, `name`, and `default` value of a config parameter - ``[SECTION, NAME, DEFAULT]``: use ``DEFAULT`` if the option is not set
- `section`, `name`, `default` value, and `filter` (either `first` or `not`) - ``[SECTION, NAME, DEFAULT, FILTER]``: apply a filter to the value (see below)
Filter options: The ``application.<name>`` section refers to the configuration section of the
``<name>`` application (e.g. ``application.peertube``).
- `first`: get the first item of the value Available filters:
- `not`: get the opposite value (to be used with booleans)
Example - ``first``: get the first item of the value
------- - ``not``: get the opposite value (to be used with booleans)
Getting a value from Libervia configuration:
Examples
~~~~~~~~
Getting a value from an application configuration section:
.. code-block:: yaml .. 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-app_generate_pwd:
!libervia_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. 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 - `size` (int): Size of the password to generate. Default to 30
Example Example
------- ~~~~~~~
Generating a password named ``some_password`` with a size of 32 characters: Generating a password named ``some_password`` with a size of 32 characters:
.. code-block:: yaml .. code-block:: yaml
@ -162,35 +209,23 @@ Generating a password named ``some_password`` with a size of 32 characters:
.. _libervia-app_param: .. _libervia-app_param:
!libervia_param !libervia_param
++++++++++++++ ^^^^^^^^^^^^^^^
Get a parameter specified when starting the application. The value can be either the name 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]`. of the parameter to get, or a list as `[name, default_value]`.
Available parameters: Available parameters:
url_prefix url_prefix
The internal URL where the app will be served. The internal URL where the app will be served, it is given by the Web frontend.
web_label front_url_path
Label which will be used in Web frontend. The URL path which will be used in the Web frontend to access the app.
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.
Example Example
------- ~~~~~~~
Getting a parameter: Getting a parameter:
.. code-block:: yaml .. code-block:: yaml
!libervia_param [url_prefix, /some_app] !libervia_param [url_prefix, /some_app]
.. _PyYAML: https://pyyaml.org/

View file

@ -1,8 +1,8 @@
.. _components: .. _components:
=================== ==========
Libervia Components Components
=================== ==========
Libervia can act as an XMPP server component, which can be seen as a generic plugin for Libervia can act as an XMPP server component, which can be seen as a generic plugin for
XMPP servers. XMPP servers.
@ -38,23 +38,20 @@ OpenFire
Prosody Prosody
https://prosody.im/doc/components https://prosody.im/doc/components
Tigase .. Tigase documentation is down (2026-11-03, the whole docs.tigase.net site is 404),
https://docs.tigase.net/en/latest/Tigase_Administration/Components/_Components.html#external-component-configuration uncomment once it's back:
.. Tigase
.. https://docs.tigase.net/en/latest/Tigase_Administration/Components/_Components.html#external-component-configuration
Simplified Component Configuration Simplified Component Configuration
================================== ==================================
Most user will want to use this simplified configuration. To set up a component, you Most user will want to use this simplified configuration. To set up a component, you
simply need to create a configuration section with ``[component.<entry_point>]`` where simply need to create a configuration section named after the entry point, with dashes
you'll set the ``jid`` and ``secret`` options. Everything will be automatically configured replaced by underscores (e.g. the ``email-gateway`` entry point uses the
on backend startup, and the component will start automatically. ``[component.email_gateway]`` section), where you'll set the ``jid`` and ``secret``
options. Everything will be automatically configured on backend startup, and the
settings component will start automatically.
--------
In ``[component.<entry_point>]``:
.. autopydantic_settings:: libervia.backend.tools.config.ComponentSettings
example example
------- -------
@ -69,6 +66,10 @@ find that it's ``pubsub``, then you simply put in your configuration (usually
jid = "pubsub.example.org" jid = "pubsub.example.org"
secret = "your_shared_secret" 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 Manual Component Configuration
============================== ==============================
@ -108,6 +109,14 @@ wants to connect it immediately, she can use::
$ li profile connect -cp ap-gateway $ 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 Available Components
==================== ====================
@ -139,7 +148,7 @@ entities (JIDs). Permissions can be set through Ad-Hoc Commands.
Configuration 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`` ``http_upload_port``
port to use for HTTP File Upload port to use for HTTP File Upload
@ -216,7 +225,13 @@ documentation to find how this must be done.
Configuration 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: .. _ap-actor-from-xmpp:
@ -745,7 +760,7 @@ any kind of extra informations) while AP only indicates if one will attend or no
event. event.
.. _Mobilizon: https://joinmobilizon.org/ .. _Mobilizon: https://mobilizon.org/
Using the Component (for developers) 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/ .. _Galène project: https://galene.org/
.. _email-gateway:
Email Gateway Email Gateway
------------- -------------

View file

@ -60,6 +60,9 @@ rst_prolog = """
# ones. # ones.
extensions = ["sphinx.ext.autodoc", "docstring", "sphinxcontrib.autodoc_pydantic"] 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. # Add any paths that contain templates here, relative to this directory.
templates_path = [".templates"] templates_path = [".templates"]
@ -82,7 +85,8 @@ language = "en"
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path. # 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. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = None pygments_style = None
@ -120,7 +124,7 @@ html_static_path = [".static"]
# -- Options for HTMLHelp output --------------------------------------------- # -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = "SalutToidoc" htmlhelp_basename = "LiberviaBackenddoc"
# -- Options for LaTeX output ------------------------------------------------ # -- Options for LaTeX output ------------------------------------------------
@ -144,7 +148,13 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ 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 = [ texinfo_documents = [
( (
master_doc, master_doc,
"SalutToi", "LiberviaBackend",
"Libervia Documentation", "Libervia Backend Documentation",
author, author,
"SalutToi", "LiberviaBackend",
"One line description of project.", "One line description of project.",
"Miscellaneous", "Miscellaneous",
), ),

View file

@ -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 - ``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 - ``roster``: contact lists (from the roster cache in the database, kept up to date on
every connection) 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) - ``gpg``: GPG home per profile (public and secret keys)
- ``files``: shared files (the blobs of ``~/.sat/files/``) - ``files``: shared files (the blobs of ``~/.sat/files/``)
- ``db``: full copy of the database (not portable, SQLite only) - ``db``: full copy of the database (not portable, SQLite only)

View file

@ -4,18 +4,23 @@
How-To: Email Gateway 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 Prerequisites
------------- -------------
Before configuring the Email Gateway, ensure you have: Before configuring the Email Gateway, ensure you have:
1. Libervia backend installed and running (see :ref:`installation`) 1. Libervia backend installed and working (see :ref:`installation`)
2. An XMPP server configured with component support (such as Prosody, Ejabberd, Openfire, 2. An XMPP server configured with component support (such as Prosody, Ejabberd,
Tigase, MongooseIM, etc). Openfire, Tigase, MongooseIM, etc).
Configuration Steps Configuration Steps
------------------- -------------------
@ -30,7 +35,7 @@ Configuration Steps
-- Add to /etc/prosody/prosody.cfg.lua -- Add to /etc/prosody/prosody.cfg.lua
Component "email.example.org" Component "email.example.org"
component_secret = "your_unique_secret" component_secret = "secret"
*For Ejabberd:* *For Ejabberd:*
@ -46,30 +51,26 @@ Configuration Steps
module: ejabberd_service module: ejabberd_service
hosts: hosts:
"email.example.org": "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 \ [component.email_gateway]
-j email.example.org \ jid = "email.example.org"
-p "" \ secret = "secret"
-x "your_unique_secret" \
-C email-gateway \
-A
This creates a profile named `email-gateway` with: The component profile is then created automatically on backend startup, and the
component starts automatically. See :ref:`components` for details about the
- JID: `email.example.org` components configuration.
- 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`)
3. **User Registration** 3. **User Registration**
@ -81,7 +82,7 @@ Configuration Steps
.. code-block:: bash .. 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** **Registration Fields**
@ -117,7 +118,7 @@ Configuration Steps
SMTP Password SMTP Password
Password for SMTP authentication Password for SMTP authentication
**Important Notes** .. note::
- Ensure your IMAP/SMTP servers are accessible from the Libervia backend - Ensure your IMAP/SMTP servers are accessible from the Libervia backend
- The IMAP server must support the IDLE extension for real-time notifications - The IMAP server must support the IDLE extension for real-time notifications
@ -125,47 +126,8 @@ Configuration Steps
Configuration 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. Besides the component configuration described above, no other configuration is needed:
the gateway uses user-specific data provided 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.
Troubleshooting Troubleshooting
--------------- ---------------
@ -178,37 +140,33 @@ Troubleshooting
- Solution - Solution
* - XMPP component connection failed * - XMPP component connection failed
- Verify the shared secret matches on **both** Libervia and the XMPP server. - 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. The component JID must *exactly* match the JID configured on the XMPP server
“email.example.org” in Prosody must match the JID you set when creating the (e.g. "email.example.org" in Prosody must match the JID you set in the Libervia
Libervia profile). configuration).
* - User registration fails * - 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. Libervia host.
* Verify that the IMAP server supports the **IDLE** extension (required for * Verify that the IMAP server supports the **IDLE** extension (required for
real‑time notifications). real-time notifications).
* If you use self‑signed certificates, add them to the system trust store. * If you use self-signed certificates, add them to the system trust store.
* - Email not received * - 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. * Check Libervia Backend logs to ensure that the IMAP connection succeeded.
* Verify that messages are arriving in the user’s INBOX. * Verify that messages are arriving in the user's INBOX.
* - “DBus not launched” error * - Email not sent
- Follow :ref:`installation_dbus` instructions for headless servers – the - * Check the SMTP credentials and make sure the SMTP server is reachable from the
Libervia backend relies on D‑Bus to run. Libervia host.
* Check the Libervia backend logs for the sending error.
* - Attachments not appearing * - Attachments not appearing
- The Email Gateway can *retrieve* attachments, but does not yet deliver them with - The Email Gateway can *retrieve* attachments, but does not yet deliver them with
outgoing messages (feature work in progress). outgoing messages (feature work in progress).
* - Encrypted emails not working * - Encrypted emails not working
- “Gateway Relayed Encryption” is a work‑in‑progress extension (not yet an official XEP). - "Gateway Relayed Encryption" is a work-in-progress extension (not yet an official
Use a client that implements this specification. XEP). Use a client that implements this specification.
* - Mailing lists not converted * - 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. must match the format expected for PubSub node creation.
.. note:: .. note::
The Email Gateway is under active development. Some features may have limitations. The Email Gateway is under active development. Some features may have limitations.
See Also
--------
- For Libervia components overview: :ref:`components`

View file

@ -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`` A backup is made of data *types*, selected with ``-i/--include`` and ``-e/--exclude``
(repeatable): ``params``, ``private``, ``roster``, ``pep``, ``gpg``, ``files`` and ``db`` (repeatable): ``params``, ``private``, ``roster``, ``pep``, ``gpg``, ``files`` and ``db``
(full copy of the database, included by default on export). A backup is complete by (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 private: it is written with mode 0600, and ``--encrypt`` can be used for untrusted
storage. storage.

View file

@ -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 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 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 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. prompt instead.
.. _pubsub_common: .. _pubsub_common:

View file

@ -1,6 +1,6 @@
========================== ===========================
gateway: Gateway management 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. ``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.

View file

@ -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:: get the available options with ``--help`` as explained above::
$ li message send --help $ li message send --help
usage: li message send [-h] [-p PROFILE] [--pwd PASSWORD] [-c] [-l LANG] [-s] usage: li message send [-h] [-p PROFILE] [--pwd PASSWORD] [-c]
[-n] [-S SUBJECT] [-L SUBJECT_LANG]
[-t {chat,error,groupchat,headline,normal,auto}] [-t {chat,error,groupchat,headline,normal,auto}]
[-e ALGORITHM] [--encrypt-noreplace] [-x | -r] [-e ALGORITHM] [--encrypt-noreplace] […]
jid jid
positional arguments: positional arguments:
jid the destination jid jid the destination jid
optional arguments: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-p PROFILE, --profile PROFILE -p, --profile PROFILE
Use PROFILE profile key (default: @DEFAULT@) Use PROFILE profile key (default: @DEFAULT@)
--pwd PASSWORD Password used to connect profile, if necessary --pwd PASSWORD Password used to connect profile, if necessary
-c, --connect Connect the profile before doing anything else -c, --connect Connect the profile before doing anything else
-l LANG, --lang LANG language of the message -t, --type {chat,error,groupchat,headline,normal,auto}
-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}
type of the message type of the message
-e ALGORITHM, --encrypt ALGORITHM -e, --encrypt ALGORITHM
encrypt message using given 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 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, If you want to send a message to, say, ``pierre@example.net``, and encrypt it with OMEMO,
just do the following:: 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 (note that with OMEMO, you need to have previously validated fingerprint of your contact
for this to work). 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. The different commands are explained in dedicated sections.
.. toctree:: .. toctree::

View file

@ -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 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 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. 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 Multi-Users Chat), and some related features (end 2 end encryption, bookmarks, gateways
interaction, file sending, etc.). interaction, file sending, etc.).
Usage 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: in the same way as in vi/vim:
- From any mode, press ``[Esc]`` to switch to normal mode. - 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 - From normal mode, press ``i`` to switch to insert mode – the one you
use to write messages to your contacts. 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. Below, the keyboard shortcuts are explained.
Keyboard handling Keyboard handling
@ -46,8 +46,8 @@ Main keys
``[Tab]`` and/or ``CTRL + up/down arrows`` ``[Tab]`` and/or ``CTRL + up/down arrows``
change focus change focus
``CTRL + X`` ``CTRL + X``
quit Primitivus quit Libervia TUI
``CTRL + F, 1`` or ``CTRL + F, M`` ``CTRL + F, 1`` or ``CTRL + F, M``
focus on the menu focus on the menu
``CTRL + F, 2`` or ``CTRL + F, B`` ``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). mode, then the ``:`` at the beginning of the command will switch to command mode).
``:quit`` ``:quit``
quit Primitivus quit Libervia TUI
``:messages`` ``:messages``
display log messages (see also the logging configuration) 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]`` ``: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: 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`` - ``online``

View file

@ -113,24 +113,25 @@ The `official Libervia website`_ is made with Libervia web framework.
.. _Jinja2: http://jinja.pocoo.org/ .. _Jinja2: http://jinja.pocoo.org/
.. _official Libervia website: https://salut-a-toi.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 Libervia TUI is the Terminal User Interface. In other words, it works in console and is
works in console and is intended for people at ease with it. Its text only interface has intended for people at ease with it. Its text only interface has several advantages: you
several advantages: you get rid of many distracting things (like images), it works without get rid of many distracting things (like images), it works without graphical environment
graphical environment installed (which is often the case on servers) and it works on installed (which is often the case on servers) and it works on distant shell (like `ssh`_)
distant shell (like `ssh`_) while staying gentle with your bandwidth. while staying gentle with your bandwidth.
Primitivus is shipped with the backend, so it should be always available once Libervia is Libervia TUI is shipped with the backend, so it should be always available once Libervia
installed (but some distributions may provide it separately). is installed (but some distributions may provide it separately).
You can check :doc:`Libervia TUI documentation <libervia-tui/index>` 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 .. _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 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. powerful tool which allows to do nearly everything you can do with other frontends.

View file

@ -145,7 +145,7 @@ class MiscAccount(object):
host.bridge.add_method( host.bridge.add_method(
"credentials_xmpp_connect", "credentials_xmpp_connect",
".plugin", ".plugin",
in_sign="ss", in_sign="ssb",
out_sign="b", out_sign="b",
method=self.credentials_xmpp_connect, method=self.credentials_xmpp_connect,
async_=True, async_=True,
@ -293,11 +293,11 @@ class MiscAccount(object):
# there is no email when an existing XMPP account is used # there is no email when an existing XMPP account is used
body = f"New account created on {domain}: {profile} [{email or '<no email>'}]" body = f"New account created on {domain}: {profile} [{email or '<no email>'}]"
d_admin = sat_email.send_email( d_admin = sat_email.send_email(
self.get_email_conf(),
self.host.memory.config, self.host.memory.config,
admins_emails, admins_emails,
subject, subject,
body, body,
email_settings=self.get_email_conf(),
) )
admins_emails_txt = ", ".join(["<" + addr + ">" for addr in admins_emails]) 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 # XXX: this will not fail when the email address doesn't exist
# FIXME: check email reception to validate email given by the user # FIXME: check email reception to validate email given by the user
# FIXME: delete the profile if the email could not been sent? # 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( d_user.addCallbacks(
lambda __: log.debug( lambda __: log.debug(
"Account creation confirmation sent to <{}>".format(email) "Account creation confirmation sent to <{}>".format(email)
@ -689,12 +695,13 @@ class MiscAccount(object):
d.addCallbacks(deleted, errback) d.addCallbacks(deleted, errback)
return d 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. """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. Re-use given JID and XMPP password for the profile name and profile password.
@param jid_s (unicode): JID @param jid_s (unicode): JID
@param password (unicode): XMPP password @param password (unicode): XMPP password
@param send_email (bool): send the administrator notification email
@return Deferred(bool) @return Deferred(bool)
@raise exceptions.PasswordError, exceptions.ConflictError @raise exceptions.PasswordError, exceptions.ConflictError
""" """
@ -716,6 +723,7 @@ class MiscAccount(object):
) )
def connected(result): def connected(result):
if send_email:
self.send_emails(None, profile=jid_s) self.send_emails(None, profile=jid_s)
return result return result

View file

@ -34,6 +34,7 @@ def send_email(
subject: str = "", subject: str = "",
body: str = "", body: str = "",
from_email: str | None = None, from_email: str | None = None,
email_settings: tools_config.EmailSettings | None = None,
) -> defer.Deferred: ) -> defer.Deferred:
"""Send an email using Libervia configuration """Send an email using Libervia configuration
@ -43,12 +44,14 @@ def send_email(
@param subject: subject of the message @param subject: subject of the message
@param body: body of the message @param body: body of the message
@param from_email: address of the sender @param from_email: address of the sender
@param email_settings: ``EmailSettings`` to use, default to ``config.email``
@return:Same as smtp.sendmail. @return:Same as smtp.sendmail.
""" """
if isinstance(to_emails, str): if isinstance(to_emails, str):
to_emails = to_emails.split() to_emails = to_emails.split()
email_host = config.email.server settings = email_settings or config.email
email_from = from_email or config.email.from_ email_host = settings.server
email_from = from_email or settings.from_
# we suppose that email domain and XMPP domain are identical # we suppose that email domain and XMPP domain are identical
domain = config.common.xmpp_domain domain = config.common.xmpp_domain
@ -60,12 +63,12 @@ def send_email(
if email_from is None: if email_from is None:
email_from = "no_reply@" + domain email_from = "no_reply@" + domain
email_sender_domain = config.email.sender_domain or domain email_sender_domain = settings.sender_domain or domain
email_port = config.email.port email_port = settings.port
email_username = config.email.username email_username = settings.username
email_password = config.email.password email_password = settings.password
email_auth = config.email.auth email_auth = settings.auth
email_starttls = config.email.starttls email_starttls = settings.starttls
msg = MIMEText(body, "plain", "UTF-8") msg = MIMEText(body, "plain", "UTF-8")
msg["Subject"] = subject msg["Subject"] = subject

View file

@ -349,7 +349,12 @@ class QuickApp:
log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_)) log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_))
def on_bridge_connected(self): 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): def _bridge_cb(self):
self.register_signal("connected") self.register_signal("connected")

View file

@ -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 <http://www.gnu.org/licenses/>.
"""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())

View file

@ -233,9 +233,16 @@ class QuickProfileManager(object):
def refill_profiles(self): def refill_profiles(self):
"""Rebuild the list of profiles""" """Rebuild the list of profiles"""
profiles = self.host.bridge.profiles_list_get() self.host.bridge.profiles_list_get(
profiles.sort() callback=self._profiles_loaded,
self.set_profiles(profiles) 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 ## ## Method which must be implemented by frontends ##

View file

@ -28,9 +28,10 @@ from libervia.backend.core import log as logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
from libervia.backend.tools import config as tools_config from libervia.backend.tools import config as tools_config
import urwid import urwid
from urwid.util import is_wide_char from urwid import is_wide_char
from urwid_satext import sat_widgets from urwid_satext import sat_widgets
from libervia.frontends.quick_frontend.quick_app import QuickApp 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_utils
from libervia.frontends.quick_frontend import quick_chat from libervia.frontends.quick_frontend import quick_chat
from libervia.tui.profile_manager import ProfileManager 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.frontends.tools.misc import InputHistory
from libervia.backend.tools.common import dynamic_import from libervia.backend.tools.common import dynamic_import
from libervia.frontends.tools import jid from libervia.frontends.tools import jid
import asyncio
import signal import signal
import sys import sys
@ -67,10 +69,19 @@ class TUISettings(tools_config.FrontendSettings, key="tui"):
## bridge handling ## bridge handling
# we get bridge name from conf and initialise the right class accordingly # we get bridge name from conf and initialise the right class accordingly
main_config = tools_config.parse_main_conf(ignore_cache=True) 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: if "dbus" not in bridge_name:
print("only D-Bus bridge is currently supported") print("only D-Bus bridge is currently supported")
sys.exit(3) 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): class EditBar(sat_widgets.ModalEdit):
@ -338,9 +349,7 @@ class LiberviaTUIApp(QuickApp, InputHistory):
AVATARS_HANDLER = False AVATARS_HANDLER = False
def __init__(self): def __init__(self):
bridge_module = dynamic_import.import_bridge( bridge_module = dynamic_import.import_bridge(bridge_name)
bridge_name, "libervia.frontends.bridge"
)
if bridge_module is None: if bridge_module is None:
log.error("Can't import {} bridge".format(bridge_name)) log.error("Can't import {} bridge".format(bridge_name))
sys.exit(3) sys.exit(3)
@ -348,16 +357,18 @@ class LiberviaTUIApp(QuickApp, InputHistory):
log.debug("Loading {} bridge".format(bridge_name)) log.debug("Loading {} bridge".format(bridge_name))
QuickApp.__init__( QuickApp.__init__(
self, 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, xmlui=xmlui,
check_options=quick_utils.check_options, check_options=quick_utils.check_options,
connect_bridge=False, connect_bridge=False,
) )
self.tui_settings = main_config.get_settings(TUISettings) self.tui_settings = main_config.get_settings(TUISettings)
## main loop setup ## ## main loop setup ##
event_loop = ( # the new D-Bus bridge is twisted based (txdbus)
urwid.GLibEventLoop if "dbus" in bridge_name else urwid.TwistedEventLoop event_loop = urwid.TwistedEventLoop
)
self.loop = urwid.MainLoop( self.loop = urwid.MainLoop(
urwid.SolidFill(), urwid.SolidFill(),
C.PALETTE, C.PALETTE,
@ -373,6 +384,10 @@ class LiberviaTUIApp(QuickApp, InputHistory):
def on_bridge_connected(self): def on_bridge_connected(self):
##misc setup## ##misc setup##
self._version = None
self.bridge.version_get(
callback=self._version_get_cb, errback=lambda __: None
)
self._visible_widgets = set() self._visible_widgets = set()
self.notif_bar = sat_widgets.NotificationBar() self.notif_bar = sat_widgets.NotificationBar()
urwid.connect_signal(self.notif_bar, "change", self.on_notification) 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.loop.widget = self.main_widget = ProfileManager(self)
self.post_init() self.post_init()
def _version_get_cb(self, version):
self._version = version
@property @property
def visible_widgets(self): def visible_widgets(self):
return self._visible_widgets return self._visible_widgets
@ -579,7 +597,7 @@ class LiberviaTUIApp(QuickApp, InputHistory):
self.__saved_overlay = None self.__saved_overlay = None
elif ( 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 ): # Debug only for dev versions
self.debug() self.debug()
elif input_ == a_key["CONTACTS_HIDE"]: # user wants to (un)hide the contact lists 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): def add_menus(self, menu, type_filter, menu_data=None):
"""Add cached menus to instance """Add cached menus to instance
The menus are retrieved asynchronously from the bridge.
@param menu: sat_widgets.Menu instance @param menu: sat_widgets.Menu instance
@param type_filter: menu type like is sat.core.sat_main.import_menu @param type_filter: menu type like is sat.core.sat_main.import_menu
@param menu_data: data to send with these menus @param menu_data: data to send with these menus
@ -623,17 +644,25 @@ class LiberviaTUIApp(QuickApp, InputHistory):
def add_menu_cb(callback_id): def add_menu_cb(callback_id):
self.action_launch(callback_id, menu_data, profile=self.current_profile) self.action_launch(callback_id, menu_data, profile=self.current_profile)
for id_, type_, path, path_i18n, extra in self.bridge.menus_get( def menus_get_cb(menus):
"", C.NO_SECURITY_LIMIT for id_, type_, path, path_i18n, extra in menus: # TODO: manage extra
): # TODO: manage extra
if type_ != type_filter: if type_ != type_filter:
continue continue
if len(path) != 2: if len(path) != 2:
raise NotImplementedError("Menu with a path != 2 are not implemented yet") raise NotImplementedError(
"Menu with a path != 2 are not implemented yet"
)
menu.add_menu( menu.add_menu(
path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_) 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): def _build_menu_roller(self):
menu = sat_widgets.Menu(self.loop) menu = sat_widgets.Menu(self.loop)
general = _("General") general = _("General")
@ -1016,10 +1045,12 @@ class LiberviaTUIApp(QuickApp, InputHistory):
def on_join_room_request(self, menu): def on_join_room_request(self, menu):
"""User wants to join a MUC room""" """User wants to join a MUC room"""
def show_join_dialog(default_service):
pop_up_widget = sat_widgets.InputDialog( pop_up_widget = sat_widgets.InputDialog(
_("Entering a MUC room"), _("Entering a MUC room"),
_("Please enter MUC's JID"), _("Please enter MUC's JID"),
default_txt=self.bridge.muc_get_default_service(), default_txt=default_service,
ok_cb=self.on_join_room, ok_cb=self.on_join_room,
) )
pop_up_widget.set_callback( pop_up_widget.set_callback(
@ -1027,8 +1058,12 @@ class LiberviaTUIApp(QuickApp, InputHistory):
) )
self.show_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("")
)
def on_about_request(self, menu): 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# # MISC CALLBACKS#

View file

@ -174,10 +174,13 @@ class Card(TarotCard):
return CardDisplayer(self) return CardDisplayer(self)
class Table(urwid.FlowWidget): class Table(urwid.Widget):
"""Represent the cards currently on the table""" """Represent the cards currently on the table"""
_sizing = frozenset([urwid.Sizing.FLOW])
def __init__(self): def __init__(self):
super().__init__()
self.top = self.left = self.bottom = self.right = None self.top = self.left = self.bottom = self.right = None
def put_card(self, location, card): def put_card(self, location, card):
@ -313,7 +316,7 @@ class TarotGame(QuickTarotGame, urwid.WidgetWrap):
if not winners and not loosers: if not winners and not loosers:
title = _("Draw game") title = _("Draw game")
else: 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( form = xmlui.create(
self.parent.host, self.parent.host,
xml_data, xml_data,

View file

@ -17,7 +17,10 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import dbus try:
import dbus
except ImportError:
dbus = None
class Notify(object): class Notify(object):

View file

@ -37,11 +37,11 @@ class ProfileManager(QuickProfileManager, urwid.WidgetWrap):
self.pass_wid = sat_widgets.Password(_("Password:"), align="center") self.pass_wid = sat_widgets.Password(_("Password:"), align="center")
style = ["no_first_select"] style = ["no_first_select"]
profiles = host.bridge.profiles_list_get()
profiles.sort()
self.list_profile = sat_widgets.List( 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 # new & delete buttons
buttons = [ buttons = [

View file

@ -54,25 +54,32 @@ class Progress(urwid.WidgetWrap, quick_widgets.QuickWidget):
def progress_cb(self, loop, data): def progress_cb(self, loop, data):
progress_id, message, profile = data progress_id, message, profile = data
data = self.host.bridge.progress_get(progress_id, profile)
pbar = self.progress_dict[(progress_id, profile)]["progress"] 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 data:
if self.progress_dict[(progress_id, profile)]["state"] == "init": if entry["state"] == "init":
# first answer, we must construct the bar # first answer, we must construct the bar
self.progress_dict[(progress_id, profile)]["state"] = "progress" entry["state"] = "progress"
pbar.done = float(data["size"]) pbar.done = float(data["size"])
pbar.set_completion(float(data["position"])) pbar.set_completion(float(data["position"]))
self.update_not_bar() self.update_not_bar()
else: else:
if self.progress_dict[(progress_id, profile)]["state"] == "progress": if entry["state"] == "progress":
self.progress_dict[(progress_id, profile)]["state"] = "done" entry["state"] = "done"
pbar.set_completion(pbar.done) pbar.set_completion(pbar.done)
self.update_not_bar() self.update_not_bar()
return return
loop.set_alarm_in(0.2, self.progress_cb, (progress_id, message, profile)) 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): def _remove_bar(self, progress_id, profile):
wid = self.progress_dict[(progress_id, profile)]["full"] wid = self.progress_dict[(progress_id, profile)]["full"]
self.progress_list.remove(wid) self.progress_list.remove(wid)

View file

@ -45,11 +45,12 @@ class StatusBar(urwid.Columns):
urwid.connect_signal(self.status, "click", self.on_status_click) urwid.connect_signal(self.status, "click", self.on_status_click)
def on_presence_click(self, sender=None): def on_presence_click(self, sender=None):
if not self.host.bridge.is_connected( def show_presence_popup(connected):
self.host.current_profile if not connected:
): # FIXME: manage multi-profiles
return return
options = [commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE] options = [
commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE
]
list_widget = sat_widgets.GenericList( list_widget = sat_widgets.GenericList(
options=options, options=options,
option_type=sat_widgets.ClickableText, option_type=sat_widgets.ClickableText,
@ -60,10 +61,14 @@ class StatusBar(urwid.Columns):
) )
self.host.show_pop_up(decorated) self.host.show_pop_up(decorated)
# FIXME: manage multi-profiles
self.host.bridge.is_connected(
self.host.current_profile, callback=show_presence_popup
)
def on_status_click(self, sender=None): def on_status_click(self, sender=None):
if not self.host.bridge.is_connected( def show_status_popup(connected):
self.host.current_profile if not connected:
): # FIXME: manage multi-profiles
return return
pop_up_widget = sat_widgets.InputDialog( pop_up_widget = sat_widgets.InputDialog(
_("Set your status"), _("Set your status"),
@ -74,6 +79,11 @@ class StatusBar(urwid.Columns):
) )
self.host.show_pop_up(pop_up_widget) 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
)
def on_change(self, sender=None, user_data=None): def on_change(self, sender=None, user_data=None):
new_value = user_data.get_text() new_value = user_data.get_text()
previous = ( previous = (

View file

@ -57,7 +57,7 @@ dependencies = [
"twisted[tls] >= 22.10", "twisted[tls] >= 22.10",
"twomemo >= 1.0.0, < 2", "twomemo >= 1.0.0, < 2",
"txdbus", "txdbus",
"urwid >= 1.2.0, < 3", "urwid >= 4.2.5, < 5",
"urwid-satext", "urwid-satext",
"xmlschema < 4.3.1", "xmlschema < 4.3.1",
"tx-xmpp>=0.15", "tx-xmpp>=0.15",