]> icculus.org git repositories - dana/openbox.git/blob - obt/linkbase.c
Store all links in the linkbase grouped by their main category.
[dana/openbox.git] / obt / linkbase.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2  
3    obt/linkbase.c for the Openbox window manager
4    Copyright (c) 2010        Dana Jansens
5  
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.
10  
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.
15  
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "obt/linkbase.h"
20 #include "obt/link.h"
21 #include "obt/paths.h"
22 #include "obt/watch.h"
23
24 #ifdef HAVE_STRING_H
25 # include <string.h>
26 #endif
27
28 typedef struct _ObtLinkBaseEntry ObtLinkBaseEntry;
29
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. */
34     gint priority;
35     ObtLink *link;
36 };
37
38 struct _ObtLinkBase {
39     gint ref;
40
41     /*! A bitflag of values from ObtLinkEnvFlags indicating which environments
42       are to be considered active. */
43     guint environments;
44
45     const gchar *language;
46     const gchar *country;
47     const gchar *modifier;
48
49     ObtPaths *paths;
50     ObtWatch *watch;
51     /*! This holds a GSList of ObtLinkBaseEntrys sorted by priority in
52       increasing order (by precedence in decreasing order). */
53     GHashTable *base;
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;
57
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;
63
64     ObtLinkBaseUpdateFunc update_func;
65     gpointer update_data;
66 };
67
68 static void base_entry_free(ObtLinkBaseEntry *e)
69 {
70     obt_link_unref(e->link);
71     g_slice_free(ObtLinkBaseEntry, e);
72 }
73
74 static void base_entry_list_free(gpointer key, gpointer value, gpointer data)
75 {
76     GSList *it, *list; (void)key; (void)data;
77
78     list = value;
79     for (it = list; it; it = g_slist_next(it))
80         base_entry_free(it->data);
81     g_slist_free(list);
82 }
83
84 static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
85 {
86     GSList *it;
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)
90             break;
91     }
92     return it;
93 }
94
95 /*! Finds the first entry in the list with a priority number >= @priority. */
96 static GSList* find_base_entry_priority(GSList *list, gint priority)
97 {
98     GSList *it;
99     for (it = list; it; it = g_slist_next(it)) {
100         ObtLinkBaseEntry *e = it->data;
101         if (e->priority >= priority)
102             break;
103     }
104     return it;
105 }
106
107 static void main_category_add(ObtLinkBase *lb, GQuark cat, ObtLink *link)
108 {
109     GSList *list;
110
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);
114 }
115
116 static void main_category_remove(ObtLinkBase *lb, GQuark cat, ObtLink *link)
117 {
118     GSList *list, *it;
119
120     list = g_hash_table_lookup(lb->main_categories, &cat);
121     it = list;
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);
126 }
127
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,
133                    gpointer data)
134 {
135     ObtLinkBase *self = data;
136     gchar *id;
137     GSList *list, *it;
138     gint *priority;
139     gboolean add = FALSE;
140
141     if (!g_str_has_suffix(sub_path, ".desktop"))
142         return; /* ignore non-.desktop files */
143
144     id = obt_link_id_from_ddfile(sub_path);
145     list = g_hash_table_lookup(self->base, id);
146
147     switch (type) {
148     case OBT_WATCH_SELF_REMOVED:
149         break;
150     case OBT_WATCH_REMOVED:
151         it = find_base_entry_path(list, full_path);
152         if (it) {
153             /* it may be false if the link was skipped during the add because
154                it did not want to be displayed */
155
156             ObtLink *link = it->data;
157
158             if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION)
159                 main_category_remove(
160                     self, obt_link_app_main_category(link), link);
161
162             list = g_slist_delete_link(list, it);
163             base_entry_free(it->data);
164
165             if (list) {
166                 /* this will free 'id' */
167                 g_hash_table_insert(self->base, id, list);
168                 id = NULL;
169             }
170             else {
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);
174             }
175         }
176         break;
177     case OBT_WATCH_MODIFIED:
178         it = find_base_entry_path(list, full_path);
179         if (it) {
180             /* it may be false if the link was skipped during the add because
181                it did not want to be displayed */
182
183             ObtLink *link = it->data;
184
185             if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION)
186                 main_category_remove(
187                     self, obt_link_app_main_category(link), link);
188
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 */
192             add = TRUE;
193         }
194         break;
195     case OBT_WATCH_ADDED:
196         priority = g_hash_table_lookup(self->path_to_priority, base_path);
197         add = TRUE;
198
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) {
203                 /* already exists */
204                 add = FALSE;
205             }
206         }
207         break;
208     }
209
210     if (add) {
211         ObtLink *link;
212
213         link = obt_link_from_ddfile(full_path, self->paths,
214                                     self->language, self->country,
215                                     self->modifier);
216         if (link) {
217             if (!obt_link_display(link, self->environments)) {
218                 obt_link_unref(link);
219             }
220             else {
221                 ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
222                 e->priority = *priority;
223                 e->link = link;
224                 list = g_slist_insert_before(list, it, e);
225
226                 /* this will free 'id' */
227                 g_hash_table_insert(self->base, id, list);
228                 id = NULL;
229
230                 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION)
231                     main_category_add(
232                         self, obt_link_app_main_category(link), link);
233             }
234         }
235     }
236
237     g_free(id);
238
239     if (self->update_func)
240         self->update_func(self, self->update_data);
241 }
242
243 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
244                               guint environments)
245 {
246     ObtLinkBase *self;
247     GSList *it;
248     gint priority;
249     gint i;
250     
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,
256                                                 g_free, g_free);
257     self->main_categories = g_hash_table_new(g_int_hash, g_int_equal);
258     self->paths = paths;
259     obt_paths_ref(paths);
260
261     /* parse the locale string to determine the language, country, and
262        modifier settings */
263     for (i = 0; ; ++i)
264         if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
265             locale[i] == '@')
266         {
267             self->language = g_strndup(locale, i);
268             break;
269         }
270         else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
271                  ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
272             break;
273     if (self->language && locale[i] == '_') {
274         locale += i+1;
275         for (i = 0; ; ++i)
276             if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
277                 self->country = g_strndup(locale, i);
278                 break;
279             }
280             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
281                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
282                 break;
283     }
284     if (self->country && locale[i] == '.')
285         for (; ; ++i)
286             if (!locale[i] || locale[i] == '@')
287                 break;
288             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
289                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
290                 break;
291     if (self->country && locale[i] == '@') {
292         locale += i+1;
293         for (i = 0; ; ++i)
294             if (!locale[i]) {
295                 self->modifier = g_strndup(locale, i);
296                 break;
297             }
298             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
299                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
300                 break;
301     }
302
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). */
306     priority = 0;
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)) {
309             gchar *base_path;
310             gint *pri;
311
312             base_path = g_build_filename(it->data, "applications", NULL);
313
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);
318             *pri = priority;
319             g_hash_table_insert(self->path_to_priority,
320                                 g_strdup(base_path), pri);
321
322             obt_watch_add(self->watch, base_path, FALSE, update, self);
323
324             ++priority;
325         }
326     }
327     return self;
328 }
329
330 void obt_linkbase_ref(ObtLinkBase *self)
331 {
332     ++self->ref;
333 }
334
335 void obt_linkbase_unref(ObtLinkBase *self)
336 {
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
342            back in.
343          */
344         g_hash_table_foreach(self->base, base_entry_list_free, NULL);
345
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);
352     }
353 }
354
355 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
356                                   gpointer data)
357 {
358     lb->update_func = func;
359     lb->update_data = data;
360 }