GNOME Shell 的搜索
打算 hack 一下 GNOME Shell 的搜索功能,所以稍微调查了一下。
Javascript-side
首先当然是从 Javascript 代码下手啦。首先找到 js/ui/search.js
。
不过从这里我们也能看出其工作方式:
GridSearchResult
和ListSearchResult
决定了搜索结果呈现的形态是 App 图标矩阵还是列表。ProviderInfo
是搜索结果左边的 Button。updateSearch
等接口摆明了结果可以异步获取。
它给出了搜索界面有关组件的接口,但并没有任何和搜索本身相关的东西。很快发现
this._registerProvider(new AppDisplay.AppSearchProvider());
于是找到 js/ui/appDisplay.js
下,就找到了我们要找的 AppSearchProvider
,重点关注
getInitialResultSet: function(terms, callback, cancellable) {
let query = terms.join(' ');
let groups = Shell.AppSystem.search(query);
let usage = Shell.AppUsage.get_default();
let results = [];
groups.forEach(function(group) {
group = group.filter(function(appID) {
let app = Gio.DesktopAppInfo.new(appID);
return app && app.should_show();
});
results = results.concat(group.sort(function(a, b) {
return usage.compare('', a, b);
}));
});
callback(results);
}
再找 Shell.AppSystem.search
,发现居然跑到 C 代码里了,实现是 shell-app-system.c
中 的 shell_app_system_search()
:
char ***
shell_app_system_search (const char *search_string)
{
char ***results = g_desktop_app_info_search (search_string);
char ***groups, **ids;
for (groups = results; *groups; groups++)
for (ids = *groups; *ids; ids++)
if (!g_utf8_validate (*ids, -1, NULL))
**ids = '\0';
return results;
}
GLib-side
熟练地调出 DevHelp 查 g_desktop_app_info_search
:「The algorithm for determining matches is undefined and may change at any time.」噗…(吐血
得,继续查源码吧。再经过一番毫不费力的追踪,找到了这样两个比较重要的函数:
static void
desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
{
GHashTableIter iter;
gpointer app, path;
dir->memory_index = memory_index_new ();
dir->memory_implementations = memory_index_new ();
/* Nothing to search? */
if (dir->app_names == NULL)
return;
g_hash_table_iter_init (&iter, dir->app_names);
while (g_hash_table_iter_next (&iter, &app, &path))
{
GKeyFile *key_file;
if (desktop_file_dir_app_name_is_masked (dir, app))
continue;
key_file = g_key_file_new ();
if (g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, NULL) &&
!g_key_file_get_boolean (key_file, "Desktop Entry", "Hidden", NULL))
{
/* Index the interesting keys... */
gchar **implements;
gint i;
for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++)
{
const gchar *value;
gchar *raw;
if (!desktop_key_match_category[i])
continue;
raw = g_key_file_get_locale_string (key_file, "Desktop Entry", desktop_key_get_name (i), NULL, NULL);
value = raw;
if (i == DESKTOP_KEY_Exec && raw != NULL)
{
/* Special handling: only match basename of first field */
gchar *space;
gchar *slash;
/* Remove extra arguments, if any */
space = raw + strcspn (raw, " \t\n"); /* IFS */
*space = '\0';
/* Skip the pathname, if any */
if ((slash = strrchr (raw, '/')))
value = slash + 1;
}
if (value)
memory_index_add_string (dir->memory_index, value, desktop_key_match_category[i], app);
g_free (raw);
}
/* Make note of the Implements= line */
implements = g_key_file_get_string_list (key_file, "Desktop Entry", "Implements", NULL, NULL);
for (i = 0; implements && implements[i]; i++)
memory_index_add_token (dir->memory_implementations, implements[i], 0, app);
g_strfreev (implements);
}
g_key_file_free (key_file);
}
}
static void
desktop_file_dir_unindexed_search (DesktopFileDir *dir,
const gchar *search_token)
{
GHashTableIter iter;
gpointer key, value;
if (!dir->memory_index)
desktop_file_dir_unindexed_setup_search (dir);
g_hash_table_iter_init (&iter, dir->memory_index);
while (g_hash_table_iter_next (&iter, &key, &value))
{
MemoryIndexEntry *mie = value;
if (!g_str_has_prefix (key, search_token))
continue;
while (mie)
{
add_token_result (mie->app_name, mie->match_category);
mie = mie->next;
}
}
}
看看 g_str_has_prefix
,本来想发一通牢骚,不过克制住了,毕竟,此处无声胜有声……
所以呢?
可以看出 GLib 匹配时是粗暴地把 .application
拆成 token,然后比较前缀。那么他所谓的 the interesting keys 是些什么呢?
enum
{
DESKTOP_KEY_Comment,
DESKTOP_KEY_Exec,
DESKTOP_KEY_GenericName,
DESKTOP_KEY_Keywords,
DESKTOP_KEY_Name,
DESKTOP_KEY_X_GNOME_FullName,
N_DESKTOP_KEYS
};
const gchar desktop_key_match_category[N_DESKTOP_KEYS] = {
/* Note: lower numbers are a better match.
*
* In case we want two keys to match at the same level, we can just
* use the same number for the two different keys.
*/
[DESKTOP_KEY_Name] = 1,
[DESKTOP_KEY_Exec] = 2,
[DESKTOP_KEY_Keywords] = 3,
[DESKTOP_KEY_GenericName] = 4,
[DESKTOP_KEY_X_GNOME_FullName] = 5,
[DESKTOP_KEY_Comment] = 6
};
可见,就是 Exec
Name
Keywords
Name
GenericName
X_GNOME_FullName
,并按下面这个数组的顺序依次匹配。
总之,知道这些应该就够了,毕竟要 hack 的是 GNOME Shell,而 GLib 我们没法直接 hack,对吧。
Loading Disqus...
(Alternatively, drop me an E-mail to comment on this post.)