From 70f9124545e35c1e55915594722f5aa8c7b7edcc Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Sat, 23 Oct 2021 18:03:46 +0100 Subject: [PATCH] Show translated firmware release notes when provided MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- libfwupd/fwupd-client.c | 75 +++++++++++++++++++++++++++++++- libfwupd/fwupd-client.h | 2 + libfwupd/fwupd.map | 6 +++ src/fu-engine-request.c | 19 +++++++++ src/fu-engine-request.h | 4 ++ src/fu-engine.c | 27 +++++++++++- src/fu-main.c | 80 +++++++++++++++++++++++++++-------- src/org.freedesktop.fwupd.xml | 19 +++++++++ 8 files changed, 211 insertions(+), 21 deletions(-) diff --git a/libfwupd/fwupd-client.c b/libfwupd/fwupd-client.c index b5d6128f5..70c8c2df7 100644 --- a/libfwupd/fwupd-client.c +++ b/libfwupd/fwupd-client.c @@ -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); diff --git a/libfwupd/fwupd-client.h b/libfwupd/fwupd-client.h index f2749fc19..9b4f182d2 100644 --- a/libfwupd/fwupd-client.h +++ b/libfwupd/fwupd-client.h @@ -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 diff --git a/libfwupd/fwupd.map b/libfwupd/fwupd.map index c4031a76a..cfcc3af4a 100644 --- a/libfwupd/fwupd.map +++ b/libfwupd/fwupd.map @@ -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; diff --git a/src/fu-engine-request.c b/src/fu-engine-request.c index 3df25526d..1ba8f5fdf 100644 --- a/src/fu-engine-request.c +++ b/src/fu-engine-request.c @@ -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) { diff --git a/src/fu-engine-request.h b/src/fu-engine-request.h index 7835e2808..c26fc79b9 100644 --- a/src/fu-engine-request.h +++ b/src/fu-engine-request.h @@ -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 diff --git a/src/fu-engine.c b/src/fu-engine.c index eab08c1f1..2280c176f 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -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); diff --git a/src/fu-main.c b/src/fu-main.c index 40b8cb5bb..5848a139f 100644 --- a/src/fu-main.c +++ b/src/fu-main.c @@ -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 */ diff --git a/src/org.freedesktop.fwupd.xml b/src/org.freedesktop.fwupd.xml index a63f2c643..5a10fd4a9 100644 --- a/src/org.freedesktop.fwupd.xml +++ b/src/org.freedesktop.fwupd.xml @@ -307,6 +307,25 @@ + + + + + + Sets optional hints from the client that may affect the list of devices. + A typical hint might be locale and unknown hints should be ignored. + + + + + + + An array of string key values. + + + + +