Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement environment property for component screenshots #530

Merged
merged 5 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compose/asc-utils-screenshots.c
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ asc_process_screenshots (AscResult *cres,
if (media_export_root == NULL)
store_screenshots = FALSE;

screenshots = as_component_get_screenshots (cpt);
screenshots = as_component_get_screenshots_all (cpt);
if (screenshots->len == 0)
return;

Expand Down
50 changes: 50 additions & 0 deletions data/desktop-env-header.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* This file was automatically generated.
*/

#pragma once

/* DO NOT EDIT THIS FILE MANUALLY! */

/*
* Desktop environment names from: data/desktop-environments.txt
* Environment/Style names from: data/desktop-style-ids.txt
*/

#include <glib/gi18n-lib.h>
#include "as-macros-private.h"

AS_BEGIN_PRIVATE_DECLS
/* clang-format off */

/**
* AsDesktopEnvData:
*
* Registered desktop environments.
*/
typedef struct {
const gchar *id;
const gchar *name;
} AsDesktopEnvData;

/**
* AsGUIEnvStyleData:
*
* Graphical environment/style IDs.
*/
typedef struct {
const gchar *id;
const gchar *name;
} AsGUIEnvStyleData;

AsDesktopEnvData as_desktop_env_data[] = {
@DE_DEFS@
};

AsGUIEnvStyleData as_gui_env_style_data[] = {
@GUI_ENV_STYLE_DEFS@
};

/* clang-format on */
AS_END_PRIVATE_DECLS
32 changes: 17 additions & 15 deletions data/desktop-environments.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
# List of desktop environments
GNOME
KDE
Plasma
LXDE
LXQt
MATE
Razor
ROX
Unity
XFCE
EDE
Cinnamon
Pantheon
DDE
Endless
#
# ID Human-readable name
Cinnamon Cinnamon
DDE Deepin
EDE EDE
Endless Endless
GNOME GNOME
KDE KDE Plasma
LXDE LXDE
LXQt LXQt
MATE MATE
Pantheon Pantheon
Plasma KDE Plasma
Razor Razor
ROX ROX
Unity Unity
XFCE XFCE
18 changes: 18 additions & 0 deletions data/desktop-style-ids.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# List of recognized GUI environments
# ID Human-readable name
#
cinnamon Cinnamon
dde Deepin
ede EDE
endless Endless
gnome GNOME
gnome:dark GNOME (Dark)
lxde LXDE
lxqt LXQt
mate Mate
pantheon Pantheon
plasma KDE Plasma
razor Razor
rox Rox
unity Unity
xfce Xfce
5 changes: 2 additions & 3 deletions data/spdx-license-header.tmpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* This file was automatically generated from SPDX metadata.
*
*/

#pragma once
Expand All @@ -13,8 +12,8 @@
* Exceptions: @EXCEPTION_LIST_VERSION@
*/

#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gi18n-lib.h>
#include "as-macros-private.h"

AS_BEGIN_PRIVATE_DECLS
/* clang-format off */
Expand Down
142 changes: 116 additions & 26 deletions data/update-data.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,53 @@ def _read_dfsg_free_license_list():
return licenses


def _write_c_array_header(tmpl_fname, out_fname, l10n_keys=None, l10n_hints=None, **kwargs):
"""Create a C header with lists based on a template."""

if not l10n_keys:
l10n_keys = set()
l10n_keys = set(l10n_keys)
if not l10n_hints:
l10n_hints = {}

with open(tmpl_fname, 'r') as f:
header = f.read()

for key, data in kwargs.items():
if isinstance(data, list):
if not data:
continue

array_def = ''
for entry in data:
entry_c = ''
if key in l10n_hints:
v = list(entry.values())[0]
entry_c += '\t/* TRANSLATORS: {} */\n'.format(l10n_hints[key].format(v))
entry_c += '\t{'
for k, v in entry.items():
v = v.replace('"', '\\"')

if k in l10n_keys:
v_c = f'N_("{v}")'
else:
v_c = f'"{v}"'
entry_c += f' {v_c},'
array_def += entry_c[:-1] + ' },\n'

zero_term = 'NULL, ' * len(data[0])
array_def += '\t{ ' + zero_term[:-2] + ' },\n'

header = header.replace(f'@{key}@', array_def.strip())
else:
header = header.replace(f'@{key}@', str(data))

with open(out_fname, 'w') as f:
f.write(header)


def update_spdx_id_list(
git_url,
header_template_fname,
licenselist_header_fname,
licenselist_free_fname,
with_deprecated=True,
Expand All @@ -133,30 +177,15 @@ def update_spdx_id_list(
exception_list = license_data['exceptions']
license_list_ver = license_data['license_list_ver']

with open(header_template_fname, 'r') as f:
header_tmpl = f.read()

with open(licenselist_header_fname, 'w') as f:
lic_array_def = ''
exc_array_def = ''
for license in license_list:
lic_id = license['id'].replace('"', '\\"')
lic_name = license['name'].replace('"', '\\"')
lic_array_def += f'\t{{"{lic_id}", N_("{lic_name}")}},\n'
for exception in exception_list:
exc_id = exception['id'].replace('"', '\\"')
exc_name = exception['name'].replace('"', '\\"')
exc_array_def += f'\t{{"{exc_id}", N_("{exc_name}")}},\n'

lic_array_def += f'\t{{NULL, NULL}}'
exc_array_def += f'\t{{NULL, NULL}}'

header = header_tmpl.replace('@LICENSE_INFO_DEFS@', lic_array_def.strip())
header = header.replace('@EXCEPTION_INFO_DEFS@', exc_array_def.strip())
header = header.replace('@LICENSE_LIST_VERSION@', license_list_ver)
header = header.replace('@EXCEPTION_LIST_VERSION@', license_data['exception_list_ver'])

f.write(header)
_write_c_array_header(
'spdx-license-header.tmpl',
licenselist_header_fname,
['name'],
LICENSE_INFO_DEFS=license_list,
EXCEPTION_INFO_DEFS=exception_list,
LICENSE_LIST_VERSION=license_list_ver,
EXCEPTION_LIST_VERSION=license_data['exception_list_ver'],
)

license_free_data = _read_spdx_licenses(tdir.name, last_tag_ver, only_free=True)
with open(licenselist_free_fname, 'w') as f:
Expand Down Expand Up @@ -216,6 +245,67 @@ def update_categories_list(spec_url, cat_fname):
f.write('\n')


def update_gui_env_ids(data_header_fname):
print('Updating GUI environment IDs...')

desktops_list = []
desktops_set = set()
with open('desktop-environments.txt', 'r') as f:
for line in f.readlines():
if line.startswith('#'):
continue
de_id, de_name = line.strip().split(' ', 1)
de_id = de_id.strip()
if not de_id:
continue
desktops_set.add(de_id.lower())
desktops_list.append({'id': de_id, 'name': de_name.strip()})

# fixup
desktops_set.remove('kde')
desktops_set.add('plasma')

# extend the existing styles list
gui_env_ids = set()
gui_env_list = []
gui_envs_raw = []
with open('desktop-style-ids.txt', 'r') as f:
for line in f.readlines():
if line.startswith('#'):
continue
line = line.strip()
gui_envs_raw.append(line)
es_id, es_name = line.split(' ', 1)
es_id = es_id.strip().lower()
if not de_id:
continue
gui_env_ids.add(es_id)
gui_env_list.append({'id': es_id, 'name': es_name.strip()})

for de_id in desktops_set:
if de_id not in gui_env_ids:
gui_envs_raw.append(f'{de_id} {de_id.capitalize()}')

with open('desktop-style-ids.txt', 'w') as f:
f.write('# List of recognized GUI environments\n')
f.write('# ID Human-readable name\n#\n')
f.write('\n'.join(sorted(gui_envs_raw)))
f.write('\n')

# write C header with all data
_write_c_array_header(
'desktop-env-header.tmpl',
data_header_fname,
['name'],
l10n_hints={
'DE_DEFS': 'Name of the "{}" desktop environment.',
'GUI_ENV_STYLE_DEFS': 'Name of the "{}" visual environment style.',
},
DE_DEFS=desktops_list,
GUI_ENV_STYLE_DEFS=gui_env_list,
)


def main():
data_dir = os.path.dirname(os.path.abspath(__file__))
print('Data directory is: {}'.format(data_dir))
Expand All @@ -224,12 +314,12 @@ def main():
update_tld_list(IANA_TLD_LIST_URL, 'iana-filtered-tld-list.txt')
update_spdx_id_list(
SPDX_REPO_URL,
os.path.join(data_dir, 'spdx-license-header.tmpl'),
'../src/as-spdx-data.h',
'spdx-free-license-ids.txt',
)
update_categories_list(MENU_SPEC_URL, 'xdg-category-names.txt')
update_platforms_data()
update_gui_env_ids('../src/as-desktop-env-data.h')

print('All done.')

Expand Down
37 changes: 33 additions & 4 deletions docs/xml/metainfo-component.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1203,12 +1203,31 @@
</para>
<para>
The <code>&lt;screenshots/&gt;</code> tag contains multiple <code>&lt;screenshot/&gt;</code> children, where at least one of them must have the property
<code>type="default"</code> to indicate the primary screenshot of the software. Every <code>&lt;screenshot/&gt;</code> tag must have at least
one <code>&lt;image/&gt;</code> or <code>&lt;video/&gt;</code> child, but never an <literal>image</literal> <emphasis>and</emphasis> <literal>video</literal>
at the same time.
<code>type="default"</code> to indicate the primary and most representative screenshot of the software.
</para>
<para>
Screenshots containing a video must not be the default screenshot.
Optionally, a <literal>screenshot</literal> may also have an <code>environment</code> property.
This string property denotes the GUI environment the screenshot was recorded in, in the form of <code>{env}:{style}</code>,
where <literal>{env}</literal> is a desktop-environment name in lowercase and <literal>{style}</literal> is a specific style
that the desktop environment recognizes, e.g. <code>light</code> and <code>dark</code> for light and dark themes.
See <ulink url="https://github.com/ximion/appstream/blob/main/data/desktop-style-ids.txt">desktop-style-ids.txt</ulink> for a list
of currently recognized environment and style combinations.
</para>
<para>
Software centers displaying the component will usually prefer screenshots of the current environment and style, and display them first,
even before the screenshot marked as <literal>default</literal>.
</para>
<para>
In general, screenshots should be displayed in the order the are defined in in their <literal>screenshots</literal> block
for the respective component on a per-environment basis (all screenshots of the same environment/style will be displayed in the order
they are listed in the XML, but may be moved to the front of the list as a whole depending on the current environment).
</para>
<para>
Every <code>&lt;screenshot/&gt;</code> element must have at least one <code>&lt;image/&gt;</code> <emphasis>or</emphasis> <code>&lt;video/&gt;</code> child,
but never an <literal>image</literal> <emphasis>and</emphasis> <literal>video</literal> at the same time.
</para>
<para>
Screenshots containing videos must not be the default screenshot.
</para>
<para>
The value of the <code>&lt;image/&gt;</code> tag is a direct HTTP/HTTPS URL to a screenshot uploaded to a public location on the web.
Expand Down Expand Up @@ -1314,6 +1333,16 @@
<screenshot>
<video codec="av1" width="1600" height="900">https://example.com/foobar/screencast.mkv</video>
</screenshot>

<screenshot environment="plasma-mobile">
<caption>The FooBar main window, but on Plasma Mobile</caption>
<image type="source" width="1600" height="900">https://example.com/foobar/screenshot-1_plasma-mobile.png</image>
</screenshot>

<screenshot environment="gnome:dark">
<caption>The FooBar main window, on GNOME in dark mode</caption>
<image type="source" width="1600" height="900">https://example.com/foobar/screenshot-1_gnome_dark.png</image>
</screenshot>
</screenshots>]]></programlisting>

</listitem>
Expand Down
4 changes: 2 additions & 2 deletions qt/component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,11 +564,11 @@ void AppStream::Component::addProvided(const AppStream::Provided &provided)
as_component_add_provided(d->cpt, provided.asProvided());
}

