summaryrefslogtreecommitdiffstats
path: root/doc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--doc/api/gnome-software-docs.xml778
-rw-r--r--doc/api/gnome-software.types6
-rw-r--r--doc/api/gs-example-details.pngbin0 -> 53714 bytes
-rw-r--r--doc/api/gs-example-installed.pngbin0 -> 106482 bytes
-rw-r--r--doc/api/gs-example-search.pngbin0 -> 24934 bytes
-rw-r--r--doc/api/meson.build11
-rw-r--r--doc/app-developers.md69
-rw-r--r--doc/design.svg448
-rw-r--r--doc/kudos.md70
-rw-r--r--doc/meson.build3
-rw-r--r--doc/privacy-policy.html31
-rwxr-xr-xdoc/update.sh1
-rw-r--r--doc/use-cases.md38
-rw-r--r--doc/vendor-customisation.md214
14 files changed, 1669 insertions, 0 deletions
diff --git a/doc/api/gnome-software-docs.xml b/doc/api/gnome-software-docs.xml
new file mode 100644
index 0000000..aa16279
--- /dev/null
+++ b/doc/api/gnome-software-docs.xml
@@ -0,0 +1,778 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+ <!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
+]>
+<book id="index">
+ <bookinfo>
+ <title>GNOME Software Reference Manual</title>
+ </bookinfo>
+
+ <reference id="tutorial">
+ <title>GNOME Software Plugin Tutorial</title>
+ <partintro>
+ <para>
+ GNOME Software is a software installer designed to be easy to use.
+ </para>
+
+ <section>
+ <title>Introduction</title>
+ <para>
+ At the heart of gnome software the application is just a plugin loader
+ that has some GTK UI that gets created for various result types.
+ The idea is we have lots of small plugins that each do one thing and
+ then pass the result onto the other plugins.
+ These are ordered by dependencies against each other at runtime and
+ each one can do things like editing an existing application or adding a
+ new application to the result set.
+ This is how we can add support for things like firmware updating,
+ GNOME Shell web-apps and flatpak bundles without making big
+ changes all over the source tree.
+ </para>
+ <para>
+ There are broadly 3 types of plugin methods:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para><emphasis role="strong">Actions</emphasis>: Do something on a specific GsApp</para>
+ </listitem>
+ <listitem>
+ <para><emphasis role="strong">Refine</emphasis>: Get details about a specific GsApp</para>
+ </listitem>
+ <listitem>
+ <para><emphasis role="strong">Adopt</emphasis>: Can this plugin handle this GsApp</para>
+ </listitem>
+ </itemizedlist>
+ <para>
+ In general, building things out-of-tree isn't something that I think is
+ a very good idea; the API and ABI inside gnome-software is still
+ changing and there's a huge benefit to getting plugins upstream where
+ they can undergo review and be ported as the API adapts.
+ I'm also super keen to provide configurability in GSettings for doing
+ obviously-useful things, the sort of thing Fleet Commander can set for
+ groups of users.
+ </para>
+ <para>
+ However, now we're shipping gnome-software in enterprise-class distros
+ we might want to allow customers to ship their own plugins to make
+ various business-specific changes that don't make sense upstream.
+ This might involve querying a custom LDAP server and changing the
+ suggested apps to reflect what groups the user is in, or might involve
+ showing a whole new class of applications that does not conform to the
+ Linux-specific <emphasis>application is a desktop-file</emphasis> paradigm.
+ This is where a plugin makes sense.
+ </para>
+
+ <para>
+ The plugin needs to create a class derived from <type>GsPlugin</type>,
+ and define the vfuncs that it needs. The
+ plugin name is taken automatically from the suffix of the
+ <filename>.so</filename> file. The type of the plugin is exposed to
+ gnome-software using <function>gs_plugin_query_type()</function>, which
+ must be exported from the module.
+ </para>
+ <example>
+ <title>A sample plugin</title>
+ <programlisting>
+/*
+ * Copyright (C) 2016 Richard Hughes
+ */
+
+#include &lt;glib.h&gt;
+#include &lt;gnome-software.h&gt;
+
+struct _GsPluginSample {
+ GsPlugin parent;
+
+ /* private data here */
+};
+
+G_DEFINE_TYPE (GsPluginSample, gs_plugin_sample, GS_TYPE_PLUGIN)
+
+static void
+gs_plugin_sample_init (GsPluginSample *self)
+{
+ GsPlugin *plugin = GS_PLUGIN (self);
+
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "appstream");
+}
+
+static void
+gs_plugin_sample_list_apps_async (GsPlugin *plugin,
+ GsAppQuery *query,
+ GsPluginListAppsFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GsPluginSample *self = GS_PLUGIN_SAMPLE (plugin);
+ g_autoptr(GTask) task = NULL;
+ const gchar * const *keywords;
+ g_autoptr(GsAppList) list = gs_app_list_new ();
+
+ task = gs_plugin_list_apps_data_new_task (plugin, query, flags,
+ cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_sample_list_apps_async);
+
+ if (query == NULL ||
+ gs_app_query_get_keywords (query) == NULL ||
+ gs_app_query_get_n_properties_set (query) != 1) {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unsupported query");
+ return;
+ }
+
+ keywords = gs_app_query_get_keywords (query);
+
+ for (gsize i = 0; keywords[i] != NULL; i++) {
+ if (g_str_equal (keywords[i], "fotoshop")) {
+ g_autoptr(GsApp) app = gs_app_new ("gimp.desktop");
+ gs_app_add_quirk (app, GS_APP_QUIRK_IS_WILDCARD);
+ gs_app_list_add (list, app);
+ }
+ }
+
+ g_task_return_pointer (task, g_steal_pointer (&amp;list), g_object_unref);
+}
+
+static GsAppList *
+gs_plugin_sample_list_apps_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+gs_plugin_sample_class_init (GsPluginSampleClass *klass)
+{
+ GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass);
+
+ plugin_class->list_apps_async = gs_plugin_sample_list_apps_async;
+ plugin_class->list_apps_finish = gs_plugin_sample_list_apps_finish;
+}
+
+GType
+gs_plugin_query_type (void)
+{
+ return GS_TYPE_PLUGIN_SAMPLE;
+}
+ </programlisting>
+ </example>
+
+ <para>
+ We have to define when our plugin is run in reference to other plugins,
+ in this case, making sure we run before <code>appstream</code>.
+ As we're such a simple plugin we're relying on another plugin to run
+ after us to actually make the GsApp <emphasis>complete</emphasis>,
+ i.e. loading icons and setting a localised long description.
+ </para>
+ <para>
+ In this example we want to show GIMP as a result (from any provider,
+ e.g. flatpak or a distro package) when the user searches exactly for
+ <code>fotoshop</code>.
+ </para>
+ <para>
+ We can then build and install the plugin using:
+ </para>
+ <informalexample>
+ <programlisting>
+gcc -shared -o libgs_plugin_example.so gs-plugin-example.c -fPIC \
+ $(pkg-config --libs --cflags gnome-software) \
+ -DI_KNOW_THE_GNOME_SOFTWARE_API_IS_SUBJECT_TO_CHANGE &amp;&amp; \
+cp libgs_plugin_example.so $(pkg-config gnome-software --variable=plugindir)
+ </programlisting>
+ </informalexample>
+
+ <mediaobject id="gs-example-search">
+ <imageobject>
+ <imagedata format="PNG" fileref="gs-example-search.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ </section>
+
+ <section>
+ <title>Distribution Specific Functionality</title>
+ <para>
+ Some plugins should only run on specific distributions, for instance
+ the <code>fedora-pkgdb-collections</code> plugin should only be used on
+ Fedora systems.
+ This can be achieved with a simple runtime check using the helper
+ <code>gs_plugin_check_distro_id()</code> method or the <code>GsOsRelease</code>
+ object where more complicated rules are required.
+ </para>
+ <example>
+ <title>Self disabling on other distributions</title>
+ <programlisting>
+static void
+gs_plugin_sample_init (GsPluginSample *self)
+{
+ GsPlugin *plugin = GS_PLUGIN (self);
+
+ if (!gs_plugin_check_distro_id (plugin, "ubuntu")) {
+ gs_plugin_set_enabled (plugin, FALSE);
+ return;
+ }
+ /* set up private data etc. */
+}
+ </programlisting>
+ </example>
+
+ </section>
+
+ <section>
+ <title>Custom Applications in the Installed List</title>
+ <para>
+ Next is returning custom applications in the installed list.
+ The use case here is a proprietary software distribution method that
+ installs custom files into your home directory, but you can use your
+ imagination for how this could be useful.
+ The example here is all hardcoded, and a true plugin would have to
+ derive the details about the GsApp, for example reading in an XML
+ file or YAML config file somewhere.
+ </para>
+ <example>
+ <title>Example showing a custom installed application</title>
+ <programlisting>
+static void
+gs_plugin_sample_init (GsPluginSample *self)
+{
+ GsPlugin *plugin = GS_PLUGIN (self);
+
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "icons");
+}
+
+static void
+gs_plugin_custom_list_apps_async (GsPlugin *plugin,
+ GsAppQuery *query,
+ GsPluginListAppsFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autofree gchar *fn = NULL;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(GIcon) icon = NULL;
+ g_autoptr(GsAppList) list = gs_app_list_new ();
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (plugin, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_custom_list_apps_async);
+
+ /* We’re only listing installed apps in this example. */
+ if (query == NULL ||
+ gs_app_query_get_is_installed (query) != GS_APP_QUERY_TRISTATE_TRUE ||
+ gs_app_query_get_n_properties_set (query) != 1) {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "Unsupported query");
+ return;
+ }
+
+ /* check if the app exists */
+ fn = g_build_filename (g_get_home_dir (), "chiron", NULL);
+ if (!g_file_test (fn, G_FILE_TEST_EXISTS)) {
+ g_task_return_pointer (task, g_steal_pointer (&amp;list), g_object_unref);
+ return;
+ }
+
+ /* the trigger exists, so create a fake app */
+ app = gs_app_new ("chiron.desktop");
+ gs_app_set_management_plugin (app, plugin);
+ gs_app_set_kind (app, AS_COMPONENT_KIND_DESKTOP_APP);
+ gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+ gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "Chiron");
+ gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "A teaching application");
+ gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
+ "Chiron is the name of an application.\n\n"
+ "It can be used to demo some of our features");
+
+ /* these are all optional, but make details page looks better */
+ gs_app_set_version (app, "1.2.3");
+ gs_app_set_size_installed (app, GS_SIZE_TYPE_VALID, 2 * 1024 * 1024);
+ gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 3 * 1024 * 1024);
+ gs_app_set_origin_hostname (app, "http://www.teaching-example.org/");
+ gs_app_add_category (app, "Game");
+ gs_app_add_category (app, "ActionGame");
+ gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
+ gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0+ and LGPL-2.1+");
+
+ /* use a stock icon */
+ icon = g_themed_icon_new ("input-gaming");
+ gs_app_add_icon (app, icon);
+
+ /* return new app */
+ gs_app_list_add (list, app);
+
+ g_task_return_pointer (task, g_steal_pointer (&amp;list), g_object_unref);
+}
+
+static GsAppList *
+gs_plugin_custom_list_apps_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+ </programlisting>
+ </example>
+ <para>
+ This shows a lot of the plugin architecture in action. Some notable points:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ Setting the management plugin means we can check for this string
+ when working out if we can handle the install or remove action.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Most applications want a kind of <code>AS_COMPONENT_KIND_DESKTOP_APP</code>
+ to be visible as an application.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The origin is where the application originated from — usually
+ this will be something like <emphasis>Fedora Updates</emphasis>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The <code>GS_APP_KUDO_INSTALLS_USER_DOCS</code> means we get the
+ blue "Documentation" award in the details page; there are many
+ kudos to award to deserving apps.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Setting the license means we don't get the non-free warning —
+ removing the 3rd party warning can be done using
+ <code>GS_APP_QUIRK_PROVENANCE</code>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The icon will be loaded into a pixbuf of the correct size when
+ needed by the UI. You must ensure that icons are available at
+ common sizes. For icons of type <code>GsRemoteIcon</code>, the
+ <code>icons</code> plugin will download and cache the icon
+ locally.
+ </para>
+ </listitem>
+ </itemizedlist>
+ <para>
+ To show this fake application just compile and install the plugin,
+ <code>touch ~/chiron</code> and then restart gnome-software.
+ To avoid restarting <filename>gnome-software</filename> each time a
+ proper plugin would create a <code>GFileMonitor</code> object to
+ monitor files.
+ </para>
+
+ <mediaobject id="gs-example-installed">
+ <imageobject>
+ <imagedata format="PNG" fileref="gs-example-installed.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>
+ By filling in the optional details (which can also be filled in using
+ <code>refine_async()</code> you can also make the details
+ page a much more exciting place.
+ Adding a set of screenshots is left as an exercise to the reader.
+ </para>
+
+ <mediaobject id="gs-example-details">
+ <imageobject>
+ <imagedata format="PNG" fileref="gs-example-details.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ </section>
+
+ <section>
+ <title>Downloading Metadata and Updates</title>
+
+ <para>
+ The plugin loader supports a <code>refresh_metadata_async()</code> vfunc that
+ is called in various situations.
+ To ensure plugins have the minimum required metadata on disk it is
+ called at startup, but with a cache age of <emphasis>infinite</emphasis>.
+ This basically means the plugin must just ensure that
+ <emphasis role="strong">any</emphasis> data exists no matter what the age.
+ </para>
+ <para>
+ Usually once per hour, we'll call <code>refresh_metadata_async()</code> but
+ with the correct cache age set (typically a little over 24 hours) which
+ allows the plugin to download new metadata or payload files from remote
+ servers.
+ The <code>gs_utils_get_file_age()</code> utility helper can help you
+ work out the cache age of a file, or the plugin can handle it some other
+ way.
+ </para>
+ <para>
+ For the Flatpak plugin we just make sure the AppStream metadata exists
+ at startup, which allows us to show search results in the UI.
+ If the metadata did not exist (e.g. if the user had added a remote
+ using the command-line without gnome-software running) then we would
+ show a loading screen with a progress bar before showing the main UI.
+ On fast connections we should only show that for a couple of seconds,
+ but it's a good idea to try any avoid that if at all possible in the
+ plugin.
+ Once per day the <code>gs_plugin_get_updates()</code> method is called,
+ and then <code>gs_plugin_download_app()</code> may be called if the
+ user has configured automatic updates.
+ This is where the Flatpak plugin would download any ostree trees (but
+ not doing the deploy step) so that the applications can be updated live
+ in the details panel without having to wait for the download to complete.
+ In a similar way, the fwupd plugin downloads the tiny LVFS metadata with
+ <code>refresh_metadata_async()</code> and then downloads the large firmware
+ files themselves when <code>gs_plugin_download()</code> or
+ <code>gs_plugin_download_app()</code> is called.
+ </para>
+ <para>
+ Note, if the downloading fails it's okay to return <code>FALSE</code>;
+ the plugin loader continues to run all plugins and just logs an error
+ to the console. We'll be calling into <code>refresh_metadata_async()</code>
+ again in only another hour, so there's no need to bother the user.
+ For actions like <code>gs_plugin_app_install</code> we also do the same
+ thing, but we also save the error on the GsApp itself so that the UI is
+ free to handle that how it wants, for instance showing a GtkDialog
+ window for example.
+ </para>
+ <example>
+ <title>Refresh example</title>
+ <programlisting>
+static void progress_cb (gsize bytes_downloaded,
+ gsize total_download_size,
+ gpointer user_data);
+static void download_file_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data);
+
+static void
+gs_plugin_example_refresh_metadata_async (GsPlugin *plugin,
+ guint64 cache_age_secs,
+ GsPluginRefreshMetadataFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *metadata_filename = "/var/cache/example/metadata.xml";
+ const gchar *metadata_url = "https://www.example.com/new.xml";
+ g_autoptr(GFile) file = g_file_new_for_path (metadata_filename);
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(SoupSession) soup_session = NULL;
+
+ task = g_task_new (plugin, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_example_refresh_metadata_async);
+
+ soup_session = gs_build_soup_session ();
+
+ /* is the metadata missing or too old? */
+ if (gs_utils_get_file_age (file) &gt; cache_age_secs) {
+ gs_download_file_async (soup_session,
+ metadata_url,
+ file,
+ G_PRIORITY_LOW,
+ progress_cb,
+ plugin,
+ cancellable,
+ download_file_cb,
+ g_steal_pointer (&amp;task));
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+progress_cb (gsize bytes_downloaded,
+ gsize total_download_size,
+ gpointer user_data)
+{
+ g_debug ("Downloaded %zu of %zu bytes", bytes_downloaded, total_download_size);
+}
+
+static void
+download_file_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GsPlugin *plugin = GS_PLUGIN (source_object);
+ g_autoptr(GTask) task = g_steal_pointer (&amp;user_data);
+
+ if (!gs_download_file_finish (result, &amp;local_error)) {
+ g_task_return_error (task, g_steal_pointer (&amp;local_error));
+ } else {
+ g_debug ("successfully downloaded new metadata");
+ g_task_return_boolean (task, TRUE);
+ }
+}
+
+static gboolean
+gs_plugin_example_refresh_metadata_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+ </programlisting>
+ </example>
+ </section>
+
+ <section>
+ <title>Adding Application Information Using Refine</title>
+
+ <para>
+ As previous examples have shown it's very easy to add a new
+ application to the search results, updates list or installed list.
+ Some plugins don't want to add more applications, but want to modify
+ existing applications to add more information depending on what is
+ required by the UI code.
+ The reason we don't just add everything at once is that for
+ search-as-you-type to work effectively we need to return results in
+ less than about 50ms and querying some data can take a long time.
+ For example, it might take a few hundred ms to work out the download
+ size for an application when a plugin has to also look at what
+ dependencies are already installed.
+ We only need this information once the user has clicked the search
+ results and when the user is in the details panel, so we can save a
+ ton of time not working out properties that are not useful.
+ </para>
+ <example>
+ <title>Refine example</title>
+ <programlisting>
+static void
+gs_plugin_example_refine_async (GsPlugin *plugin,
+ GsAppList *list,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (plugin, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gs_plugin_example_refine_async);
+
+ /* not required */
+ if ((flags &amp; GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) == 0) {
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ for (guint i = 0; i &lt; gs_app_list_length (list); i++) {
+ GsApp *app = gs_app_list_index (list, i);
+
+ /* already set */
+ if (gs_app_get_license (app) != NULL) {
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ /* FIXME, not just hardcoded! */
+ if (g_strcmp0 (gs_app_get_id (app, "chiron.desktop") == 0))
+ gs_app_set_license (app, "GPL-2.0 and LGPL-2.0+");
+ }
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_example_refine_finish (GsPlugin *plugin,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+ </programlisting>
+ </example>
+ <para>
+ This is a simple example, but shows what a plugin needs to do.
+ It first checks if the action is required, in this case
+ <code>GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE</code>.
+ This request is more common than you might expect as even the search
+ results shows a non-free label if the license is unspecified or
+ non-free.
+ It then checks if the license is already set, returning with success
+ if so.
+ If not, it checks the application ID and hardcodes a license; in the
+ real world this would be querying a database or parsing an additional
+ config file.
+ As mentioned before, if the license value is freely available without
+ any extra work then it's best just to set this at the same time as
+ when adding the app with <code>gs_app_list_add()</code>.
+ Think of refine as <emphasis>adding things that cost time to
+ calculate only when really required</emphasis>.
+ </para>
+ <para>
+ The UI in gnome-software is quite forgiving for missing data, hiding
+ sections or labels as required.
+ Some things are required however, and forgetting to assign an icon or
+ short description will get the application vetoed so that it's not
+ displayed at all.
+ Helpfully, running <code>gnome-software --verbose</code> on the
+ command line will tell you why an application isn't shown along with
+ any extra data.
+ </para>
+
+ </section>
+
+ <section>
+ <title>Adopting AppStream Applications</title>
+
+ <para>
+ There's a lot of flexibility in the gnome-software plugin structure;
+ a plugin can add custom applications and handle things like search and
+ icon loading in a totally custom way.
+ Most of the time you don't care about how search is implemented or how
+ icons are going to be loaded, and you can re-use a lot of the existing
+ code in the <code>appstream</code> plugin.
+ To do this you just save an AppStream-format XML file in either
+ <filename>/usr/share/swcatalog/xml/</filename>,
+ <filename>/var/cache/swcatalog/xml/</filename> or
+ <filename>~/.local/share/swcatalog/xml/</filename>.
+ GNOME Software will immediately notice any new files, or changes to
+ existing files as it has set up the various inotify watches.
+ </para>
+ <para>
+ This allows plugins to care a lot less about how applications are
+ going to be shown.
+ For example, the <code>flatpak</code> plugin downloads AppStream data
+ for configured remotes during <code>refresh_metadata_async()</code>.
+ </para>
+ <para>
+ The only extra step a plugin providing its own apps needs to do
+ is to implement the <code>gs_plugin_adopt_app()</code> function.
+ This is called when an application does not have a management plugin
+ set, and allows the plugin to <emphasis>claim</emphasis> the
+ application for itself so it can handle installation, removal and
+ updating.
+ </para>
+ <para>
+ Another good example is the <code>fwupd</code> that wants to handle
+ any firmware we've discovered in the AppStream XML.
+ This might be shipped by the vendor in a package using Satellite,
+ or downloaded from the LVFS. It wouldn't be kind to set a management
+ plugin explicitly in case XFCE or KDE want to handle this in a
+ different way. This adoption function in this case is trivial:
+ </para>
+
+ <informalexample>
+ <programlisting>
+void
+gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
+{
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_FIRMWARE)
+ gs_app_set_management_plugin (app, plugin);
+}
+ </programlisting>
+ </informalexample>
+ </section>
+
+ <section>
+ <title>Using The Plugin Cache</title>
+
+ <para>
+ GNOME Software used to provide a per-process plugin cache,
+ automatically de-duplicating applications and trying to be smarter
+ than the plugins themselves.
+ This involved merging applications created by different plugins and
+ really didn't work very well.
+ For versions 3.20 and later we moved to a per-plugin cache which
+ allows the plugin to control getting and adding applications to the
+ cache and invalidating it when it made sense.
+ This seems to work a lot better and is an order of magnitude less
+ complicated.
+ Plugins can trivially be ported to using the cache using something
+ like this:
+ </para>
+ <informalexample>
+ <programlisting>
+ /* create new object */
+ id = gs_plugin_flatpak_build_id (inst, xref);
+- app = gs_app_new (id);
++ app = gs_plugin_cache_lookup (plugin, id);
++ if (app == NULL) {
++ app = gs_app_new (id);
++ gs_plugin_cache_add (plugin, id, app);
++ }
+ </programlisting>
+ </informalexample>
+ <para>
+ Using the cache has two main benefits for plugins.
+ The first is that we avoid creating duplicate GsApp objects for the
+ same logical thing.
+ This means we can query the installed list, start installing an
+ application, then query it again before the install has finished.
+ The GsApp returned from the second <code>list_apps()</code>
+ request will be the same GObject, and thus all the signals connecting
+ up to the UI will still be correct.
+ This means we don't have to care about <emphasis>migrating</emphasis>
+ the UI widgets as the object changes and things like progress bars just
+ magically work.
+ </para>
+ <para>
+ The other benefit is more obvious.
+ If we know the application state from a previous request we don't have
+ to query a daemon or do another blocking library call to get it.
+ This does of course imply that the plugin is properly invalidating
+ the cache using <code>gs_plugin_cache_invalidate()</code> which it
+ should do whenever a change is detected.
+ Whether a plugin uses the cache for this reason is up to the plugin,
+ but if it does it is up to the plugin to make sure the cache doesn't
+ get out of sync.
+ </para>
+ </section>
+
+ </partintro>
+ </reference>
+
+ <reference id="api">
+ <partintro>
+ <para>
+ This documentation is auto-generated.
+ If you see any issues, please file bugs.
+ </para>
+ </partintro>
+ <title>GNOME Software Plugin API</title>
+ <xi:include href="xml/gs-app.xml"/>
+ <xi:include href="xml/gs-app-collation.xml"/>
+ <xi:include href="xml/gs-app-list.xml"/>
+ <xi:include href="xml/gs-app-query.xml"/>
+ <xi:include href="xml/gs-appstream.xml"/>
+ <xi:include href="xml/gs-category.xml"/>
+ <xi:include href="xml/gs-category-manager.xml"/>
+ <xi:include href="xml/gs-debug.xml"/>
+ <xi:include href="xml/gs-desktop-data.xml"/>
+ <xi:include href="xml/gs-download-utils.xml"/>
+ <xi:include href="xml/gs-external-appstream-utils.xml"/>
+ <xi:include href="xml/gs-fedora-third-party.xml"/>
+ <xi:include href="xml/gs-icon.xml"/>
+ <xi:include href="xml/gs-ioprio.xml"/>
+ <xi:include href="xml/gs-key-colors.xml"/>
+ <xi:include href="xml/gs-metered.xml"/>
+ <xi:include href="xml/gs-odrs-provider.xml"/>
+ <xi:include href="xml/gs-os-release.xml"/>
+ <xi:include href="xml/gs-plugin.xml"/>
+ <xi:include href="xml/gs-plugin-event.xml"/>
+ <xi:include href="xml/gs-plugin-helpers.xml"/>
+ <xi:include href="xml/gs-plugin-job-list-apps.xml"/>
+ <xi:include href="xml/gs-plugin-job-list-categories.xml"/>
+ <xi:include href="xml/gs-plugin-job-list-distro-upgrades.xml"/>
+ <xi:include href="xml/gs-plugin-job-refine.xml"/>
+ <xi:include href="xml/gs-plugin-job-refresh-metadata.xml"/>
+ <xi:include href="xml/gs-plugin-job.xml"/>
+ <xi:include href="xml/gs-plugin-loader-sync.xml"/>
+ <xi:include href="xml/gs-plugin-loader.xml"/>
+ <xi:include href="xml/gs-plugin-types.xml"/>
+ <xi:include href="xml/gs-plugin-vfuncs.xml"/>
+ <xi:include href="xml/gs-remote-icon.xml"/>
+ <xi:include href="xml/gs-test.xml"/>
+ <xi:include href="xml/gs-worker-thread.xml"/>
+ <xi:include href="xml/gs-utils.xml"/>
+ </reference>
+
+</book>
diff --git a/doc/api/gnome-software.types b/doc/api/gnome-software.types
new file mode 100644
index 0000000..5f0a7b0
--- /dev/null
+++ b/doc/api/gnome-software.types
@@ -0,0 +1,6 @@
+gs_app_get_type
+gs_app_list_get_type
+gs_category_get_type
+gs_os_release_get_type
+gs_plugin_event_get_type
+gs_plugin_get_type
diff --git a/doc/api/gs-example-details.png b/doc/api/gs-example-details.png
new file mode 100644
index 0000000..88c29e4
--- /dev/null
+++ b/doc/api/gs-example-details.png
Binary files differ
diff --git a/doc/api/gs-example-installed.png b/doc/api/gs-example-installed.png
new file mode 100644
index 0000000..0cd424a
--- /dev/null
+++ b/doc/api/gs-example-installed.png
Binary files differ
diff --git a/doc/api/gs-example-search.png b/doc/api/gs-example-search.png
new file mode 100644
index 0000000..77611bd
--- /dev/null
+++ b/doc/api/gs-example-search.png
Binary files differ
diff --git a/doc/api/meson.build b/doc/api/meson.build
new file mode 100644
index 0000000..2ccab7b
--- /dev/null
+++ b/doc/api/meson.build
@@ -0,0 +1,11 @@
+gnome.gtkdoc(
+ 'gnome-software',
+ src_dir : join_paths(meson.project_source_root(), 'lib'),
+ main_xml : 'gnome-software-docs.xml',
+ html_assets : [
+ 'gs-example-details.png',
+ 'gs-example-installed.png',
+ 'gs-example-search.png'
+ ],
+ install : true
+)
diff --git a/doc/app-developers.md b/doc/app-developers.md
new file mode 100644
index 0000000..dad5da7
--- /dev/null
+++ b/doc/app-developers.md
@@ -0,0 +1,69 @@
+Tools in GNOME Software for application developers
+==================================================
+
+GNOME Software is often where users will first come into contact with an
+application which they might later install, so the impression the user gets of
+that application is important. Application developers want to see how their
+application will appear in GNOME Software, and to have some control over it.
+
+GNOME Software provides some tools to help with this.
+
+If there is a supported tool which is not in this document, please
+[submit a merge request](https://gitlab.gnome.org/GNOME/gnome-software/-/merge_requests/new)
+to document it.
+
+Metainfo/Appstream
+------------------
+
+The information about an application comes from its metainfo (or, as it was
+previously known: appdata) file. Metainfo files for multiple applications are
+concatenated into ‘appstream’ files. Almost all of the customisation which GNOME
+Software provides for applications comes from information in metainfo or
+appstream files.
+
+So the first thing to check for your application is that its metainfo file is
+complete and valid. See the
+[metainfo file specification](https://www.freedesktop.org/software/appstream/docs/).
+
+To validate your metainfo file, run
+```
+appstreamcli validate /path/to/app.metainfo.xml
+```
+
+You can add this as a unit test to your application by adding the following to
+the appropriate `meson.build` in your application:
+```
+metainfo_file = files('com.example.MyApp.metainfo.xml')
+appstreamcli = find_program('appstreamcli', required: false)
+if appstreamcli.found()
+ test (
+ 'Validate metainfo file',
+ appstreamcli,
+ args: ['validate', '--no-net', metainfo_file],
+ )
+endif
+```
+
+Context tiles
+-------------
+
+The context tiles which are shown on an application’s details page in GNOME
+Software are derived from the application’s metainfo.
+
+There’s more detailed information about them, and the information they are built
+from, [on the GNOME Software wiki](https://gitlab.gnome.org/GNOME/gnome-software/-/wikis/Software-metadata).
+
+Previewing the details page for an application
+----------------------------------------------
+
+GNOME Software allows previewing how an application will appear, by loading its
+metainfo file directly. This allows previewing in-progress changes to an
+application without publishing it to a repository.
+
+Do this with the `--show-metainfo` argument:
+```
+gnome-software --show-metainfo=/path/to/app.metainfo.xml,icon=/path/to/icon.png
+```
+
+This will show the application in the details page of GNOME Software, and will
+also display it in the featured carousel on the overview page.
diff --git a/doc/design.svg b/doc/design.svg
new file mode 100644
index 0000000..4f9a181
--- /dev/null
+++ b/doc/design.svg
@@ -0,0 +1,448 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="design.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.9899495"
+ inkscape:cx="290.74866"
+ inkscape:cy="1216.6706"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="1200"
+ inkscape:window-height="1881"
+ inkscape:window-x="1600"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="opacity:0.97154475;fill:#c3cdee;fill-opacity:1;stroke:#000000;stroke-width:0.51179862;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect2987-5"
+ width="115.2915"
+ height="34.988201"
+ x="179.70259"
+ y="127.36808"
+ ry="1.8272164"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <rect
+ style="opacity:0.97154475;fill:#cef1d7;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect2987-5-5-5-3"
+ width="110"
+ height="210"
+ x="75"
+ y="127.36218"
+ ry="4.6491089"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <path
+ style="fill:#000000;fill-opacity:0.97695851;stroke:none"
+ d="m 295,145.69551 0,3.33334 65,0 0,3.33333 20,-5 -20,-5 0,3.33333 z"
+ id="path3757-0"
+ inkscape:connector-curvature="0"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="240"
+ y="152.36218"
+ id="text3801-0"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8"
+ x="240"
+ y="152.36218"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;text-anchor:middle;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans">Plugins</tspan></text>
+ <rect
+ style="opacity:0.97154474999999996;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect3900"
+ width="75"
+ height="75"
+ x="380"
+ y="92.362183"
+ ry="11.155499"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="130"
+ y="152.36218"
+ id="text3801-0-7"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8-6"
+ x="130"
+ y="152.36218"
+ style="font-size:18px;text-align:center;text-anchor:middle;-inkscape-font-specification:Liberation Sans;font-family:Liberation Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">PackageKit</tspan></text>
+ <rect
+ style="opacity:0.97154475;fill:#cef1d7;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect2987-5-5-5"
+ width="110"
+ height="35"
+ x="75"
+ y="162.36218"
+ ry="0"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="130"
+ y="187.36218"
+ id="text3801-0-7-9"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8-6-7"
+ x="130"
+ y="187.36218"
+ style="font-size:18px;text-align:center;text-anchor:middle;-inkscape-font-specification:Liberation Sans;font-family:Liberation Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">AppStream</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="130"
+ y="222.36218"
+ id="text3801-0-7-4"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8-6-3"
+ x="130"
+ y="222.36218"
+ style="font-size:18px;text-align:center;text-anchor:middle;-inkscape-font-specification:Liberation Sans;font-family:Liberation Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">glick2</tspan></text>
+ <rect
+ style="opacity:0.97154475;fill:#cef1d7;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect2987-5-5-5-4"
+ width="110"
+ height="35"
+ x="75"
+ y="232.36218"
+ ry="0"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="130"
+ y="257.36218"
+ id="text3801-0-7-9-9"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8-6-7-3"
+ x="130"
+ y="257.36218"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;text-anchor:middle;font-family:Liberation Sans;-inkscape-font-specification:Liberation Sans">epiphany</tspan></text>
+ <rect
+ style="opacity:0.97154475;fill:#cef1d7;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect2987-5-5-2"
+ width="110"
+ height="35"
+ x="75"
+ y="267.36218"
+ ry="0"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="130"
+ y="292.36218"
+ id="text3801-0-7-1"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8-6-8"
+ x="130"
+ y="292.36218"
+ style="font-size:18px;text-align:center;text-anchor:middle;-inkscape-font-specification:Liberation Sans;font-family:Liberation Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">listaller</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="130"
+ y="327.36218"
+ id="text3801-0-7-9-7"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8-6-7-2"
+ x="130"
+ y="327.36218"
+ style="font-size:18px;text-align:center;text-anchor:middle;font-weight:normal;-inkscape-font-specification:Liberation Sans;font-family:Liberation Sans;font-style:normal;font-stretch:normal;font-variant:normal">ostree</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="190"
+ y="172.36218"
+ id="text3801-0-7-9-5"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ x="190"
+ y="172.36218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3913">list = Search(text)</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="186.11218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3951">list = AddInstalled()</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="199.86218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3917">list = AddUpdates()</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="213.61218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4121">list = AddPopular()</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="227.36218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3947">bool = AppInstall(app)</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="241.11218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3928">bool = AppUpdate(app)</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="254.86218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3930">bool = AppRemove(app)</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="268.61218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3934">bool = Refine(app)</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="282.36218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3941">bool = SetRating(app)</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="296.11218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3943">list = AddCategories()</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="309.86218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4050">:str name</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="323.61218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3044">:list deps</tspan><tspan
+ sodipodi:role="line"
+ x="190"
+ y="337.36218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3914">:bool enabled</tspan></text>
+ <rect
+ style="opacity:0.97154475;fill:#c3cdee;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect2987-5-6-0"
+ width="110"
+ height="35"
+ x="365"
+ y="197.36218"
+ ry="1.8278326"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <rect
+ style="opacity:0.97154475;fill:#c3cdee;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect2987-5-6"
+ width="110"
+ height="35"
+ x="360"
+ y="202.36218"
+ ry="1.8278326"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="415"
+ y="225.36218"
+ id="text3801-0-3"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ id="tspan3803-8-7"
+ x="415"
+ y="225.36218"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;text-anchor:middle;font-family:PakType Naqsh;-inkscape-font-specification:PakType Naqsh">Applications</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="360"
+ y="247.36218"
+ id="text3801-0-7-9-5-7"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957"><tspan
+ sodipodi:role="line"
+ x="360"
+ y="247.36218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4073">:str app-id (e.g. foo.desktop)</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="261.11218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4046">:ptr source (e.g. pkgname)</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="274.86218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4048">:str management_plugin</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="288.61218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4052">:str title (localized)</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="302.36218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4054">:str description (localized)</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="316.11218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4056">:str screenshots</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="329.86218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4058">:str icon_uri</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="343.61218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4093">:int rating</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="357.36218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan4064">:int state (e.g. installed)</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="371.11218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3037">:int app-kind (e.g. codec)</tspan><tspan
+ sodipodi:role="line"
+ x="360"
+ y="384.86218"
+ style="font-size:11px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Nimbus Mono L;-inkscape-font-specification:Nimbus Mono L"
+ id="tspan3959">:int install_date</tspan></text>
+ <path
+ style="fill:#000000;fill-opacity:0.97695851;stroke:none"
+ d="m 405,167.36218 -5,0 0.37902,15.83248 -3.33333,0 5,20 5,-20 -3.33333,0 z"
+ id="path3757-0-1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccc"
+ inkscape:export-filename="/home/hughsie/Code/gnome3/gnome-software/design.png"
+ inkscape:export-xdpi="120.03957"
+ inkscape:export-ydpi="120.03957" />
+ <image
+ y="96.362183"
+ x="385"
+ id="image3134"
+ xlink:href="file:///home/hughsie/Code/gnome3/gnome-software/data/icons/256x256/gnome-software.png"
+ height="66"
+ width="66" />
+ <flowRoot
+ xml:space="preserve"
+ id="flowRoot3137"
+ style="fill:black;stroke:none;stroke-opacity:1;stroke-width:1px;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;font-family:Abyssinica SIL;font-style:normal;font-weight:normal;font-size:8px;line-height:125%;letter-spacing:0px;word-spacing:0px;-inkscape-font-specification:Abyssinica SIL;font-stretch:normal;font-variant:normal"><flowRegion
+ id="flowRegion3139"><rect
+ id="rect3141"
+ width="118.69292"
+ height="96.469566"
+ x="349.00769"
+ y="375.0549" /></flowRegion><flowPara
+ id="flowPara3143"></flowPara></flowRoot> </g>
+</svg>
diff --git a/doc/kudos.md b/doc/kudos.md
new file mode 100644
index 0000000..f43aa51
--- /dev/null
+++ b/doc/kudos.md
@@ -0,0 +1,70 @@
+Kudos used in Software
+======================
+
+As part of the AppStream generation process we explode various parts of the
+distro binary package and try to build metadata by merging various sources
+together, for example AppData, desktop files and icons.
+
+As part of this we also have access to the finished binary and libraries, and
+so can also run tools on them to get a metric of awesomeness. So far, the
+metrics of awesomeness (here-on known as "kudos") are:
+
+ * `HiDpiIcon` — installs a 128x128 or larger application icon
+ * `HighContrast` — uses hicontrast or scalable icons for visually impaired users
+ * `ModernToolkit` — uses a modern toolkit like Gtk-3 or QT-5
+ * `Notifications` — registers desktop notifications
+ * `SearchProvider` — provides a search provider for GNOME Shell or KDE Plasma
+ * `UserDocs` — provides user documentation
+
+These attempt to define how tightly the application is integrated with the
+platform, which is usually a pretty good metric of awesomeness. Of course,
+some applications like Blender are an island in terms of integration, but of
+course are awesome.
+
+There are some other "run-time" kudos used as well. These are not encoded by
+the builder as they require some user information or are too specific to
+GNOME Software. These include:
+
+ * `FeaturedRecommended` — One of the design team chose to feature this
+ * `HasKeywords` — there are keywords in the desktop file used for searching
+ * `HasScreenshots` — more than one screenshot is supplied
+ * `MyLanguage` — has a populated translation in my locale, or a locale fallback
+ * `PerfectScreenshots` — screenshots are perfectly sized, in 16:9 aspect
+ * `Popular` — lots of people have downloaded this (only available on Fedora)
+ * `RecentRelease` — there been an upstream release in the last year
+
+You can verify the kudos your application is getting by doing something like:
+
+ killall gnome-software
+ gnome-software --verbose
+
+and then navigating to the details for an application you'll see on the console:
+
+ id-kind: desktop
+ state: available
+ id: blender.desktop
+ kudo: recent-release
+ kudo: featured-recommended
+ kudo: has-screenshots
+ kudo: popular
+
+Manually Adding Kudos
+---------------------
+
+If the AppStream generator fails to detect a specific kudo then you can add them
+manually in the AppData file and they will be used in the AppStream metadata.
+To do this, just add something like:
+
+ <kudos>
+ <kudo>ModernToolkit</kudo>
+ <kudo>UserDocs</kudo>
+ </kudos>
+
+Although, please bear in mind any application that is found cheating, i.e.
+adding kudos artificially will have **all** the kudos manually removed
+with a blocklist rule in the AppStream builder.
+
+If you are a vendor, or a system distributor and just want to increase the
+number of kudos for your pet proprietary application that's essential to
+business function, a good kudo to manually add would be `FeaturedRecommended`,
+or `GnomeSoftware::popular`.
diff --git a/doc/meson.build b/doc/meson.build
new file mode 100644
index 0000000..1a1d9bf
--- /dev/null
+++ b/doc/meson.build
@@ -0,0 +1,3 @@
+if get_option('gtk_doc')
+ subdir('api')
+endif
diff --git a/doc/privacy-policy.html b/doc/privacy-policy.html
new file mode 100644
index 0000000..ad97cac
--- /dev/null
+++ b/doc/privacy-policy.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+ <title>GNOME Software Privacy Policy</title>
+</head>
+<body>
+ <h1>Policy</h1>
+ <p>
+ GNOME Software can optionally use a ratings and reviews service offered by
+ your distribution.
+ If enabled, GNOME Software will notify a web service that a specific
+ application has been installed or removed.
+ This information about which software you use helps us provide other users
+ with more accurate recommendations and helps us to choose applications to
+ feature on the overview page.
+ </p>
+ <p>
+ When submitting ratings your IP address is sent along with the usage information.
+ This is exactly the same information submitted viewing this web page.
+ A one-way hash of this address is also stored in a database to prevent
+ duplicate entries being added by the same user, but this can not be used to
+ identify specific users or network addresses.
+ </p>
+ <p>
+ We will never share the list of applications or your IP address with third
+ parties other than the original distribution web-service used by GNOME Software.
+ </p>
+ <h1>History of Changes</h1>
+ <p>
+ Modified 2014-02-04 by Richard Hughes
+ </p>
+</html>
diff --git a/doc/update.sh b/doc/update.sh
new file mode 100755
index 0000000..5abf679
--- /dev/null
+++ b/doc/update.sh
@@ -0,0 +1 @@
+scp api/html/* hughsient@people.freedesktop.org:~/public_html/gnome-software
diff --git a/doc/use-cases.md b/doc/use-cases.md
new file mode 100644
index 0000000..c60eb4c
--- /dev/null
+++ b/doc/use-cases.md
@@ -0,0 +1,38 @@
+This document will evolve over time to indicate what goals and use cases
+gnome-software targets at the moment.
+
+Primary goals
+=============
+
+ * Allow people to find apps by browsing or search:
+ - a specific app that they're looking for, or
+ - apps in a particular category, or with particular functionality that they require
+ * Allow people to effectively inspect and appraise apps before they install them (screenshots, descriptions, ratings, comments, metadata)
+ * Allow people to view which apps are installed and remove them
+ * Present a positive view of the app ecosystem
+ - Reinforce the sense that there are lots of high quality apps
+ - Encourage people to engage with that ecosystem, both as users and as contributors
+ - When browsing, present and promote the best apps that are available
+ - Facilitate accidental discovery of great apps
+ * Handle software updates. Make software updates as little work for users as possible. To include: apps, OS updates (PackageKit, eos, rpm-ostree), firmware
+ * Support multiple software repositories, defined by both the distributor and users.
+ - Show which repos are configured. Allow them to be added/removed.
+ - Handle cases where the same app can be installed from multiple sources.
+
+Secondary goals
+===============
+
+ * OS upgrades
+ * Hardware driver installation
+ * Input method installation
+ * Respond to application queries for software (apps, codecs, languages)
+ * Offline and metered connections
+ * OS updates end of life
+ * App end of life
+
+Non-goals
+=========
+
+ * Not a package manager front-end
+ * Not all repos are equal
+ * Not all apps are equal
diff --git a/doc/vendor-customisation.md b/doc/vendor-customisation.md
new file mode 100644
index 0000000..8866910
--- /dev/null
+++ b/doc/vendor-customisation.md
@@ -0,0 +1,214 @@
+Vendor customisation of GNOME Software
+======================================
+
+GNOME Software is in an unusual position in a distribution, as it lies at the
+interface of the GNOME project and the distribution’s packaging and release
+infrastructure. GNOME Software is the user interface which a lot of users will
+use to see updates and new releases from their distribution. Distributions
+understandably want to be able to put some of their own branding on this
+interface, both to publicise their distribution and to confer some level of
+official authority on the updates being provided.
+
+For this reason, GNOME Software has a few ways which vendors can use to
+customise its appearance.
+
+A variety of different customisations have been implemented in the past, some of
+which have been removed and others are still present. This document aims to
+document the ones which are still present and supported. This document is *not
+necessarily complete*. It will be added to over time as different customisations
+are refreshed and updated.
+
+If there is a supported customisation method which is not in this document,
+please [submit a merge request](https://gitlab.gnome.org/GNOME/gnome-software/-/merge_requests/new)
+to document it.
+
+Likewise, if your distribution would like to customise gnome-software in a way
+which isn’t currently supported, please
+[open a new issue](https://gitlab.gnome.org/GNOME/gnome-software/-/issues/new?issue%5Bmilestone_id%5D=)
+to discuss it. We don’t guarantee to implement anything, and customisations are
+limited to adding branding in specific areas.
+
+Principles
+----------
+
+The principles which guide vendor customisation features in GNOME Software are:
+ * Avoid requiring vendor specific code.
+ - Otherwise vendors have to maintain and test GNOME Software plugins, which
+ is a lot of work.
+ * Don’t use GSettings unless customisations really should be per-user.
+ - While GSettings overrides are convenient, they are designed for user
+ preferences, not packaging customisation.
+ * Don’t require downstream patching of GNOME Software, although configure-time
+ arguments are OK.
+ - Many distributions are derived from other ones and would not like to have
+ to maintain a packaging fork in order to make small customisations.
+ * Be mindful of release cadences.
+ - If customisations related to a new OS version were tied to the release
+ cycle of GNOME Software, a new GNOME Software packaging release would have
+ to be done by a distribution in advance of making their new OS release,
+ which is a burden.
+ - It’s easier to allow distributions to put customisations specific to a new
+ OS version into a separate package.
+
+Upgrade background image
+------------------------
+
+The background image which is shown when a new OS upgrade is available is
+customisable in several ways. It’s displayed by the `GsUpgradeBanner` widget,
+and shown on the updates page.
+
+If your distribution has a specific GNOME Software plugin providing its upgrade
+information, that plugin can provide CSS for rendering the background. See the
+`fedora-pkgdb-collections` plugin for an example of this.
+
+Otherwise, the background image is looked up from several well-known locations,
+in order:
+ * `${DATADIR}/gnome-software/backgrounds/${os_id}-${version}.png`
+ * `${DATADIR}/gnome-software/backgrounds/${os_id}.png`
+
+`${DATADIR}` is the configured data directory (typically `/usr/share`).
+`${os_id}` is the `ID=` value from `/etc/os-release`, and `${version}` is the
+version string being upgraded to.
+
+Featured apps and Editor’s Choice
+---------------------------------
+
+There are several ways to promote and highlight specific applications in GNOME
+Software. On the overview page, there’s a carousel of featured applications
+(`featured_carousel`), and an “Editor’s Choice” section (`box_curated`). Both of
+them highlight curated sets of applications. The same is true on each category
+page: a carousel (`top_carousel`) and an “Editor’s Choice” section
+(`featured_flow_box`) are present.
+
+Both pages also have a “New & Updated” section (`box_recent` or
+`recently_updated_flow_box`) presented below “Editor’s Choice”. The applications
+listed in the new and updated section are not curated: they are chosen as the
+applications which have had a recent release, according to the
+`component/releases/release[@timestamp]` attribute in their metainfo.
+Technically these are the results of a `GsPlugin.list_apps_async()` query with
+`GsAppQuery:released-since` set.
+
+Applications are included in any of the curated sets through having special
+metadata in their metainfo. The required metadata is different for the different
+sections:
+ * Carousel on the overview page: Applications are included if they have
+ `component/custom/value[@key='GnomeSoftware::FeatureTile]` or
+ `component/custom/value[@key='GnomeSoftware::FeatureTile-css]` set in their
+ metainfo. They are also required to have a high-resolution icon, and the set
+ of applications shown in the carousel is randomised and limited to (for
+ example) 5. Technically these are the results of a
+ `GsPlugin.list_apps_async()` query with `GsAppQuery:is-featured` set.
+ * “Editor’s Choice” on the overview page: Applications are included if they
+ have `component/kudos/kudo[text()='GnomeSoftware::popular']` set in their
+ metainfo. Technically these are the results of a `GsPlugin.list_apps_async()`
+ query with `GsAppQuery:is-curated` set.
+ * Carousel on the category page: Applications are included if they are in the
+ `Featured` subcategory of the displayed category. They are also required to
+ have a high-resolution icon, and the set of applications shown in the carousel
+ is randomised and limited to (for example) 5.
+ * “Editor’s Choice” on the category page: Applications are included if they
+ meet the requirements for being in the carousel, but weren’t chosen as part
+ of the randomisation process.
+
+Example:
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<components>
+ <component merge="append">
+ <!-- The ID must always be present to allow merging -->
+ <id>org.gnome.Podcasts</id>
+
+ <!-- Make the app a candidate for inclusion in the carousel on the
+ overview page (if it has a hi-res icon). -->
+ <custom>
+ <value key="GnomeSoftware::FeatureTile">True</value>
+ </custom>
+
+ <!-- Include the app in the “Editor’s Choice” section on the overview page. -->
+ <kudos>
+ <kudo>GnomeSoftware::popular</kudo>
+ </kudos>
+
+ <!-- Make the app a candidate for inclusion in the carousel or
+ “Editor’s Choice” section on category pages (if it has a hi-res icon). -->
+ <categories>
+ <!-- Note that, due to a bug (https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1649),
+ currently all the other categories for the app must also be listed
+ here, as well as the additional ‘Featured’ category. -->
+ <category>AudioVideo</category>
+ <category>Player</category>
+ <!-- This category has been added: -->
+ <category>Featured</category>
+ </categories>
+ </component>
+ <!-- more components -->
+</components>
+```
+
+There are several ways to modify the metainfo for applications so that they are
+highlighted as required, all of which involve providing an additional appstream
+file which sets the additional metainfo for those applications.
+
+The main approach is to ship an additional distro-specific appstream file in
+`${DATADIR}/swcatalog/xml`, providing and updating it via normal distribution
+channels. For example, by packaging it in its own package which is updated
+regularly.
+
+For distributions which can’t do regular updates of individual files – such as
+image-based distributions – GNOME Software can download distro-specific
+appstream files from the internet. List them in the `external-appstream-urls`
+GSetting in `/org/gnome/software`, typically via a distribution-provided
+GSettings override. Each URL must be HTTPS, and must point to a valid appstream
+file. GNOME Software must be configured with `-Dexternal_appstream=true` for
+this to work.
+
+GNOME Software will periodically check and download any updates to these
+files, and will cache them locally. Ensure the `If-Modified-Since` HTTP header
+functions correctly on your server, or GNOME Software’s caching will be
+ineffective.
+
+The `external-appstream-urls` mechanism may change in future.
+
+GNOME Software ships a default list of featured applications, chosen to match
+the [GNOME Circle](https://circle.gnome.org/). See
+`data/assets/org.gnome.Software.Featured.xml` for this list, and for an example
+of the metainfo XML needed to feature or highlight applications. See
+`data/assets/org.gnome.Software.Curated.xml` for a default hard-coded list of
+curated high quality applications, which is displayed in the “Editor’s Choice”
+section of the overview page.
+
+Pass `-Ddefault_featured_apps=false` when configuring GNOME Software to disable
+the default list of featured applications. Pass `-Dhardcoded_curated=false` to
+disable the default list of “Editor’s Choice” applications.
+
+Deployment Featured Apps
+------------------------
+
+Deployments can feature their own applications, which will be shown in the Explore
+page in its own section. To have the section shown, two files need to be provided.
+The number of deployment-featured apps is limited in the UI, and if not enough
+deployment-featured apps are found, then the section will not be shown at all.
+
+The first file is `org.gnome.Software.DeploymentFeatured.xml`, which works similarly
+to `org.gnome.Software.Featured.xml` and should be saved beside it in an appstream
+directory. It sets the `GnomeSoftware::DeploymentFeatured` key on apps which should
+be featured for this distribution or deployment. The value of this key is a string
+containing the deployment name as an identifier.
+
+The second file is `deployment-featured.ini`, which contains a human-readable title and
+the selector for the section. The title is a localized key, and is used to set the heading
+for the section on the Explore page. The selector defines which apps should be picked.
+It is a semicolon-separated list of `GnomeSoftware::DeploymentFeatured` key values, thus
+the deployment can feature apps from zero or more vendors.
+
+The `deployment-featured.ini` file should be saved in one of the `sysconfdir`, system
+config dirs or system data dirs. They are checked, in that order, for existence of
+the `gnome-software/deployment-featured.ini` file. The first directory containing
+it will be used. The relevant file names are `/etc/xdg/gnome-software/deployment-featured.ini`,
+`/usr/local/share/gnome-software/deployment-featured.ini` and
+`/usr/share/gnome-software/deployment-featured.ini`.
+
+Any changes to these files, including adding or removing them, will only be noticed
+when gnome-software is restarted.
+
+Example files can be found in the `contrib/` directory.