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 (self->update_func)
181 self, OBT_LINKBASE_REMOVED, link, self->update_data);
183 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
187 cats = obt_link_app_categories(link, &n);
188 for (i = 0; i < n; ++i)
189 category_remove(self, cats[i], link);
192 list = g_slist_delete_link(list, it);
193 base_entry_free(it->data);
196 /* this will free 'id' */
197 g_hash_table_insert(self->base, id, list);
201 /* the value is already freed by deleting it above so we don't
202 need to free it here. id will still need to be freed tho. */
203 g_hash_table_remove(self->base, id);
207 case OBT_WATCH_MODIFIED:
208 it = find_base_entry_path(list, full_path);
210 /* it may be false if the link was skipped during the add because
211 it did not want to be displayed */
213 ObtLink *link = it->data;
215 if (self->update_func)
217 self, OBT_LINKBASE_REMOVED, link, self->update_data);
219 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
223 cats = obt_link_app_categories(link, &n);
224 for (i = 0; i < n; ++i)
225 category_remove(self, cats[i], link);
228 list = g_slist_delete_link(list, it);
229 base_entry_free(it->data);
230 /* this will put the modified list into the hash table */
234 case OBT_WATCH_ADDED:
235 priority = g_hash_table_lookup(self->path_to_priority, base_path);
238 /* find the first position in the list with a higher priority value */
239 if ((it = find_base_entry_priority(list, *priority))) {
240 const ObtLinkBaseEntry *e = it->data;
241 if (e->priority == *priority) {
252 link = obt_link_from_ddfile(full_path, self->paths,
253 self->language, self->country,
256 if (!obt_link_display(link, self->environments)) {
257 obt_link_unref(link);
260 ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
261 e->priority = *priority;
264 if (self->update_func)
266 self, OBT_LINKBASE_ADDED, link, self->update_data);
268 list = g_slist_insert_before(list, it, e);
270 /* this will free 'id' */
271 g_hash_table_insert(self->base, id, list);
274 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
278 cats = obt_link_app_categories(link, &n);
279 for (i = 0; i < n; ++i)
280 category_add(self, cats[i], link);
289 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
297 self = g_slice_new0(ObtLinkBase);
298 self->environments = environments;
299 self->watch = obt_watch_new();
300 self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
301 self->path_to_priority = g_hash_table_new_full(
302 g_str_hash, g_str_equal, g_free, g_free);
303 self->categories = g_hash_table_new_full(
304 g_int_hash, g_int_equal, NULL, (GDestroyNotify)category_free);
306 obt_paths_ref(paths);
308 /* parse the locale string to determine the language, country, and
311 if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
314 self->language = g_strndup(locale, i);
317 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
318 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
320 if (self->language && locale[i] == '_') {
323 if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
324 self->country = g_strndup(locale, i);
327 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
328 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
331 if (self->country && locale[i] == '.')
333 if (!locale[i] || locale[i] == '@')
335 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
336 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
338 if (self->country && locale[i] == '@') {
342 self->modifier = g_strndup(locale, i);
345 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
346 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
350 /* run through each directory, foo, in the XDG_DATA_DIRS, and add
351 foo/applications to the list of directories to watch here, with
352 increasing priority (decreasing importance). */
354 for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
355 if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
359 base_path = g_build_filename(it->data, "applications", NULL);
361 /* add to the hash table before adding the watch. the new watch
362 will be calling our update handler, immediately with any
363 files present in the directory */
364 pri = g_new(gint, 1);
366 g_hash_table_insert(self->path_to_priority,
367 g_strdup(base_path), pri);
369 obt_watch_add(self->watch, base_path, FALSE, update, self);
377 void obt_linkbase_ref(ObtLinkBase *self)
382 void obt_linkbase_unref(ObtLinkBase *self)
384 if (--self->ref < 1) {
385 /* free all the values in the hash table
386 we can't do this with a value_destroy_function in the hash table,
387 because when we replace values, we are doing so with the same list
388 (modified), and that would cause it to free the list we are putting
391 g_hash_table_foreach(self->base, base_entry_list_free, NULL);
393 obt_watch_unref(self->watch);
394 g_hash_table_unref(self->categories);
395 g_hash_table_unref(self->path_to_priority);
396 g_hash_table_unref(self->base);
397 obt_paths_unref(self->paths);
398 g_slice_free(ObtLinkBase, self);
402 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
405 lb->update_func = func;
406 lb->update_data = data;
409 GSList *obt_linkbase_category(ObtLinkBase *lb, GQuark category)
411 ObtLinkBaseCategory *cat;
413 cat = g_hash_table_lookup(lb->categories, &category);
414 if (!cat) return NULL;
415 else return cat->links;