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.
+
+
+
+
+