From dd8a890f0d3a731b834c4a53a1beebadd12450bf Mon Sep 17 00:00:00 2001 From: Eva M Date: Wed, 22 Apr 2026 22:33:59 -0700 Subject: [PATCH 1/5] refresh entries with a subprocess Introduces `bazaar-refresh-worker` which pulls remote flatpak repo content outside of the main bazaar process. Once this subprocess exits, the many many allocations made by the complicated refresh routines, especially inside libxmlb, can be reaped instantly in a way that is more effective than using `malloc_trim()`. This significantly reduces memory usage over time. --- meson.build | 3 + src/bz-application.c | 216 ++++++++++++++++++++++++++----------------- src/bz-io.c | 11 ++- src/meson.build | 10 +- src/refresh-worker.c | 153 ++++++++++++++++++++++++++++++ 5 files changed, 301 insertions(+), 92 deletions(-) create mode 100644 src/refresh-worker.c diff --git a/meson.build b/meson.build index 021fd92f..a7605801 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,9 @@ else config_h.set_quoted('DONATE_LINK', 'https://ko-fi.com/kolunmi') config_h.set_quoted('RELEASE_PAGE', run_command('version.sh', 'get-gh-release').stdout().strip()) + refresh_worker_bin_name = 'bazaar-refresh-worker' + config_h.set_quoted('REFRESH_WORKER_BIN_NAME', refresh_worker_bin_name) + dl_worker_bin_name = 'bazaar-dl-worker' config_h.set_quoted('DL_WORKER_BIN_NAME', dl_worker_bin_name) diff --git a/src/bz-application.c b/src/bz-application.c index a6039574..92b082f5 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -184,6 +184,9 @@ BZ_DEFINE_DATA ( static DexFuture * init_fiber (GWeakRef *wr); +static DexFuture * +enumerate_disk_entries_fiber (GWeakRef *wr); + static DexFuture * cache_flathub_fiber (GWeakRef *wr); @@ -217,8 +220,8 @@ cache_write_back_finally (DexFuture *future, CacheWriteBackData *data); static DexFuture * -sync_then (DexFuture *future, - GWeakRef *wr); +sync_finally (DexFuture *future, + GWeakRef *wr); static DexFuture * watch_backend_notifs_then_loop_cb (DexFuture *future, @@ -930,9 +933,8 @@ init_fiber (GWeakRef *wr) g_autofree char *root_cache_dir = NULL; g_autoptr (GFile) root_cache_dir_file = NULL; g_autoptr (GListModel) repos = NULL; - gboolean has_flathub = FALSE; - gboolean result = FALSE; - g_autoptr (GHashTable) cached_set = NULL; + gboolean has_flathub = FALSE; + gboolean result = FALSE; g_autofree char *flathub_cache = NULL; g_autoptr (GFile) flathub_cache_file = NULL; @@ -1103,74 +1105,14 @@ init_fiber (GWeakRef *wr) } /* Revive old cache from previous Bazaar process */ - cached_set = dex_await_boxed ( - bz_entry_cache_manager_enumerate_disk (self->cache), - &local_error); - if (cached_set != NULL) - { - g_autoptr (GPtrArray) futures = NULL; - GHashTableIter iter = { 0 }; - g_autoptr (GPtrArray) entries = NULL; - - futures = g_ptr_array_new_with_free_func (dex_unref); - - g_hash_table_iter_init (&iter, cached_set); - for (;;) - { - char *checksum = NULL; - - if (!g_hash_table_iter_next ( - &iter, (gpointer *) &checksum, NULL)) - break; - - g_ptr_array_add ( - futures, - bz_entry_cache_manager_get_by_checksum ( - self->cache, checksum)); - } - g_clear_pointer (&cached_set, g_hash_table_unref); - - if (futures->len > 0) - dex_await (dex_future_allv ( - (DexFuture *const *) futures->pdata, - futures->len), - NULL); - - entries = g_ptr_array_new_with_free_func (g_object_unref); - for (guint i = 0; i < futures->len; i++) - { - DexFuture *future = NULL; - const GValue *value = NULL; - - future = g_ptr_array_index (futures, i); - value = dex_future_get_value (future, &local_error); - if (value != NULL) - g_ptr_array_add (entries, g_value_dup_object (value)); - else - { - g_warning ("Unable to retrieve cached entry: %s", local_error->message); - g_clear_error (&local_error); - } - } - - g_ptr_array_sort_values_with_data ( - entries, (GCompareDataFunc) cmp_entry, NULL); - for (guint i = 0; i < entries->len; i++) - { - BzEntry *entry = NULL; - - entry = g_ptr_array_index (entries, i); - fiber_replace_entry (self, entry); - } - - gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_LESS_STRICT); - gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_LESS_STRICT); - } - else - { - g_warning ("Unable to enumerate cached entries: %s", local_error->message); - g_clear_error (&local_error); - } + dex_await ( + dex_scheduler_spawn ( + dex_scheduler_get_default (), + bz_get_dex_stack_size (), + (DexFiberFunc) enumerate_disk_entries_fiber, + bz_track_weak (self), + bz_weak_release), + NULL); flathub_cache_file = fiber_dup_flathub_cache_file (&flathub_cache, &local_error); if (flathub_cache_file != NULL) @@ -1231,6 +1173,84 @@ init_fiber (GWeakRef *wr) return dex_future_new_true (); } +static DexFuture * +enumerate_disk_entries_fiber (GWeakRef *wr) +{ + g_autoptr (BzApplication) self = NULL; + g_autoptr (GError) local_error = NULL; + g_autoptr (GHashTable) cached_set = NULL; + g_autoptr (GPtrArray) futures = NULL; + GHashTableIter iter = { 0 }; + g_autoptr (GPtrArray) entries = NULL; + + bz_weak_get_or_return_reject (self, wr); + + cached_set = dex_await_boxed ( + bz_entry_cache_manager_enumerate_disk (self->cache), + &local_error); + if (cached_set == NULL) + { + g_warning ("Unable to enumerate cached entries: %s", local_error->message); + return dex_future_new_for_error (g_steal_pointer (&local_error)); + } + + futures = g_ptr_array_new_with_free_func (dex_unref); + + g_hash_table_iter_init (&iter, cached_set); + for (;;) + { + char *checksum = NULL; + + if (!g_hash_table_iter_next ( + &iter, (gpointer *) &checksum, NULL)) + break; + + g_ptr_array_add ( + futures, + bz_entry_cache_manager_get_by_checksum ( + self->cache, checksum)); + } + g_clear_pointer (&cached_set, g_hash_table_unref); + + if (futures->len > 0) + dex_await (dex_future_allv ( + (DexFuture *const *) futures->pdata, + futures->len), + NULL); + + entries = g_ptr_array_new_with_free_func (g_object_unref); + for (guint i = 0; i < futures->len; i++) + { + DexFuture *future = NULL; + const GValue *value = NULL; + + future = g_ptr_array_index (futures, i); + value = dex_future_get_value (future, &local_error); + if (value != NULL) + g_ptr_array_add (entries, g_value_dup_object (value)); + else + { + g_warning ("Unable to retrieve cached entry: %s", local_error->message); + g_clear_error (&local_error); + } + } + + g_ptr_array_sort_values_with_data ( + entries, (GCompareDataFunc) cmp_entry, NULL); + for (guint i = 0; i < entries->len; i++) + { + BzEntry *entry = NULL; + + entry = g_ptr_array_index (entries, i); + fiber_replace_entry (self, entry); + } + + gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_LESS_STRICT); + gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_LESS_STRICT); + + return dex_future_new_true (); +} + static DexFuture * cache_flathub_fiber (GWeakRef *wr) { @@ -1832,11 +1852,15 @@ backend_sync_finally (DexFuture *future, bz_weak_get_or_return_reject (self, wr); - bz_state_info_set_online (self->state, dex_future_is_resolved (future)); - bz_state_info_set_syncing (self->state, FALSE); - bz_state_info_set_allow_manual_sync (self->state, TRUE); - - return dex_future_new_true (); + if (dex_future_is_resolved (future)) + return dex_scheduler_spawn ( + dex_scheduler_get_default (), + bz_get_dex_stack_size (), + (DexFiberFunc) enumerate_disk_entries_fiber, + bz_track_weak (self), + bz_weak_release); + else + return dex_ref (future); } static DexFuture * @@ -1896,14 +1920,21 @@ cache_write_back_finally (DexFuture *future, } static DexFuture * -sync_then (DexFuture *future, - GWeakRef *wr) +sync_finally (DexFuture *future, + GWeakRef *wr) { g_autoptr (BzApplication) self = NULL; bz_weak_get_or_return_reject (self, wr); + bz_state_info_set_online (self->state, dex_future_is_resolved (future)); + bz_state_info_set_allow_manual_sync (self->state, TRUE); + bz_state_info_set_busy (self->state, FALSE); + bz_state_info_set_syncing (self->state, FALSE); + finish_with_background_task_label (self); + dex_promise_resolve_boolean (self->ready_to_open_files, TRUE); + return dex_future_new_true (); } @@ -3441,14 +3472,27 @@ validate_group_for_ui (BzApplication *self, static DexFuture * make_sync_future (BzApplication *self) { - g_autoptr (DexFuture) backend_future = NULL; - g_autoptr (DexFuture) flathub_future = NULL; - g_autoptr (DexFuture) ret_future = NULL; + g_autoptr (GError) local_error = NULL; + g_autoptr (GSubprocess) refresh_worker = NULL; + g_autoptr (DexFuture) backend_future = NULL; + g_autoptr (DexFuture) flathub_future = NULL; + g_autoptr (DexFuture) ret_future = NULL; bz_state_info_set_allow_manual_sync (self->state, FALSE); bz_state_info_set_syncing (self->state, TRUE); - backend_future = bz_backend_retrieve_remote_entries (BZ_BACKEND (self->flatpak), NULL); + + refresh_worker = g_subprocess_new ( + G_SUBPROCESS_FLAGS_NONE, + &local_error, + REFRESH_WORKER_BIN_NAME, + NULL); + if (refresh_worker == NULL) + g_critical ("FATAL!!! The refresh worker could not be spawned: %s", + local_error->message); + g_assert (refresh_worker != NULL); + + backend_future = dex_subprocess_wait_check (refresh_worker); backend_future = dex_future_finally ( backend_future, (DexFutureCallback) backend_sync_finally, @@ -3466,9 +3510,9 @@ make_sync_future (BzApplication *self) dex_ref (backend_future), dex_ref (flathub_future), NULL); - ret_future = dex_future_then ( + ret_future = dex_future_finally ( ret_future, - (DexFutureCallback) sync_then, + (DexFutureCallback) sync_finally, bz_track_weak (self), bz_weak_release); return g_steal_pointer (&ret_future); } diff --git a/src/bz-io.c b/src/bz-io.c index 76a3c597..0184be48 100644 --- a/src/bz-io.c +++ b/src/bz-io.c @@ -387,14 +387,17 @@ get_all_user_data_ids_fiber (void) char * bz_dup_root_cache_dir (void) { - const char *user_cache = NULL; - const char *id = NULL; + GApplication *application = NULL; + const char *user_cache = NULL; + const char *id = NULL; user_cache = g_get_user_cache_dir (); - id = g_application_get_application_id (g_application_get_default ()); + application = g_application_get_default (); + if (application != NULL) + id = g_application_get_application_id (application); if (id == NULL) - id = "Bazaar"; + id = "io.github.kolunmi.Bazaar"; return g_build_filename (user_cache, id, NULL); } diff --git a/src/meson.build b/src/meson.build index ee8c8634..69f48958 100644 --- a/src/meson.build +++ b/src/meson.build @@ -163,7 +163,6 @@ bz_sources = files( 'bz-world-map.c', 'bz-yaml-parser.c', 'bz-zoom.c', - 'main.c', ) subdir('progress-bar-designs') @@ -359,7 +358,14 @@ bz_sources += gnome.compile_resources('bz-resources', dependencies: [blueprints, release_notes, countries_gvariant], ) -executable('bazaar', bz_sources, gdbus_src, marshalers, +executable('bazaar', bz_sources + ['main.c'], gdbus_src, marshalers, + dependencies: bz_deps, + install: true, +) + +executable(refresh_worker_bin_name, + bz_sources + ['refresh-worker.c'], + gdbus_src, marshalers, dependencies: bz_deps, install: true, ) diff --git a/src/refresh-worker.c b/src/refresh-worker.c new file mode 100644 index 00000000..53d756da --- /dev/null +++ b/src/refresh-worker.c @@ -0,0 +1,153 @@ +/* refresh-worker.c + * + * Copyright 2026 Eva M + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "BAZAAR::REFRESH-WORKER" + +#include "bz-backend-notification.h" +#include "bz-backend.h" +#include "bz-entry-cache-manager.h" +#include "bz-env.h" +#include "bz-flatpak-instance.h" +#include "bz-util.h" + +BZ_DEFINE_DATA ( + main, + Main, + { + GMainLoop *loop; + GIOChannel *stdout_channel; + int rv; + }, + BZ_RELEASE_DATA (loop, g_main_loop_unref); + BZ_RELEASE_DATA (stdout_channel, g_io_channel_unref)); + +static DexFuture * +run (MainData *data); + +int +main (int argc, + char *argv[]) +{ + g_autoptr (GIOChannel) stdout_channel = NULL; + g_autoptr (GMainLoop) main_loop = NULL; + g_autoptr (MainData) data = NULL; + g_autoptr (DexFuture) future = NULL; + + g_log_writer_default_set_use_stderr (TRUE); + dex_init (); + + stdout_channel = g_io_channel_unix_new (STDOUT_FILENO); + g_assert (g_io_channel_set_encoding (stdout_channel, NULL, NULL)); + g_io_channel_set_buffered (stdout_channel, FALSE); + + main_loop = g_main_loop_new (NULL, FALSE); + + data = main_data_new (); + data->loop = g_main_loop_ref (main_loop); + data->stdout_channel = g_io_channel_ref (stdout_channel); + data->rv = EXIT_SUCCESS; + + future = dex_scheduler_spawn ( + dex_scheduler_get_default (), + bz_get_dex_stack_size (), + (DexFiberFunc) run, + main_data_ref (data), main_data_unref); + g_main_loop_run (main_loop); + + return data->rv; +} + +static DexFuture * +run (MainData *data) +{ + gboolean result = FALSE; + g_autoptr (GError) local_error = NULL; + g_autoptr (BzEntryCacheManager) cache = NULL; + g_autoptr (BzFlatpakInstance) flatpak = NULL; + g_autoptr (DexChannel) channel = NULL; + g_autoptr (DexFuture) all_notifs = NULL; + guint n_notifs = 0; + g_autoptr (GPtrArray) write_backs = NULL; + + cache = bz_entry_cache_manager_new (); + + flatpak = dex_await_object ( + bz_flatpak_instance_new (), + &local_error); + if (flatpak == NULL) + goto err; + + channel = bz_backend_create_notification_channel (BZ_BACKEND (flatpak)); + if (channel == NULL) + goto err; + + result = dex_await ( + bz_backend_retrieve_remote_entries ( + BZ_BACKEND (flatpak), NULL), + &local_error); + if (!result) + goto err; + + all_notifs = dex_channel_receive_all (channel); + n_notifs = dex_future_set_get_size (DEX_FUTURE_SET (all_notifs)); + + write_backs = g_ptr_array_new_with_free_func (dex_unref); + for (guint i = 0; i < n_notifs; i++) + { + DexFuture *future = NULL; + g_autoptr (BzBackendNotification) notif = NULL; + BzBackendNotificationKind kind = 0; + + future = dex_future_set_get_future_at ( + DEX_FUTURE_SET (all_notifs), i); + + notif = dex_await_object (dex_ref (future), NULL); + if (notif == NULL) + continue; + + kind = bz_backend_notification_get_kind (notif); + if (kind == BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY) + { + BzEntry *entry = NULL; + + entry = bz_backend_notification_get_entry (notif); + g_ptr_array_add ( + write_backs, + bz_entry_cache_manager_add (cache, entry)); + } + } + if (write_backs->len > 0) + dex_await ( + dex_future_allv ( + (DexFuture *const *) write_backs->pdata, + write_backs->len), + NULL); + + data->rv = EXIT_SUCCESS; + g_main_loop_quit (data->loop); + return dex_future_new_true (); + +err: + if (local_error != NULL) + g_critical ("Unable to complete refresh: %s", local_error->message); + data->rv = EXIT_FAILURE; + g_main_loop_quit (data->loop); + return dex_future_new_false (); +} From 6e6628cfbbfb70cd7f9c9f9ff1142ea66a7cef2f Mon Sep 17 00:00:00 2001 From: Eva M Date: Wed, 22 Apr 2026 23:41:39 -0700 Subject: [PATCH 2/5] remember to check for updates --- src/bz-application.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bz-application.c b/src/bz-application.c index 92b082f5..d0199015 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -1248,6 +1248,10 @@ enumerate_disk_entries_fiber (GWeakRef *wr) gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_LESS_STRICT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_LESS_STRICT); + bz_state_info_set_background_task_label (self->state, _ ("Checking for updates…")); + fiber_check_for_updates (self); + finish_with_background_task_label (self); + return dex_future_new_true (); } From ae8dae41bf742ce46263e0e90552cbadfb1b1fce Mon Sep 17 00:00:00 2001 From: Eva M Date: Wed, 22 Apr 2026 23:44:13 -0700 Subject: [PATCH 3/5] ensure background task label is set when refresh starts --- src/bz-application.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bz-application.c b/src/bz-application.c index d0199015..d870f0ca 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -3485,6 +3485,7 @@ make_sync_future (BzApplication *self) bz_state_info_set_allow_manual_sync (self->state, FALSE); bz_state_info_set_syncing (self->state, TRUE); + finish_with_background_task_label (self); refresh_worker = g_subprocess_new ( G_SUBPROCESS_FLAGS_NONE, From ae2fae143340083d3ccf067448780d2731b44f71 Mon Sep 17 00:00:00 2001 From: Eva M Date: Thu, 23 Apr 2026 00:07:27 -0700 Subject: [PATCH 4/5] ensure eol runtime entries don't linger around --- src/bz-application.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/bz-application.c b/src/bz-application.c index d870f0ca..f449f059 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -2063,9 +2063,9 @@ fiber_replace_entry (BzApplication *self, if (bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_APPLICATION)) { - gboolean ignore_eol = FALSE; - const char *runtime_name = NULL; - BzEntry *eol_runtime = NULL; + gboolean ignore_eol = FALSE; + const char *runtime_name = NULL; + g_autoptr (BzEntry) eol_runtime = NULL; BzEntryGroup *group = NULL; GHashTable *ref_to_addon_group_ids = NULL; GPtrArray *pending = NULL; @@ -2074,8 +2074,19 @@ fiber_replace_entry (BzApplication *self, ignore_eol = g_hash_table_contains (self->ignore_eol_set, id); runtime_name = bz_flatpak_entry_get_application_runtime (BZ_FLATPAK_ENTRY (entry)); - if (!ignore_eol && runtime_name != NULL) - eol_runtime = g_hash_table_lookup (self->eol_runtimes, runtime_name); + if (!ignore_eol && + runtime_name != NULL) + { + char *runtime_checksum = NULL; + + runtime_checksum = g_hash_table_lookup (self->eol_runtimes, runtime_name); + if (runtime_checksum != NULL) + eol_runtime = dex_await_object ( + bz_entry_cache_manager_get_by_checksum ( + self->cache, + runtime_checksum), + NULL); + } group = ensure_group_and_add (self, id, entry, eol_runtime, ignore_eol, installed); @@ -2106,7 +2117,7 @@ fiber_replace_entry (BzApplication *self, g_hash_table_replace ( self->eol_runtimes, g_strdup (stripped), - g_object_ref (entry)); + g_strdup (unique_id_checksum)); else g_hash_table_remove (self->eol_runtimes, stripped); } @@ -3048,7 +3059,7 @@ init_service_struct (BzApplication *self, self->ids_to_groups = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_object_unref); self->eol_runtimes = g_hash_table_new_full ( - g_str_hash, g_str_equal, g_free, g_object_unref); + g_str_hash, g_str_equal, g_free, g_free); self->sys_name_to_addons = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); self->usr_name_to_addons = g_hash_table_new_full ( From 2dd3108a67c7ced3d43b92fc4b5f7f79e4f3536c Mon Sep 17 00:00:00 2001 From: Eva M Date: Thu, 23 Apr 2026 01:24:43 -0700 Subject: [PATCH 5/5] fix stateinfo and icon themes not being present in refresh process --- src/bz-application.c | 53 +++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/bz-application.c b/src/bz-application.c index f449f059..0c6becfa 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -355,6 +355,9 @@ make_sync_future (BzApplication *self); static void finish_with_background_task_label (BzApplication *self); +static void +state_info_set_icon_themes (BzStateInfo *state); + static void bz_application_dispose (GObject *object) { @@ -917,7 +920,23 @@ bz_state_info_get_default (void) app = g_application_get_default (); if G_UNLIKELY (app == NULL) - return NULL; + { + static BzStateInfo *fallback_state = NULL; + + if (g_once_init_enter_pointer (&fallback_state)) + { + g_autoptr (BzStateInfo) tmp = NULL; + + tmp = bz_state_info_new (); + state_info_set_icon_themes (tmp); + + g_once_init_leave_pointer ( + &fallback_state, + g_steal_pointer (&tmp)); + } + + return fallback_state; + } self = (BzApplication *) app; g_assert (BZ_IS_APPLICATION (self)); @@ -2922,20 +2941,7 @@ init_service_struct (BzApplication *self, bz_state_info_set_donation_prompt_dismissed (self->state, TRUE); bz_state_info_set_parental_age_rating (self->state, -1); - { - g_autoptr (GtkIconTheme) user_theme = NULL; - g_autoptr (GtkIconTheme) system_theme = NULL; - g_autofree char *user_export_dir = NULL; - - user_theme = gtk_icon_theme_new (); - user_export_dir = g_build_filename (g_get_home_dir (), ".local/share/flatpak/exports/share/icons", NULL); - gtk_icon_theme_add_search_path (user_theme, user_export_dir); - bz_state_info_set_user_icon_theme (self->state, user_theme); - - system_theme = gtk_icon_theme_new (); - gtk_icon_theme_add_search_path (system_theme, "/var/lib/flatpak/exports/share/icons"); - bz_state_info_set_system_icon_theme (self->state, system_theme); - } + state_info_set_icon_themes (self->state); { g_autoptr (GError) bus_error = NULL; @@ -3546,3 +3552,20 @@ finish_with_background_task_label (BzApplication *self) else bz_state_info_set_background_task_label (self->state, NULL); } + +static void +state_info_set_icon_themes (BzStateInfo *state) +{ + g_autoptr (GtkIconTheme) user_theme = NULL; + g_autoptr (GtkIconTheme) system_theme = NULL; + g_autofree char *user_export_dir = NULL; + + user_theme = gtk_icon_theme_new (); + user_export_dir = g_build_filename (g_get_home_dir (), ".local/share/flatpak/exports/share/icons", NULL); + gtk_icon_theme_add_search_path (user_theme, user_export_dir); + bz_state_info_set_user_icon_theme (state, user_theme); + + system_theme = gtk_icon_theme_new (); + gtk_icon_theme_add_search_path (system_theme, "/var/lib/flatpak/exports/share/icons"); + bz_state_info_set_system_icon_theme (state, system_theme); +}