]> icculus.org git repositories - dana/openbox.git/blob - obt/linkbase.c
Move the GSource attach out to the generic watch code, and avoid blocking reads
[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     ObtLinkBaseUpdateFunc update_func;
59     gpointer update_data;
60 };
61
62 static void base_entry_free(ObtLinkBaseEntry *e)
63 {
64     obt_link_unref(e->link);
65     g_slice_free(ObtLinkBaseEntry, e);
66 }
67
68 static void base_entry_list_free(GSList *list)
69 {
70     GSList *it;
71     for (it = list; it; it = g_slist_next(it))
72         base_entry_free(it->data);
73     g_slist_free(list);
74 }
75
76 static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
77 {
78     GSList *it;
79     for (it = list; it; it = g_slist_next(it)) {
80         ObtLinkBaseEntry *e = it->data;
81         if (strcmp(obt_link_source_file(e->link), full_path) == 0)
82             break;
83     }
84     return it;
85 }
86
87 /*! Finds the first entry in the list with a priority number >= @priority. */
88 static GSList* find_base_entry_priority(GSList *list, gint priority)
89 {
90     GSList *it;
91     for (it = list; it; it = g_slist_next(it)) {
92         ObtLinkBaseEntry *e = it->data;
93         if (e->priority >= priority)
94             break;
95     }
96     return it;
97 }
98
99 /*! Called when a change happens in the filesystem. */
100 static void update(ObtWatch *w, const gchar *base_path,
101                    const gchar *sub_path,
102                    const gchar *full_path,
103                    ObtWatchNotifyType type,
104                    gpointer data)
105 {
106     ObtLinkBase *self = data;
107     gchar *id;
108     GSList *list, *it;
109     gint *priority;
110     gboolean add = FALSE;
111
112     if (!g_str_has_suffix(sub_path, ".desktop"))
113         return; /* ignore non-.desktop files */
114
115     id = obt_link_id_from_ddfile(sub_path);
116     list = g_hash_table_lookup(self->base, id);
117
118     switch (type) {
119     case OBT_WATCH_SELF_REMOVED:
120         break;
121     case OBT_WATCH_REMOVED:
122         it = find_base_entry_path(list, full_path);
123         if (it) {
124             /* it may be false if the link was skipped during the add because
125                it did not want to be displayed */
126             list = g_slist_delete_link(list, it);
127             base_entry_free(it->data);
128
129             /* this will free 'id' */
130             g_hash_table_insert(self->base, id, list);
131             id = NULL;
132         }
133         break;
134     case OBT_WATCH_MODIFIED:
135         it = find_base_entry_path(list, full_path);
136         if (it) {
137             /* it may be false if the link was skipped during the add because
138                it did not want to be displayed */
139             list = g_slist_delete_link(list, it);
140             base_entry_free(it->data);
141             /* this will put the modified list into the hash table */
142             add = TRUE;
143         }
144         break;
145     case OBT_WATCH_ADDED:
146         priority = g_hash_table_lookup(self->path_to_priority, base_path);
147         add = TRUE;
148
149         /* find the first position in the list with a higher priority value */
150         if ((it = find_base_entry_priority(list, *priority))) {
151             const ObtLinkBaseEntry *e = it->data;
152             if (e->priority == *priority) {
153                 /* already exists */
154                 add = FALSE;
155             }
156         }
157         break;
158     }
159
160     if (add) {
161         ObtLink *link;
162
163         link = obt_link_from_ddfile(full_path, self->paths,
164                                     self->language, self->country,
165                                     self->modifier);
166         if (link) {
167             if (!obt_link_display(link, self->environments)) {
168                 obt_link_unref(link);
169             }
170             else {
171                 ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
172                 e->priority = *priority;
173                 e->link = link;
174                 list = g_slist_insert_before(list, it, e);
175
176                 /* this will free 'id' */
177                 g_hash_table_insert(self->base, id, list);
178                 id = NULL;
179             }
180         }
181     }
182
183     g_free(id);
184
185     if (self->update_func)
186         self->update_func(self, self->update_data);
187 }
188
189 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
190                               guint environments)
191 {
192     ObtLinkBase *self;
193     GSList *it;
194     gint priority;
195     gint i;
196     
197     self = g_slice_new0(ObtLinkBase);
198     self->environments = environments;
199     self->watch = obt_watch_new();
200     self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
201                                     (GDestroyNotify)base_entry_list_free);
202     self->path_to_priority = g_hash_table_new_full(g_str_hash, g_str_equal,
203                                                 g_free, g_free);
204     self->paths = paths;
205     obt_paths_ref(paths);
206
207     /* parse the locale string to determine the language, country, and
208        modifier settings */
209     for (i = 0; ; ++i)
210         if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
211             locale[i] == '@')
212         {
213             self->language = g_strndup(locale, i);
214             break;
215         }
216         else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
217                  ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
218             break;
219     if (self->language && locale[i] == '_') {
220         locale += i+1;
221         for (i = 0; ; ++i)
222             if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
223                 self->country = g_strndup(locale, i);
224                 break;
225             }
226             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
227                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
228                 break;
229     }
230     if (self->country && locale[i] == '.')
231         for (; ; ++i)
232             if (!locale[i] || locale[i] == '@')
233                 break;
234             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
235                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
236                 break;
237     if (self->country && locale[i] == '@') {
238         locale += i+1;
239         for (i = 0; ; ++i)
240             if (!locale[i]) {
241                 self->modifier = g_strndup(locale, i);
242                 break;
243             }
244             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
245                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
246                 break;
247     }
248
249     /* run through each directory, foo, in the XDG_DATA_DIRS, and add
250        foo/applications to the list of directories to watch here, with
251        increasing priority (decreasing importance). */
252     priority = 0;
253     for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
254         if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
255             gchar *base_path;
256             gint *pri;
257
258             base_path = g_build_filename(it->data, "applications", NULL);
259
260             /* add to the hash table before adding the watch. the new watch
261                will be calling our update handler, immediately with any
262                files present in the directory */
263             pri = g_new(gint, 1);
264             *pri = priority;
265             g_hash_table_insert(self->path_to_priority,
266                                 g_strdup(base_path), pri);
267
268             obt_watch_add(self->watch, base_path, FALSE, update, self);
269
270             ++priority;
271         }
272     }
273     return self;
274 }
275
276 void obt_linkbase_ref(ObtLinkBase *self)
277 {
278     ++self->ref;
279 }
280
281 void obt_linkbase_unref(ObtLinkBase *self)
282 {
283     if (--self->ref < 1) {
284         obt_watch_unref(self->watch);
285         g_hash_table_unref(self->path_to_priority);
286         g_hash_table_unref(self->base);
287         obt_paths_unref(self->paths);
288         g_slice_free(ObtLinkBase, self);
289     }
290 }
291
292 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
293                                   gpointer data)
294 {
295     lb->update_func = func;
296     lb->update_data = data;
297 }