QList<Screenshot> Component::screenshots() const
QList<Screenshot> Component::screenshotsAll() const
{
QList<Screenshot> res;

auto screenshots = as_component_get_screenshots(d->cpt);
auto screenshots = as_component_get_screenshots_all(d->cpt);
res.reserve(screenshots->len);
for (uint i = 0; i < screenshots->len; i++) {
auto scr = AS_SCREENSHOT(g_ptr_array_index(screenshots, i));
Expand Down
2 changes: 1 addition & 1 deletion qt/component.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ class APPSTREAMQT_EXPORT Component
AppStream::Provided provided(Provided::Kind kind) const;
void addProvided(const AppStream::Provided &provided);

QList<AppStream::Screenshot> screenshots() const;
QList<AppStream::Screenshot> screenshotsAll() const;
void addScreenshot(const AppStream::Screenshot &screenshot);

Releases releasesPlain() const;
Expand Down
1 change: 0 additions & 1 deletion src/appstream.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<gresource prefix="/org/freedesktop/appstream">
<file>xdg-category-names.txt</file>
<file>iana-filtered-tld-list.txt</file>
<file>desktop-environments.txt</file>
<file>spdx-free-license-ids.txt</file>
<file>platform_arch.txt</file>
<file>platform_os.txt</file>
Expand Down
Loading