1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/linkbase.c for the Openbox window manager
4 Copyright (c) 2010 Dana Jansens
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 See the COPYING file for a copy of the GNU General Public License.
19 #include "obt/linkbase.h"
21 #include "obt/paths.h"
22 #include "obt/watch.h"
28 typedef struct _ObtLinkBaseEntry ObtLinkBaseEntry;
29 typedef struct _ObtLinkBaseCategory ObtLinkBaseCategory;
31 struct _ObtLinkBaseEntry {
32 /*! Links come from a set of paths. Links found in earlier paths get lower
33 priority values (higher precedence). This is the index in that set of
34 paths of the base directory under which the link was found. */
39 struct _ObtLinkBaseCategory {
47 /*! A bitflag of values from ObtLinkEnvFlags indicating which environments
48 are to be considered active. */
51 const gchar *language;
53 const gchar *modifier;
57 /*! This holds a GSList of ObtLinkBaseEntrys sorted by priority in
58 increasing order (by precedence in decreasing order). */
60 /*! This holds the paths in which we look for links, and the data is an
61 integer that is the priority of that directory. */
62 GHashTable *path_to_priority;
64 /*! This maps GQuark main categories to ObtLinkBaseCategory objects,
65 containing lists of ObtLink objects found in
66 the category. The ObtLink objects are not reffed to be placed in this
67 structure since they will always be in the base hash table as well. So
68 they are not unreffed when they are removed. */
69 GHashTable *categories;
71 ObtLinkBaseUpdateFunc update_func;
75 static void base_entry_free(ObtLinkBaseEntry *e)
77 obt_link_unref(e->link);
78 g_slice_free(ObtLinkBaseEntry, e);
81 static void base_entry_list_free(gpointer key, gpointer value, gpointer data)
83 GSList *it, *list; (void)key; (void)data;
86 for (it = list; it; it = g_slist_next(it))
87 base_entry_free(it->data);
91 static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
94 for (it = list; it; it = g_slist_next(it)) {
95 ObtLinkBaseEntry *e = it->data;
96 if (strcmp(obt_link_source_file(e->link), full_path) == 0)
102 /*! Finds the first entry in the list with a priority number >= @priority. */
103 static GSList* find_base_entry_priority(GSList *list, gint priority)
106 for (it = list; it; it = g_slist_next(it)) {
107 ObtLinkBaseEntry *e = it->data;
108 if (e->priority >= priority)
114 static void category_add(ObtLinkBase *lb, GQuark cat, ObtLink *link)
116 ObtLinkBaseCategory *lc;
118 lc = g_hash_table_lookup(lb->categories, &cat);
120 lc = g_slice_new(ObtLinkBaseCategory);
123 g_hash_table_insert(lb->categories, &lc->cat, lc);
125 lc->links = g_slist_prepend(lc->links, link);
128 static void category_remove(ObtLinkBase *lb, GQuark cat, ObtLink *link)
130 ObtLinkBaseCategory *lc;
133 lc = g_hash_table_lookup(lb->categories, &cat);
136 while (it->data != link)
137 it = g_slist_next(it);
138 lc->links = g_slist_delete_link(lc->links, it);
140 g_hash_table_remove(lb->categories, &cat);
143 static void category_free(ObtLinkBaseCategory *lc)
145 g_slist_free(lc->links);
146 g_slice_free(ObtLinkBaseCategory, lc);
149 /*! Called when a change happens in the filesystem. */
150 static void update(ObtWatch *w, const gchar *base_path,
151 const gchar *sub_path,
152 const gchar *full_path,
153 ObtWatchNotifyType type,
156 ObtLinkBase *self = data;
160 gboolean add = FALSE;
162 if (!g_str_has_suffix(sub_path, ".desktop"))
163 return; /* ignore non-.desktop files */
165 id = obt_link_id_from_ddfile(sub_path);
166 list = g_hash_table_lookup(self->base, id);
169 case OBT_WATCH_SELF_REMOVED:
171 case OBT_WATCH_REMOVED:
172 it = find_base_entry_path(list, full_path);
174 /* it may be false if the link was skipped during the add because
175 it did not want to be displayed */
177 ObtLink *link = it->data;
179 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
183 cats = obt_link_app_categories(link, &n);
184 for (i = 0; i < n; ++i)
185 category_remove(self, cats[i], link);
188 list = g_slist_delete_link(list, it);
189 base_entry_free(it->data);
192 /* this will free 'id' */
193 g_hash_table_insert(self->base, id, list);
197 /* the value is already freed by deleting it above so we don't
198 need to free it here. id will still need to be freed tho. */
199 g_hash_table_remove(self->base, id);
203 case OBT_WATCH_MODIFIED:
204 it = find_base_entry_path(list, full_path);
206 /* it may be false if the link was skipped during the add because
207 it did not want to be displayed */
209 ObtLink *link = it->data;
211 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
215 cats = obt_link_app_categories(link, &n);
216 for (i = 0; i < n; ++i)
217 category_remove(self, cats[i], link);
220 list = g_slist_delete_link(list, it);
221 base_entry_free(it->data);
222 /* this will put the modified list into the hash table */
226 case OBT_WATCH_ADDED:
227 priority = g_hash_table_lookup(self->path_to_priority, base_path);
230 /* find the first position in the list with a higher priority value */
231 if ((it = find_base_entry_priority(list, *priority))) {
232 const ObtLinkBaseEntry *e = it->data;
233 if (e->priority == *priority) {
244 link = obt_link_from_ddfile(full_path, self->paths,
245 self->language, self->country,
248 if (!obt_link_display(link, self->environments)) {
249 obt_link_unref(link);
252 ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
253 e->priority = *priority;
255 list = g_slist_insert_before(list, it, e);
257 /* this will free 'id' */
258 g_hash_table_insert(self->base, id, list);
261 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
265 cats = obt_link_app_categories(link, &n);
266 for (i = 0; i < n; ++i)
267 category_add(self, cats[i], link);
275 if (self->update_func)
276 self->update_func(self, self->update_data);
279 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
287 self = g_slice_new0(ObtLinkBase);
288 self->environments = environments;
289 self->watch = obt_watch_new();
290 self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
291 self->path_to_priority = g_hash_table_new_full(
292 g_str_hash, g_str_equal, g_free, g_free);
293 self->categories = g_hash_table_new_full(
294 g_int_hash, g_int_equal, NULL, (GDestroyNotify)category_free);
296 obt_paths_ref(paths);
298 /* parse the locale string to determine the language, country, and
301 if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
304 self->language = g_strndup(locale, i);
307 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
308 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
310 if (self->language && locale[i] == '_') {
313 if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
314 self->country = g_strndup(locale, i);
317 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
318 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
321 if (self->country && locale[i] == '.')
323 if (!locale[i] || locale[i] == '@')
325 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
326 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
328 if (self->country && locale[i] == '@') {
332 self->modifier = g_strndup(locale, i);
335 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
336 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
340 /* run through each directory, foo, in the XDG_DATA_DIRS, and add
341 foo/applications to the list of directories to watch here, with
342 increasing priority (decreasing importance). */
344 for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
345 if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
349 base_path = g_build_filename(it->data, "applications", NULL);
351 /* add to the hash table before adding the watch. the new watch
352 will be calling our update handler, immediately with any
353 files present in the directory */
354 pri = g_new(gint, 1);
356 g_hash_table_insert(self->path_to_priority,
357 g_strdup(base_path), pri);
359 obt_watch_add(self->watch, base_path, FALSE, update, self);
367 void obt_linkbase_ref(ObtLinkBase *self)
372 void obt_linkbase_unref(ObtLinkBase *self)
374 if (--self->ref < 1) {
375 /* free all the values in the hash table
376 we can't do this with a value_destroy_function in the hash table,
377 because when we replace values, we are doing so with the same list
378 (modified), and that would cause it to free the list we are putting
381 g_hash_table_foreach(self->base, base_entry_list_free, NULL);
383 obt_watch_unref(self->watch);
384 g_hash_table_unref(self->categories);
385 g_hash_table_unref(self->path_to_priority);
386 g_hash_table_unref(self->base);
387 obt_paths_unref(self->paths);
388 g_slice_free(ObtLinkBase, self);
392 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
395 lb->update_func = func;
396 lb->update_data = data;