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