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