]> icculus.org git repositories - dana/openbox.git/blob - obt/link.c
Store all links in the linkbase grouped by their main category.
[dana/openbox.git] / obt / link.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    obt/link.c for the Openbox window manager
4    Copyright (c) 2009-2011   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/link.h"
20 #include "obt/bsearch.h"
21 #include "obt/ddparse.h"
22 #include "obt/paths.h"
23 #include "obt/util.h"
24 #include <glib.h>
25
26 #ifdef HAVE_STDLIB_H
27 #include <stdlib.h>
28 #endif
29
30 struct _ObtLink {
31     guint ref;
32
33     gchar *path; /*!< The path to the file where the link came from */
34
35     ObtLinkType type;
36     gchar *name; /*!< Specific name for the object (eg Firefox) */
37     gboolean display; /*<! When false, do not display this link in menus or
38                            launchers, etc */
39     gboolean deleted; /*<! When true, the Link could exist but is deleted
40                            for the current user */
41     gchar *generic; /*!< Generic name for the object (eg Web Browser) */
42     gchar *comment; /*!< Comment/description to display for the object */
43     gchar *icon; /*!< Name/path for an icon for the object */
44     guint env_required; /*!< The environments that must be present to use this
45                           link. */
46     guint env_restricted; /*!< The environments that must _not_ be present to
47                             use this link. */
48
49     union _ObtLinkData {
50         struct _ObtLinkApp {
51             gchar *exec; /*!< Executable to run for the app */
52             gchar *wdir; /*!< Working dir to run the app in */
53             gboolean term; /*!< Run the app in a terminal or not */
54             ObtLinkAppOpen open;
55
56             gchar **mime; /*!< Mime types the app can open */
57
58             GQuark main_category; /*!< The first main category listed */
59             GQuark *categories; /*!< Array of quarks representing the
60                                   application's categories */
61             gulong  n_categories; /*!< Number of categories for the app */
62
63             ObtLinkAppStartup startup;
64             gchar *startup_wmclass;
65         } app;
66         struct _ObtLinkLink {
67             gchar *addr;
68         } url;
69         struct _ObtLinkDir {
70         } dir;
71     } d;
72 };
73
74 #define NUM_MAIN_CATEGORIES 10
75 static GQuark MAIN_CATEGORIES[NUM_MAIN_CATEGORIES];
76 static gboolean setup = FALSE;
77
78 static void startup()
79 {
80     if (setup) return;
81
82     setup = TRUE;
83
84     /* From http://standards.freedesktop.org/menu-spec/latest/apa.html */
85     MAIN_CATEGORIES[0] = g_quark_from_static_string("AudioVideo");
86     MAIN_CATEGORIES[1] = g_quark_from_static_string("Development");
87     MAIN_CATEGORIES[2] = g_quark_from_static_string("Education");
88     MAIN_CATEGORIES[3] = g_quark_from_static_string("Game");
89     MAIN_CATEGORIES[4] = g_quark_from_static_string("Graphics");
90     MAIN_CATEGORIES[5] = g_quark_from_static_string("Network");
91     MAIN_CATEGORIES[6] = g_quark_from_static_string("Office");
92     MAIN_CATEGORIES[7] = g_quark_from_static_string("Settings");
93     MAIN_CATEGORIES[8] = g_quark_from_static_string("System");
94     MAIN_CATEGORIES[9] = g_quark_from_static_string("Utility");
95     qsort(MAIN_CATEGORIES, NUM_MAIN_CATEGORIES, sizeof(guint), obt_guint_cmp);
96 }
97
98 ObtLink* obt_link_from_ddfile(const gchar *path, ObtPaths *p,
99                               const gchar *language,
100                               const gchar *country,
101                               const gchar *modifier)
102 {
103     ObtLink *link;
104     GHashTable *groups, *keys;
105     ObtDDParseGroup *g;
106     ObtDDParseValue *v;
107
108     startup();
109
110     /* parse the file, and get a hash table of the groups */
111     groups = obt_ddparse_file(path, language, country, modifier);
112     if (!groups) return NULL; /* parsing failed */
113
114     /* grab the Desktop Entry group */
115     g = g_hash_table_lookup(groups, "Desktop Entry");
116     g_assert(g != NULL);
117     /* grab the keys that appeared in the Desktop Entry group */
118     keys = obt_ddparse_group_keys(g);
119
120     /* build the ObtLink (we steal all strings from the parser) */
121     link = g_slice_new0(ObtLink);
122     link->ref = 1;
123     link->path = g_strdup(path);
124     link->display = TRUE;
125
126     v = g_hash_table_lookup(keys, "Type");
127     g_assert(v);
128     link->type = v->value.enumerable;
129
130     if ((v = g_hash_table_lookup(keys, "Hidden")))
131         link->deleted = v->value.boolean;
132
133     if ((v = g_hash_table_lookup(keys, "NoDisplay")))
134         link->display = !v->value.boolean;
135
136     if ((v = g_hash_table_lookup(keys, "GenericName")))
137         link->generic = v->value.string, v->value.string = NULL;
138
139     if ((v = g_hash_table_lookup(keys, "Comment")))
140         link->comment = v->value.string, v->value.string = NULL;
141
142     if ((v = g_hash_table_lookup(keys, "Icon")))
143         link->icon = v->value.string, v->value.string = NULL;
144
145     if ((v = g_hash_table_lookup(keys, "OnlyShowIn")))
146         link->env_required = v->value.environments;
147     else
148         link->env_required = 0;
149
150     if ((v = g_hash_table_lookup(keys, "NotShowIn")))
151         link->env_restricted = v->value.environments;
152     else
153         link->env_restricted = 0;
154
155     /* type-specific keys */
156
157     if (link->type == OBT_LINK_TYPE_APPLICATION) {
158         gchar *c;
159         gboolean percent;
160
161         v = g_hash_table_lookup(keys, "Exec");
162         g_assert(v);
163         link->d.app.exec = v->value.string;
164         v->value.string = NULL;
165
166         /* parse link->d.app.exec to determine link->d.app.open */
167         percent = FALSE;
168         for (c = link->d.app.exec; *c; ++c) {
169             if (percent) {
170                 switch (*c) {
171                 case 'f': link->d.app.open = OBT_LINK_APP_SINGLE_LOCAL; break;
172                 case 'F': link->d.app.open = OBT_LINK_APP_MULTI_LOCAL; break;
173                 case 'u': link->d.app.open = OBT_LINK_APP_SINGLE_URL; break;
174                 case 'U': link->d.app.open = OBT_LINK_APP_MULTI_URL; break;
175                 default: percent = FALSE;
176                 }
177                 if (percent) break; /* found f/F/u/U */
178             }
179             else if (*c == '%') percent = TRUE;
180         }
181
182         if ((v = g_hash_table_lookup(keys, "TryExec"))) {
183             /* XXX spawn a thread to check TryExec? */
184             link->display = link->display &&
185                 obt_paths_try_exec(p, v->value.string);
186         }
187
188         if ((v = g_hash_table_lookup(keys, "Path"))) {
189             /* steal the string */
190             link->d.app.wdir = v->value.string;
191             v->value.string = NULL;
192         }
193
194         if ((v = g_hash_table_lookup(keys, "Terminal")))
195             link->d.app.term = v->value.boolean;
196
197         if ((v = g_hash_table_lookup(keys, "StartupNotify")))
198             link->d.app.startup = v->value.boolean ?
199                 OBT_LINK_APP_STARTUP_PROTOCOL_SUPPORT :
200                 OBT_LINK_APP_STARTUP_NO_SUPPORT;
201         else {
202             link->d.app.startup = OBT_LINK_APP_STARTUP_LEGACY_SUPPORT;
203             if ((v = g_hash_table_lookup(keys, "StartupWMClass"))) {
204                 /* steal the string */
205                 link->d.app.startup_wmclass = v->value.string;
206                 v->value.string = NULL;
207             }
208         }
209
210         if ((v = g_hash_table_lookup(keys, "Categories"))) {
211             gulong i;
212             gchar *end;
213             gboolean found_main;
214             GQuark cat;
215
216             link->d.app.categories = g_new(GQuark, v->value.strings.n);
217             link->d.app.n_categories = v->value.strings.n;
218
219             found_main = FALSE;
220             for (i = 0; i < v->value.strings.n; ++i) {
221                 cat = link->d.app.categories[i] =
222                     g_quark_from_string(v->value.strings.a[i]);
223
224                 if (!found_main) {
225                     BSEARCH_SETUP(guint);
226                     BSEARCH(guint, MAIN_CATEGORIES, 0, NUM_MAIN_CATEGORIES,
227                             cat);
228                     if (BSEARCH_FOUND()) {
229                         found_main = TRUE;
230                         link->d.app.main_category = cat;
231                     }
232                 }
233
234                 c = end = end+1; /* next */
235             }
236         }
237
238         if ((v = g_hash_table_lookup(keys, "MimeType"))) {
239             /* steal the string array */
240             link->d.app.mime = v->value.strings.a;
241             v->value.strings.a = NULL;
242             v->value.strings.n = 0;
243         }
244     }
245     else if (link->type == OBT_LINK_TYPE_URL) {
246         v = g_hash_table_lookup(keys, "URL");
247         g_assert(v);
248         link->d.url.addr = v->value.string;
249         v->value.string = NULL;
250     }
251
252     /* destroy the parsing info */
253     g_hash_table_destroy(groups);
254
255     return link;
256 }
257
258 void obt_link_ref(ObtLink *dd)
259 {
260     ++dd->ref;
261 }
262
263 void obt_link_unref(ObtLink *dd)
264 {
265     if (--dd->ref < 1) {
266         g_free(dd->name);
267         g_free(dd->generic);
268         g_free(dd->comment);
269         g_free(dd->icon);
270         if (dd->type == OBT_LINK_TYPE_APPLICATION) {
271             g_free(dd->d.app.exec);
272             g_free(dd->d.app.wdir);
273             g_strfreev(dd->d.app.mime);
274             g_free(dd->d.app.categories);
275             g_free(dd->d.app.startup_wmclass);
276         }
277         else if (dd->type == OBT_LINK_TYPE_URL)
278             g_free(dd->d.url.addr);
279         g_slice_free(ObtLink, dd);
280     }
281 }
282
283 ObtLinkType obt_link_type (ObtLink *e)
284 {
285     g_return_val_if_fail(e != NULL, 0);
286
287     return e->type;
288 }
289
290 GQuark obt_link_app_main_category          (ObtLink *e)
291 {
292     g_return_val_if_fail(e != NULL, 0);
293     g_return_val_if_fail(e->type == OBT_LINK_TYPE_APPLICATION, 0);
294
295     return e->d.app.main_category;
296 }
297
298 const GQuark* obt_link_app_categories(ObtLink *e, gulong *n)
299 {
300     g_return_val_if_fail(e != NULL, NULL);
301     g_return_val_if_fail(e->type == OBT_LINK_TYPE_APPLICATION, NULL);
302     g_return_val_if_fail(n != NULL, NULL);
303
304     *n = e->d.app.n_categories;
305     return e->d.app.categories;
306 }
307
308 const gchar *obt_link_source_file(ObtLink *e)
309 {
310     return e->path;
311 }
312
313 gchar* obt_link_id_from_ddfile(const gchar *filename)
314 {
315     return obt_ddparse_file_to_id(filename);
316 }
317
318 gboolean obt_link_display(ObtLink *e, const guint environments)
319 {
320     return
321         /* display if the link wants to be displayed and TryExec passed */
322         e->display &&
323         /* display if no environment is required, or we match at least one of
324            the requirements */
325         (!e->env_required || (e->env_required & environments)) &&
326         /* display if no environment is restricted, or we do not match any of
327            the restrictions */
328         (!e->env_restricted || !(e->env_restricted & environments));
329 }