]> icculus.org git repositories - dana/openbox.git/blob - obt/linkbase.c
Make warnings about parse problems in .desktop files "debug" messages. Most people...
[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 typedef struct _ObtLinkBaseCategory ObtLinkBaseCategory;
30
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. */
35     gint priority;
36     ObtLink *link;
37 };
38
39 struct _ObtLinkBaseCategory {
40     GQuark cat;
41     GSList *links;
42 };
43
44 struct _ObtLinkBase {
45     gint ref;
46
47     /*! A bitflag of values from ObtLinkEnvFlags indicating which environments
48       are to be considered active. */
49     guint environments;
50
51     const gchar *language;
52     const gchar *country;
53     const gchar *modifier;
54
55     ObtPaths *paths;
56     ObtWatch *watch;
57     /*! This holds a GList of ObtLinkBaseEntrys sorted by priority in
58       increasing order (by precedence in decreasing order). */
59     GHashTable *base;
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;
63
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;
70
71     ObtLinkBaseUpdateFunc update_func;
72     gpointer update_data;
73 };
74
75 static void base_entry_free(ObtLinkBaseEntry *e)
76 {
77     obt_link_unref(e->link);
78     g_slice_free(ObtLinkBaseEntry, e);
79 }
80
81 static void base_entry_list_free(gpointer key, gpointer value, gpointer data)
82 {
83     GList *it, *list; (void)key; (void)data;
84
85     list = value;
86     for (it = list; it; it = g_list_next(it))
87         base_entry_free(it->data);
88     g_list_free(list);
89 }
90
91 static GList* find_base_entry_path(GList *list, const gchar *full_path)
92 {
93     GList *it;
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)
97             break;
98     }
99     return it;
100 }
101
102 /*! Finds the first entry in the list with a priority number >= @priority. */
103 static GList* find_base_entry_priority(GList *list, gint priority)
104 {
105     GList *it;
106     for (it = list; it; it = g_list_next(it)) {
107         ObtLinkBaseEntry *e = it->data;
108         if (e->priority >= priority)
109             break;
110     }
111     return it;
112 }
113
114 static void category_add(ObtLinkBase *lb, GQuark cat, ObtLink *link)
115 {
116     ObtLinkBaseCategory *lc;
117
118     lc = g_hash_table_lookup(lb->categories, &cat);
119     if (!lc) {
120         lc = g_slice_new(ObtLinkBaseCategory);
121         lc->cat = cat;
122         lc->links = NULL;
123         g_hash_table_insert(lb->categories, &lc->cat, lc);
124     }
125     lc->links = g_slist_prepend(lc->links, link);
126 }
127
128 static void category_remove(ObtLinkBase *lb, GQuark cat, ObtLink *link)
129 {
130     ObtLinkBaseCategory *lc;
131     GSList *it;
132
133     lc = g_hash_table_lookup(lb->categories, &cat);
134
135     it = lc->links;
136     while (it->data != link)
137         it = g_slist_next(it);
138     lc->links = g_slist_delete_link(lc->links, it);
139     if (!lc->links)
140         g_hash_table_remove(lb->categories, &cat);
141 }
142
143 static void category_add_app(ObtLinkBase *lb, ObtLink *link)
144 {
145     if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
146         const GQuark *cats;
147         gulong i, n;
148
149         cats = obt_link_app_categories(link, &n);
150         for (i = 0; i < n; ++i)
151             category_add(lb, cats[i], link);
152     }
153 }
154
155 static void category_remove_app(ObtLinkBase *lb, ObtLink *link)
156 {
157     if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
158         const GQuark *cats;
159         gulong i, n;
160
161         cats = obt_link_app_categories(link, &n);
162         for (i = 0; i < n; ++i)
163             category_remove(lb, cats[i], link);
164     }
165 }
166
167 static void category_free(ObtLinkBaseCategory *lc)
168 {
169     g_slist_free(lc->links);
170     g_slice_free(ObtLinkBaseCategory, lc);
171 }
172
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,
177                    gpointer data)
178 {
179     ObtLinkBase *self = data;
180     ObtLinkBaseEntry *add = NULL;
181     ObtLinkBaseEntry *remove = NULL;
182     ObtLinkBaseEntry *show, *hide;
183     ObtLink *link;
184     gchar *id, *full_path;
185     GList *list, *it;
186     GList *remove_it = NULL;
187     gint *priority;
188
189     if (!g_str_has_suffix(sub_path, ".desktop"))
190         return; /* ignore non-.desktop files */
191
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);
195
196     switch (type) {
197     case OBT_WATCH_SELF_REMOVED:
198         break;
199     case OBT_WATCH_ADDED:
200         priority = g_hash_table_lookup(self->path_to_priority, base_path);
201
202         /* make sure an entry doesn't already exist from the same
203            @base_path */
204         it = find_base_entry_priority(list, *priority);
205         if (it) {
206             ObtLinkBaseEntry *e = it->data;
207             if (e->priority == *priority) {
208                 break;
209             }
210         }
211     case OBT_WATCH_MODIFIED:
212         link = obt_link_from_ddfile(full_path, self->paths,
213                                     self->language, self->country,
214                                     self->modifier);
215         if (link && !obt_link_display(link, self->environments)) {
216             obt_link_unref(link);
217             link = NULL;
218         }
219         if (link) {
220             add = g_slice_new(ObtLinkBaseEntry);
221             add->priority = *priority;
222             add->link = link;
223         }
224
225         if (type != OBT_WATCH_MODIFIED)
226             break;
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;
232         break;
233     }
234
235     /* figure out which entry should be shown (which will have highest
236        precedence) */
237     show = hide = NULL;
238     if (add) {
239         ObtLinkBaseEntry *first = list ? list->data : NULL;
240
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) {
244             show = add;
245             hide = first;
246         }
247     }
248     else if (remove) {
249         ObtLinkBaseEntry *first = list ? list->data : NULL;
250
251         if (first == remove) {
252             hide = first;
253             show = list->next ? list->next->data : NULL;
254         }
255     }
256
257     if (hide)
258         category_remove_app(self, hide->link);
259     if (show)
260         category_add_app(self, show->link);
261
262     if ((remove || add) && self->update_func) {
263         ObtLink *const a = show ? show->link : NULL;
264         ObtLink *const r = hide ? hide->link : NULL;
265         self->update_func(
266             self, r, a, self->update_data);
267     }
268
269     /* do the actual removal/addition to the base list for the @id */
270     if (remove_it) {
271         base_entry_free(remove_it->data);
272         list = g_list_delete_link(list, remove_it);
273     }
274     if (add) {
275         it = find_base_entry_priority(list, *priority);
276         list = g_list_insert_before(list, it, add);
277     }
278
279     if (remove || add) {
280         /* update the list in the hash table */
281         if (list) {
282             /* this will free 'id' */
283             g_hash_table_insert(self->base, id, list);
284             id = NULL;
285         }
286         else {
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);
290         }
291     }
292     g_free(full_path);
293     g_free(id);
294 }
295
296 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
297                               guint environments)
298 {
299     ObtLinkBase *self;
300     GSList *it;
301     gint priority;
302     gint i;
303     
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);
312     self->paths = paths;
313     obt_paths_ref(paths);
314
315     /* parse the locale string to determine the language, country, and
316        modifier settings */
317     for (i = 0; ; ++i)
318         if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
319             locale[i] == '@')
320         {
321             self->language = g_strndup(locale, i);
322             break;
323         }
324         else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
325                  ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
326             break;
327     if (self->language && locale[i] == '_') {
328         locale += i+1;
329         for (i = 0; ; ++i)
330             if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
331                 self->country = g_strndup(locale, i);
332                 break;
333             }
334             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
335                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
336                 break;
337     }
338     if (self->country && locale[i] == '.')
339         for (; ; ++i)
340             if (!locale[i] || locale[i] == '@')
341                 break;
342             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
343                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
344                 break;
345     if (self->country && locale[i] == '@') {
346         locale += i+1;
347         for (i = 0; ; ++i)
348             if (!locale[i]) {
349                 self->modifier = g_strndup(locale, i);
350                 break;
351             }
352             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
353                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
354                 break;
355     }
356
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). */
360     priority = 0;
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)) {
363             gchar *base_path;
364             gint *pri;
365
366             base_path = g_build_filename(it->data, "applications", NULL);
367
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);
372             *pri = priority;
373             g_hash_table_insert(self->path_to_priority,
374                                 g_strdup(base_path), pri);
375
376             obt_watch_add(self->watch, base_path, FALSE, update, self);
377
378             ++priority;
379         }
380     }
381     return self;
382 }
383
384 void obt_linkbase_ref(ObtLinkBase *self)
385 {
386     ++self->ref;
387 }
388
389 void obt_linkbase_unref(ObtLinkBase *self)
390 {
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
396            back in.
397          */
398         g_hash_table_foreach(self->base, base_entry_list_free, NULL);
399
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);
406     }
407 }
408
409 void obt_linkbase_refresh(ObtLinkBase *lb)
410 {
411     obt_watch_refresh(lb->watch);
412 }
413
414 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
415                                   gpointer data)
416 {
417     lb->update_func = func;
418     lb->update_data = data;
419 }
420
421 GSList *obt_linkbase_category(ObtLinkBase *lb, GQuark category)
422 {
423     ObtLinkBaseCategory *cat;
424
425     cat = g_hash_table_lookup(lb->categories, &category);
426     if (!cat) return NULL;
427     else      return cat->links;
428 }