1 /*
2 * menu-cache.c
3 *
4 * Copyright 2008 PCMan <pcman.tw@gmail.com>
5 * Copyright 2009 Jürgen Hötzel <juergen@archlinux.org>
6 * Copyright 2012-2017 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include "version.h"
28
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <time.h>
36 #include <sys/socket.h>
37 #include <sys/un.h>
38 #include <sys/fcntl.h>
39 #include <errno.h>
40 #include <sys/wait.h>
41
42 #include <gio/gio.h>
43
44 #include "menu-cache.h"
45
46 #ifdef G_ENABLE_DEBUG
47 #define DEBUG(...) g_debug(__VA_ARGS__)
48 #else
49 #define DEBUG(...)
50 #endif
51
52 #if GLIB_CHECK_VERSION(2, 32, 0)
53 static GRecMutex _cache_lock;
54 # define MENU_CACHE_LOCK g_rec_mutex_lock(&_cache_lock)
55 # define MENU_CACHE_UNLOCK g_rec_mutex_unlock(&_cache_lock)
56 /* for sync lookup */
57 static GMutex sync_run_mutex;
58 static GCond sync_run_cond;
59 #define SET_CACHE_READY(_cache_) do { \
60 g_mutex_lock(&sync_run_mutex); \
61 _cache_->ready = TRUE; \
62 g_cond_broadcast(&sync_run_cond); \
63 g_mutex_unlock(&sync_run_mutex); } while(0)
64 #else
65 /* before 2.32 GLib had another entity for statically allocated mutexes */
66 static GStaticRecMutex _cache_lock = G_STATIC_REC_MUTEX_INIT;
67 # define MENU_CACHE_LOCK g_static_rec_mutex_lock(&_cache_lock)
68 # define MENU_CACHE_UNLOCK g_static_rec_mutex_unlock(&_cache_lock)
69 /* for sync lookup */
70 static GMutex *sync_run_mutex = NULL;
71 static GCond *sync_run_cond = NULL;
72 #define SET_CACHE_READY(_cache_) do { \
73 g_mutex_lock(sync_run_mutex); \
74 _cache_->ready = TRUE; \
75 if(sync_run_cond) g_cond_broadcast(sync_run_cond); \
76 g_mutex_unlock(sync_run_mutex); } while(0)
77 #endif
78
79 typedef struct
80 {
81 char *dir;
82 gint n_ref;
83 } MenuCacheFileDir;
84
85 struct _MenuCacheItem
86 {
87 guint n_ref;
88 MenuCacheType type;
89 char* id;
90 char* name;
91 char* comment;
92 char* icon;
93 MenuCacheFileDir* file_dir;
94 char* file_name;
95 MenuCacheDir* parent;
96 };
97
98 struct _MenuCacheDir
99 {
100 MenuCacheItem item;
101 GSList* children;
102 guint32 flags;
103 };
104
105 struct _MenuCacheApp
106 {
107 MenuCacheItem item;
108 char* generic_name;
109 char* exec;
110 char* working_dir;
111 guint32 show_in_flags;
112 guint32 flags;
113 char* try_exec;
114 const char **categories;
115 char* keywords;
116 };
117
118 struct _MenuCache
119 {
120 guint n_ref;
121 MenuCacheDir* root_dir;
122 char* menu_name;
123 char* reg; /* includes md5 sum */
124 char* md5; /* link inside of reg */
125 char* cache_file;
126 char** known_des;
127 GSList* notifiers;
128 GThread* thr;
129 GCancellable* cancellable;
130 guint version;
131 guint reload_id;
132 gboolean ready : 1; /* used for sync access */
133 };
134
135 static int server_fd = -1;
136 G_LOCK_DEFINE(connect); /* for server_fd */
137
138 static GHashTable* hash = NULL;
139
140 /* Don't call this API directly. Use menu_cache_lookup instead. */
141 static MenuCache* menu_cache_new( const char* cache_file );
142
143 static gboolean connect_server(GCancellable* cancellable);
144 static gboolean register_menu_to_server(MenuCache* cache);
145 static void unregister_menu_from_server( MenuCache* cache );
146
147 /* keep them for backward compatibility */
148 #ifdef G_DISABLE_DEPRECATED
149 MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache );
150 MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item );
151 MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path );
152 GSList* menu_cache_dir_get_children( MenuCacheDir* dir );
153 #endif
154
menu_cache_init(int flags)155 void menu_cache_init(int flags)
156 {
157 #if !GLIB_CHECK_VERSION(2, 36, 0)
158 g_type_init();
159 #endif
160 }
161
162 static MenuCacheItem* read_item(GDataInputStream* f, MenuCache* cache,
163 MenuCacheFileDir** all_used_files, int n_all_used_files);
164
165 /* functions read_dir(), read_app(), and read_item() should be called for
166 items that aren't accessible yet, therefore no lock is required */
read_dir(GDataInputStream * f,MenuCacheDir * dir,MenuCache * cache,MenuCacheFileDir ** all_used_files,int n_all_used_files)167 static void read_dir(GDataInputStream* f, MenuCacheDir* dir, MenuCache* cache,
168 MenuCacheFileDir** all_used_files, int n_all_used_files)
169 {
170 MenuCacheItem* item;
171 char *line;
172 gsize len;
173
174 /* nodisplay flag */
175 if (cache->version >= 2)
176 {
177 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
178 if (G_UNLIKELY(line == NULL))
179 return;
180 dir->flags = (guint32)atoi(line);
181 g_free(line);
182 }
183
184 /* load child items in the dir */
185 while( (item = read_item( f, cache, all_used_files, n_all_used_files )) )
186 {
187 /* menu_cache_ref shouldn't be called here for dir.
188 * Otherwise, circular reference will happen. */
189 item->parent = dir;
190 dir->children = g_slist_prepend( dir->children, item );
191 }
192
193 dir->children = g_slist_reverse( dir->children );
194
195 /* set flag by children if working with old cache generator */
196 if (cache->version == 1)
197 {
198 if (dir->children == NULL)
199 dir->flags = FLAG_IS_NODISPLAY;
200 else if ((line = menu_cache_item_get_file_path(MENU_CACHE_ITEM(dir))) != NULL)
201 {
202 GKeyFile *kf = g_key_file_new();
203 if (g_key_file_load_from_file(kf, line, G_KEY_FILE_NONE, NULL) &&
204 g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP,
205 G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL))
206 dir->flags = FLAG_IS_NODISPLAY;
207 g_key_file_free(kf);
208 g_free(line);
209 }
210 }
211 }
212
_unescape_lf(char * str)213 static char *_unescape_lf(char *str)
214 {
215 char *c, *p = str;
216 gsize len = 0;
217
218 while ((c = strchr(p, '\\')) != NULL)
219 {
220 if (p != &str[len])
221 memmove(&str[len], p, c - p);
222 len += (c - p);
223 if (c[1] == 'n')
224 {
225 str[len++] = '\n';
226 c++;
227 }
228 else if (c != &str[len])
229 str[len++] = *c;
230 p = &c[1];
231 }
232 if (p != &str[len])
233 memmove(&str[len], p, strlen(p) + 1);
234 return str;
235 }
236
read_app(GDataInputStream * f,MenuCacheApp * app,MenuCache * cache)237 static void read_app(GDataInputStream* f, MenuCacheApp* app, MenuCache* cache)
238 {
239 char *line;
240 gsize len;
241 GString *str;
242
243 /* generic name */
244 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
245 if(G_UNLIKELY(line == NULL))
246 return;
247 if(G_LIKELY(len > 0))
248 app->generic_name = _unescape_lf(line);
249 else
250 g_free(line);
251
252 /* exec */
253 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
254 if(G_UNLIKELY(line == NULL))
255 return;
256 if(G_LIKELY(len > 0))
257 app->exec = _unescape_lf(line);
258 else
259 g_free(line);
260
261 /* terminal / startup notify */
262 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
263 if(G_UNLIKELY(line == NULL))
264 return;
265 app->flags = (guint32)atoi(line);
266 g_free(line);
267
268 /* ShowIn flags */
269 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
270 if(G_UNLIKELY(line == NULL))
271 return;
272 app->show_in_flags = (guint32)atol(line);
273 g_free(line);
274
275 if (cache->version < 2)
276 return;
277
278 /* TryExec */
279 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
280 if (G_UNLIKELY(line == NULL))
281 return;
282 if (G_LIKELY(len > 0))
283 app->try_exec = g_strchomp(line);
284 else
285 g_free(line);
286
287 /* Path */
288 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
289 if (G_UNLIKELY(line == NULL))
290 return;
291 if (G_LIKELY(len > 0))
292 app->working_dir = line;
293 else
294 g_free(line);
295
296 /* Categories */
297 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
298 if (G_UNLIKELY(line == NULL))
299 return;
300 if (G_LIKELY(len > 0))
301 {
302 const char **x;
303
304 /* split and intern all the strings so categories can be processed
305 later for search doing g_quark_try_string()+g_quark_to_string() */
306 app->categories = x = (const char **)g_strsplit(line, ";", 0);
307 while (*x != NULL)
308 {
309 char *cat = (char *)*x;
310 *x = g_intern_string(cat);
311 g_free(cat);
312 x++;
313 }
314 }
315 g_free(line);
316
317 /* Keywords */
318 str = g_string_new(MENU_CACHE_ITEM(app)->name);
319 if (G_LIKELY(app->exec != NULL))
320 {
321 char *sp = strchr(app->exec, ' ');
322 char *bn = strrchr(app->exec, G_DIR_SEPARATOR);
323
324 g_string_append_c(str, ',');
325 if (bn == NULL && sp == NULL)
326 g_string_append(str, app->exec);
327 else if (bn == NULL || (sp != NULL && sp < bn))
328 g_string_append_len(str, app->exec, sp - app->exec);
329 else if (sp == NULL)
330 g_string_append(str, &bn[1]);
331 else
332 g_string_append_len(str, &bn[1], sp - &bn[1]);
333 }
334 if (app->generic_name != NULL)
335 {
336 g_string_append_c(str, ',');
337 g_string_append(str, app->generic_name);
338 }
339 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
340 if (G_UNLIKELY(line == NULL))
341 return;
342 if (len > 0)
343 {
344 g_string_append_c(str, ',');
345 g_string_append(str, line);
346 }
347 app->keywords = g_utf8_casefold(str->str, str->len);
348 g_string_free(str, TRUE);
349 g_free(line);
350 }
351
read_item(GDataInputStream * f,MenuCache * cache,MenuCacheFileDir ** all_used_files,int n_all_used_files)352 static MenuCacheItem* read_item(GDataInputStream* f, MenuCache* cache,
353 MenuCacheFileDir** all_used_files, int n_all_used_files)
354 {
355 MenuCacheItem* item;
356 char *line;
357 gsize len;
358 gint idx;
359
360 /* desktop/menu id */
361 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
362 if(G_UNLIKELY(line == NULL))
363 return NULL;
364
365 if( G_LIKELY(len >= 1) )
366 {
367 if( line[0] == '+' ) /* menu dir */
368 {
369 item = (MenuCacheItem*)g_slice_new0( MenuCacheDir );
370 item->n_ref = 1;
371 item->type = MENU_CACHE_TYPE_DIR;
372 }
373 else if( line[0] == '-' ) /* menu item */
374 {
375 item = (MenuCacheItem*)g_slice_new0( MenuCacheApp );
376 item->n_ref = 1;
377 if( G_LIKELY( len > 1 ) ) /* application item */
378 item->type = MENU_CACHE_TYPE_APP;
379 else /* separator */
380 {
381 item->type = MENU_CACHE_TYPE_SEP;
382 return item;
383 }
384 }
385 else
386 return NULL;
387
388 item->id = g_strndup( line + 1, len - 1 );
389 g_free(line);
390 }
391 else
392 {
393 g_free(line);
394 return NULL;
395 }
396
397 /* name */
398 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
399 if(G_UNLIKELY(line == NULL))
400 goto _fail;
401 if(G_LIKELY(len > 0))
402 item->name = _unescape_lf(line);
403 else
404 g_free(line);
405
406 /* comment */
407 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
408 if(G_UNLIKELY(line == NULL))
409 goto _fail;
410 if(G_LIKELY(len > 0))
411 item->comment = _unescape_lf(line);
412 else
413 g_free(line);
414
415 /* icon */
416 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
417 if(G_UNLIKELY(line == NULL))
418 goto _fail;
419 if(G_LIKELY(len > 0))
420 item->icon = line;
421 else
422 g_free(line);
423
424 /* file dir/basename */
425
426 /* file name */
427 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
428 if(G_UNLIKELY(line == NULL))
429 goto _fail;
430 if(G_LIKELY(len > 0))
431 item->file_name = line;
432 else if( item->type == MENU_CACHE_TYPE_APP )
433 {
434 /* When file name is the same as desktop_id, which is
435 * quite common in desktop files, we use this trick to
436 * save memory usage. */
437 item->file_name = item->id;
438 g_free(line);
439 }
440 else
441 g_free(line);
442
443 /* desktop file dir */
444 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
445 if(G_UNLIKELY(line == NULL))
446 {
447 _fail:
448 g_free(item->id);
449 g_free(item->name);
450 g_free(item->comment);
451 g_free(item->icon);
452 if(item->file_name && item->file_name != item->id)
453 g_free(item->file_name);
454 if(item->type == MENU_CACHE_TYPE_DIR)
455 g_slice_free(MenuCacheDir, MENU_CACHE_DIR(item));
456 else
457 g_slice_free(MenuCacheApp, MENU_CACHE_APP(item));
458 return NULL;
459 }
460 idx = atoi( line );
461 g_free(line);
462 if( G_LIKELY( idx >=0 && idx < n_all_used_files ) )
463 {
464 item->file_dir = all_used_files[ idx ];
465 g_atomic_int_inc(&item->file_dir->n_ref);
466 }
467
468 if( item->type == MENU_CACHE_TYPE_DIR )
469 read_dir( f, MENU_CACHE_DIR(item), cache, all_used_files, n_all_used_files );
470 else if( item->type == MENU_CACHE_TYPE_APP )
471 read_app( f, MENU_CACHE_APP(item), cache );
472
473 return item;
474 }
475
menu_cache_file_dir_unref(MenuCacheFileDir * file_dir)476 static void menu_cache_file_dir_unref(MenuCacheFileDir *file_dir)
477 {
478 if (file_dir && g_atomic_int_dec_and_test(&file_dir->n_ref))
479 {
480 g_free(file_dir->dir);
481 g_free(file_dir);
482 }
483 }
484
read_all_used_files(GDataInputStream * f,MenuCache * cache,MenuCacheFileDir *** all_used_files)485 static gint read_all_used_files(GDataInputStream* f, MenuCache* cache,
486 MenuCacheFileDir*** all_used_files)
487 {
488 char *line;
489 gsize len;
490 int i, n;
491 MenuCacheFileDir** dirs;
492
493 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
494 if(G_UNLIKELY(line == NULL))
495 return -1;
496
497 n = atoi( line );
498 g_free(line);
499 if (G_UNLIKELY(n <= 0))
500 return n;
501
502 dirs = g_new0( MenuCacheFileDir *, n );
503
504 for( i = 0; i < n; ++i )
505 {
506 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
507 if(G_UNLIKELY(line == NULL))
508 {
509 while (i-- > 0)
510 menu_cache_file_dir_unref(dirs[i]);
511 g_free(dirs);
512 return -1;
513 }
514 dirs[i] = g_new(MenuCacheFileDir, 1);
515 dirs[i]->n_ref = 1;
516 dirs[i]->dir = line; /* don't include \n */
517 }
518 *all_used_files = dirs;
519 return n;
520 }
521
read_all_known_des(GDataInputStream * f,MenuCache * cache)522 static gboolean read_all_known_des(GDataInputStream* f, MenuCache* cache)
523 {
524 char *line;
525 gsize len;
526 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
527 if(G_UNLIKELY(line == NULL))
528 return FALSE;
529 cache->known_des = g_strsplit_set( line, ";\n", 0 );
530 g_free(line);
531 return TRUE;
532 }
533
menu_cache_new(const char * cache_file)534 static MenuCache* menu_cache_new( const char* cache_file )
535 {
536 MenuCache* cache;
537 cache = g_slice_new0( MenuCache );
538 cache->cache_file = g_strdup( cache_file );
539 cache->n_ref = 1;
540 return cache;
541 }
542
543 /**
544 * menu_cache_ref
545 * @cache: a menu cache descriptor
546 *
547 * Increases reference counter on @cache.
548 *
549 * Returns: @cache.
550 *
551 * Since: 0.1.0
552 */
menu_cache_ref(MenuCache * cache)553 MenuCache* menu_cache_ref(MenuCache* cache)
554 {
555 g_atomic_int_inc( &cache->n_ref );
556 return cache;
557 }
558
559 /**
560 * menu_cache_unref
561 * @cache: a menu cache descriptor
562 *
563 * Descreases reference counter on @cache. When reference count becomes 0
564 * then resources associated with @cache will be freed.
565 *
566 * Since: 0.1.0
567 */
menu_cache_unref(MenuCache * cache)568 void menu_cache_unref(MenuCache* cache)
569 {
570 /* DEBUG("cache_unref: %d", cache->n_ref); */
571 /* we need a lock here unfortunately because item in hash isn't protected
572 by reference therefore another thread may get access to it right now */
573 MENU_CACHE_LOCK;
574 if( g_atomic_int_dec_and_test(&cache->n_ref) )
575 {
576 /* g_assert(cache->reload_id != 0); */
577 unregister_menu_from_server( cache );
578 /* DEBUG("unregister to server"); */
579 g_hash_table_remove( hash, cache->menu_name );
580 if( g_hash_table_size(hash) == 0 )
581 {
582 /* DEBUG("destroy hash"); */
583 g_hash_table_destroy(hash);
584
585 /* DEBUG("disconnect from server"); */
586 G_LOCK(connect);
587 shutdown(server_fd, SHUT_RDWR); /* the IO thread will terminate itself */
588 server_fd = -1;
589 G_UNLOCK(connect);
590 hash = NULL;
591 }
592 MENU_CACHE_UNLOCK;
593
594 if(G_LIKELY(cache->thr))
595 {
596 g_cancellable_cancel(cache->cancellable);
597 g_thread_join(cache->thr);
598 }
599 g_object_unref(cache->cancellable);
600 if( G_LIKELY(cache->root_dir) )
601 {
602 /* DEBUG("unref root dir"); */
603 menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) );
604 /* DEBUG("unref root dir finished"); */
605 }
606 g_free( cache->cache_file );
607 g_free( cache->menu_name );
608 g_free(cache->reg);
609 /* g_free( cache->menu_file_path ); */
610 g_strfreev(cache->known_des);
611 g_slist_free(cache->notifiers);
612 g_slice_free( MenuCache, cache );
613 }
614 else
615 MENU_CACHE_UNLOCK;
616 }
617
618 /**
619 * menu_cache_get_root_dir
620 * @cache: a menu cache instance
621 *
622 * Since: 0.1.0
623 *
624 * Deprecated: 0.3.4: Use menu_cache_dup_root_dir() instead.
625 */
menu_cache_get_root_dir(MenuCache * cache)626 MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache )
627 {
628 MenuCacheDir* dir = menu_cache_dup_root_dir(cache);
629 /* NOTE: this is very ugly hack but cache->root_dir may be changed by
630 cache reload in server-io thread, so we should keep it alive :( */
631 if(dir)
632 g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir);
633 return dir;
634 }
635
636 /**
637 * menu_cache_dup_root_dir
638 * @cache: a menu cache instance
639 *
640 * Retrieves root directory for @cache. Returned data should be freed
641 * with menu_cache_item_unref() after usage.
642 *
643 * Returns: (transfer full): root item or %NULL in case of error.
644 *
645 * Since: 0.3.4
646 */
menu_cache_dup_root_dir(MenuCache * cache)647 MenuCacheDir* menu_cache_dup_root_dir( MenuCache* cache )
648 {
649 MenuCacheDir* dir;
650 MENU_CACHE_LOCK;
651 dir = cache->root_dir;
652 if(G_LIKELY(dir))
653 menu_cache_item_ref(MENU_CACHE_ITEM(dir));
654 MENU_CACHE_UNLOCK;
655 return dir;
656 }
657
658 /**
659 * menu_cache_item_ref
660 * @item: a menu cache item
661 *
662 * Increases reference counter on @item.
663 *
664 * Returns: @item.
665 *
666 * Since: 0.1.0
667 */
menu_cache_item_ref(MenuCacheItem * item)668 MenuCacheItem* menu_cache_item_ref(MenuCacheItem* item)
669 {
670 g_atomic_int_inc( &item->n_ref );
671 /* DEBUG("item_ref %s: %d -> %d", item->id, item->n_ref-1, item->n_ref); */
672 return item;
673 }
674
menu_cache_reload_idle(gpointer cache)675 static gboolean menu_cache_reload_idle(gpointer cache)
676 {
677 /* do reload once */
678 if (!g_source_is_destroyed(g_main_current_source()))
679 menu_cache_reload(cache);
680 return FALSE;
681 }
682
683 typedef struct _CacheReloadNotifier
684 {
685 MenuCacheReloadNotify func;
686 gpointer user_data;
687 }CacheReloadNotifier;
688
689 struct _MenuCacheNotifyId
690 {
691 GSList l;
692 };
693
694 /**
695 * menu_cache_add_reload_notify
696 * @cache: a menu cache instance
697 * @func: callback to call when menu cache is reloaded
698 * @user_data: user data provided for @func
699 *
700 * Adds a @func to list of callbacks that are called each time menu cache
701 * is loaded.
702 *
703 * Returns: an ID of added callback.
704 *
705 * Since: 0.1.0
706 */
menu_cache_add_reload_notify(MenuCache * cache,MenuCacheReloadNotify func,gpointer user_data)707 MenuCacheNotifyId menu_cache_add_reload_notify(MenuCache* cache, MenuCacheReloadNotify func, gpointer user_data)
708 {
709 GSList* l = g_slist_alloc();
710 CacheReloadNotifier* n = g_slice_new(CacheReloadNotifier);
711 gboolean is_first;
712 n->func = func;
713 n->user_data = user_data;
714 l->data = n;
715 MENU_CACHE_LOCK;
716 is_first = (cache->root_dir == NULL && cache->notifiers == NULL);
717 cache->notifiers = g_slist_concat( cache->notifiers, l );
718 /* reload existing file first so it will be ready right away */
719 if(is_first && cache->reload_id == 0)
720 cache->reload_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE,
721 menu_cache_reload_idle,
722 menu_cache_ref(cache),
723 (GDestroyNotify)menu_cache_unref);
724 MENU_CACHE_UNLOCK;
725 return (MenuCacheNotifyId)l;
726 }
727
728 /**
729 * menu_cache_remove_reload_notify
730 * @cache: a menu cache instance
731 * @notify_id: an ID of callback
732 *
733 * Removes @notify_id from list of callbacks added for @cache by previous
734 * call to menu_cache_add_reload_notify().
735 *
736 * Since: 0.1.0
737 */
menu_cache_remove_reload_notify(MenuCache * cache,MenuCacheNotifyId notify_id)738 void menu_cache_remove_reload_notify(MenuCache* cache, MenuCacheNotifyId notify_id)
739 {
740 MENU_CACHE_LOCK;
741 g_slice_free( CacheReloadNotifier, ((GSList*)notify_id)->data );
742 cache->notifiers = g_slist_delete_link( cache->notifiers, (GSList*)notify_id );
743 MENU_CACHE_UNLOCK;
744 }
745
reload_notify(gpointer data)746 static gboolean reload_notify(gpointer data)
747 {
748 MenuCache* cache = (MenuCache*)data;
749 GSList* l;
750 MENU_CACHE_LOCK;
751 /* we have it referenced and there is no source removal so no check */
752 for( l = cache->notifiers; l; l = l->next )
753 {
754 CacheReloadNotifier* n = (CacheReloadNotifier*)l->data;
755 if(n->func)
756 n->func( cache, n->user_data );
757 }
758 MENU_CACHE_UNLOCK;
759 return FALSE;
760 }
761
762 /**
763 * menu_cache_reload
764 * @cache: a menu cache instance
765 *
766 * Reloads menu cache from file generated by menu-cached.
767 *
768 * Returns: %TRUE if reload was successful.
769 *
770 * Since: 0.1.0
771 */
menu_cache_reload(MenuCache * cache)772 gboolean menu_cache_reload( MenuCache* cache )
773 {
774 char* line;
775 gsize len;
776 GFile* file;
777 GFileInputStream* istr = NULL;
778 GDataInputStream* f;
779 MenuCacheFileDir** all_used_files;
780 int i, n;
781 int ver_maj, ver_min;
782
783 MENU_CACHE_LOCK;
784 if (cache->reload_id)
785 g_source_remove(cache->reload_id);
786 cache->reload_id = 0;
787 MENU_CACHE_UNLOCK;
788 file = g_file_new_for_path(cache->cache_file);
789 if(!file)
790 return FALSE;
791 istr = g_file_read(file, cache->cancellable, NULL);
792 g_object_unref(file);
793 if(!istr)
794 return FALSE;
795 f = g_data_input_stream_new(G_INPUT_STREAM(istr));
796 g_object_unref(istr);
797 if( ! f )
798 return FALSE;
799
800 /* the first line is version number */
801 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
802 if(G_LIKELY(line))
803 {
804 len = sscanf(line, "%d.%d", &ver_maj, &ver_min);
805 g_free(line);
806 if(len < 2)
807 goto _fail;
808 if( ver_maj != VER_MAJOR ||
809 ver_min > VER_MINOR || ver_min < VER_MINOR_SUPPORTED )
810 goto _fail;
811 }
812 else
813 goto _fail;
814
815 g_debug("menu cache: got file version 1.%d", ver_min);
816 /* the second line is menu name */
817 line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
818 if(G_UNLIKELY(line == NULL))
819 goto _fail;
820 g_free(line);
821
822 /* FIXME: this may lock other threads for some time */
823 MENU_CACHE_LOCK;
824 if(cache->notifiers == NULL)
825 {
826 /* nobody aware of reloads, stupid clients may think root is forever */
827 MENU_CACHE_UNLOCK;
828 goto _fail;
829 }
830
831 /* get all used files */
832 n = read_all_used_files( f, cache, &all_used_files );
833 if (n <= 0)
834 {
835 MENU_CACHE_UNLOCK;
836 goto _fail;
837 }
838
839 /* read known DEs */
840 g_strfreev( cache->known_des );
841 if( ! read_all_known_des( f, cache ) )
842 {
843 cache->known_des = NULL;
844 MENU_CACHE_UNLOCK;
845 for (i = 0; i < n; i++)
846 menu_cache_file_dir_unref(all_used_files[i]);
847 g_free(all_used_files);
848 _fail:
849 g_object_unref(f);
850 return FALSE;
851 }
852 cache->version = ver_min;
853
854 if(cache->root_dir)
855 menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) );
856
857 cache->root_dir = (MenuCacheDir*)read_item( f, cache, all_used_files, n );
858 g_object_unref(f);
859
860 g_idle_add_full(G_PRIORITY_HIGH_IDLE, reload_notify, menu_cache_ref(cache),
861 (GDestroyNotify)menu_cache_unref);
862 MENU_CACHE_UNLOCK;
863
864 for (i = 0; i < n; i++)
865 menu_cache_file_dir_unref(all_used_files[i]);
866 g_free(all_used_files);
867
868 return TRUE;
869 }
870
871 /**
872 * menu_cache_item_unref
873 * @item: a menu cache item
874 *
875 * Decreases reference counter on @item. When reference count becomes 0
876 * then resources associated with @item will be freed.
877 *
878 * Returns: %FALSE (since 0.5.0)
879 *
880 * Since: 0.1.0
881 */
menu_cache_item_unref(MenuCacheItem * item)882 gboolean menu_cache_item_unref(MenuCacheItem* item)
883 {
884 /* DEBUG("item_unref(%s): %d", item->id, item->n_ref); */
885 /* We need a lock here unfortunately since another thread may have access
886 to it via some child->parent which isn't protected by reference */
887 MENU_CACHE_LOCK; /* lock may be recursive here */
888 if( g_atomic_int_dec_and_test( &item->n_ref ) )
889 {
890 /* DEBUG("free item: %s", item->id); */
891 g_free( item->id );
892 g_free( item->name );
893 g_free( item->comment );
894 g_free( item->icon );
895
896 menu_cache_file_dir_unref(item->file_dir);
897
898 if( item->file_name && item->file_name != item->id )
899 g_free( item->file_name );
900
901 if( item->parent )
902 {
903 /* DEBUG("remove %s from parent %s", item->id, MENU_CACHE_ITEM(item->parent)->id); */
904 /* remove ourselve from the parent node. */
905 item->parent->children = g_slist_remove(item->parent->children, item);
906 }
907
908 if( item->type == MENU_CACHE_TYPE_DIR )
909 {
910 MenuCacheDir* dir = MENU_CACHE_DIR(item);
911 GSList* l;
912 for(l = dir->children; l; )
913 {
914 MenuCacheItem* child = MENU_CACHE_ITEM(l->data);
915 /* remove ourselve from the children. */
916 child->parent = NULL;
917 l = l->next;
918 menu_cache_item_unref(child);
919 }
920 g_slist_free( dir->children );
921 g_slice_free( MenuCacheDir, dir );
922 }
923 else
924 {
925 MenuCacheApp* app = MENU_CACHE_APP(item);
926 g_free( app->exec );
927 g_free(app->try_exec);
928 g_free(app->working_dir);
929 g_free(app->categories);
930 g_free(app->keywords);
931 g_slice_free( MenuCacheApp, app );
932 }
933 }
934 MENU_CACHE_UNLOCK;
935 return FALSE;
936 }
937
938 /**
939 * menu_cache_item_get_type
940 * @item: a menu cache item
941 *
942 * Checks type of @item.
943 *
944 * Returns: type of @item.
945 *
946 * Since: 0.1.0
947 */
menu_cache_item_get_type(MenuCacheItem * item)948 MenuCacheType menu_cache_item_get_type( MenuCacheItem* item )
949 {
950 return item->type;
951 }
952
953 /**
954 * menu_cache_item_get_id
955 * @item: a menu cache item
956 *
957 * Retrieves ID (short name such as 'application.desktop') of @item.
958 * Returned data are owned by menu cache and should be not freed by caller.
959 *
960 * Returns: (transfer none): item ID.
961 *
962 * Since: 0.1.0
963 */
menu_cache_item_get_id(MenuCacheItem * item)964 const char* menu_cache_item_get_id( MenuCacheItem* item )
965 {
966 return item->id;
967 }
968
969 /**
970 * menu_cache_item_get_name
971 * @item: a menu cache item
972 *
973 * Retrieves display name of @item. Returned data are owned by menu
974 * cache and should be not freed by caller.
975 *
976 * Returns: (transfer none): @item display name or %NULL.
977 *
978 * Since: 0.1.0
979 */
menu_cache_item_get_name(MenuCacheItem * item)980 const char* menu_cache_item_get_name( MenuCacheItem* item )
981 {
982 return item->name;
983 }
984
985 /**
986 * menu_cache_item_get_comment
987 * @item: a menu cache item
988 *
989 * Retrieves comment of @item. The comment can be used to show tooltip
990 * on @item. Returned data are owned by menu cache and should be not
991 * freed by caller.
992 *
993 * Returns: (transfer none): @item comment or %NULL.
994 *
995 * Since: 0.1.0
996 */
menu_cache_item_get_comment(MenuCacheItem * item)997 const char* menu_cache_item_get_comment( MenuCacheItem* item )
998 {
999 return item->comment;
1000 }
1001
1002 /**
1003 * menu_cache_item_get_icon
1004 * @item: a menu cache item
1005 *
1006 * Retrieves name of icon of @item. Returned data are owned by menu
1007 * cache and should be not freed by caller.
1008 *
1009 * Returns: (transfer none): @item icon name or %NULL.
1010 *
1011 * Since: 0.1.0
1012 */
menu_cache_item_get_icon(MenuCacheItem * item)1013 const char* menu_cache_item_get_icon( MenuCacheItem* item )
1014 {
1015 return item->icon;
1016 }
1017
1018 /**
1019 * menu_cache_item_get_file_basename
1020 * @item: a menu cache item
1021 *
1022 * Retrieves basename of @item. This API can return %NULL if @item is a
1023 * directory and have no directory desktop entry file. Returned data are
1024 * owned by menu cache and should be not freed by caller.
1025 *
1026 * Returns: (transfer none): @item file basename or %NULL.
1027 *
1028 * Since: 0.2.0
1029 */
menu_cache_item_get_file_basename(MenuCacheItem * item)1030 const char* menu_cache_item_get_file_basename( MenuCacheItem* item )
1031 {
1032 return item->file_name;
1033 }
1034
1035 /**
1036 * menu_cache_item_get_file_dirname
1037 * @item: a menu cache item
1038 *
1039 * Retrieves path to directory where @item desktop enrty file is located.
1040 * This API can return %NULL if @item is a directory and have no
1041 * desktop entry file. Returned data are owned by menu cache and should
1042 * be not freed by caller.
1043 *
1044 * Returns: (transfer none): @item file parent directory path or %NULL.
1045 *
1046 * Since: 0.2.0
1047 */
menu_cache_item_get_file_dirname(MenuCacheItem * item)1048 const char* menu_cache_item_get_file_dirname( MenuCacheItem* item )
1049 {
1050 return item->file_dir ? item->file_dir->dir + 1 : NULL;
1051 }
1052
1053 /**
1054 * menu_cache_item_get_file_path
1055 * @item: a menu cache item
1056 *
1057 * Retrieves path to @item desktop enrty file. This API can return %NULL
1058 * if @item is a directory and have no desktop entry file. Returned data
1059 * should be freed with g_free() after usage.
1060 *
1061 * Returns: (transfer full): @item file path or %NULL.
1062 *
1063 * Since: 0.2.0
1064 */
menu_cache_item_get_file_path(MenuCacheItem * item)1065 char* menu_cache_item_get_file_path( MenuCacheItem* item )
1066 {
1067 if( ! item->file_name || ! item->file_dir )
1068 return NULL;
1069 return g_build_filename( item->file_dir->dir + 1, item->file_name, NULL );
1070 }
1071
1072 /**
1073 * menu_cache_item_get_parent
1074 * @item: a menu cache item
1075 *
1076 * Since: 0.1.0
1077 *
1078 * Deprecated: 0.3.4: Use menu_cache_item_dup_parent() instead.
1079 */
menu_cache_item_get_parent(MenuCacheItem * item)1080 MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item )
1081 {
1082 MenuCacheDir* dir = menu_cache_item_dup_parent(item);
1083 /* NOTE: this is very ugly hack but parent may be changed by item freeing
1084 so we should keep it alive :( */
1085 if(dir)
1086 g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir);
1087 return dir;
1088 }
1089
1090 /**
1091 * menu_cache_item_dup_parent
1092 * @item: a menu item
1093 *
1094 * Retrieves parent (directory) for @item. Returned data should be freed
1095 * with menu_cache_item_unref() after usage.
1096 *
1097 * Returns: (transfer full): parent item or %NULL in case of error.
1098 *
1099 * Since: 0.3.4
1100 */
menu_cache_item_dup_parent(MenuCacheItem * item)1101 MenuCacheDir* menu_cache_item_dup_parent( MenuCacheItem* item )
1102 {
1103 MenuCacheDir* dir;
1104 MENU_CACHE_LOCK;
1105 dir = item->parent;
1106 if(G_LIKELY(dir))
1107 menu_cache_item_ref(MENU_CACHE_ITEM(dir));
1108 MENU_CACHE_UNLOCK;
1109 return dir;
1110 }
1111
1112 /**
1113 * menu_cache_dir_get_children
1114 * @dir: a menu cache item
1115 *
1116 * Retrieves list of items contained in @dir. Returned data are owned by
1117 * menu cache and should be not freed by caller.
1118 * This API is thread unsafe and should be never called from outside of
1119 * default main loop.
1120 *
1121 * Returns: (transfer none) (element-type MenuCacheItem): list of items.
1122 *
1123 * Since: 0.1.0
1124 *
1125 * Deprecated: 0.4.0: Use menu_cache_dir_list_children() instead.
1126 */
menu_cache_dir_get_children(MenuCacheDir * dir)1127 GSList* menu_cache_dir_get_children( MenuCacheDir* dir )
1128 {
1129 /* NOTE: this is very ugly hack but dir may be freed by cache reload
1130 in server-io thread, so we should keep it alive :( */
1131 g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref,
1132 menu_cache_item_ref(MENU_CACHE_ITEM(dir)));
1133 return dir->children;
1134 }
1135
1136 /**
1137 * menu_cache_dir_list_children
1138 * @dir: a menu cache item
1139 *
1140 * Retrieves list of items contained in @dir. Returned data should be
1141 * freed with g_slist_free_full(list, menu_cache_item_unref) after usage.
1142 *
1143 * Returns: (transfer full) (element-type MenuCacheItem): list of items.
1144 *
1145 * Since: 0.4.0
1146 */
menu_cache_dir_list_children(MenuCacheDir * dir)1147 GSList* menu_cache_dir_list_children(MenuCacheDir* dir)
1148 {
1149 GSList *children, *l;
1150
1151 if(MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR)
1152 return NULL;
1153 MENU_CACHE_LOCK;
1154 children = g_slist_copy(dir->children);
1155 for(l = children; l; l = l->next)
1156 menu_cache_item_ref(l->data);
1157 MENU_CACHE_UNLOCK;
1158 return children;
1159 }
1160
1161 /**
1162 * menu_cache_find_child_by_id
1163 * @dir: a menu cache item
1164 * @id: a string to find
1165 *
1166 * Checks if @dir has a child with given @id. Returned data should be
1167 * freed with menu_cache_item_unref() when no longer needed.
1168 *
1169 * Returns: (transfer full): found item or %NULL.
1170 *
1171 * Since: 0.5.0
1172 */
menu_cache_find_child_by_id(MenuCacheDir * dir,const char * id)1173 MenuCacheItem *menu_cache_find_child_by_id(MenuCacheDir *dir, const char *id)
1174 {
1175 GSList *child;
1176 MenuCacheItem *item = NULL;
1177
1178 if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || id == NULL)
1179 return NULL;
1180 MENU_CACHE_LOCK;
1181 for (child = dir->children; child; child = child->next)
1182 if (g_strcmp0(MENU_CACHE_ITEM(child->data)->id, id) == 0)
1183 {
1184 item = menu_cache_item_ref(child->data);
1185 break;
1186 }
1187 MENU_CACHE_UNLOCK;
1188 return item;
1189 }
1190
1191 /**
1192 * menu_cache_find_child_by_name
1193 * @dir: a menu cache item
1194 * @name: a string to find
1195 *
1196 * Checks if @dir has a child with given @name. Returned data should be
1197 * freed with menu_cache_item_unref() when no longer needed.
1198 *
1199 * Returns: (transfer full): found item or %NULL.
1200 *
1201 * Since: 0.5.0
1202 */
menu_cache_find_child_by_name(MenuCacheDir * dir,const char * name)1203 MenuCacheItem *menu_cache_find_child_by_name(MenuCacheDir *dir, const char *name)
1204 {
1205 GSList *child;
1206 MenuCacheItem *item = NULL;
1207
1208 if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || name == NULL)
1209 return NULL;
1210 MENU_CACHE_LOCK;
1211 for (child = dir->children; child; child = child->next)
1212 if (g_strcmp0(MENU_CACHE_ITEM(child->data)->name, name) == 0)
1213 {
1214 item = menu_cache_item_ref(child->data);
1215 break;
1216 }
1217 MENU_CACHE_UNLOCK;
1218 return item;
1219 }
1220
1221 /**
1222 * menu_cache_dir_is_visible
1223 * @dir: a menu cache item
1224 *
1225 * Checks if @dir should be visible.
1226 *
1227 * Returns: %TRUE if @dir is visible.
1228 *
1229 * Since: 0.5.0
1230 */
menu_cache_dir_is_visible(MenuCacheDir * dir)1231 gboolean menu_cache_dir_is_visible(MenuCacheDir *dir)
1232 {
1233 return ((dir->flags & FLAG_IS_NODISPLAY) == 0);
1234 }
1235
1236 /**
1237 * menu_cache_app_get_generic_name
1238 * @app: a menu cache item
1239 *
1240 * Retrieves generic name for @app. Returned data are owned by menu
1241 * cache and should not be freed by caller.
1242 *
1243 * Returns: (transfer none): app's generic name or %NULL.
1244 *
1245 * Since: 1.0.3
1246 */
menu_cache_app_get_generic_name(MenuCacheApp * app)1247 const char* menu_cache_app_get_generic_name( MenuCacheApp* app )
1248 {
1249 return app->generic_name;
1250 }
1251
1252 /**
1253 * menu_cache_app_get_exec
1254 * @app: a menu cache item
1255 *
1256 * Retrieves execution string for @app. Returned data are owned by menu
1257 * cache and should be not freed by caller.
1258 *
1259 * Returns: (transfer none): item execution string or %NULL.
1260 *
1261 * Since: 0.1.0
1262 */
menu_cache_app_get_exec(MenuCacheApp * app)1263 const char* menu_cache_app_get_exec( MenuCacheApp* app )
1264 {
1265 return app->exec;
1266 }
1267
1268 /**
1269 * menu_cache_app_get_working_dir
1270 * @app: a menu cache item
1271 *
1272 * Retrieves working directory for @app. Returned data are owned by menu
1273 * cache and should be not freed by caller.
1274 *
1275 * Returns: (transfer none): item working directory or %NULL.
1276 *
1277 * Since: 0.1.0
1278 */
menu_cache_app_get_working_dir(MenuCacheApp * app)1279 const char* menu_cache_app_get_working_dir( MenuCacheApp* app )
1280 {
1281 return app->working_dir;
1282 }
1283
1284 /**
1285 * menu_cache_app_get_categories
1286 * @app: a menu cache item
1287 *
1288 * Retrieves list of categories for @app. Returned data are owned by menu
1289 * cache and should be not freed by caller.
1290 *
1291 * Returns: (transfer none): list of categories or %NULL.
1292 *
1293 * Since: 1.0.0
1294 */
menu_cache_app_get_categories(MenuCacheApp * app)1295 const char * const * menu_cache_app_get_categories(MenuCacheApp* app)
1296 {
1297 return app->categories;
1298 }
1299
1300 /**
1301 * menu_cache_app_get_use_terminal
1302 * @app: a menu cache item
1303 *
1304 * Checks if @app should be ran in terminal.
1305 *
1306 * Returns: %TRUE if @app requires terminal to run.
1307 *
1308 * Since: 0.1.0
1309 */
menu_cache_app_get_use_terminal(MenuCacheApp * app)1310 gboolean menu_cache_app_get_use_terminal( MenuCacheApp* app )
1311 {
1312 return ( (app->flags & FLAG_USE_TERMINAL) != 0 );
1313 }
1314
1315 /**
1316 * menu_cache_app_get_use_sn
1317 * @app: a menu cache item
1318 *
1319 * Checks if @app wants startup notification.
1320 *
1321 * Returns: %TRUE if @app wants startup notification.
1322 *
1323 * Since: 0.1.0
1324 */
menu_cache_app_get_use_sn(MenuCacheApp * app)1325 gboolean menu_cache_app_get_use_sn( MenuCacheApp* app )
1326 {
1327 return ( (app->flags & FLAG_USE_SN) != 0 );
1328 }
1329
1330 /**
1331 * menu_cache_app_get_show_flags
1332 * @app: a menu cache item
1333 *
1334 * Retrieves list of desktop environments where @app should be visible.
1335 *
1336 * Returns: bit mask of DE.
1337 *
1338 * Since: 0.2.0
1339 */
menu_cache_app_get_show_flags(MenuCacheApp * app)1340 guint32 menu_cache_app_get_show_flags( MenuCacheApp* app )
1341 {
1342 return app->show_in_flags;
1343 }
1344
_can_be_exec(MenuCacheApp * app)1345 static gboolean _can_be_exec(MenuCacheApp *app)
1346 {
1347 char *path;
1348
1349 if (app->try_exec == NULL)
1350 return TRUE;
1351 path = g_find_program_in_path(app->try_exec);
1352 g_free(path);
1353 return (path != NULL);
1354 }
1355
1356 /**
1357 * menu_cache_app_get_is_visible
1358 * @app: a menu cache item
1359 * @de_flags: bit mask of DE to test
1360 *
1361 * Checks if @app should be visible in any of desktop environments
1362 * @de_flags.
1363 *
1364 * Returns: %TRUE if @app is visible.
1365 *
1366 * Since: 0.2.0
1367 */
menu_cache_app_get_is_visible(MenuCacheApp * app,guint32 de_flags)1368 gboolean menu_cache_app_get_is_visible( MenuCacheApp* app, guint32 de_flags )
1369 {
1370 if(app->flags & FLAG_IS_NODISPLAY)
1371 return FALSE;
1372 return (!app->show_in_flags || (app->show_in_flags & de_flags)) &&
1373 _can_be_exec(app);
1374 }
1375
1376 /*
1377 MenuCacheApp* menu_cache_find_app_by_exec( const char* exec )
1378 {
1379 return NULL;
1380 }
1381 */
1382
1383 /**
1384 * menu_cache_get_dir_from_path
1385 * @cache: a menu cache instance
1386 * @path: item path
1387 *
1388 * Since: 0.1.0
1389 *
1390 * Deprecated: 0.3.4: Use menu_cache_item_from_path() instead.
1391 */
menu_cache_get_dir_from_path(MenuCache * cache,const char * path)1392 MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path )
1393 {
1394 char** names = g_strsplit( path + 1, "/", -1 );
1395 int i = 0;
1396 MenuCacheDir* dir = NULL;
1397
1398 if( !names )
1399 return NULL;
1400
1401 if( G_UNLIKELY(!names[0]) )
1402 {
1403 g_strfreev(names);
1404 return NULL;
1405 }
1406 /* the topmost dir of the path should be the root menu dir. */
1407 MENU_CACHE_LOCK;
1408 dir = cache->root_dir;
1409 if (G_UNLIKELY(dir == NULL) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id))
1410 {
1411 MENU_CACHE_UNLOCK;
1412 return NULL;
1413 }
1414
1415 for( ++i; names[i]; ++i )
1416 {
1417 GSList* l;
1418 for( l = dir->children; l; l = l->next )
1419 {
1420 MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
1421 if( item->type == MENU_CACHE_TYPE_DIR && 0 == strcmp( item->id, names[i] ) )
1422 dir = MENU_CACHE_DIR(item);
1423 }
1424 /* FIXME: we really should ref it on return since other thread may
1425 destroy the parent at this time and returned data become invalid.
1426 Therefore this call isn't thread-safe! */
1427 if( ! dir )
1428 {
1429 MENU_CACHE_UNLOCK;
1430 return NULL;
1431 }
1432 }
1433 MENU_CACHE_UNLOCK;
1434 return dir;
1435 }
1436
1437 /**
1438 * menu_cache_item_from_path
1439 * @cache: cache to inspect
1440 * @path: item path
1441 *
1442 * Searches item @path in the @cache. The @path consists of item IDs
1443 * separated by slash ('/'). Returned data should be freed with
1444 * menu_cache_item_unref() after usage.
1445 *
1446 * Returns: (transfer full): found item or %NULL if no item found.
1447 *
1448 * Since: 0.3.4
1449 */
menu_cache_item_from_path(MenuCache * cache,const char * path)1450 MenuCacheItem* menu_cache_item_from_path( MenuCache* cache, const char* path )
1451 {
1452 char** names = g_strsplit( path + 1, "/", -1 );
1453 int i;
1454 MenuCacheDir* dir;
1455 MenuCacheItem* item = NULL;
1456
1457 if( !names )
1458 return NULL;
1459
1460 if( G_UNLIKELY(!names[0]) )
1461 {
1462 g_strfreev(names);
1463 return NULL;
1464 }
1465 /* the topmost dir of the path should be the root menu dir. */
1466 MENU_CACHE_LOCK;
1467 dir = cache->root_dir;
1468 if( G_UNLIKELY(!dir) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id) != 0 )
1469 goto _end;
1470
1471 for( i = 1; names[i]; ++i )
1472 {
1473 GSList* l;
1474 item = NULL;
1475 if( !dir )
1476 break;
1477 l = dir->children;
1478 dir = NULL;
1479 for( ; l; l = l->next )
1480 {
1481 item = MENU_CACHE_ITEM(l->data);
1482 if( g_strcmp0( item->id, names[i] ) == 0 )
1483 {
1484 if( item->type == MENU_CACHE_TYPE_DIR )
1485 dir = MENU_CACHE_DIR(item);
1486 break;
1487 }
1488 item = NULL;
1489 }
1490 if( !item )
1491 break;
1492 }
1493 if(item)
1494 menu_cache_item_ref(item);
1495 _end:
1496 MENU_CACHE_UNLOCK;
1497 g_strfreev(names);
1498 return item;
1499 }
1500
1501 /**
1502 * menu_cache_dir_make_path
1503 * @dir: a menu cache item
1504 *
1505 * Retrieves path of @dir. The path consists of item IDs separated by
1506 * slash ('/'). Returned data should be freed with g_free() after usage.
1507 *
1508 * Returns: (transfer full): item path.
1509 *
1510 * Since: 0.1.0
1511 */
menu_cache_dir_make_path(MenuCacheDir * dir)1512 char* menu_cache_dir_make_path( MenuCacheDir* dir )
1513 {
1514 GString* path = g_string_sized_new(1024);
1515 MenuCacheItem* it;
1516
1517 MENU_CACHE_LOCK;
1518 while( (it = MENU_CACHE_ITEM(dir)) ) /* this is not top dir */
1519 {
1520 g_string_prepend( path, menu_cache_item_get_id(it) );
1521 g_string_prepend_c( path, '/' );
1522 /* FIXME: if parent is already unref'd by another thread then
1523 path being made will be broken. Is there any way to avoid that? */
1524 dir = it->parent;
1525 }
1526 MENU_CACHE_UNLOCK;
1527 return g_string_free( path, FALSE );
1528 }
1529
get_socket_name(char * buf,int len)1530 static void get_socket_name( char* buf, int len )
1531 {
1532 char* dpy = g_strdup(g_getenv("DISPLAY"));
1533 if(dpy && *dpy)
1534 {
1535 char* p = strchr(dpy, ':');
1536 for(++p; *p && *p != '.' && *p != '\n';)
1537 ++p;
1538 if(*p)
1539 *p = '\0';
1540 }
1541 #if GLIB_CHECK_VERSION(2, 28, 0)
1542 g_snprintf( buf, len, "%s/menu-cached-%s", g_get_user_runtime_dir(),
1543 dpy ? dpy : ":0" );
1544 #else
1545 g_snprintf( buf, len, "%s/.menu-cached-%s-%s", g_get_tmp_dir(),
1546 dpy ? dpy : ":0", g_get_user_name() );
1547 #endif
1548 g_free(dpy);
1549 }
1550
1551 #define MAX_RETRIES 25
1552
fork_server(const char * path)1553 static gboolean fork_server(const char *path)
1554 {
1555 int ret, pid, status;
1556
1557 if (!g_file_test (MENUCACHE_LIBEXECDIR "/menu-cached", G_FILE_TEST_IS_EXECUTABLE))
1558 {
1559 g_error("failed to find menu-cached");
1560 }
1561
1562 /* Start daemon */
1563 pid = fork();
1564 if (pid == 0)
1565 {
1566 execl(MENUCACHE_LIBEXECDIR "/menu-cached", MENUCACHE_LIBEXECDIR "/menu-cached",
1567 path, NULL);
1568 g_print("failed to exec %s %s\n", MENUCACHE_LIBEXECDIR "/menu-cached", path);
1569 }
1570
1571 /*
1572 * do a waitpid on the intermediate process to avoid zombies.
1573 */
1574 retry_wait:
1575 ret = waitpid(pid, &status, 0);
1576 if (ret < 0) {
1577 if (errno == EINTR)
1578 goto retry_wait;
1579 }
1580 return TRUE;
1581 }
1582
1583 /* this thread is started by connect_server() */
server_io_thread(gpointer data)1584 static gpointer server_io_thread(gpointer data)
1585 {
1586 char buf[1024]; /* protocol has a lot shorter strings */
1587 ssize_t sz;
1588 size_t ptr = 0;
1589 int fd = GPOINTER_TO_INT(data);
1590 GHashTableIter it;
1591 char* menu_name;
1592 MenuCache* cache;
1593
1594 while(fd >= 0)
1595 {
1596 sz = read(fd, &buf[ptr], sizeof(buf) - ptr);
1597 if(sz <= 0) /* socket error or EOF */
1598 {
1599 MENU_CACHE_LOCK;
1600 ptr = hash ? g_hash_table_size(hash) : 0;
1601 MENU_CACHE_UNLOCK;
1602 if (ptr == 0) /* don't need it anymore */
1603 break;
1604 G_LOCK(connect);
1605 if(fd != server_fd) /* someone replaced us?! go out immediately! */
1606 {
1607 G_UNLOCK(connect);
1608 break;
1609 }
1610 server_fd = -1;
1611 G_UNLOCK(connect);
1612 DEBUG("connect failed, trying reconnect");
1613 sleep(1);
1614 if( ! connect_server(NULL) )
1615 {
1616 g_critical("fail to re-connect to the server.");
1617 MENU_CACHE_LOCK;
1618 if(hash)
1619 {
1620 g_hash_table_iter_init(&it, hash);
1621 while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
1622 SET_CACHE_READY(cache);
1623 }
1624 MENU_CACHE_UNLOCK;
1625 break;
1626 }
1627 DEBUG("successfully reconnected server, re-register menus.");
1628 /* re-register all menu caches */
1629 MENU_CACHE_LOCK;
1630 if(hash)
1631 {
1632 g_hash_table_iter_init(&it, hash);
1633 while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
1634 register_menu_to_server(cache);
1635 /* FIXME: need we remove it from hash if failed? */
1636 }
1637 MENU_CACHE_UNLOCK;
1638 break; /* next thread will do it */
1639 }
1640 while(sz > 0)
1641 {
1642 while(sz > 0)
1643 {
1644 if(buf[ptr] == '\n')
1645 break;
1646 sz--;
1647 ptr++;
1648 }
1649 if(ptr == sizeof(buf)) /* EOB reached, seems we got garbage */
1650 {
1651 g_warning("menu cache: got garbage from server, break connect");
1652 shutdown(fd, SHUT_RDWR); /* drop connection */
1653 break; /* we handle it above */
1654 }
1655 else if(sz == 0) /* incomplete line, wait for data again */
1656 break;
1657 /* we got a line, let check what we got */
1658 buf[ptr] = '\0';
1659 if(memcmp(buf, "REL:", 4) == 0) /* reload */
1660 {
1661 DEBUG("server ask us to reload cache: %s", &buf[4]);
1662 MENU_CACHE_LOCK;
1663 if(hash)
1664 {
1665 g_hash_table_iter_init(&it, hash);
1666 while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
1667 {
1668 if(memcmp(cache->md5, &buf[4], 32) == 0)
1669 {
1670 DEBUG("RELOAD!");
1671 menu_cache_reload(cache);
1672 SET_CACHE_READY(cache);
1673 break;
1674 }
1675 }
1676 }
1677 MENU_CACHE_UNLOCK;
1678 /* DEBUG("cache reloaded"); */
1679 }
1680 else
1681 g_warning("menu cache: unrecognized input: %s", buf);
1682 /* go to next line */
1683 sz--;
1684 if(sz > 0)
1685 memmove(buf, &buf[ptr+1], sz);
1686 ptr = 0;
1687 }
1688 }
1689 G_LOCK(connect);
1690 if (fd == server_fd)
1691 server_fd = -1;
1692 G_UNLOCK(connect);
1693 close(fd);
1694 /* DEBUG("server io thread terminated"); */
1695 #if GLIB_CHECK_VERSION(2, 32, 0)
1696 g_thread_unref(g_thread_self());
1697 #endif
1698 return NULL;
1699 }
1700
connect_server(GCancellable * cancellable)1701 static gboolean connect_server(GCancellable* cancellable)
1702 {
1703 int fd, rc;
1704 struct sockaddr_un addr;
1705 int retries = 0;
1706
1707 G_LOCK(connect);
1708 if(server_fd != -1 || (cancellable && g_cancellable_is_cancelled(cancellable)))
1709 {
1710 G_UNLOCK(connect);
1711 return TRUE;
1712 }
1713
1714 retry:
1715 fd = socket(PF_UNIX, SOCK_STREAM, 0);
1716 if (fd < 0)
1717 {
1718 g_print("Failed to create socket\n");
1719 G_UNLOCK(connect);
1720 return FALSE;
1721 }
1722
1723 fcntl (fd, F_SETFD, FD_CLOEXEC);
1724
1725 memset(&addr, 0, sizeof(addr));
1726 addr.sun_family = AF_UNIX;
1727
1728 get_socket_name( addr.sun_path, sizeof( addr.sun_path ) );
1729
1730 if( connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
1731 {
1732 rc = errno;
1733 close(fd);
1734 if(cancellable && g_cancellable_is_cancelled(cancellable))
1735 {
1736 G_UNLOCK(connect);
1737 return TRUE;
1738 }
1739 if((rc == ECONNREFUSED || rc == ENOENT) && retries == 0)
1740 {
1741 DEBUG("no running server found, starting it");
1742 fork_server(addr.sun_path);
1743 ++retries;
1744 goto retry;
1745 }
1746 if(retries < MAX_RETRIES)
1747 {
1748 usleep(50000);
1749 ++retries;
1750 goto retry;
1751 }
1752 g_print("Unable to connect\n");
1753 G_UNLOCK(connect);
1754 return FALSE;
1755 }
1756 server_fd = fd;
1757 G_UNLOCK(connect);
1758 #if GLIB_CHECK_VERSION(2, 32, 0)
1759 g_thread_new("menu-cache-io", server_io_thread, GINT_TO_POINTER(fd));
1760 #else
1761 g_thread_create(server_io_thread, GINT_TO_POINTER(fd), FALSE, NULL);
1762 #endif
1763 return TRUE;
1764 }
1765
1766 #define CACHE_VERSION __num2str(VER_MAJOR) "." __num2str(VER_MINOR)
1767 #define __num2str(s) __def2str(s)
1768 #define __def2str(s) #s
1769
_validate_env(const char * env)1770 static inline char *_validate_env(const char *env)
1771 {
1772 char *res, *c;
1773
1774 if (env)
1775 res = g_strdup(env);
1776 else
1777 res = g_strdup("");
1778 for (c = res; *c; c++)
1779 if (*c == '\n' || *c == '\t')
1780 *c = ' ';
1781 return res;
1782 }
1783
menu_cache_create(const char * menu_name)1784 static MenuCache* menu_cache_create(const char* menu_name)
1785 {
1786 MenuCache* cache;
1787 const gchar * const * langs = g_get_language_names();
1788 const char* xdg_cfg_env = g_getenv("XDG_CONFIG_DIRS");
1789 const char* xdg_prefix_env = g_getenv("XDG_MENU_PREFIX");
1790 const char* xdg_data_env = g_getenv("XDG_DATA_DIRS");
1791 const char* xdg_cfg_home_env = g_getenv("XDG_CONFIG_HOME");
1792 const char* xdg_data_home_env = g_getenv("XDG_DATA_HOME");
1793 const char* xdg_cache_home_env = g_getenv("XDG_CACHE_HOME");
1794 char *xdg_cfg, *xdg_prefix, *xdg_data, *xdg_cfg_home, *xdg_data_home, *xdg_cache_home;
1795 char* buf;
1796 const char* md5;
1797 char* file_name;
1798 int len = 0;
1799 GChecksum *sum;
1800 char *langs_list;
1801
1802 xdg_cfg = _validate_env(xdg_cfg_env);
1803 xdg_prefix = _validate_env(xdg_prefix_env);
1804 xdg_data = _validate_env(xdg_data_env);
1805 xdg_cfg_home = _validate_env(xdg_cfg_home_env);
1806 xdg_data_home = _validate_env(xdg_data_home_env);
1807 xdg_cache_home = _validate_env(xdg_cache_home_env);
1808
1809 /* reconstruct languages list in form as it should be in $LANGUAGES */
1810 langs_list = g_strjoinv(":", (char **)langs);
1811 for (buf = langs_list; *buf; buf++) /* reusing buf var as char pointer */
1812 if (*buf == '\n' || *buf == '\t')
1813 *buf = ' ';
1814
1815 buf = g_strdup_printf( "REG:%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t" CACHE_VERSION
1816 "\t00000000000000000000000000000000\n",
1817 menu_name,
1818 langs_list,
1819 xdg_cache_home,
1820 xdg_cfg,
1821 xdg_prefix,
1822 xdg_data,
1823 xdg_cfg_home,
1824 xdg_data_home );
1825
1826 /* calculate the md5 sum of menu name + lang + all environment variables */
1827 sum = g_checksum_new(G_CHECKSUM_MD5);
1828 len = strlen(buf);
1829 g_checksum_update(sum, (guchar*)buf + 4, len - 38);
1830 md5 = g_checksum_get_string(sum);
1831 file_name = g_build_filename( g_get_user_cache_dir(), "menus", md5, NULL );
1832 DEBUG("cache file_name = %s", file_name);
1833 cache = menu_cache_new( file_name );
1834 cache->reg = buf;
1835 cache->md5 = buf + len - 33;
1836 memcpy( cache->md5, md5, 32 );
1837 cache->menu_name = g_strdup(menu_name);
1838 g_free( file_name );
1839 g_free(langs_list);
1840 g_free(xdg_cfg);
1841 g_free(xdg_prefix);
1842 g_free(xdg_data);
1843 g_free(xdg_cfg_home);
1844 g_free(xdg_data_home);
1845 g_free(xdg_cache_home);
1846
1847 g_checksum_free(sum); /* md5 is also freed here */
1848
1849 MENU_CACHE_LOCK;
1850 g_hash_table_insert( hash, g_strdup(menu_name), cache );
1851 MENU_CACHE_UNLOCK;
1852
1853 return cache;
1854 }
1855
register_menu_to_server(MenuCache * cache)1856 static gboolean register_menu_to_server(MenuCache* cache)
1857 {
1858 ssize_t len = strlen(cache->reg);
1859 /* FIXME: do unblocking I/O */
1860 if(write(server_fd, cache->reg, len) < len)
1861 {
1862 DEBUG("register_menu_to_server: sending failed");
1863 return FALSE; /* socket write failed */
1864 }
1865 return TRUE;
1866 }
1867
unregister_menu_from_server(MenuCache * cache)1868 static void unregister_menu_from_server( MenuCache* cache )
1869 {
1870 char buf[38];
1871 g_snprintf( buf, 38, "UNR:%s\n", cache->md5 );
1872 /* FIXME: do unblocking I/O */
1873 if(write( server_fd, buf, 37 ) <= 0)
1874 {
1875 DEBUG("unregister_menu_from_server: sending failed");
1876 }
1877 }
1878
menu_cache_loader_thread(gpointer data)1879 static gpointer menu_cache_loader_thread(gpointer data)
1880 {
1881 MenuCache* cache = (MenuCache*)data;
1882
1883 /* try to connect server now */
1884 if(!connect_server(cache->cancellable))
1885 {
1886 g_print("unable to connect to menu-cached.\n");
1887 SET_CACHE_READY(cache);
1888 return NULL;
1889 }
1890 /* and request update from server */
1891 if ((cache->cancellable && g_cancellable_is_cancelled(cache->cancellable)) ||
1892 !register_menu_to_server(cache))
1893 SET_CACHE_READY(cache);
1894 return NULL;
1895 }
1896
1897 /**
1898 * menu_cache_lookup
1899 * @menu_name: a menu name
1900 *
1901 * Searches for connection to menu-cached for @menu_name. If there is no
1902 * such connection exist then creates new one. Caller can be notified
1903 * when cache is (re)loaded by adding callback. Caller should check if
1904 * the cache is already loaded trying to retrieve its root.
1905 *
1906 * See also: menu_cache_add_reload_notify(), menu_cache_item_dup_parent().
1907 *
1908 * Returns: (transfer full): menu cache descriptor.
1909 *
1910 * Since: 0.1.0
1911 */
menu_cache_lookup(const char * menu_name)1912 MenuCache* menu_cache_lookup( const char* menu_name )
1913 {
1914 MenuCache* cache;
1915
1916 /* lookup in a hash table for already loaded menus */
1917 MENU_CACHE_LOCK;
1918 #if !GLIB_CHECK_VERSION(2, 32, 0)
1919 /* FIXME: destroy them on application exit? */
1920 if(!sync_run_mutex)
1921 sync_run_mutex = g_mutex_new();
1922 if(!sync_run_cond)
1923 sync_run_cond = g_cond_new();
1924 #endif
1925 if( G_UNLIKELY( ! hash ) )
1926 hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL );
1927 else
1928 {
1929 cache = (MenuCache*)g_hash_table_lookup(hash, menu_name);
1930 if( cache )
1931 {
1932 menu_cache_ref(cache);
1933 MENU_CACHE_UNLOCK;
1934 return cache;
1935 }
1936 }
1937 MENU_CACHE_UNLOCK;
1938
1939 cache = menu_cache_create(menu_name);
1940 cache->cancellable = g_cancellable_new();
1941 #if GLIB_CHECK_VERSION(2, 32, 0)
1942 cache->thr = g_thread_new(menu_name, menu_cache_loader_thread, cache);
1943 #else
1944 cache->thr = g_thread_create(menu_cache_loader_thread, cache, TRUE, NULL);
1945 #endif
1946 return cache;
1947 }
1948
1949 /**
1950 * menu_cache_lookup_sync
1951 * @menu_name: a menu name
1952 *
1953 * Searches for data from menu-cached for @menu_name. If no connection
1954 * exists yet then creates new one and retrieves all data.
1955 *
1956 * Returns: (transfer full): menu cache descriptor.
1957 *
1958 * Since: 0.3.1
1959 */
menu_cache_lookup_sync(const char * menu_name)1960 MenuCache* menu_cache_lookup_sync( const char* menu_name )
1961 {
1962 MenuCache* mc = menu_cache_lookup(menu_name);
1963 MenuCacheDir* root_dir = menu_cache_dup_root_dir(mc);
1964 /* ensure that the menu cache is loaded */
1965 if(root_dir)
1966 menu_cache_item_unref(MENU_CACHE_ITEM(root_dir));
1967 else /* if it's not yet loaded */
1968 {
1969 MenuCacheNotifyId notify_id;
1970 /* add stub */
1971 notify_id = menu_cache_add_reload_notify(mc, NULL, NULL);
1972 #if GLIB_CHECK_VERSION(2, 32, 0)
1973 g_mutex_lock(&sync_run_mutex);
1974 while(!mc->ready)
1975 g_cond_wait(&sync_run_cond, &sync_run_mutex);
1976 g_mutex_unlock(&sync_run_mutex);
1977 #else
1978 g_mutex_lock(sync_run_mutex);
1979 g_debug("menu_cache_lookup_sync: enter wait %p", mc);
1980 while(!mc->ready)
1981 g_cond_wait(sync_run_cond, sync_run_mutex);
1982 g_debug("menu_cache_lookup_sync: leave wait");
1983 g_mutex_unlock(sync_run_mutex);
1984 #endif
1985 menu_cache_remove_reload_notify(mc, notify_id);
1986 }
1987 return mc;
1988 }
1989
list_app_in_dir(MenuCacheDir * dir,GSList * list)1990 static GSList* list_app_in_dir(MenuCacheDir* dir, GSList* list)
1991 {
1992 GSList* l;
1993 for( l = dir->children; l; l = l->next )
1994 {
1995 MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
1996 switch( menu_cache_item_get_type(item) )
1997 {
1998 case MENU_CACHE_TYPE_DIR:
1999 list = list_app_in_dir( MENU_CACHE_DIR(item), list );
2000 break;
2001 case MENU_CACHE_TYPE_APP:
2002 list = g_slist_prepend(list, menu_cache_item_ref(item));
2003 break;
2004 case MENU_CACHE_TYPE_NONE:
2005 case MENU_CACHE_TYPE_SEP:
2006 break;
2007 }
2008 }
2009 return list;
2010 }
2011
2012 /**
2013 * menu_cache_list_all_apps
2014 * @cache: a menu cache descriptor
2015 *
2016 * Retrieves full list of applications in menu cache. Returned list
2017 * should be freed with g_slist_free_full(list, menu_cache_item_unref)
2018 * after usage.
2019 *
2020 * Returns: (transfer full) (element-type MenuCacheItem): list of items.
2021 *
2022 * Since: 0.1.2
2023 */
menu_cache_list_all_apps(MenuCache * cache)2024 GSList* menu_cache_list_all_apps(MenuCache* cache)
2025 {
2026 GSList* list;
2027 MENU_CACHE_LOCK;
2028 if (G_UNLIKELY(!cache->root_dir)) /* empty cache */
2029 list = NULL;
2030 else
2031 list = list_app_in_dir(cache->root_dir, NULL);
2032 MENU_CACHE_UNLOCK;
2033 return list;
2034 }
2035
2036 /**
2037 * menu_cache_get_desktop_env_flag
2038 * @cache: a menu cache descriptor
2039 * @desktop_env: desktop environment name
2040 *
2041 * Makes bit mask of desktop environment from its name. The @desktop_env
2042 * may be simple string or colon separated list of compatible session
2043 * names according to XDG_CURRENT_DESKTOP freedesktop.org specification.
2044 *
2045 * Returns: DE bit mask.
2046 *
2047 * Since: 0.2.0
2048 */
menu_cache_get_desktop_env_flag(MenuCache * cache,const char * desktop_env)2049 guint32 menu_cache_get_desktop_env_flag( MenuCache* cache, const char* desktop_env )
2050 {
2051 char** de;
2052 char **envs;
2053 guint32 flags = 0;
2054 int j;
2055
2056 if (desktop_env == NULL || desktop_env[0] == '\0')
2057 return flags;
2058
2059 envs = g_strsplit(desktop_env, ":", -1);
2060 MENU_CACHE_LOCK;
2061 de = cache->known_des;
2062 for (j = 0; envs[j]; j++)
2063 {
2064 if( de )
2065 {
2066 int i;
2067 for( i = 0; de[i]; ++i )
2068 if (strcmp(envs[j], de[i]) == 0)
2069 break;
2070 if (de[i])
2071 {
2072 flags |= 1 << (i + N_KNOWN_DESKTOPS);
2073 continue;
2074 }
2075 }
2076 if (strcmp(envs[j], "GNOME") == 0)
2077 flags |= SHOW_IN_GNOME;
2078 else if (strcmp(envs[j], "KDE") == 0)
2079 flags |= SHOW_IN_KDE;
2080 else if (strcmp(envs[j], "XFCE") == 0)
2081 flags |= SHOW_IN_XFCE;
2082 else if (strcmp(envs[j], "LXDE") == 0)
2083 flags |= SHOW_IN_LXDE;
2084 else if (strcmp(envs[j], "ROX") == 0)
2085 flags |= SHOW_IN_ROX;
2086 }
2087 MENU_CACHE_UNLOCK;
2088 g_strfreev(envs);
2089 return flags;
2090 }
2091
_scan_by_id(MenuCacheItem * item,const char * id)2092 static MenuCacheItem *_scan_by_id(MenuCacheItem *item, const char *id)
2093 {
2094 GSList *l;
2095
2096 if (item)
2097 switch (menu_cache_item_get_type(item))
2098 {
2099 case MENU_CACHE_TYPE_DIR:
2100 for (l = MENU_CACHE_DIR(item)->children; l; l = l->next)
2101 {
2102 item = _scan_by_id(MENU_CACHE_ITEM(l->data), id);
2103 if (item)
2104 return item;
2105 }
2106 break;
2107 case MENU_CACHE_TYPE_APP:
2108 if (g_strcmp0(menu_cache_item_get_id(item), id) == 0)
2109 return item;
2110 break;
2111 default: ;
2112 }
2113 return NULL;
2114 }
2115
2116 /**
2117 * menu_cache_find_item_by_id
2118 * @cache: a menu cache descriptor
2119 * @id: item ID (name such as 'application.desktop')
2120 *
2121 * Searches if @id already exists within @cache and returns found item.
2122 * Returned data should be freed with menu_cache_item_unref() after usage.
2123 *
2124 * Returns: (transfer full): found item or %NULL.
2125 *
2126 * Since: 0.5.0
2127 */
menu_cache_find_item_by_id(MenuCache * cache,const char * id)2128 MenuCacheItem *menu_cache_find_item_by_id(MenuCache *cache, const char *id)
2129 {
2130 MenuCacheItem *item = NULL;
2131
2132 MENU_CACHE_LOCK;
2133 if (cache && id)
2134 item = _scan_by_id(MENU_CACHE_ITEM(cache->root_dir), id);
2135 if (item)
2136 menu_cache_item_ref(item);
2137 MENU_CACHE_UNLOCK;
2138 return item;
2139 }
2140
list_app_in_dir_for_cat(MenuCacheDir * dir,GSList * list,const char * id)2141 static GSList* list_app_in_dir_for_cat(MenuCacheDir *dir, GSList *list, const char *id)
2142 {
2143 const char **cat;
2144 GSList *l;
2145
2146 for (l = dir->children; l; l = l->next)
2147 {
2148 MenuCacheItem *item = MENU_CACHE_ITEM(l->data);
2149 switch (item->type)
2150 {
2151 case MENU_CACHE_TYPE_DIR:
2152 list = list_app_in_dir_for_cat(MENU_CACHE_DIR(item), list, id);
2153 break;
2154 case MENU_CACHE_TYPE_APP:
2155 cat = MENU_CACHE_APP(item)->categories;
2156 if (cat) while (*cat)
2157 if (*cat++ == id)
2158 {
2159 list = g_slist_prepend(list, menu_cache_item_ref(item));
2160 break;
2161 }
2162 break;
2163 case MENU_CACHE_TYPE_NONE:
2164 case MENU_CACHE_TYPE_SEP:
2165 break;
2166 }
2167 }
2168 return list;
2169 }
2170
2171 /**
2172 * menu_cache_list_all_for_category
2173 * @cache: a menu cache descriptor
2174 * @category: category to list items
2175 *
2176 * Retrieves list of applications in menu cache which have @category in
2177 * their list of categories. The search is case-sensitive. Returned list
2178 * should be freed with g_slist_free_full(list, menu_cache_item_unref)
2179 * after usage.
2180 *
2181 * Returns: (transfer full) (element-type MenuCacheItem): list of items.
2182 *
2183 * Since: 1.0.0
2184 */
menu_cache_list_all_for_category(MenuCache * cache,const char * category)2185 GSList *menu_cache_list_all_for_category(MenuCache* cache, const char *category)
2186 {
2187 GQuark q;
2188 GSList *list;
2189
2190 g_return_val_if_fail(cache != NULL && category != NULL, NULL);
2191 q = g_quark_try_string(category);
2192 if (q == 0)
2193 return NULL;
2194 MENU_CACHE_LOCK;
2195 if (G_UNLIKELY(cache->root_dir == NULL))
2196 list = NULL;
2197 else
2198 list = list_app_in_dir_for_cat(cache->root_dir, NULL, g_quark_to_string(q));
2199 MENU_CACHE_UNLOCK;
2200 return list;
2201 }
2202
list_app_in_dir_for_kw(MenuCacheDir * dir,GSList * list,const char * kw)2203 static GSList* list_app_in_dir_for_kw(MenuCacheDir *dir, GSList *list, const char *kw)
2204 {
2205 GSList *l;
2206
2207 for (l = dir->children; l; l = l->next)
2208 {
2209 MenuCacheItem *item = MENU_CACHE_ITEM(l->data);
2210 switch (item->type)
2211 {
2212 case MENU_CACHE_TYPE_DIR:
2213 list = list_app_in_dir_for_kw(MENU_CACHE_DIR(item), list, kw);
2214 break;
2215 case MENU_CACHE_TYPE_APP:
2216 if (strstr(MENU_CACHE_APP(item)->keywords, kw) != NULL)
2217 list = g_slist_prepend(list, menu_cache_item_ref(item));
2218 break;
2219 case MENU_CACHE_TYPE_NONE:
2220 case MENU_CACHE_TYPE_SEP:
2221 break;
2222 }
2223 }
2224 return list;
2225 }
2226
2227 /**
2228 * menu_cache_list_all_for_keyword
2229 * @cache: a menu cache descriptor
2230 * @keyword: a keyword to search
2231 *
2232 * Retrieves list of applications in menu cache which have a @keyword
2233 * as either a word or part of word in exec command, name, generic name
2234 * or defined keywords. The search is case-insensitive. Returned list
2235 * should be freed with g_slist_free_full(list, menu_cache_item_unref)
2236 * after usage.
2237 *
2238 * Returns: (transfer full) (element-type MenuCacheItem): list of items.
2239 *
2240 * Since: 1.0.0
2241 */
menu_cache_list_all_for_keyword(MenuCache * cache,const char * keyword)2242 GSList *menu_cache_list_all_for_keyword(MenuCache* cache, const char *keyword)
2243 {
2244 char *casefolded = g_utf8_casefold(keyword, -1);
2245 GSList *list;
2246
2247 g_return_val_if_fail(cache != NULL && keyword != NULL, NULL);
2248 MENU_CACHE_LOCK;
2249 if (G_UNLIKELY(cache->root_dir == NULL))
2250 list = NULL;
2251 else
2252 list = list_app_in_dir_for_kw(cache->root_dir, NULL, casefolded);
2253 MENU_CACHE_UNLOCK;
2254 g_free(casefolded);
2255 return list;
2256 }
2257