diff --git a/SOURCES/0001-environment-reduce-calls-to-g_time_zone_new_local.patch b/SOURCES/0001-environment-reduce-calls-to-g_time_zone_new_local.patch new file mode 100644 index 0000000..74b0c23 --- /dev/null +++ b/SOURCES/0001-environment-reduce-calls-to-g_time_zone_new_local.patch @@ -0,0 +1,65 @@ +From 921681a36075b7017224c16453f26ac4246970db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 27 Feb 2020 13:46:44 -0800 +Subject: [PATCH 1/7] environment: reduce calls to g_time_zone_new_local() + +Creating a new GTimeZone for the local timezone can be quite expensive if +done repeatedly. It requires an open(), mmap(), and parsing of +/etc/localtime. + +This patch was provided by Florian, and I've tested it as far back as +3.28.4 to ensure that we are really reducing the number of open() calls +on the compositor thread. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/1051 + +Signed-off-by: Christian Hergert +--- + js/ui/environment.js | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/js/ui/environment.js b/js/ui/environment.js +index e22ec7402..f3f2d17c7 100644 +--- a/js/ui/environment.js ++++ b/js/ui/environment.js +@@ -11,6 +11,9 @@ imports.gi.versions.TelepathyLogger = '0.2'; + + const { Clutter, GLib, Shell, St } = imports.gi; + const Gettext = imports.gettext; ++const System = imports.system; ++ ++let _localTimeZone = null; + + // We can't import shell JS modules yet, because they may have + // variable initializations, etc, that depend on init() already having +@@ -110,9 +113,26 @@ function init() { + } + }; + ++ // Override to clear our own timezone cache as well ++ const origClearDateCaches = System.clearDateCaches; ++ System.clearDateCaches = function () { ++ _localTimeZone = null; ++ origClearDateCaches(); ++ }; ++ + // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783 + Date.prototype.toLocaleFormat = function(format) { +- return Shell.util_format_date(format, this.getTime()); ++ if (_localTimeZone === null) ++ _localTimeZone = GLib.TimeZone.new_local(); ++ ++ let dt = GLib.DateTime.new(_localTimeZone, ++ this.getYear(), ++ this.getMonth() + 1, ++ this.getDate(), ++ this.getHours(), ++ this.getMinutes(), ++ this.getSeconds()); ++ return dt ? dt.format(format) : ''; + }; + + let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR'); +-- +2.26.0 + diff --git a/SOURCES/0002-environment-Fix-date-conversion.patch b/SOURCES/0002-environment-Fix-date-conversion.patch new file mode 100644 index 0000000..6db409e --- /dev/null +++ b/SOURCES/0002-environment-Fix-date-conversion.patch @@ -0,0 +1,33 @@ +From e143c1634a8542eabc6c6dc8cde9e844cc2cff30 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 2 Mar 2020 13:46:04 +0100 +Subject: [PATCH 2/7] environment: Fix date conversion + +This is a regression from commit 06b690ff21204: + +GLib.DateTime.new() expects the full four-digit year, so passing +the abbreviated year from Date() will result in a bogus datetime. + +Today is *not* Saturday March 2nd, 120 ... + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1061 +--- + js/ui/environment.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/js/ui/environment.js b/js/ui/environment.js +index f3f2d17c7..a9cc16dee 100644 +--- a/js/ui/environment.js ++++ b/js/ui/environment.js +@@ -126,7 +126,7 @@ function init() { + _localTimeZone = GLib.TimeZone.new_local(); + + let dt = GLib.DateTime.new(_localTimeZone, +- this.getYear(), ++ this.getFullYear(), + this.getMonth() + 1, + this.getDate(), + this.getHours(), +-- +2.26.0 + diff --git a/SOURCES/0003-shell-app-system-Monitor-for-icon-theme-changes.patch b/SOURCES/0003-shell-app-system-Monitor-for-icon-theme-changes.patch new file mode 100644 index 0000000..a6acc85 --- /dev/null +++ b/SOURCES/0003-shell-app-system-Monitor-for-icon-theme-changes.patch @@ -0,0 +1,152 @@ +From 4daf9699837bf207c7461240bbd316b32bd46a21 Mon Sep 17 00:00:00 2001 +From: Georges Basile Stavracas Neto +Date: Thu, 1 Aug 2019 20:58:20 -0300 +Subject: [PATCH 3/7] shell/app-system: Monitor for icon theme changes + +Whenever an app is installed, the usual routine is +to run 'gtk-update-icon-cache' after installing all +of the app's files. + +The side effect of that is that the .desktop file of +the application is installed before the icon theme +is updated. By the time GAppInfoMonitor emits the +'changed' signal, the icon theme is not yet updated, +leading to StIcon use the fallback icon. + +Under some circumstances (e.g. on very slow spinning +disks) the app icon is never actually loaded, and we +see the fallback icon forever. + +Monitor the icon theme for changes when an app is +installed. Try as many as 6 times before giving up +on detecting an icon theme update. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/661 +--- + src/shell-app-system.c | 54 +++++++++++++++++++++++++++++++++++++++ + src/st/st-texture-cache.c | 8 ++++++ + src/st/st-texture-cache.h | 2 ++ + 3 files changed, 64 insertions(+) + +diff --git a/src/shell-app-system.c b/src/shell-app-system.c +index f632cbe54..127f29ef0 100644 +--- a/src/shell-app-system.c ++++ b/src/shell-app-system.c +@@ -14,6 +14,14 @@ + #include "shell-app-system-private.h" + #include "shell-global.h" + #include "shell-util.h" ++#include "st.h" ++ ++/* Rescan for at most RESCAN_TIMEOUT_MS * MAX_RESCAN_RETRIES. That ++ * should be plenty of time for even a slow spinning drive to update ++ * the icon cache. ++ */ ++#define RESCAN_TIMEOUT_MS 2500 ++#define MAX_RESCAN_RETRIES 6 + + /* Vendor prefixes are something that can be preprended to a .desktop + * file name. Undo this. +@@ -51,6 +59,9 @@ struct _ShellAppSystemPrivate { + GHashTable *id_to_app; + GHashTable *startup_wm_class_to_id; + GList *installed_apps; ++ ++ guint rescan_icons_timeout_id; ++ guint n_rescan_retries; + }; + + static void shell_app_system_finalize (GObject *object); +@@ -157,12 +168,54 @@ stale_app_remove_func (gpointer key, + return app_is_stale (value); + } + ++static gboolean ++rescan_icon_theme_cb (gpointer user_data) ++{ ++ ShellAppSystemPrivate *priv; ++ ShellAppSystem *self; ++ StTextureCache *texture_cache; ++ gboolean rescanned; ++ ++ self = (ShellAppSystem *) user_data; ++ priv = self->priv; ++ ++ texture_cache = st_texture_cache_get_default (); ++ rescanned = st_texture_cache_rescan_icon_theme (texture_cache); ++ ++ priv->n_rescan_retries++; ++ ++ if (rescanned || priv->n_rescan_retries >= MAX_RESCAN_RETRIES) ++ { ++ priv->n_rescan_retries = 0; ++ priv->rescan_icons_timeout_id = 0; ++ return G_SOURCE_REMOVE; ++ } ++ ++ return G_SOURCE_CONTINUE; ++} ++ ++static void ++rescan_icon_theme (ShellAppSystem *self) ++{ ++ ShellAppSystemPrivate *priv = self->priv; ++ ++ priv->n_rescan_retries = 0; ++ ++ if (priv->rescan_icons_timeout_id > 0) ++ return; ++ ++ priv->rescan_icons_timeout_id = g_timeout_add (RESCAN_TIMEOUT_MS, ++ rescan_icon_theme_cb, ++ self); ++} ++ + static void + installed_changed (GAppInfoMonitor *monitor, + gpointer user_data) + { + ShellAppSystem *self = user_data; + ++ rescan_icon_theme (self); + scan_startup_wm_class_to_id (self); + + g_hash_table_foreach_remove (self->priv->id_to_app, stale_app_remove_func, NULL); +@@ -200,6 +253,7 @@ shell_app_system_finalize (GObject *object) + g_hash_table_destroy (priv->id_to_app); + g_hash_table_destroy (priv->startup_wm_class_to_id); + g_list_free_full (priv->installed_apps, g_object_unref); ++ g_clear_handle_id (&priv->rescan_icons_timeout_id, g_source_remove); + + G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object); + } +diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c +index 40a11dd6d..35e9d036f 100644 +--- a/src/st/st-texture-cache.c ++++ b/src/st/st-texture-cache.c +@@ -1551,3 +1551,11 @@ st_texture_cache_get_default (void) + instance = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL); + return instance; + } ++ ++gboolean ++st_texture_cache_rescan_icon_theme (StTextureCache *cache) ++{ ++ StTextureCachePrivate *priv = cache->priv; ++ ++ return gtk_icon_theme_rescan_if_needed (priv->icon_theme); ++} +diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h +index 9079d1fda..84742be46 100644 +--- a/src/st/st-texture-cache.h ++++ b/src/st/st-texture-cache.h +@@ -114,4 +114,6 @@ CoglTexture * st_texture_cache_load (StTextureCache *cache, + void *data, + GError **error); + ++gboolean st_texture_cache_rescan_icon_theme (StTextureCache *cache); ++ + #endif /* __ST_TEXTURE_CACHE_H__ */ +-- +2.26.0 + diff --git a/SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch b/SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch new file mode 100644 index 0000000..75891dd --- /dev/null +++ b/SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch @@ -0,0 +1,124 @@ +From 10f1505f50274f2a954069aebe9b8c297d3d9fad Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Wed, 26 Feb 2020 14:46:20 -0800 +Subject: [PATCH 4/7] global: force fsync() to worker thread when saving state + +The g_file_replace_contents_async() API can potentially call fsync() from +the thread calling into it upon completion. This can have disasterous +effects when run from the compositor main thread such as complete stalls. + +This is a followup to 86a00b6872375a266449beee1ea6d5e94f1ebbcb which +assumed (like the rest of us) that the fsync() would be performed on the +thread that was doing the I/O operations. + +You can verify this with an strace -e fsync and cause terminal to display +a command completed notification (eg: from a backdrop window). + +This also fixes a lifecycle bug for the variant, as +g_file_replace_contents_async() does not copy the data during the operation +as that is the responsibility of the caller. Instead, we just use a GBytes +variant and reference the variant there. + +https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/1050 +--- + src/shell-global.c | 70 +++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 63 insertions(+), 7 deletions(-) + +diff --git a/src/shell-global.c b/src/shell-global.c +index df84b6b0d..4b33778e0 100644 +--- a/src/shell-global.c ++++ b/src/shell-global.c +@@ -1572,6 +1572,55 @@ delete_variant_cb (GObject *object, + g_hash_table_remove (global->save_ops, object); + } + ++static void ++replace_contents_worker (GTask *task, ++ gpointer source_object, ++ gpointer task_data, ++ GCancellable *cancellable) ++{ ++ GFile *file = source_object; ++ GBytes *bytes = task_data; ++ GError *error = NULL; ++ const gchar *data; ++ gsize len; ++ ++ data = g_bytes_get_data (bytes, &len); ++ ++ if (!g_file_replace_contents (file, data, len, NULL, FALSE, ++ G_FILE_CREATE_REPLACE_DESTINATION, ++ NULL, cancellable, &error)) ++ g_task_return_error (task, g_steal_pointer (&error)); ++ else ++ g_task_return_boolean (task, TRUE); ++} ++ ++static void ++replace_contents_async (GFile *path, ++ GBytes *bytes, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr(GTask) task = NULL; ++ ++ g_assert (G_IS_FILE (path)); ++ g_assert (bytes != NULL); ++ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ ++ task = g_task_new (path, cancellable, callback, user_data); ++ g_task_set_source_tag (task, replace_contents_async); ++ g_task_set_task_data (task, g_bytes_ref (bytes), (GDestroyNotify)g_bytes_unref); ++ g_task_run_in_thread (task, replace_contents_worker); ++} ++ ++static gboolean ++replace_contents_finish (GFile *file, ++ GAsyncResult *result, ++ GError **error) ++{ ++ return g_task_propagate_boolean (G_TASK (result), error); ++} ++ + static void + replace_variant_cb (GObject *object, + GAsyncResult *result, +@@ -1580,7 +1629,7 @@ replace_variant_cb (GObject *object, + ShellGlobal *global = user_data; + GError *error = NULL; + +- if (!g_file_replace_contents_finish (G_FILE (object), result, NULL, &error)) ++ if (!replace_contents_finish (G_FILE (object), result, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { +@@ -1616,12 +1665,19 @@ save_variant (ShellGlobal *global, + } + else + { +- g_file_replace_contents_async (path, +- g_variant_get_data (variant), +- g_variant_get_size (variant), +- NULL, FALSE, +- G_FILE_CREATE_REPLACE_DESTINATION, +- cancellable, replace_variant_cb, global); ++ g_autoptr(GBytes) bytes = NULL; ++ ++ bytes = g_bytes_new_with_free_func (g_variant_get_data (variant), ++ g_variant_get_size (variant), ++ (GDestroyNotify)g_variant_unref, ++ g_variant_ref (variant)); ++ /* g_file_replace_contents_async() can potentially fsync() from the ++ * calling thread when completing the asynchronous task. Instead, we ++ * want to force that fsync() to a thread to avoid blocking the ++ * compository main loop. Using our own replace_contents_async() ++ * simply executes the operation synchronously from a thread. ++ */ ++ replace_contents_async (path, bytes, cancellable, replace_variant_cb, global); + } + + g_object_unref (path); +-- +2.26.0 + diff --git a/SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch b/SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch new file mode 100644 index 0000000..aeb9c8d --- /dev/null +++ b/SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch @@ -0,0 +1,674 @@ +From fd92f6c03dee2f00f022896bf7b2979659899c29 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Thu, 27 Feb 2020 19:36:14 -0800 +Subject: [PATCH 5/7] app-cache: add ShellAppCache for GAppInfo caching + +This caches GAppInfo so that the compositor thread does not have to perform +costly disk access to load them. Instead, they are loaded from a worker +thread and the ShellAppCache notifies of changes. + +To simplify maintenance, ShellAppCache manages this directly and the +existing ShellAppSystem wraps the cache. We may want to graft these +together in the future, but now it provides the easiest way to backport +changes to older Shell releases. + +Another source of compositor thread disk access was in determining the +name for an application directory. Translations are provided via GKeyFile +installed in "desktop-directories". Each time we would build the name +for a label (or update it) we would have to load all of these files. + +Instead, the ShellAppCache caches that information and updates the cache +in bulk when those change. We can reduce this in the future to do less +work, but chances are these will come together anyway so that is probably +worth fixing if we ever come across it. + +https://gitlab.gnome.org/GNOME/gnome-shell/issues/2282 +--- + js/ui/appDisplay.js | 12 +- + src/meson.build | 5 +- + src/shell-app-cache-private.h | 19 ++ + src/shell-app-cache.c | 404 ++++++++++++++++++++++++++++++++++ + src/shell-app-system.c | 34 ++- + src/shell-util.c | 16 ++ + src/shell-util.h | 2 + + 7 files changed, 463 insertions(+), 29 deletions(-) + create mode 100644 src/shell-app-cache-private.h + create mode 100644 src/shell-app-cache.c + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index 7fad02cd0..a2d691085 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -78,15 +78,9 @@ function _getFolderName(folder) { + let name = folder.get_string('name'); + + if (folder.get_boolean('translate')) { +- let keyfile = new GLib.KeyFile(); +- let path = 'desktop-directories/' + name; +- +- try { +- keyfile.load_from_data_dirs(path, GLib.KeyFileFlags.NONE); +- name = keyfile.get_locale_string('Desktop Entry', 'Name', null); +- } catch(e) { +- return name; +- } ++ let translated = Shell.util_get_translated_folder_name(name); ++ if (translated !== null) ++ return translated; + } + + return name; +diff --git a/src/meson.build b/src/meson.build +index 97a5a796c..2b911d347 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -108,6 +108,7 @@ endif + + libshell_private_headers = [ + 'shell-app-private.h', ++ 'shell-app-cache-private.h', + 'shell-app-system-private.h', + 'shell-global-private.h', + 'shell-window-tracker-private.h', +@@ -146,7 +147,9 @@ if have_networkmanager + libshell_sources += 'shell-network-agent.c' + endif + +-libshell_private_sources = [] ++libshell_private_sources = [ ++ 'shell-app-cache.c', ++] + + if enable_recorder + libshell_sources += ['shell-recorder.c'] +diff --git a/src/shell-app-cache-private.h b/src/shell-app-cache-private.h +new file mode 100644 +index 000000000..b73094ab1 +--- /dev/null ++++ b/src/shell-app-cache-private.h +@@ -0,0 +1,19 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++#ifndef __SHELL_APP_CACHE_PRIVATE_H__ ++#define __SHELL_APP_CACHE_PRIVATE_H__ ++ ++#include ++#include ++ ++#define SHELL_TYPE_APP_CACHE (shell_app_cache_get_type()) ++ ++G_DECLARE_FINAL_TYPE (ShellAppCache, shell_app_cache, SHELL, APP_CACHE, GObject) ++ ++ShellAppCache *shell_app_cache_get_default (void); ++GList *shell_app_cache_get_all (ShellAppCache *cache); ++GDesktopAppInfo *shell_app_cache_get_info (ShellAppCache *cache, ++ const char *id); ++char *shell_app_cache_translate_folder (ShellAppCache *cache, ++ const char *name); ++ ++#endif /* __SHELL_APP_CACHE_PRIVATE_H__ */ +diff --git a/src/shell-app-cache.c b/src/shell-app-cache.c +new file mode 100644 +index 000000000..15d4734d0 +--- /dev/null ++++ b/src/shell-app-cache.c +@@ -0,0 +1,404 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++#include "config.h" ++ ++#include "shell-app-cache-private.h" ++ ++/** ++ * SECTION:shell-app-cache ++ * @title: ShellAppCache ++ * @short_description: application information cache ++ * ++ * The #ShellAppCache is responsible for caching information about #GAppInfo ++ * to ensure that the compositor thread never needs to perform disk reads to ++ * access them. All of the work is done off-thread. When the new data has ++ * been loaded, a #ShellAppCache::changed signal is emitted. ++ * ++ * Additionally, the #ShellAppCache caches information about translations for ++ * directories. This allows translation provided in [Desktop Entry] GKeyFiles ++ * to be available when building StLabel and other elements without performing ++ * costly disk reads. ++ * ++ * Various monitors are used to keep this information up to date while the ++ * Shell is running. ++ */ ++ ++#define DEFAULT_TIMEOUT_SECONDS 5 ++ ++struct _ShellAppCache ++{ ++ GObject parent_instance; ++ ++ GAppInfoMonitor *monitor; ++ GPtrArray *dir_monitors; ++ GHashTable *folders; ++ GCancellable *cancellable; ++ GList *app_infos; ++ ++ guint queued_update; ++}; ++ ++typedef struct ++{ ++ GList *app_infos; ++ GHashTable *folders; ++} CacheState; ++ ++G_DEFINE_TYPE (ShellAppCache, shell_app_cache, G_TYPE_OBJECT) ++ ++enum { ++ CHANGED, ++ N_SIGNALS ++}; ++ ++static guint signals [N_SIGNALS]; ++ ++static void ++cache_state_free (CacheState *state) ++{ ++ g_clear_pointer (&state->folders, g_hash_table_unref); ++ g_list_free_full (state->app_infos, g_object_unref); ++ g_slice_free (CacheState, state); ++} ++ ++static CacheState * ++cache_state_new (void) ++{ ++ CacheState *state; ++ ++ state = g_slice_new0 (CacheState); ++ state->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); ++ ++ return g_steal_pointer (&state); ++} ++ ++/** ++ * shell_app_cache_get_default: ++ * ++ * Gets the default #ShellAppCache. ++ * ++ * Returns: (transfer none): a #ShellAppCache ++ */ ++ShellAppCache * ++shell_app_cache_get_default (void) ++{ ++ static ShellAppCache *instance; ++ ++ if (instance == NULL) ++ { ++ instance = g_object_new (SHELL_TYPE_APP_CACHE, NULL); ++ g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance); ++ } ++ ++ return instance; ++} ++ ++static void ++load_folder (GHashTable *folders, ++ const char *path) ++{ ++ g_autoptr(GDir) dir = NULL; ++ const char *name; ++ ++ g_assert (folders != NULL); ++ g_assert (path != NULL); ++ ++ dir = g_dir_open (path, 0, NULL); ++ if (dir == NULL) ++ return; ++ ++ while ((name = g_dir_read_name (dir))) ++ { ++ g_autofree gchar *filename = NULL; ++ g_autoptr(GKeyFile) keyfile = NULL; ++ ++ /* First added wins */ ++ if (g_hash_table_contains (folders, name)) ++ continue; ++ ++ filename = g_build_filename (path, name, NULL); ++ keyfile = g_key_file_new (); ++ ++ if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, NULL)) ++ { ++ gchar *translated; ++ ++ translated = g_key_file_get_locale_string (keyfile, ++ "Desktop Entry", "Name", ++ NULL, NULL); ++ ++ if (translated != NULL) ++ g_hash_table_insert (folders, g_strdup (name), translated); ++ } ++ } ++} ++ ++static void ++load_folders (GHashTable *folders) ++{ ++ const char * const *dirs; ++ g_autofree gchar *userdir = NULL; ++ guint i; ++ ++ g_assert (folders != NULL); ++ ++ userdir = g_build_filename (g_get_user_data_dir (), "desktop-directories", NULL); ++ load_folder (folders, userdir); ++ ++ dirs = g_get_system_data_dirs (); ++ for (i = 0; dirs[i] != NULL; i++) ++ { ++ g_autofree gchar *sysdir = g_build_filename (dirs[i], "desktop-directories", NULL); ++ load_folder (folders, sysdir); ++ } ++} ++ ++static void ++shell_app_cache_worker (GTask *task, ++ gpointer source_object, ++ gpointer task_data, ++ GCancellable *cancellable) ++{ ++ CacheState *state; ++ ++ g_assert (G_IS_TASK (task)); ++ g_assert (SHELL_IS_APP_CACHE (source_object)); ++ ++ state = cache_state_new (); ++ state->app_infos = g_app_info_get_all (); ++ load_folders (state->folders); ++ ++ g_task_return_pointer (task, state, (GDestroyNotify) cache_state_free); ++} ++ ++static void ++apply_update_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ ShellAppCache *cache = (ShellAppCache *)object; ++ g_autoptr(GError) error = NULL; ++ CacheState *state; ++ ++ g_assert (SHELL_IS_APP_CACHE (cache)); ++ g_assert (G_IS_TASK (result)); ++ g_assert (user_data == NULL); ++ ++ state = g_task_propagate_pointer (G_TASK (result), &error); ++ ++ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ return; ++ ++ g_list_free_full (cache->app_infos, g_object_unref); ++ cache->app_infos = g_steal_pointer (&state->app_infos); ++ ++ g_clear_pointer (&cache->folders, g_hash_table_unref); ++ cache->folders = g_steal_pointer (&state->folders); ++ ++ g_signal_emit (cache, signals[CHANGED], 0); ++ ++ cache_state_free (state); ++} ++ ++static gboolean ++shell_app_cache_do_update (gpointer user_data) ++{ ++ ShellAppCache *cache = user_data; ++ g_autoptr(GTask) task = NULL; ++ ++ cache->queued_update = 0; ++ ++ /* Reset the cancellable state so we don't race with ++ * two updates coming back overlapped and applying the ++ * information in the wrong order. ++ */ ++ g_cancellable_cancel (cache->cancellable); ++ g_clear_object (&cache->cancellable); ++ cache->cancellable = g_cancellable_new (); ++ ++ task = g_task_new (cache, cache->cancellable, apply_update_cb, NULL); ++ g_task_set_source_tag (task, shell_app_cache_do_update); ++ g_task_run_in_thread (task, shell_app_cache_worker); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++shell_app_cache_queue_update (ShellAppCache *self) ++{ ++ g_assert (SHELL_IS_APP_CACHE (self)); ++ ++ if (self->queued_update != 0) ++ g_source_remove (self->queued_update); ++ ++ self->queued_update = g_timeout_add_seconds (DEFAULT_TIMEOUT_SECONDS, ++ shell_app_cache_do_update, ++ self); ++} ++ ++static void ++monitor_desktop_directories_for_data_dir (ShellAppCache *self, ++ const gchar *directory) ++{ ++ g_autofree gchar *subdir = NULL; ++ g_autoptr(GFile) file = NULL; ++ g_autoptr(GFileMonitor) monitor = NULL; ++ ++ g_assert (SHELL_IS_APP_CACHE (self)); ++ ++ if (directory == NULL) ++ return; ++ ++ subdir = g_build_filename (directory, "desktop-directories", NULL); ++ file = g_file_new_for_path (subdir); ++ monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); ++ ++ if (monitor != NULL) ++ { ++ g_file_monitor_set_rate_limit (monitor, DEFAULT_TIMEOUT_SECONDS * 1000); ++ g_signal_connect_object (monitor, ++ "changed", ++ G_CALLBACK (shell_app_cache_queue_update), ++ self, ++ G_CONNECT_SWAPPED); ++ g_ptr_array_add (self->dir_monitors, g_steal_pointer (&monitor)); ++ } ++} ++ ++static void ++shell_app_cache_finalize (GObject *object) ++{ ++ ShellAppCache *self = (ShellAppCache *)object; ++ ++ g_clear_object (&self->monitor); ++ ++ if (self->queued_update) ++ { ++ g_source_remove (self->queued_update); ++ self->queued_update = 0; ++ } ++ ++ g_clear_pointer (&self->dir_monitors, g_ptr_array_unref); ++ g_clear_pointer (&self->folders, g_hash_table_unref); ++ g_list_free_full (self->app_infos, g_object_unref); ++ ++ G_OBJECT_CLASS (shell_app_cache_parent_class)->finalize (object); ++} ++ ++static void ++shell_app_cache_class_init (ShellAppCacheClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = shell_app_cache_finalize; ++ ++ /** ++ * ShellAppCache::changed: ++ * ++ * The "changed" signal is emitted when the cache has updated ++ * information about installed applications. ++ */ ++ signals [CHANGED] = ++ g_signal_new ("changed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, NULL, NULL, NULL, ++ G_TYPE_NONE, 0); ++} ++ ++static void ++shell_app_cache_init (ShellAppCache *self) ++{ ++ const gchar * const *sysdirs; ++ guint i; ++ ++ /* Monitor directories for translation changes */ ++ self->dir_monitors = g_ptr_array_new_with_free_func (g_object_unref); ++ monitor_desktop_directories_for_data_dir (self, g_get_user_data_dir ()); ++ sysdirs = g_get_system_data_dirs (); ++ for (i = 0; sysdirs[i] != NULL; i++) ++ monitor_desktop_directories_for_data_dir (self, sysdirs[i]); ++ ++ /* Load translated directory names immediately */ ++ self->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); ++ load_folders (self->folders); ++ ++ /* Setup AppMonitor to track changes */ ++ self->monitor = g_app_info_monitor_get (); ++ g_signal_connect_object (self->monitor, ++ "changed", ++ G_CALLBACK (shell_app_cache_queue_update), ++ self, ++ G_CONNECT_SWAPPED); ++ self->app_infos = g_app_info_get_all (); ++} ++ ++/** ++ * shell_app_cache_get_all: ++ * @cache: (nullable): a #ShellAppCache or %NULL ++ * ++ * Like g_app_info_get_all() but always returns a ++ * cached set of application info so the caller can be ++ * sure that I/O will not happen on the current thread. ++ * ++ * Returns: (transfer none) (element-type GAppInfo): ++ * a #GList of references to #GAppInfo. ++ */ ++GList * ++shell_app_cache_get_all (ShellAppCache *cache) ++{ ++ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL); ++ ++ return cache->app_infos; ++} ++ ++/** ++ * shell_app_cache_get_info: ++ * @cache: (nullable): a #ShellAppCache or %NULL ++ * @id: the application id ++ * ++ * A replacement for g_desktop_app_info_new() that will lookup the ++ * information from the cache instead of (re)loading from disk. ++ * ++ * Returns: (nullable) (transfer none): a #GDesktopAppInfo or %NULL ++ */ ++GDesktopAppInfo * ++shell_app_cache_get_info (ShellAppCache *cache, ++ const char *id) ++{ ++ const GList *iter; ++ ++ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL); ++ ++ for (iter = cache->app_infos; iter != NULL; iter = iter->next) ++ { ++ GAppInfo *info = iter->data; ++ ++ if (g_strcmp0 (id, g_app_info_get_id (info)) == 0) ++ return G_DESKTOP_APP_INFO (info); ++ } ++ ++ return NULL; ++} ++ ++/** ++ * shell_app_cache_translate_folder: ++ * @cache: (nullable): a #ShellAppCache or %NULL ++ * @name: the folder name ++ * ++ * Gets the translated folder name for @name if any exists. ++ * ++ * Returns: (nullable): the translated string or %NULL if there is no ++ * translation. ++ */ ++char * ++shell_app_cache_translate_folder (ShellAppCache *cache, ++ const char *name) ++{ ++ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL); ++ ++ if (name == NULL) ++ return NULL; ++ ++ return g_strdup (g_hash_table_lookup (cache->folders, name)); ++} +diff --git a/src/shell-app-system.c b/src/shell-app-system.c +index 127f29ef0..828fa726a 100644 +--- a/src/shell-app-system.c ++++ b/src/shell-app-system.c +@@ -9,6 +9,7 @@ + #include + #include + ++#include "shell-app-cache-private.h" + #include "shell-app-private.h" + #include "shell-window-tracker-private.h" + #include "shell-app-system-private.h" +@@ -94,14 +95,14 @@ static void + scan_startup_wm_class_to_id (ShellAppSystem *self) + { + ShellAppSystemPrivate *priv = self->priv; +- GList *l; ++ const GList *l; ++ GList *all; + + g_hash_table_remove_all (priv->startup_wm_class_to_id); + +- g_list_free_full (priv->installed_apps, g_object_unref); +- priv->installed_apps = g_app_info_get_all (); ++ all = shell_app_cache_get_all (shell_app_cache_get_default ()); + +- for (l = priv->installed_apps; l != NULL; l = l->next) ++ for (l = all; l != NULL; l = l->next) + { + GAppInfo *info = l->data; + const char *startup_wm_class, *id, *old_id; +@@ -131,7 +132,8 @@ app_is_stale (ShellApp *app) + if (shell_app_is_window_backed (app)) + return FALSE; + +- info = g_desktop_app_info_new (shell_app_get_id (app)); ++ info = shell_app_cache_get_info (shell_app_cache_get_default (), ++ shell_app_get_id (app)); + if (!info) + return TRUE; + +@@ -156,7 +158,6 @@ app_is_stale (ShellApp *app) + g_icon_equal (g_app_info_get_icon (old_info), + g_app_info_get_icon (new_info)); + +- g_object_unref (info); + return !is_unchanged; + } + +@@ -210,11 +211,9 @@ rescan_icon_theme (ShellAppSystem *self) + } + + static void +-installed_changed (GAppInfoMonitor *monitor, +- gpointer user_data) ++installed_changed (ShellAppCache *cache, ++ ShellAppSystem *self) + { +- ShellAppSystem *self = user_data; +- + rescan_icon_theme (self); + scan_startup_wm_class_to_id (self); + +@@ -227,7 +226,7 @@ static void + shell_app_system_init (ShellAppSystem *self) + { + ShellAppSystemPrivate *priv; +- GAppInfoMonitor *monitor; ++ ShellAppCache *cache; + + self->priv = priv = shell_app_system_get_instance_private (self); + +@@ -238,9 +237,9 @@ shell_app_system_init (ShellAppSystem *self) + + priv->startup_wm_class_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + +- monitor = g_app_info_monitor_get (); +- g_signal_connect (monitor, "changed", G_CALLBACK (installed_changed), self); +- installed_changed (monitor, self); ++ cache = shell_app_cache_get_default (); ++ g_signal_connect (cache, "changed", G_CALLBACK (installed_changed), self); ++ installed_changed (cache, self); + } + + static void +@@ -293,13 +292,12 @@ shell_app_system_lookup_app (ShellAppSystem *self, + if (app) + return app; + +- info = g_desktop_app_info_new (id); ++ info = shell_app_cache_get_info (shell_app_cache_get_default (), id); + if (!info) + return NULL; + + app = _shell_app_new (info); + g_hash_table_insert (priv->id_to_app, (char *) shell_app_get_id (app), app); +- g_object_unref (info); + return app; + } + +@@ -506,7 +504,5 @@ shell_app_system_search (const char *search_string) + GList * + shell_app_system_get_installed (ShellAppSystem *self) + { +- ShellAppSystemPrivate *priv = self->priv; +- +- return priv->installed_apps; ++ return shell_app_cache_get_all (shell_app_cache_get_default ()); + } +diff --git a/src/shell-util.c b/src/shell-util.c +index 9c25643c6..a727f418c 100644 +--- a/src/shell-util.c ++++ b/src/shell-util.c +@@ -16,6 +16,7 @@ + #include + #include + ++#include "shell-app-cache-private.h" + #include "shell-util.h" + #include + #include +@@ -583,3 +584,18 @@ shell_util_check_cloexec_fds (void) + fdwalk (check_cloexec, NULL); + g_info ("Open fd CLOEXEC check complete"); + } ++ ++/** ++ * shell_util_get_translated_folder_name: ++ * @name: the untranslated folder name ++ * ++ * Attempts to translate the folder @name using translations provided ++ * by .directory files. ++ * ++ * Returns: (nullable): a translated string or %NULL ++ */ ++char * ++shell_util_get_translated_folder_name (const char *name) ++{ ++ return shell_app_cache_translate_folder (shell_app_cache_get_default (), name); ++} +diff --git a/src/shell-util.h b/src/shell-util.h +index 049c3fe18..27ff2aeb7 100644 +--- a/src/shell-util.h ++++ b/src/shell-util.h +@@ -57,6 +57,8 @@ cairo_surface_t * shell_util_composite_capture_images (ClutterCapture *captures + + void shell_util_check_cloexec_fds (void); + ++char *shell_util_get_translated_folder_name (const char *name); ++ + G_END_DECLS + + #endif /* __SHELL_UTIL_H__ */ +-- +2.26.0 + diff --git a/SOURCES/0006-js-Always-use-AppSystem-to-lookup-apps.patch b/SOURCES/0006-js-Always-use-AppSystem-to-lookup-apps.patch new file mode 100644 index 0000000..dda7e33 --- /dev/null +++ b/SOURCES/0006-js-Always-use-AppSystem-to-lookup-apps.patch @@ -0,0 +1,66 @@ +From 8de865ada7510b9ad2f036565f12d0fedaca1248 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 14 Mar 2020 14:45:42 +0100 +Subject: [PATCH 6/7] js: Always use AppSystem to lookup apps + +There is no good reason for bypassing the application cache in +AppSystem and loading .desktop files again. + +https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1093 +--- + js/ui/appDisplay.js | 4 ++-- + js/ui/calendar.js | 16 ++++++++++------ + 2 files changed, 12 insertions(+), 8 deletions(-) + +diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js +index a2d691085..cb2be7d3c 100644 +--- a/js/ui/appDisplay.js ++++ b/js/ui/appDisplay.js +@@ -1001,8 +1001,8 @@ var AppSearchProvider = class AppSearchProvider { + let results = []; + groups.forEach(group => { + group = group.filter(appID => { +- let app = Gio.DesktopAppInfo.new(appID); +- return app && app.should_show(); ++ const app = this._appSys.lookup_app(appID); ++ return app && app.app_info.should_show(); + }); + results = results.concat(group.sort( + (a, b) => usage.compare(a, b) +diff --git a/js/ui/calendar.js b/js/ui/calendar.js +index cd3e879c4..3ae2e44f8 100644 +--- a/js/ui/calendar.js ++++ b/js/ui/calendar.js +@@ -791,8 +791,9 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection { + this._title.connect('clicked', this._onTitleClicked.bind(this)); + this._title.connect('key-focus-in', this._onKeyFocusIn.bind(this)); + +- Shell.AppSystem.get_default().connect('installed-changed', +- this._appInstalledChanged.bind(this)); ++ this._appSys = Shell.AppSystem.get_default(); ++ this._appSys.connect('installed-changed', ++ this._appInstalledChanged.bind(this)); + this._appInstalledChanged(); + } + +@@ -883,10 +884,13 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection { + Main.overview.hide(); + Main.panel.closeCalendar(); + +- let app = this._getCalendarApp(); +- if (app.get_id() == 'evolution.desktop') +- app = Gio.DesktopAppInfo.new('evolution-calendar.desktop'); +- app.launch([], global.create_app_launch_context(0, -1)); ++ let appInfo = this._getCalendarApp(); ++ if (app.get_id() == 'evolution.desktop') { ++ let app = this._appSys.lookup_app('evolution-calendar.desktop'); ++ if (app) ++ appInfo = app.app_info; ++ } ++ appInfo.launch([], global.create_app_launch_context(0, -1)); + } + + setDate(date) { +-- +2.26.0 + diff --git a/SPECS/gnome-shell.spec b/SPECS/gnome-shell.spec index ee37be1..7cac721 100644 --- a/SPECS/gnome-shell.spec +++ b/SPECS/gnome-shell.spec @@ -1,6 +1,6 @@ Name: gnome-shell Version: 3.32.2 -Release: 14%{?dist} +Release: 16%{?dist} Summary: Window management and application launching for GNOME Group: User Interface/Desktops @@ -48,6 +48,14 @@ Patch49: 0001-Do-not-change-Wacom-LEDs-through-g-s-d.patch # Backport JS invalid access warnings (#1651894, #1663171, #1642482, #1637622) Patch54: fix-invalid-access-warnings.patch +# Backport performance fixes under load (#1820760) +Patch60: 0001-environment-reduce-calls-to-g_time_zone_new_local.patch +Patch61: 0002-environment-Fix-date-conversion.patch +Patch62: 0003-shell-app-system-Monitor-for-icon-theme-changes.patch +Patch63: 0004-global-force-fsync-to-worker-thread-when-saving-stat.patch +Patch64: 0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch +Patch65: 0006-js-Always-use-AppSystem-to-lookup-apps.patch + # suspend/resume fix on nvidia (#1663440) Patch10001: 0001-background-refresh-after-suspend-on-wayland.patch Patch10002: 0002-background-rebuild-background-not-just-animation-on-.patch @@ -227,6 +235,14 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/evolution-calendar.de %{_mandir}/man1/%{name}.1.gz %changelog +* Fri Apr 17 2020 Florian Müllner - 3.32.2-16 +- Drop bad upstream patch + Resolves: #1820760 + +* Wed Apr 08 2020 Florian Müllner - 3.32.2-15 +- Improve performance under load + Resolves: #1820760 + * Wed Mar 04 2020 Carlos Garnacho - 3.32.2-14 - Do not set Wacom LEDs through gnome-settings-daemon, rely on kernel driver Resolves: #1687979