Commit a9c4eb41 authored by David Boddie's avatar David Boddie
Browse files

Merge branch 'master' into 'network-connection'

# Conflicts:
#   links.txt
parents 6c89633f 6b40a2d3
Pipeline #51926 passed with stage
in 1 minute and 19 seconds
......@@ -38,6 +38,6 @@ publish:
- touch ~/.ssh/id_rsa
- chmod 0600 ~/.ssh/id_rsa
- echo "$SSH_KEY" >> ~/.ssh/id_rsa
- rsync -av -i --stats -e "ssh -l $SSH_USER -p $SSH_PORT" --delete-after _build/html/ $RSYNC_TARGET
- rsync -av -I -t -i --stats -e "ssh -l $SSH_USER -i ~/.ssh/id_rsa -p $SSH_PORT" _build/html/ "$RSYNC_TARGET"
- master
......@@ -22,3 +22,9 @@
[submodule "Apps/Examples/Files/Pictures/app"]
path = Apps/Examples/Files/Pictures/app
url =
[submodule "Apps/Guides/Working_with_UI_Files/app"]
path = Apps/Guides/Working_with_UI_Files/app
url =
[submodule "Apps/Examples/General/Simple_Weather/app"]
path = Apps/Examples/General/Simple_Weather/app
url =
.. index:: API Docs
.. api_docs:
.. _api_docs:
API Documentation
......@@ -16,8 +16,8 @@ Librem 5 software stack.
Phone/Messaging APIs
* Modem/Sim card access is provided via the `ModemManager`_ D-Bus APIs
* Access to network connections is provided via `NetworkManager`_ D-Bus APIs
* Modem/Sim card access is provided via the `ModemManager D-Bus APIs`_.
* Access to network connections is provided via `NetworkManager D-Bus APIs`_.
Shell APIs
......@@ -42,13 +42,42 @@ D-Bus APIs provided by the Phone Shell (:ref:`phosh`):
authentication dialogs (used by e.g. gnupg's pinentry, SIM card pin
entry, etc.)
On-Screen Keyboard
The visibility of the on-screen (virtual) keyboard can be controlled using its
D-Bus API, exposed on the session bus as the ``sm.puri.OSK0`` service, on the
``/sm/puri/OSK0`` object path, with the ``sm.puri.OSK0`` interface, as
described in the `Squeekboard`_ documentation.
This interface provides the following property:
* ``Visible`` ``b``
This can be read and changed with the following methods:
* ``GetVisible(out b visible)``
* ``SetVisible(in b visible)``
Mobile Widgets
* `libhandy reference manual <>`_
* The `libhandy documentation`_ is a reference manual for the adaptive widgets
* The `libhandy PyGObject API documentation`_ contains API documentation for
Generally, the GTK and GNOME documentation contains information about the C
APIs to use for the underlying widgets and technologies. Documentation specific
to each programming language is also available:
* General documentation and C API information is found in the `GNOME API Reference`_.
* The `PyGObject API Reference`_ site contains GNOME API documentation adapted
for use with Python.
.. _`org.gnome.Mutter.DisplayConfig`:
.. _`org.gnome.Mutter.IdleMonitor`:
.. _`org.gnome.keyring.SystemPrompter`:
.. _`ModemManager`:
.. _`NetworkManager`:
.. include:: /links.txt
......@@ -2,5 +2,6 @@
.. |manifest-path| replace:: app/
.. |app-id| replace::
.. |extra-permissions-info| replace:: We also specify the ``--filesystem=xdg-pictures`` permission to request access to the user's *Pictures* directory, as described in the :ref:`flatpak_app_permissions_guide` guide.
.. |meson-app-module| replace:: pictures
.. include:: ../../common/Packaging_the_App.txt
......@@ -11,7 +11,7 @@ Most of the code for the application is included in a single ```` file wh
Much of the is very similar to other examples and tutorials. We will focus on the parts that are specific to this example.
Relevant Modules
Besides standard Python modules such as ``os`` and ``sys``, the ``Handy`` module helps us to create adaptive user interfaces. This module is imported in the same way as the ``Gtk`` module:
......@@ -23,7 +23,7 @@ Besides standard Python modules such as ``os`` and ``sys``, the ``Handy`` module
The ``widgets`` module contains a helper class that we won't cover in any detail.
Setting up the User Interface
The ``Application`` class provides the usual methods to set up the application
and perform tasks when it is run.
......@@ -81,7 +81,7 @@ The second page is also a `Gtk.ScrolledWindow`_ widget, but only contains a sing
As for the first page, we also return the widget that represents the page.
Loading and Displaying Images
The ``load_thumbnails`` method begins by locating the user's *Pictures* directory:
......@@ -108,7 +108,7 @@ The ``show_details`` method loads an image at its full size for display in the d
:start-at: def show_details
:end-at: self.pages.show_page
We use the `Gtk.TreePath`_ passed to this method, along with a `Gtk.TreeIter`_ object, to obtain the file name of the image from the model. A good introduction to this class is provided by the `Tree and List Widgets`_ chapter of the `Python GTK+ 3 Tutorial`_.
We use the `Gtk.TreePath`_ passed to this method, along with a `Gtk.TreeIter`_ object, to obtain the file name of the image from the model. A good introduction to this class is provided by the `Tree and List Widgets`_ chapter of the `Python GTK 3 Tutorial`_.
Subproject commit fb3a6947f9b4bb0376178e17167f70f4320dc709
Subproject commit bf4407daec7e4ee89993593af3d4d67763ac95d3
.. |main-file| replace:: ````
.. |executable| replace:: ``simple-weather``
.. |desktop-entry-in| replace:: ````
.. |desktop-entry| replace:: ``com.example.simple_weather.desktop``
.. |desktop-entry-ref| replace:: desktop entry file
.. |svg-file| replace:: ``com.example.simple_weather.svg``
.. include:: /Apps/Examples/common/Building_the_App.txt
Adaptive UI
As well as using standard GTK widgets for user interface elements, the application also uses the widgets and features provided by the `libhandy library`_ to make the application adapt to both large and small screen devices.
.. contents:: Contents
We start by looking at the basic user interface design before examining each element in more detail.
Basic Design
The basic design of the user interface is that of a `sidebar list <Sidebar lists_>`_, with a list of locations on the left side of the window and the current forecast on the right.
.. figure:: ../images/basic-design.svg
:width: 50%
:align: center
:alt: The basic design of the user interface, showing the two elements side-by side
The basic design of the user interface, showing the two elements side-by side
On a small screen, it is expected that only one of these elements will be visible at any given time. Therefore, the user needs to be able to switch between them, and this is also something the application should do automatically in certain situations. For example, when the user selects a location in the list, we should automatically display the relevant forecast.
We can think of the two elements as pages: a locations page and a forecast page,
each with a header at the top. We will design these pages separately and bring them together later.
Designing the Pages
The locations page is basically a scrolling list of locations with a header showing the name of the application. The header bar also contains a button allowing the user to search for places and add them to the favorites list.
.. figure:: ../images/locations-page.svg
:width: 60%
:align: center
:alt: The widgets used to make the locations page
The widgets used to make the locations page
The main part of the page is a `Gtk.Box`_ widget which contains a `Gtk.ScrolledWindow`_, providing a view onto a `Gtk.TreeView`_ widget that holds an item for each of the user's favorite places.
When an item is clicked, the forecast page will be populated with information downloaded from the remote weather service, then shown.
The forecast page has a header showing the name of the place the forecast is for, along with a back button that lets the user return to the list of locations. The back button is only shown if the forecast page is shown on its own, without the locations page alongside it -- see `below <examples_Simple_Weather_leaflets_>`_ for the details.
.. figure:: ../images/forecast-page.svg
:width: 60%
:align: center
:alt: The widgets used to make the forecast page
The widgets used to make the forecast page
In the main part of the page, another `Gtk.Box`_ widget is used to manage a `Gtk.ScrolledWindow`_ containing a collection of `Gtk.Image`_ and `Gtk.Label`_ widgets, and also a `Gtk.Label`_ page footer that does not scroll.
When the user selects the back button, the locations page is shown again.
.. _examples_Simple_Weather_leaflets:
Using Leaflets
In an adaptive user interface, key elements can either be shown individually or together, depending on the amount of screen space available. When they are shown individually, there should be ways to navigate between them. When shown together, any UI components that provide this navigation should be hidden because they are unnecessary and could be distracting to the user.
We manage the pages using two `Handy.Leaflet`_ widgets from the `libhandy library`_. These containers work together to manage the dimensions of their children, showing and hiding widgets as necessary, to ensure that the UI behaves in the way described above.
.. figure:: ../images/leaflets.svg
:width: 60%
:align: center
:alt: Using leaflets to manage page headers and bodies
Using leaflets to manage page headers and bodies
When there is enough space, the pages are shown side-by-side. If there is not enough space, each leaflet is *folded*, and the pages are shown individually. The use of a back button in the forecast page lets the user return to the locations page when the leaflets are folded.
The :ref:`tutorial_Adaptive_UI` tutorial provides a basic introduction to leaflets.
Searching for Locations
When the user selects the search button in the header of the locations page, a `Handy.SearchBar`_ widget is shown, allowing the user to type a place name. Search suggestions appear in a `Gtk.Popover`_ widget that appears beneath the search bar.
.. figure:: ../images/locations-search.svg
:width: 60%
:align: center
:alt: The widgets used to implement searching and search suggestions
The widgets used to implement searching and search suggestions
The popover is given a fixed size and contains a `Gtk.ScrolledWindow`_ so that a reasonable number of suggestions can be displayed. The scrolling area contains a `Gtk.Box`_ that holds a collection of `Gtk.ModelButton`_ widgets for the matching place names. When the user selects one of these, the location it represents is added to the list of favorites and the forecast page is updated with the forecast for that location.
Removing Favorites
When the user performs a long press on an item in the favorites list, or right clicks on it when using a mouse instead of a touchscreen, a `Gtk.Popover`_ widget is used to give them the option of removing the item from the list.
.. figure:: ../images/locations-remove.svg
:width: 60%
:align: center
:alt: The widget used to implement a menu for removing items
The widget used to implement a menu for removing items
The contents of the popover are obtained from a `Gio.Menu`_ object that contains a single “Remove Place” item. Selecting it causes the item to be removed from the list. Pressing elsewhere in the window cancels the popover.
.. include:: /links.txt
.. _examples_Simple_Weather_css:
Cascading Style Sheets
Applications can be styled using Cascading Style Sheets (CSS). This approach is often quicker and more convenient than trying to fine tune the appearance of widgets using code.
.. contents:: Contents
In this example we apply a single style sheet in a very simple way.
Loading the Style Sheet
Style sheets may be loaded from the user's system, either from a standard style sheet stored in a default location, or from one installed with the application. Alternatively, they can be supplied with the application :ref:`in a resource bundle <examples_Simple_Weather_resource_bundles>`, as in this example.
In this example, the application creates an instance of the `Gtk.CssProvider`_ class in its ``do_startup`` method, and loads the ``style.css`` style sheet from the resource bundle that has already been loaded:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 8
:start-at: css = Gtk.CssProvider()
:end-at: load_from_resource
Once loaded into it, the ``CssProvider`` instance can be used to style any widget with suitable CSS nodes.
Applying the Style Sheet
The ``CssProvider`` instance can be used to style any individual widget by calling its `Gtk.Widget.get_style_context`_ method and applying the provider to it. Another approach is to apply the style sheet to the entire application:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 8
:start-at: Gtk.StyleContext
:end-at: )
Care must be taken to avoid changing the default appearance of widgets provided by the system theme. To ensure that only certain widgets are changed, we give those widgets names by setting their ``name`` properties. We define rules in the CSS file to only modify those named widgets, as shown in this snippet:
.. literalinclude:: ../app/src/ui/style.css
:language: css
:start-at: #day
:end-at: }
In this case, any widget whose ``name`` property has the value ``day`` is styled according to this rule, but other unnamed widgets have the default style. Because more than one widget can share the same value for the same property, there can be multiple widgets whose ``name`` property is also ``day``, and this allows us to style all the labels showing the days of the week consistently.
.. figure:: ../images/screenshot-forecasts.png
:scale: 50%
:align: center
:alt: Consistently styled labels for days of the week
Consistently styled labels for days of the week
Rules can be written to restrict styling to specific types of widgets, as in the following which is only applied to the header of any Gtk.TreeView_ widget whose ``name`` property is ``places``:
.. literalinclude:: ../app/src/ui/style.css
:language: css
:start-at: treeview#places
:end-at: }
Care must be taken to avoid applying too many rules to the widgets in your application, or applying rules too generally, as this can make it difficult to adjust their appearance later without causing side effects.
Further Reading
The `GTK CSS Overview`_ and `GTK CSS Properties`_ documents on the GNOME developer site are useful for determining which parts of the standard widgets can be styled, and what changes they support.
.. include:: /links.txt
Applications should be developed in a way that makes it possible for them to be customized for different natural languages. This requires user-facing text in an application's source code to be handled specially, as well as support in the build system for management of translation files.
.. contents:: Contents
In this example we use only a few user-visible labels, but we handle them in a way that makes it possible to provide translations for them.
Handling User-Visible Text
In Python applications, user-visible text in labels and other widgets is wrapped in calls to the ``_()`` translation function. In this example, the application's ``do_startup`` method sets the application name, which needs to be translatable:
.. literalinclude:: ../app/src/
:language: python3
:start-at: class Application
:end-at: _('Simple Weather')
In the following code from the ``src/`` file, only the user-visible text is passed to the translation function:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 8
:start-at: _('Remove Place')
:end-at: 'win.remove'
The ``win.remove`` text is not translated because it defines the scope and name of an action that is only used by the code, and must remain the same for the application to work correctly.
Setting up the Translation Function
The ``_()`` function is provided by the standard `Python gettext module`_ and is made available throughout the application when the application starts. We do this in the ``src/`` file which becomes the executable ``simple-weather`` file at run-time:
.. literalinclude:: ../app/src/
:language: python3
:start-at: # Enable translation
:end-at: gettext.install
The build system replaces the ``@project_name@`` placeholder with ``simple_weather`` when the application is built. The resulting calls to the functions in ``gettext`` and ``locale`` ensure that the application can use the translation files installed with it.
The translation files contain translations of the original user-visible text strings into other languages. These are generated from a single file that contains all the user-visible text for the application.
Handling Translation Files
In order to provide a translation of the application, a Portable Object Template (POT) file needs to be generated from all the files that contain user-visible text, including both source code and metadata files. The list of translatable files is the kept in the ``POTFILES`` file in the application's ``po`` directory:
.. literalinclude:: ../app/po/POTFILES
These files are searched for translatable text strings, which are extracted from the arguments of the ``_()`` function, when the ``simple_weather-update-po`` rule is run. This can be performed as part of the process of generating and updating Portable Object (PO) files for each of the languages that the application is translated into. You can run this rule on the command line from the repository root::
meson . _build
ninja -C _build simple_weather-update-po
This is something you should do after adding new user-visible strings to any of the translatable files in the application. The newly created or updated PO files can then be completed by translators before the application is built.
The ``LINGUAS`` file contains a list of the languages that the application aims to support. PO files will be generated for each of these languages. In this example, the list is very small, but other applications may contain extensive lists of languages for which translations are available.
The ``po/`` file contains the declaration that causes the necessary build rules to be generated:
.. literalinclude:: ../app/po/
:language: python3
:start-at: i18n.gettext
When the application is built, any PO files are compiled to Machine Object (MO) files that will be installed with the application. These files are loaded at run-time when the application calls ``gettext.install`` to install the translation function, as described above.
Testing Translations
Different translations for the application can be tested easily if it is started from the command line. Use the ``LANGUAGE`` environment variable to specify the language shown in the user interface, as in these examples:
.. code:: bash
LANGUAGE=en_GB simple-weather
LANGUAGE=nb simple-weather
By default, the application will run in the current locale, so this approach can help developers to discover issues with translations without having to change their working environments.
Further Reading
The `GNU gettext utilities`_ manual contains detailed information about working with translations. The `Python gettext module`_ documentation also contains good background information specific to the Python language.
.. include:: /links.txt
Network Access
Applications can access the network using the normal APIs available to them. Any application packaged as a flatpak needs to request permission to access the network, and this is done in its manifest.
Flatpak Packages
Permission to access the network is done by adding ``--socket=wayland`` to the ``finish-args`` list:
.. literalinclude:: ../app/com.example.simple_weather.json
:dedent: 4
:start-at: finish-args
:end-at: ],
The application can then request resources in the usual way without being concerned about how the network is configured.
.. include:: /links.txt
.. _examples_Simple_Weather_resource_bundles:
Resource Bundles
Resources, such as UI files, that are critical components of an application can be supplied in resource bundles. These files can be loaded at run-time and their contents accessed in a similar way to normal files. Because it is so simple to access resources from bundles, some applications may also use them to store otherwise non-essential, read-only data just for convenience.
.. contents:: Contents
In this example we only use resource bundles for one aspect of the user interface -- a Cascading Style Sheets (CSS) file describing how the application is styled.
Bundling the File
The CSS information is stored in the ``src/ui/style.css`` file, described in :ref:`examples_Simple_Weather_css`. To make the file available in a resource bundle it needs to be compiled to a format that the application's resource system can understand. This usually means declaring it in a manifest like the one in the ``src/simple_weather.gresource.xml`` file:
.. literalinclude:: ../app/src/simple_weather.gresource.xml
:language: XML
When processed by a build tool such as `Ninja`_, using information provided by `Meson`_, this file is compiled to a ``simple_weather.gresource`` resource bundle file. Note the ``prefix`` attribute and the path in the ``<file>`` element -- these will be used to refer to the file later.
See :ref:`gnome_resource_bundles` for more information about how resource bundles are compiled.
Loading the Resource Bundle
The application is run using a script that we supply as the ``src/`` file. When this is processed by Meson, an executable ``simple-weather`` file is created for installation. It is this script that loads the resource bundle -- we show a part of it here:
.. literalinclude:: ../app/src/
:language: python3
:start-at: if __name__
The ``pkgdatadir`` and ``VERSION`` values are provided by Meson when the ``simple-weather`` script is generated.
Other applications use similar scripts to run their main programs. Since the resource bundle only needs to be loaded once, early in the start-up of the application, the code to do so is included here. Compiled languages, such as C and Vala, do not need to include code to explicitly load resource bundles.
Accessing Resource Data
The only file included in the bundle is the ``style.css`` file. This was included in the manifest file as ``ui/style.css``. This path can be used with the ``/com/example/simple_weather`` prefix to access the contents of the file at run-time. In this example, we do this in the application's ``do_startup`` method, using the appropriate methods of the ``CssProvider`` class:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 8
:start-at: css = Gtk.CssProvider()
:end-at: load_from_resource
See the section about :ref:`gnome_resource_paths` for more information about resource paths.
.. include:: /links.txt
Accessing User Data
Each application has access to a user data directory that it can use to save user data separately from the application's settings. Each application that uses this feature is expected to save its data in a suitably named subdirectory of the user data directory. This location is persistent and will be available to the application each time it is run.
.. contents:: Contents
In this example we use the directory to store data that is tightly coupled to the application.
User data is different to application settings. User data is processed by the application to produce something for the user. Application settings change how the application behaves. Depending on the application, it can sometimes be difficult to clearly separate these types of information. It may help to consider how the application will function if the settings are reset to default values or if the user data is deleted. See :ref:`gnome_settings_where_to_store` for guidance on this issue.
The kind of user data handled by this example is data that the user should not need to manage, so the user data directory is an ideal place to store the application's data. Traditional data files that the user can load and save, or which are shared between applications, are stored elsewhere in the user's account on the system, and this example is not concerned with those.
The location of the user data directory is defined by the `XDG Base Directory Specification`_. The path to the directory is stored in the ``XDG_DATA_HOME`` environment variable. Applications can use library functions to obtain this at run-time instead of reading the environment variable.
Obtaining the User Data Directory
Applications use the `GLib.get_user_data_dir`_ function to obtain the location of the user data directory. In the example, the application does this in the ``do_startup`` method, storing the path to the directory in an attribute:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 4
:start-at: def do_startup
:end-at: 'com.example.simple_weather'
Once the path to the data directory is known, a subdirectory can be created for later use if it does not already exist:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 4
:start-at: try:
:end-at: self.quit
In this case, the application shows a dialog and exits if the subdirectory cannot be created.
Storing and Retrieving User Data
Each application can create its own subdirectory in the user data directory and use it to store files. In this example we read and write files using regular file operations, but we also use the `GLib.KeyFile`_ class to store data as pairs of keys and values in the subdirectory created for the application's use.
The application tries to read the contents of a key file, silently failing if no existing file can be read:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 8
:start-at: self.favorites_file
:end-at: pass
This looks in the ``General`` section of the file and reads the value of the ``Favorites`` key as a list of strings.
Writing data to the file is done using a simple method that updates the ``General`` section of the keyfile, writing the value for the ``Favorites`` key as a semicolon-separated list of strings:
.. literalinclude:: ../app/src/
:language: python3
:dedent: 8
:start-at: self.keyfile.set_string_list
:end-at: pass
`GLib.KeyFile`_ is convenient for storing and retrieving small amounts of simple structured data. Developers should use the APIs and libraries that make sense for the kinds of user data they need to manage.
If the application is packaged as a flatpak, it can access the user data directory without needing to :ref:`specify any permissions <flatpak_app_permissions_guide>` in the manifest. This is because each application only has access to its own user data directory in its sandbox -- no other subdirectories are visible to it.
Although it is possible to write to the user data directory, applications should only create a subdirectory there and store data inside that. They should not rely on having access to other subdirectories. Taking a strict approach like this prevents problems with applications in the case where they are run outside a sandbox and can access a user data directory that is shared between many applications.
This example does not need to use files that can be imported or exported, or those that are shared between several applications. Access to the directories where those files are stored would require :ref:`additional permissions <flatpak_app_permissions_guide_table>`. For example, the :ref:`examples_Files_Pictures` example uses an extra permission so that it can access the user's ``Pictures`` directory.
.. include:: /links.txt
.. |project| replace::
.. |repo| replace:: simple-weather-app.git
.. |project-repo| replace:: ````
.. include:: /Apps/Examples/common/Getting_the_App.txt
This application downloads and displays weather forecasts from the ` <>`_ weather forecast service.
It is intended to be used to show how various standard application features are
integrated and used together. The following features are used:
* Adaptive user interface widgets for use on small and large screen devices
* Resource bundles for including critical application resources
* Access to user data storage for recording application data and favorites
* Cascading Style Sheets (CSS) for application styling
* Access to network resources
* Internationalization support for localization of the application's
user-visible text
.. Not currently used, but planned for future iterations:
* Access to application settings storage for recording default settings
The following sections cover these features in more detail.
.. toctree::
:maxdepth: 2
.. |manifest| replace:: ``com.example.simple_weather.json``
.. |manifest-path| replace:: app/com.example.simple_weather.json
.. |app-id| replace:: com.example.simple_weather
.. |extra-permissions-info| replace:: The applications also requires access to the network. Other permissions will be required if you want to store and modify settings for your application, access peripherals, and perform other tasks that require the user's consent.
.. |meson-app-module| replace:: simple_weather
.. include:: ../../common/Packaging_the_App.txt
Subproject commit 49244a3274fd0bad87cfd6dc5c2bb6eb4355c492
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<svg xmlns="" height="400" width="500"
version="1.1" viewBox="0 0 500 400">
<rect x="0" y="0" width="250" height="50" stroke="black" fill="white"
stroke-width="2" />
<rect x="0" y="50" width="250" height="350" stroke="black" fill="white"
stroke-width="2" />
<rect x="8"