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 simplfy maintenace, 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 every come across it. https://gitlab.gnome.org/GNOME/gnome-shell/issues/2282
This commit is contained in:
parent
92cfb9ab1b
commit
7f7965d622
@ -92,17 +92,8 @@ function _listsIntersect(a, b) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (folder.get_boolean('translate'))
|
||||
return Shell.AppCache.get_default().translate_folder(name);
|
||||
|
||||
return name;
|
||||
}
|
||||
@ -499,7 +490,7 @@ var AllView = new Lang.Class({
|
||||
},
|
||||
|
||||
_loadApps() {
|
||||
let apps = Gio.AppInfo.get_all().filter(appInfo => {
|
||||
let apps = Shell.AppCache.get_default().get_all().filter(appInfo => {
|
||||
try {
|
||||
let id = appInfo.get_id(); // catch invalid file encodings
|
||||
} catch(e) {
|
||||
@ -1329,7 +1320,7 @@ var FolderIcon = new Lang.Class({
|
||||
folderApps.forEach(addAppId);
|
||||
|
||||
let folderCategories = this._folder.get_strv('categories');
|
||||
Gio.AppInfo.get_all().forEach(appInfo => {
|
||||
Shell.AppCache.get_default().get_all().forEach(appInfo => {
|
||||
let appCategories = _getCategories(appInfo);
|
||||
if (!_listsIntersect(folderCategories, appCategories))
|
||||
return;
|
||||
|
@ -103,6 +103,7 @@ libshell_menu_dep = declare_dependency(link_with: libshell_menu)
|
||||
|
||||
libshell_public_headers = [
|
||||
'shell-app.h',
|
||||
'shell-app-cache.h',
|
||||
'shell-app-system.h',
|
||||
'shell-app-usage.h',
|
||||
'shell-embedded-window.h',
|
||||
@ -138,6 +139,7 @@ libshell_private_headers = [
|
||||
libshell_sources = [
|
||||
'gnome-shell-plugin.c',
|
||||
'shell-app.c',
|
||||
'shell-app-cache.c',
|
||||
'shell-app-system.c',
|
||||
'shell-app-usage.c',
|
||||
'shell-embedded-window.c',
|
||||
|
439
src/shell-app-cache.c
Normal file
439
src/shell-app-cache.c
Normal file
@ -0,0 +1,439 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "shell-app-cache.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;
|
||||
|
||||
GMutex mutex;
|
||||
|
||||
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))
|
||||
g_hash_table_insert (folders,
|
||||
g_strdup (name),
|
||||
g_key_file_get_locale_string (keyfile,
|
||||
"Desktop Entry",
|
||||
"Name",
|
||||
NULL,
|
||||
NULL));
|
||||
}
|
||||
}
|
||||
|
||||
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_mutex_lock (&cache->mutex);
|
||||
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_mutex_unlock (&cache->mutex);
|
||||
|
||||
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_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_mutex_clear (&self->mutex);
|
||||
|
||||
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;
|
||||
|
||||
g_mutex_init (&self->mutex);
|
||||
|
||||
/* Monitor directories for translation changes */
|
||||
self->dir_monitors = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
monitor_data_dir (self, g_get_user_data_dir ());
|
||||
sysdirs = g_get_system_data_dirs ();
|
||||
for (i = 0; sysdirs[i] != NULL; i++)
|
||||
monitor_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 full) (element-type GAppInfo):
|
||||
* a newly allocated GList of references to GAppInfos.
|
||||
*/
|
||||
GList *
|
||||
shell_app_cache_get_all (ShellAppCache *cache)
|
||||
{
|
||||
GList *ret;
|
||||
|
||||
if (cache == NULL)
|
||||
cache = shell_app_cache_get_default ();
|
||||
|
||||
g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
|
||||
|
||||
g_mutex_lock (&cache->mutex);
|
||||
ret = g_list_copy_deep (cache->app_infos, (GCopyFunc)g_object_ref, NULL);
|
||||
g_mutex_unlock (&cache->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 full): a #GDesktopAppInfo or %NULL
|
||||
*/
|
||||
GDesktopAppInfo *
|
||||
shell_app_cache_get_info (ShellAppCache *cache,
|
||||
const char *id)
|
||||
{
|
||||
GDesktopAppInfo *ret = NULL;
|
||||
const GList *iter;
|
||||
|
||||
if (cache == NULL)
|
||||
cache = shell_app_cache_get_default ();
|
||||
|
||||
g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
|
||||
|
||||
g_mutex_lock (&cache->mutex);
|
||||
|
||||
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)
|
||||
{
|
||||
ret = g_object_ref (G_DESKTOP_APP_INFO (info));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_mutex_unlock (&cache->mutex);
|
||||
|
||||
return g_steal_pointer (&ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. Otherwise
|
||||
* a copy of @name is returned.
|
||||
*
|
||||
* Returns: the translated string
|
||||
*/
|
||||
char *
|
||||
shell_app_cache_translate_folder (ShellAppCache *cache,
|
||||
const char *name)
|
||||
{
|
||||
char *ret;
|
||||
|
||||
if (cache == NULL)
|
||||
cache = shell_app_cache_get_default ();
|
||||
|
||||
g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
|
||||
|
||||
if (name == NULL)
|
||||
return NULL;
|
||||
|
||||
g_mutex_lock (&cache->mutex);
|
||||
ret = g_strdup (g_hash_table_lookup (cache->folders, name));
|
||||
g_mutex_unlock (&cache->mutex);
|
||||
|
||||
if (ret == NULL)
|
||||
ret = g_strdup (name);
|
||||
|
||||
return g_steal_pointer (&ret);
|
||||
}
|
19
src/shell-app-cache.h
Normal file
19
src/shell-app-cache.h
Normal file
@ -0,0 +1,19 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
#ifndef __SHELL_APP_CACHE__
|
||||
#define __SHELL_APP_CACHE__
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
|
||||
#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__ */
|
@ -9,6 +9,7 @@
|
||||
#include <gio/gio.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "shell-app-cache.h"
|
||||
#include "shell-app-private.h"
|
||||
#include "shell-window-tracker-private.h"
|
||||
#include "shell-app-system-private.h"
|
||||
@ -93,12 +94,14 @@ static void
|
||||
scan_startup_wm_class_to_id (ShellAppSystem *self)
|
||||
{
|
||||
ShellAppSystemPrivate *priv = self->priv;
|
||||
GList *apps, *l;
|
||||
g_autolist(GAppInfo) all = NULL;
|
||||
const GList *l;
|
||||
|
||||
g_hash_table_remove_all (priv->startup_wm_class_to_id);
|
||||
|
||||
apps = g_app_info_get_all ();
|
||||
for (l = apps; l != NULL; l = l->next)
|
||||
all = shell_app_cache_get_all (NULL);
|
||||
|
||||
for (l = all; l != NULL; l = l->next)
|
||||
{
|
||||
GAppInfo *info = l->data;
|
||||
const char *startup_wm_class, *id, *old_id;
|
||||
@ -116,8 +119,6 @@ scan_startup_wm_class_to_id (ShellAppSystem *self)
|
||||
g_hash_table_insert (priv->startup_wm_class_to_id,
|
||||
g_strdup (startup_wm_class), g_strdup (id));
|
||||
}
|
||||
|
||||
g_list_free_full (apps, g_object_unref);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -130,7 +131,7 @@ 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 (NULL, shell_app_get_id (app));
|
||||
if (!info)
|
||||
return TRUE;
|
||||
|
||||
@ -209,11 +210,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);
|
||||
|
||||
@ -226,7 +225,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);
|
||||
|
||||
@ -237,9 +236,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,7 +292,7 @@ shell_app_system_lookup_app (ShellAppSystem *self,
|
||||
if (app)
|
||||
return app;
|
||||
|
||||
info = g_desktop_app_info_new (id);
|
||||
info = shell_app_cache_get_info (NULL, id);
|
||||
if (!info)
|
||||
return NULL;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user