]> icculus.org git repositories - dana/openbox.git/blob - obt/linkbase.c
use the filesystem's locale for opening a .desktop file
[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 GSList 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     GSList *it, *list; (void)key; (void)data;
84
85     list = value;
86     for (it = list; it; it = g_slist_next(it))
87         base_entry_free(it->data);
88     g_slist_free(list);
89 }
90
91 static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
92 {
93     GSList *it;
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)
97             break;
98     }
99     return it;
100 }
101
102 /*! Finds the first entry in the list with a priority number >= @priority. */
103 static GSList* find_base_entry_priority(GSList *list, gint priority)
104 {
105     GSList *it;
106     for (it = list; it; it = g_slist_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_free(ObtLinkBaseCategory *lc)
144 {
145     g_slist_free(lc->links);
146     g_slice_free(ObtLinkBaseCategory, lc);
147 }
148
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,
154                    gpointer data)
155 {
156     ObtLinkBase *self = data;
157     gchar *id;
158     GSList *list, *it;
159     gint *priority;
160     gboolean add = FALSE;
161
162     if (!g_str_has_suffix(sub_path, ".desktop"))
163         return; /* ignore non-.desktop files */
164
165     id = obt_link_id_from_ddfile(sub_path);
166     list = g_hash_table_lookup(self->base, id);
167
168     switch (type) {
169     case OBT_WATCH_SELF_REMOVED:
170         break;
171     case OBT_WATCH_REMOVED:
172         it = find_base_entry_path(list, full_path);
173         if (it) {
174             /* it may be false if the link was skipped during the add because
175                it did not want to be displayed */
176
177             ObtLink *link = it->data;
178
179             if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
180                 const GQuark *cats;
181                 gulong i, n;
182
183                 cats = obt_link_app_categories(link, &n);
184                 for (i = 0; i < n; ++i)
185                     category_remove(self, cats[i], link);
186             }
187
188             list = g_slist_delete_link(list, it);
189             base_entry_free(it->data);
190
191             if (list) {
192                 /* this will free 'id' */
193                 g_hash_table_insert(self->base, id, list);
194                 id = NULL;
195             }
196             else {
197                 /* the value is already freed by deleting it above so we don't
198                    need to free it here. id will still need to be freed tho. */
199                 g_hash_table_remove(self->base, id);
200             }
201         }
202         break;
203     case OBT_WATCH_MODIFIED:
204         it = find_base_entry_path(list, full_path);
205         if (it) {
206             /* it may be false if the link was skipped during the add because
207                it did not want to be displayed */
208
209             ObtLink *link = it->data;
210
211             if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
212                 const GQuark *cats;
213                 gulong i, n;
214
215                 cats = obt_link_app_categories(link, &n);
216                 for (i = 0; i < n; ++i)
217                     category_remove(self, cats[i], link);
218             }
219
220             list = g_slist_delete_link(list, it);
221             base_entry_free(it->data);
222             /* this will put the modified list into the hash table */
223             add = TRUE;
224         }
225         break;
226     case OBT_WATCH_ADDED:
227         priority = g_hash_table_lookup(self->path_to_priority, base_path);
228         add = TRUE;
229
230         /* find the first position in the list with a higher priority value */
231         if ((it = find_base_entry_priority(list, *priority))) {
232             const ObtLinkBaseEntry *e = it->data;
233             if (e->priority == *priority) {
234                 /* already exists */
235                 add = FALSE;
236             }
237         }
238         break;
239     }
240
241     if (add) {
242         ObtLink *link;
243
244         link = obt_link_from_ddfile(full_path, self->paths,
245                                     self->language, self->country,
246                                     self->modifier);
247         if (link) {
248             if (!obt_link_display(link, self->environments)) {
249                 obt_link_unref(link);
250             }
251             else {
252                 ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
253                 e->priority = *priority;
254                 e->link = link;
255                 list = g_slist_insert_before(list, it, e);
256
257                 /* this will free 'id' */
258                 g_hash_table_insert(self->base, id, list);
259                 id = NULL;
260
261                 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
262                     const GQuark *cats;
263                     gulong i, n;
264
265                     cats = obt_link_app_categories(link, &n);
266                     for (i = 0; i < n; ++i)
267                         category_add(self, cats[i], link);
268                 }
269             }
270         }
271     }
272
273     g_free(id);
274
275     if (self->update_func)
276         self->update_func(self, self->update_data);
277 }
278
279 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
280                               guint environments)
281 {
282     ObtLinkBase *self;
283     GSList *it;
284     gint priority;
285     gint i;
286     
287     self = g_slice_new0(ObtLinkBase);
288     self->environments = environments;
289     self->watch = obt_watch_new();
290     self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
291     self->path_to_priority = g_hash_table_new_full(
292         g_str_hash, g_str_equal, g_free, g_free);
293     self->categories = g_hash_table_new_full(
294         g_int_hash, g_int_equal, NULL, (GDestroyNotify)category_free);
295     self->paths = paths;
296     obt_paths_ref(paths);
297
298     /* parse the locale string to determine the language, country, and
299        modifier settings */
300     for (i = 0; ; ++i)
301         if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
302             locale[i] == '@')
303         {
304             self->language = g_strndup(locale, i);
305             break;
306         }
307         else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
308                  ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
309             break;
310     if (self->language && locale[i] == '_') {
311         locale += i+1;
312         for (i = 0; ; ++i)
313             if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
314                 self->country = g_strndup(locale, i);
315                 break;
316             }
317             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
318                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
319                 break;
320     }
321     if (self->country && locale[i] == '.')
322         for (; ; ++i)
323             if (!locale[i] || locale[i] == '@')
324                 break;
325             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
326                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
327                 break;
328     if (self->country && locale[i] == '@') {
329         locale += i+1;
330         for (i = 0; ; ++i)
331             if (!locale[i]) {
332                 self->modifier = g_strndup(locale, i);
333                 break;
334             }
335             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
336                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
337                 break;
338     }
339
340     /* run through each directory, foo, in the XDG_DATA_DIRS, and add
341        foo/applications to the list of directories to watch here, with
342        increasing priority (decreasing importance). */
343     priority = 0;
344     for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
345         if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
346             gchar *base_path;
347             gint *pri;
348
349             base_path = g_build_filename(it->data, "applications", NULL);
350
351             /* add to the hash table before adding the watch. the new watch
352                will be calling our update handler, immediately with any
353                files present in the directory */
354             pri = g_new(gint, 1);
355             *pri = priority;
356             g_hash_table_insert(self->path_to_priority,
357                                 g_strdup(base_path), pri);
358
359             obt_watch_add(self->watch, base_path, FALSE, update, self);
360
361             ++priority;
362         }
363     }
364     return self;
365 }
366
367 void obt_linkbase_ref(ObtLinkBase *self)
368 {
369     ++self->ref;
370 }
371
372 void obt_linkbase_unref(ObtLinkBase *self)
373 {
374     if (--self->ref < 1) {
375         /* free all the values in the hash table
376            we can't do this with a value_destroy_function in the hash table,
377            because when we replace values, we are doing so with the same list
378            (modified), and that would cause it to free the list we are putting
379            back in.
380          */
381         g_hash_table_foreach(self->base, base_entry_list_free, NULL);
382
383         obt_watch_unref(self->watch);
384         g_hash_table_unref(self->categories);
385         g_hash_table_unref(self->path_to_priority);
386         g_hash_table_unref(self->base);
387         obt_paths_unref(self->paths);
388         g_slice_free(ObtLinkBase, self);
389     }
390 }
391
392 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
393                                   gpointer data)
394 {
395     lb->update_func = func;
396     lb->update_data = data;
397 }