]> icculus.org git repositories - dana/openbox.git/blob - openbox/apps_menu.c
Show all the .desktop links available in the system in the "apps-menu" menu.
[dana/openbox.git] / openbox / apps_menu.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    apps_menu.c for the Openbox window manager
4    Copyright (c) 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 "openbox.h"
20 #include "menu.h"
21 #include "menuframe.h"
22 #include "screen.h"
23 #include "apps_menu.h"
24 #include "config.h"
25 #include "gettext.h"
26 #include "obt/bsearch.h"
27 #include "obt/linkbase.h"
28 #include "obt/link.h"
29 #include "obt/paths.h"
30
31 #include <glib.h>
32
33 #define MENU_NAME "apps-menu"
34
35 typedef struct _ObAppsMenuCategory ObAppsMenuCategory;
36
37 struct _ObAppsMenuCategory {
38     /*! The quark for the internal name of the category, eg. AudioVideo. These
39       come from the XDG menu specification. */
40     GQuark q;
41     /*! The user-visible (translated) name for the category. */
42     const gchar *friendly;
43     /*! The string identifier of the menu */
44     gchar *menu_name;
45     /*! The submenu for this category.  Its label will be "apps-menu-<category>
46       where <category> is the internal name of the category referred to by q.
47     */
48     ObMenu *menu;
49     /*! The menu entry in the apps menu for this. */
50     ObMenuEntry *entry;
51     /*! */
52     GPtrArray *links;
53 };
54
55 static ObMenu *apps_menu;
56 static ObtLinkBase *linkbase;
57 static gboolean dirty;
58
59 static ObAppsMenuCategory *categories;
60 /*! An array of pointers to the categories array, sorted by their friendly
61   names. */
62 static ObAppsMenuCategory **sorted_categories;
63 static guint n_categories;
64
65 static void self_destroy(ObMenu *menu, gpointer data)
66 {
67     menu_clear_entries(menu);
68 }
69
70 static gboolean self_update(ObMenuFrame *frame, gpointer data)
71 {
72     guint i;
73     gboolean sort;
74
75     if (!dirty) return TRUE;  /* nothing has changed to be updated */
76
77     sort = FALSE;
78     for (i = 0; i < n_categories; ++i) {
79         ObAppsMenuCategory *cat = &categories[i];
80         GList *it, *entries;
81         guint j;
82         gboolean sort_submenu;
83
84         sort_submenu = FALSE; /* sort the category's submenu? */
85
86         /* add/remove categories from the main apps menu if they were/are
87            empty. */
88         if (cat->links->len == 0) {
89             if (cat->entry) {
90                 menu_entry_remove(cat->entry);
91                 cat->entry = NULL;
92             }
93         }
94         else if (!cat->entry) {
95             cat->entry = menu_add_submenu(apps_menu, 0, cat->menu_name);
96             sort = TRUE;
97         }
98
99         /* add/remove links from this category. */
100         entries = g_list_copy(cat->menu->entries);
101         it = entries; /* iterates through the entries list */
102         j = 0; /* iterates through the valid ObtLinks list */
103         while (j < cat->links->len) {
104             ObtLink *m, *l;
105             int r;
106
107             /* get the next link from the menu */
108             if (it)
109                 m = ((ObMenuEntry*)it->data)->data.normal.data;
110             else
111                 m = NULL;
112             /* get the next link from our list */
113             l = g_ptr_array_index(cat->links, j);
114
115             if (it && m == NULL) {
116                 /* the entry was removed from the linkbase and nulled here, so
117                    drop it from the menu */
118                 menu_entry_remove(it->data);
119                 r = 0;
120             }
121             else {
122                 if (it)
123                     r = obt_link_cmp_by_name(&m, &l);
124                 else
125                     /* we reached the end of the menu, but there's more in
126                        the list, so just add it */
127                     r = 1;
128
129                 if (r > 0) {
130                     /* the menu progressed faster than the list, the list has
131                        something it doesn't, so add that */
132                     ObMenuEntry *e;
133                     e = menu_add_normal(
134                         cat->menu, 0, obt_link_name(l), NULL, FALSE);
135                     e->data.normal.data = l; /* save the link in the entry */
136                     sort_submenu = TRUE;
137                 }
138                 else
139                     /* if r < 0 then we didn't null something that was removed
140                        from the list */
141                     g_assert(r == 0);
142             }
143
144             if (r == 0 && it)
145                 /* if we added something to the menu, then don't move
146                    forward in the menu, as we were inserting something before
147                    this entry */
148                 it = g_list_next(it);
149             ++j;
150         }
151
152         if (sort_submenu)
153             menu_sort_entries(cat->menu);
154     }
155
156     if (sort) {
157         menu_sort_entries(apps_menu);
158     }
159
160     dirty = FALSE;
161     return TRUE; /* always show the menu */
162 }
163
164 static gboolean add_link_to_category(ObtLink *link, ObAppsMenuCategory *cat)
165 {
166     guint i;
167     gboolean add;
168
169     /* check for link in our existing list */
170     add = TRUE;
171     for (i = 0; i < cat->links->len; ++i) {
172         ObtLink *l = g_ptr_array_index(cat->links, i);
173         if (l == link) {
174             add = FALSE;
175             break;
176         }
177     }
178
179     if (add) { /* wasn't found in our list already */
180         g_ptr_array_add(cat->links, link);
181         obt_link_ref(link);
182
183         dirty = TRUE; /* never set dirty to FALSE here.. */
184     }
185     return add;
186 }
187
188 static gboolean remove_link_from_category(ObtLink *link,
189                                           ObAppsMenuCategory *cat)
190 {
191     gboolean rm;
192
193     rm = g_ptr_array_remove(cat->links, link);
194
195     if (rm) {
196         GList *it;
197
198         dirty = TRUE; /* never set dirty to FALSE here.. */
199
200         /* set the data inside any visible menus for this link to NULL since it
201            stops existing now */
202         for (it = menu_frame_visible; it; it = g_list_next(it)) {
203             ObMenuFrame *frame = it->data;
204             if (frame->menu == cat->menu) {
205                 GList *eit;
206
207                 for (eit = frame->entries; eit; eit = g_list_next(eit)) {
208                     ObMenuEntryFrame *e = it->data;
209                     /* our submenus only contain normal entries */
210                     if (e->entry->data.normal.data == link) {
211                         /* this menu entry points to this link, but the
212                            link is going away, so point it at NULL. */
213                         e->entry->data.normal.data = NULL;
214                         break;
215                     }
216                 }
217                 break;
218             }
219         }
220     }
221     return rm;
222 }
223
224 static void linkbase_update(ObtLinkBase *lb, ObtLinkBaseUpdateType type,
225                             ObtLink *link, gpointer data)
226 {
227     const GQuark *cats;
228     gulong n_cats, i;
229
230     if (obt_link_type(link) != OBT_LINK_TYPE_APPLICATION)
231         return;  /* not in our menu */
232
233     /* For each category in the link, search our list of categories and
234        if we are showing that category in the menu, add the link to it (or
235        remove the link from it). */
236     cats = obt_link_app_categories(link, &n_cats);
237     for (i = 0; i < n_cats; ++i) {
238         BSEARCH_SETUP();
239
240 #define CAT_TO_INT(c) (c.q)
241         BSEARCH_CMP(ObAppsMenuCategory, categories, 0, n_categories,
242                     cats[i], CAT_TO_INT);
243 #undef  CAT_TO_INT
244
245         if (BSEARCH_FOUND()) {
246             if (type == OBT_LINKBASE_ADDED) {
247                 add_link_to_category(link, &categories[BSEARCH_AT()]);
248                 g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
249             }
250             else
251                 remove_link_from_category(link, &categories[BSEARCH_AT()]);
252         }
253     }
254 }
255
256 static int cat_cmp(const void *a, const void *b)
257 {
258     const ObAppsMenuCategory *ca = a, *cb = b;
259     return ca->q - cb->q;
260 }
261
262 static int cat_friendly_cmp(const void *a, const void *b)
263 {
264     ObAppsMenuCategory *const *ca = a, *const *cb = b;
265     return strcmp((*ca)->friendly, (*cb)->friendly);
266 }
267
268 void apps_menu_startup(gboolean reconfig)
269 {
270     if (!reconfig) {
271         ObtPaths *paths;
272         guint i;
273
274         paths = obt_paths_new();
275         /* XXX allow more environments, like GNOME or KDE, to be included */
276         linkbase = obt_linkbase_new(paths, ob_locale_msg,
277                                     OBT_LINK_ENV_OPENBOX);
278         obt_paths_unref(paths);
279         obt_linkbase_set_update_func(linkbase, linkbase_update, NULL);
280
281         dirty = FALSE;
282
283         /* From http://standards.freedesktop.org/menu-spec/latest/apa.html */
284         {
285             struct {
286                 const gchar *name;
287                 const gchar *friendly;
288             } const cats[] = {
289                 { "AudioVideo", "Sound & Video" },
290                 { "Development", "Programming" },
291                 { "Education", "Education" },
292                 { "Game", "Games" },
293                 { "Graphics", "Graphics" },
294                 { "Network", "Internet" },
295                 { "Office", "Office" },
296                 { "Settings", "Settings" },
297                 { "System", "System" },
298                 { "Utility", "Accessories" },
299                 { NULL, NULL }
300             };
301             guint i;
302
303             for (i = 0; cats[i].name; ++i); /* count them */
304             n_categories = i;
305
306             categories = g_new0(ObAppsMenuCategory, n_categories);
307             sorted_categories = g_new(ObAppsMenuCategory*, n_categories);
308
309             for (i = 0; cats[i].name; ++i) {
310                 categories[i].q = g_quark_from_static_string(cats[i].name);
311                 categories[i].friendly = _(cats[i].friendly);
312                 categories[i].menu_name = g_strdup_printf(
313                     "%s-%s", MENU_NAME, cats[i].name);
314                 categories[i].menu = menu_new(categories[i].menu_name,
315                                               categories[i].friendly, FALSE,
316                                               &categories[i]);
317                 categories[i].links = g_ptr_array_new_with_free_func(
318                     (GDestroyNotify)obt_link_unref);
319             }
320         }
321         /* Sort the categories by their quark values so we can binary search */
322         qsort(categories, n_categories, sizeof(ObAppsMenuCategory), cat_cmp);
323
324         for (i = 0; i < n_categories; ++i)
325             sorted_categories[i] = &categories[i];
326         qsort(sorted_categories, n_categories, sizeof(void*),
327               cat_friendly_cmp);
328
329         /* add all the existing links */
330         for (i = 0; i < n_categories; ++i) {
331             GSList *links, *it;
332
333             links = obt_linkbase_category(linkbase, categories[i].q);
334             for (it = links; it; it = g_slist_next(it))
335                 add_link_to_category(it->data, &categories[i]);
336
337             g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
338         }
339     }
340
341     apps_menu = menu_new(MENU_NAME, _("Applications"), FALSE, NULL);
342     menu_set_update_func(apps_menu, self_update);
343     menu_set_destroy_func(apps_menu, self_destroy);
344 }
345
346 void apps_menu_shutdown(gboolean reconfig)
347 {
348     if (!reconfig) {
349         guint i;
350
351         obt_linkbase_unref(linkbase);
352         linkbase = NULL;
353
354         for (i = 0; i < n_categories; ++i) {
355
356             menu_free(categories[i].menu);
357             g_free(categories[i].menu_name);
358             g_ptr_array_unref(categories[i].links);
359         }
360         g_free(categories);
361         categories = NULL;
362         g_free(sorted_categories);
363         sorted_categories = NULL;
364     }
365
366     /* freed by the hash table */
367 }