Show translated firmware release notes when provided

Send the users locale to the daemon so that it can be used to prefer
the localized update text over the default en_US version.

    $ LANG=fr_FR.UTF8 fwupdmgr get-details test.cab
    ...
    └─ACME Plan 9:
          Nouvelle version: 0.0.5
          Licence:          Propriétaire
          Urgence:          Faible
          Fournisseur:      ACME Ltd.
          Description:      Cette version stable corrige des bugs.

I decided to send the locale to the daemon rather than change the
`Description` to return GVariant to `a{ss}` as we also probably want
to support things like localized summary and URLs too in the future.
This commit is contained in:
Richard Hughes 2021-10-23 18:03:46 +01:00
parent 3c8339c9eb
commit 70f9124545
8 changed files with 211 additions and 21 deletions

View File

@ -34,6 +34,9 @@
#include "fwupd-request-private.h"
#include "fwupd-security-attr-private.h"
static void
fwupd_client_fixup_dbus_error(GError *error);
typedef GObject *(*FwupdClientObjectNewFunc)(void);
#define FWUPD_CLIENT_DBUS_PROXY_TIMEOUT 180000 /* ms */
@ -66,6 +69,7 @@ typedef struct {
GDBusProxy *proxy;
GProxyResolver *proxy_resolver;
gchar *user_agent;
GHashTable *hints; /* str:str */
#ifdef SOUP_SESSION_COMPAT
GObject *soup_session;
GModule *soup_module; /* we leak this */
@ -594,12 +598,40 @@ fwupd_client_curl_new(FwupdClient *self, GError **error)
}
#endif
static void
fwupd_client_set_hints_cb(GObject *source, GAsyncResult *res, gpointer user_data)
{
g_autoptr(GTask) task = G_TASK(user_data);
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) val = NULL;
val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
if (val == NULL) {
/* new libfwupd and old daemon, just swallow the error */
if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
g_debug("ignoring %s", error->message);
g_task_return_boolean(task, TRUE);
return;
}
fwupd_client_fixup_dbus_error(error);
g_task_return_error(task, g_steal_pointer(&error));
return;
}
/* success */
g_task_return_boolean(task, TRUE);
}
static void
fwupd_client_connect_get_proxy_cb(GObject *source, GAsyncResult *res, gpointer user_data)
{
g_autoptr(GTask) task = G_TASK(user_data);
GVariantBuilder builder;
GHashTableIter iter;
gpointer key, value;
FwupdClient *self = g_task_get_source_object(task);
FwupdClientPrivate *priv = GET_PRIVATE(self);
GCancellable *cancellable = g_task_get_cancellable(task);
g_autoptr(GDBusProxy) proxy = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) val = NULL;
@ -653,8 +685,21 @@ fwupd_client_connect_get_proxy_cb(GObject *source, GAsyncResult *res, gpointer u
if (val7 != NULL)
fwupd_client_set_host_security_id(self, g_variant_get_string(val7, NULL));
/* success */
g_task_return_boolean(task, TRUE);
/* build client hints */
g_variant_builder_init(&builder, G_VARIANT_TYPE_DICTIONARY);
g_hash_table_iter_init(&iter, priv->hints);
while (g_hash_table_iter_next(&iter, &key, &value))
g_variant_builder_add(&builder, "{ss}", (const gchar *)key, (const gchar *)value);
/* only supported on fwupd >= 1.7.1 */
g_dbus_proxy_call(priv->proxy,
"SetHints",
g_variant_new("(a{ss})", &builder),
G_DBUS_CALL_FLAGS_NONE,
FWUPD_CLIENT_DBUS_PROXY_TIMEOUT,
cancellable,
fwupd_client_set_hints_cb,
g_steal_pointer(&task));
}
/**
@ -4794,6 +4839,27 @@ fwupd_client_upload_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **
return g_task_propagate_pointer(G_TASK(res), error);
}
/**
* fwupd_client_add_hint:
* @self: a #FwupdClient
* @key: the key, e.g. `locale`
* @value: (nullable): the value @key should be set
*
* Sets optional hints from the client that may affect the list of devices.
*
* Since: 1.7.1
**/
void
fwupd_client_add_hint(FwupdClient *self, const gchar *key, const gchar *value)
{
FwupdClientPrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FWUPD_IS_CLIENT(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->hints, g_strdup(key), g_strdup(value));
}
#ifdef SOUP_SESSION_COMPAT
/* this is bad; we dlopen libsoup-2.4.so.1 and get the gtype manually
* to avoid deps on both libcurl and libsoup whilst preserving ABI */
@ -5177,6 +5243,10 @@ fwupd_client_init(FwupdClient *self)
priv->idle_sources =
g_ptr_array_new_with_free_func((GDestroyNotify)fwupd_client_context_helper_free);
priv->proxy_resolver = g_proxy_resolver_get_default();
priv->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
/* we get this one for free */
fwupd_client_add_hint(self, "locale", g_getenv("LANG"));
}
static void
@ -5191,6 +5261,7 @@ fwupd_client_finalize(GObject *object)
g_free(priv->host_product);
g_free(priv->host_machine_id);
g_free(priv->host_security_id);
g_hash_table_unref(priv->hints);
g_mutex_clear(&priv->idle_mutex);
if (priv->idle_id != 0)
g_source_remove(priv->idle_id);

