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 GList 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 GList *it, *list; (void)key; (void)data;
86 for (it = list; it; it = g_list_next(it))
87 base_entry_free(it->data);
91 static GList* find_base_entry_path(GList *list, const gchar *full_path)
94 for (it = list; it; it = g_list_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 GList* find_base_entry_priority(GList *list, gint priority)
106 for (it = list; it; it = g_list_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_add_app(ObtLinkBase *lb, ObtLink *link)
145 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
149 cats = obt_link_app_categories(link, &n);
150 for (i = 0; i < n; ++i)
151 category_add(lb, cats[i], link);
155 static void category_remove_app(ObtLinkBase *lb, ObtLink *link)
157 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
161 cats = obt_link_app_categories(link, &n);
162 for (i = 0; i < n; ++i)
163 category_remove(lb, cats[i], link);
167 static void category_free(ObtLinkBaseCategory *lc)
169 g_slist_free(lc->links);
170 g_slice_free(ObtLinkBaseCategory, lc);
173 /*! Called when a change happens in the filesystem. */
174 static void update(ObtWatch *w, const gchar *base_path,
175 const gchar *sub_path,
176 ObtWatchNotifyType type,
179 ObtLinkBase *self = data;
180 ObtLinkBaseEntry *add = NULL;
181 ObtLinkBaseEntry *remove = NULL;
182 ObtLinkBaseEntry *show, *hide;
184 gchar *id, *full_path;
186 GList *remove_it = NULL;
189 if (!g_str_has_suffix(sub_path, ".desktop"))
190 return; /* ignore non-.desktop files */
192 id = obt_link_id_from_ddfile(sub_path);
193 list = g_hash_table_lookup(self->base, id);
194 full_path = g_build_filename(base_path, sub_path, NULL);
197 case OBT_WATCH_SELF_REMOVED:
199 case OBT_WATCH_ADDED:
200 priority = g_hash_table_lookup(self->path_to_priority, base_path);
202 /* make sure an entry doesn't already exist from the same
204 it = find_base_entry_priority(list, *priority);
206 ObtLinkBaseEntry *e = it->data;
207 if (e->priority == *priority) {
211 case OBT_WATCH_MODIFIED:
212 link = obt_link_from_ddfile(full_path, self->paths,
213 self->language, self->country,
215 if (link && !obt_link_display(link, self->environments)) {
216 obt_link_unref(link);
220 add = g_slice_new(ObtLinkBaseEntry);
221 add->priority = *priority;
225 if (type != OBT_WATCH_MODIFIED)
227 case OBT_WATCH_REMOVED:
228 /* this may be NULL if the link was skipped during the add because
229 it did not want to be displayed */
230 remove_it = find_base_entry_path(list, full_path);
231 remove = remove_it ? remove_it->data : NULL;
235 /* figure out which entry should be shown (which will have highest
239 ObtLinkBaseEntry *first = list ? list->data : NULL;
241 /* a greater priority means a lower precedence, so
242 the new one will replace the current front of the list */
243 if (!first || first->priority >= add->priority) {
249 ObtLinkBaseEntry *first = list ? list->data : NULL;
251 if (first == remove) {
253 show = list->next ? list->next->data : NULL;
258 category_remove_app(self, hide->link);
260 category_add_app(self, show->link);
262 if ((remove || add) && self->update_func) {
263 ObtLink *const a = show ? show->link : NULL;
264 ObtLink *const r = hide ? hide->link : NULL;
266 self, r, a, self->update_data);
269 /* do the actual removal/addition to the base list for the @id */
271 base_entry_free(remove_it->data);
272 list = g_list_delete_link(list, remove_it);
275 it = find_base_entry_priority(list, *priority);
276 list = g_list_insert_before(list, it, add);
280 /* update the list in the hash table */
282 /* this will free 'id' */
283 g_hash_table_insert(self->base, id, list);
287 /* the value is already freed by deleting it above so we don't
288 need to free it here. id will still need to be freed tho. */
289 g_hash_table_steal(self->base, id);
296 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
304 self = g_slice_new0(ObtLinkBase);
305 self->environments = environments;
306 self->watch = obt_watch_new();
307 self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
308 self->path_to_priority = g_hash_table_new_full(
309 g_str_hash, g_str_equal, g_free, g_free);
310 self->categories = g_hash_table_new_full(
311 g_int_hash, g_int_equal, NULL, (GDestroyNotify)category_free);
313 obt_paths_ref(paths);
315 /* parse the locale string to determine the language, country, and
318 if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
321 self->language = g_strndup(locale, i);
324 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
325 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
327 if (self->language && locale[i] == '_') {
330 if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
331 self->country = g_strndup(locale, i);
334 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
335 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
338 if (self->country && locale[i] == '.')
340 if (!locale[i] || locale[i] == '@')
342 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
343 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
345 if (self->country && locale[i] == '@') {
349 self->modifier = g_strndup(locale, i);
352 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
353 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
357 /* run through each directory, foo, in the XDG_DATA_DIRS, and add
358 foo/applications to the list of directories to watch here, with
359 increasing priority (decreasing importance). */
361 for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
362 if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
366 base_path = g_build_filename(it->data, "applications", NULL);
368 /* add to the hash table before adding the watch. the new watch
369 will be calling our update handler, immediately with any
370 files present in the directory */
371 pri = g_new(gint, 1);
373 g_hash_table_insert(self->path_to_priority,
374 g_strdup(base_path), pri);
376 obt_watch_add(self->watch, base_path, FALSE, update, self);
384 void obt_linkbase_ref(ObtLinkBase *self)
389 void obt_linkbase_unref(ObtLinkBase *self)
391 if (--self->ref < 1) {
392 /* free all the values in the hash table
393 we can't do this with a value_destroy_function in the hash table,
394 because when we replace values, we are doing so with the same list
395 (modified), and that would cause it to free the list we are putting
398 g_hash_table_foreach(self->base, base_entry_list_free, NULL);
400 obt_watch_unref(self->watch);
401 g_hash_table_unref(self->categories);
402 g_hash_table_unref(self->path_to_priority);
403 g_hash_table_unref(self->base);
404 obt_paths_unref(self->paths);
405 g_slice_free(ObtLinkBase, self);
409 void obt_linkbase_refresh(ObtLinkBase *lb)
411 obt_watch_refresh(lb->watch);
414 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
417 lb->update_func = func;
418 lb->update_data = data;
421 GSList *obt_linkbase_category(ObtLinkBase *lb, GQuark category)
423 ObtLinkBaseCategory *cat;
425 cat = g_hash_table_lookup(lb->categories, &category);
426 if (!cat) return NULL;
427 else return cat->links;