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;
30 struct _ObtLinkBaseEntry {
31 /*! Links come from a set of paths. Links found in earlier paths get lower
32 priority values (higher precedence). This is the index in that set of
33 paths of the base directory under which the link was found. */
41 /*! A bitflag of values from ObtLinkEnvFlags indicating which environments
42 are to be considered active. */
45 const gchar *language;
47 const gchar *modifier;
51 /*! This holds a GSList of ObtLinkBaseEntrys sorted by priority in
52 increasing order (by precedence in decreasing order). */
54 /*! This holds the paths in which we look for links, and the data is an
55 integer that is the priority of that directory. */
56 GHashTable *path_to_priority;
58 /*! This maps GQuark main categories to GSLists of ObtLink objects found in
59 the category. The ObtLink objects are not reffed to be placed in this
60 structure since they will always be in the base hash table as well. So
61 they are not unreffed when they are removed. */
62 GHashTable *main_categories;
64 ObtLinkBaseUpdateFunc update_func;
68 static void base_entry_free(ObtLinkBaseEntry *e)
70 obt_link_unref(e->link);
71 g_slice_free(ObtLinkBaseEntry, e);
74 static void base_entry_list_free(gpointer key, gpointer value, gpointer data)
76 GSList *it, *list; (void)key; (void)data;
79 for (it = list; it; it = g_slist_next(it))
80 base_entry_free(it->data);
84 static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
87 for (it = list; it; it = g_slist_next(it)) {
88 ObtLinkBaseEntry *e = it->data;
89 if (strcmp(obt_link_source_file(e->link), full_path) == 0)
95 /*! Finds the first entry in the list with a priority number >= @priority. */
96 static GSList* find_base_entry_priority(GSList *list, gint priority)
99 for (it = list; it; it = g_slist_next(it)) {
100 ObtLinkBaseEntry *e = it->data;
101 if (e->priority >= priority)
107 static void main_category_add(ObtLinkBase *lb, GQuark cat, ObtLink *link)
111 list = g_hash_table_lookup(lb->main_categories, &cat);
112 list = g_slist_prepend(list, link);
113 g_hash_table_insert(lb->main_categories, &cat, list);
116 static void main_category_remove(ObtLinkBase *lb, GQuark cat, ObtLink *link)
120 list = g_hash_table_lookup(lb->main_categories, &cat);
122 while (it->data != link)
123 it = g_slist_next(it);
124 list = g_slist_delete_link(list, it);
125 g_hash_table_insert(lb->main_categories, &cat, list);
128 /*! Called when a change happens in the filesystem. */
129 static void update(ObtWatch *w, const gchar *base_path,
130 const gchar *sub_path,
131 const gchar *full_path,
132 ObtWatchNotifyType type,
135 ObtLinkBase *self = data;
139 gboolean add = FALSE;
141 if (!g_str_has_suffix(sub_path, ".desktop"))
142 return; /* ignore non-.desktop files */
144 id = obt_link_id_from_ddfile(sub_path);
145 list = g_hash_table_lookup(self->base, id);
148 case OBT_WATCH_SELF_REMOVED:
150 case OBT_WATCH_REMOVED:
151 it = find_base_entry_path(list, full_path);
153 /* it may be false if the link was skipped during the add because
154 it did not want to be displayed */
156 ObtLink *link = it->data;
158 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION)
159 main_category_remove(
160 self, obt_link_app_main_category(link), link);
162 list = g_slist_delete_link(list, it);
163 base_entry_free(it->data);
166 /* this will free 'id' */
167 g_hash_table_insert(self->base, id, list);
171 /* the value is already freed by deleting it above so we don't
172 need to free it here. id will still need to be freed tho. */
173 g_hash_table_remove(self->base, id);
177 case OBT_WATCH_MODIFIED:
178 it = find_base_entry_path(list, full_path);
180 /* it may be false if the link was skipped during the add because
181 it did not want to be displayed */
183 ObtLink *link = it->data;
185 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION)
186 main_category_remove(
187 self, obt_link_app_main_category(link), link);
189 list = g_slist_delete_link(list, it);
190 base_entry_free(it->data);
191 /* this will put the modified list into the hash table */
195 case OBT_WATCH_ADDED:
196 priority = g_hash_table_lookup(self->path_to_priority, base_path);
199 /* find the first position in the list with a higher priority value */
200 if ((it = find_base_entry_priority(list, *priority))) {
201 const ObtLinkBaseEntry *e = it->data;
202 if (e->priority == *priority) {
213 link = obt_link_from_ddfile(full_path, self->paths,
214 self->language, self->country,
217 if (!obt_link_display(link, self->environments)) {
218 obt_link_unref(link);
221 ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
222 e->priority = *priority;
224 list = g_slist_insert_before(list, it, e);
226 /* this will free 'id' */
227 g_hash_table_insert(self->base, id, list);
230 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION)
232 self, obt_link_app_main_category(link), link);
239 if (self->update_func)
240 self->update_func(self, self->update_data);
243 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
251 self = g_slice_new0(ObtLinkBase);
252 self->environments = environments;
253 self->watch = obt_watch_new();
254 self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
255 self->path_to_priority = g_hash_table_new_full(g_str_hash, g_str_equal,
257 self->main_categories = g_hash_table_new(g_int_hash, g_int_equal);
259 obt_paths_ref(paths);
261 /* parse the locale string to determine the language, country, and
264 if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
267 self->language = g_strndup(locale, i);
270 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
271 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
273 if (self->language && locale[i] == '_') {
276 if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
277 self->country = g_strndup(locale, i);
280 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
281 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
284 if (self->country && locale[i] == '.')
286 if (!locale[i] || locale[i] == '@')
288 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
289 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
291 if (self->country && locale[i] == '@') {
295 self->modifier = g_strndup(locale, i);
298 else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
299 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
303 /* run through each directory, foo, in the XDG_DATA_DIRS, and add
304 foo/applications to the list of directories to watch here, with
305 increasing priority (decreasing importance). */
307 for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
308 if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
312 base_path = g_build_filename(it->data, "applications", NULL);
314 /* add to the hash table before adding the watch. the new watch
315 will be calling our update handler, immediately with any
316 files present in the directory */
317 pri = g_new(gint, 1);
319 g_hash_table_insert(self->path_to_priority,
320 g_strdup(base_path), pri);
322 obt_watch_add(self->watch, base_path, FALSE, update, self);
330 void obt_linkbase_ref(ObtLinkBase *self)
335 void obt_linkbase_unref(ObtLinkBase *self)
337 if (--self->ref < 1) {
338 /* free all the values in the hash table
339 we can't do this with a value_destroy_function in the hash table,
340 because when we replace values, we are doing so with the same list
341 (modified), and that would cause it to free the list we are putting
344 g_hash_table_foreach(self->base, base_entry_list_free, NULL);
346 obt_watch_unref(self->watch);
347 g_hash_table_unref(self->main_categories);
348 g_hash_table_unref(self->path_to_priority);
349 g_hash_table_unref(self->base);
350 obt_paths_unref(self->paths);
351 g_slice_free(ObtLinkBase, self);
355 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
358 lb->update_func = func;
359 lb->update_data = data;