View File

@ -478,5 +478,7 @@ fwupd_client_upload_bytes_finish(FwupdClient *self,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
gboolean
fwupd_client_ensure_networking(FwupdClient *self, GError **error) G_GNUC_WARN_UNUSED_RESULT;
void
fwupd_client_add_hint(FwupdClient *self, const gchar *key, const gchar *value);
G_END_DECLS

View File

@ -709,3 +709,9 @@ LIBFWUPD_1.7.0 {
fwupd_security_attr_has_guid;
local: *;
} LIBFWUPD_1.6.2;
LIBFWUPD_1.7.1 {
global:
fwupd_client_add_hint;
local: *;
} LIBFWUPD_1.7.0;

View File

@ -15,6 +15,7 @@ struct _FuEngineRequest {
FuEngineRequestKind kind;
FwupdFeatureFlags feature_flags;
FwupdDeviceFlags device_flags;
gchar *locale;
};
G_DEFINE_TYPE(FuEngineRequest, fu_engine_request, G_TYPE_OBJECT)
@ -26,6 +27,13 @@ fu_engine_request_get_feature_flags(FuEngineRequest *self)
return self->feature_flags;
}
const gchar *
fu_engine_request_get_locale(FuEngineRequest *self)
{
g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), NULL);
return self->locale;
}
FuEngineRequestKind
fu_engine_request_get_kind(FuEngineRequest *self)
{
@ -40,6 +48,17 @@ fu_engine_request_set_feature_flags(FuEngineRequest *self, FwupdFeatureFlags fea
self->feature_flags = feature_flags;
}
void
fu_engine_request_set_locale(FuEngineRequest *self, const gchar *locale)
{
g_return_if_fail(FU_IS_ENGINE_REQUEST(self));
self->locale = g_strdup(locale);
/* remove the UTF8 suffix as it is not present in the XML */
if (self->locale != NULL)
g_strdelimit(self->locale, ".", '\0');
}
gboolean
fu_engine_request_has_feature_flag(FuEngineRequest *self, FwupdFeatureFlags feature_flag)
{

View File

@ -25,6 +25,10 @@ FwupdFeatureFlags
fu_engine_request_get_feature_flags(FuEngineRequest *self);
void
fu_engine_request_set_feature_flags(FuEngineRequest *self, FwupdFeatureFlags feature_flags);
const gchar *
fu_engine_request_get_locale(FuEngineRequest *self);
void
fu_engine_request_set_locale(FuEngineRequest *self, const gchar *locale);
gboolean
fu_engine_request_has_feature_flag(FuEngineRequest *self, FwupdFeatureFlags feature_flag);
FwupdDeviceFlags

View File

@ -452,6 +452,25 @@ fu_engine_set_release_from_artifact(FuEngine *self,
return TRUE;
}
static gchar *
fu_engine_request_get_localized_xpath(FuEngineRequest *request, const gchar *element)
{
GString *xpath = g_string_new(element);
const gchar *locale = NULL;
/* optional; not set in tests */
if (request != NULL)
locale = fu_engine_request_get_locale(request);
/* prefer the users locale if set */
if (locale != NULL) {
g_autofree gchar *xpath_locale = NULL;
xpath_locale = g_strdup_printf("%s[@xml:lang='%s']|", element, locale);
g_string_prepend(xpath, xpath_locale);
}
return g_string_free(xpath, FALSE);
}
static gboolean
fu_engine_set_release_from_appstream(FuEngine *self,
FuEngineRequest *request,
@ -465,6 +484,7 @@ fu_engine_set_release_from_appstream(FuEngine *self,
const gchar *tmp;
const gchar *remote_id;
guint64 tmp64;
g_autofree gchar *description_xpath = NULL;
g_autofree gchar *version_rel = NULL;
g_autoptr(GPtrArray) cats = NULL;
g_autoptr(GPtrArray) issues = NULL;
@ -516,7 +536,8 @@ fu_engine_set_release_from_appstream(FuEngine *self,
if (!fu_engine_set_release_from_artifact(self, rel, remote, artifact, error))
return FALSE;
}
description = xb_node_query_first(release, "description", NULL);
description_xpath = fu_engine_request_get_localized_xpath(request, "description");
description = xb_node_query_first(release, description_xpath, NULL);
if (description != NULL) {
g_autofree gchar *xml = NULL;
g_autoptr(GString) str = NULL;
@ -4209,6 +4230,7 @@ fu_engine_get_result_from_component(FuEngine *self,
GError **error)
{
FwupdReleaseFlags release_flags = FWUPD_RELEASE_FLAG_NONE;
g_autofree gchar *description_xpath = NULL;
g_autoptr(FuInstallTask) task = NULL;
g_autoptr(FuDevice) dev = NULL;
g_autoptr(FwupdRelease) rel = NULL;
@ -4300,7 +4322,8 @@ fu_engine_get_result_from_component(FuEngine *self,
}
/* create a result with all the metadata in */
description = xb_node_query_first(component, "description", NULL);
description_xpath = fu_engine_request_get_localized_xpath(request, "description");
description = xb_node_query_first(component, description_xpath, NULL);
if (description != NULL) {
g_autofree gchar *xml = NULL;
xml = xb_node_export(description, XB_NODE_EXPORT_FLAG_ONLY_CHILDREN, NULL);

View File

@ -58,13 +58,18 @@ typedef enum {
FU_MAIN_MACHINE_KIND_CONTAINER,
} FuMainMachineKind;
typedef struct {
FwupdFeatureFlags feature_flags;
GHashTable *hints; /* str:str */
} FuSenderItem;
typedef struct {
GDBusConnection *connection;
GDBusNodeInfo *introspection_daemon;
GDBusProxy *proxy_uid;
GMainLoop *loop;
GFileMonitor *argv0_monitor;
GHashTable *sender_features; /* sender:FwupdFeatureFlags */
GHashTable *sender_items; /* sender:FuSenderItem */
#if GLIB_CHECK_VERSION(2, 63, 3)
GMemoryMonitor *memory_monitor;
#endif
@ -235,7 +240,7 @@ fu_main_engine_percentage_changed_cb(FuEngine *engine, guint percentage, FuMainP
static FuEngineRequest *
fu_main_create_request(FuMainPrivate *priv, const gchar *sender, GError **error)
{
FwupdFeatureFlags *feature_flags;
FuSenderItem *sender_item;
FwupdDeviceFlags device_flags = FWUPD_DEVICE_FLAG_NONE;
uid_t calling_uid = 0;
g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE);
@ -243,10 +248,14 @@ fu_main_create_request(FuMainPrivate *priv, const gchar *sender, GError **error)
g_return_val_if_fail(sender != NULL, NULL);
/* did the client set the list of supported feature */
feature_flags = g_hash_table_lookup(priv->sender_features, sender);
if (feature_flags != NULL)
fu_engine_request_set_feature_flags(request, *feature_flags);
/* did the client set the list of supported features or any hints */
sender_item = g_hash_table_lookup(priv->sender_items, sender);
if (sender_item != NULL) {
const gchar *locale = g_hash_table_lookup(sender_item->hints, "locale");
if (locale != NULL)
fu_engine_request_set_locale(request, locale);
fu_engine_request_set_feature_flags(request, sender_item->feature_flags);
}
/* are we root and therefore trusted? */
value = g_dbus_proxy_call_sync(priv->proxy_uid,
@ -906,6 +915,19 @@ fu_main_install_with_helper(FuMainAuthHelper *helper_ref, GError **error)
return TRUE;
}
static FuSenderItem *
fu_main_ensure_sender_item(FuMainPrivate *priv, const gchar *sender)
{
FuSenderItem *sender_item;
sender_item = g_hash_table_lookup(priv->sender_items, sender);
if (sender_item == NULL) {
sender_item = g_new0(FuSenderItem, 1);
sender_item->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_insert(priv->sender_items, g_strdup(sender), sender_item);
}
return sender_item;
}
static gboolean
fu_main_device_id_valid(const gchar *device_id, GError **error)
{
@ -1510,23 +1532,37 @@ fu_main_daemon_method_call(GDBusConnection *connection,
return;
}
if (g_strcmp0(method_name, "SetFeatureFlags") == 0) {
FwupdFeatureFlags feature_flags;
FuSenderItem *sender_item;
guint64 feature_flags_u64 = 0;
g_variant_get(parameters, "(t)", &feature_flags_u64);
g_debug("Called %s(%" G_GUINT64_FORMAT ")", method_name, feature_flags_u64);
/* old flags for the same sender will be automatically destroyed */
feature_flags = feature_flags_u64;
g_hash_table_insert(priv->sender_features,
g_strdup(sender),
#if GLIB_CHECK_VERSION(2, 67, 4)
g_memdup2(&feature_flags, sizeof(feature_flags)));
#else
g_memdup(&feature_flags, sizeof(feature_flags)));
#endif
sender_item = fu_main_ensure_sender_item(priv, sender);
sender_item->feature_flags = feature_flags_u64;
g_dbus_method_invocation_return_value(invocation, NULL);
return;
}
if (g_strcmp0(method_name, "SetHints") == 0) {
FuSenderItem *sender_item;
const gchar *prop_key;
const gchar *prop_value;
g_autoptr(GVariantIter) iter = NULL;
g_variant_get(parameters, "(a{ss})", &iter);
g_debug("Called %s()", method_name);
sender_item = fu_main_ensure_sender_item(priv, sender);
while (g_variant_iter_next(iter, "{&s&s}", &prop_key, &prop_value)) {
g_debug("got hint %s=%s", prop_key, prop_value);
g_hash_table_insert(sender_item->hints,
g_strdup(prop_key),
g_strdup(prop_value));
}
g_dbus_method_invocation_return_value(invocation, NULL);
return;
}
if (g_strcmp0(method_name, "Install") == 0) {
GVariant *prop_value;
const gchar *device_id = NULL;
@ -1845,7 +1881,7 @@ fu_main_is_container(void)
static void
fu_main_private_free(FuMainPrivate *priv)
{
g_hash_table_unref(priv->sender_features);
g_hash_table_unref(priv->sender_items);
if (priv->loop != NULL)
g_main_loop_unref(priv->loop);
if (priv->owner_id > 0)
@ -1878,6 +1914,13 @@ fu_main_private_free(FuMainPrivate *priv)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuMainPrivate, fu_main_private_free)
#pragma clang diagnostic pop
static void
fu_main_sender_item_free(FuSenderItem *sender_item)
{
g_hash_table_unref(sender_item->hints);
g_free(sender_item);
}
int
main(int argc, char *argv[])
{
@ -1926,7 +1969,10 @@ main(int argc, char *argv[])
/* create new objects */
priv = g_new0(FuMainPrivate, 1);
priv->sender_features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
priv->sender_items = g_hash_table_new_full(g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify)fu_main_sender_item_free);
priv->loop = g_main_loop_new(NULL, FALSE);
/* load engine */

View File

@ -307,6 +307,25 @@
</arg>
</method>
<!--***********************************************************-->
<method name='SetHints'>
<doc:doc>
<doc:description>
<doc:para>
Sets optional hints from the client that may affect the list of devices.
A typical hint might be <doc:tt>locale</doc:tt> and unknown hints should be ignored.
</doc:para>
</doc:description>
</doc:doc>
<arg type='a{ss}' name='hints' direction='in'>
<doc:doc>
<doc:summary>
<doc:para>An array of string key values.</doc:para>
</doc:summary>
</doc:doc>
</arg>
</method>
<!--***********************************************************-->
<method name='Install'>
<doc:doc>