4
* Description: Generate menu from desktop files according to the spec on freedesktop.org
7
* Author: Hong Jen Yee (PCMan) <pcman.tw (AT) gmail.com>, (C) 2006
9
* Copyright: GNU Lesser General Public License Version 2
14
#include <glib/gi18n.h>
18
#include "ptk-app-menu.h"
20
/* Compatibility macros for older versions of glib */
21
#if ! GLIB_CHECK_VERSION(2, 10, 0)
22
/* older versions of glib don't provde g_slice API */
23
#define g_slice_alloc(size) g_malloc(size)
24
#define g_slice_alloc0(size) g_malloc0(size)
25
#define g_slice_new(type) g_new(type, 1)
26
#define g_slice_new0(type) g_new0(type, 1)
27
#define g_slice_free(type, mem) g_free(mem)
28
#define g_slice_free1(size, mem) g_free(mem)
31
#include "misc.h" /* Misc functions for lxpanel */
35
GQuark PTK_APP_MENU_ITEM_ID = 0;
37
GtkWidget* ptk_app_menu_new();
39
static const char desktop_ent[] = "Desktop Entry";
40
static const char app_dir_name[] = "applications";
42
static time_t* times = NULL;
45
typedef struct _CatInfo
50
const char** sub_cats;
53
typedef struct _PtkAppMenuItem
60
static const char* development_cats[]={
63
"Building","Debugger",
72
static const char* office_cats[] = {
82
static const char* graphics_cats[] = {
92
static const char* network_cats[] = {
107
static const char* settings_cats[] = {
115
static const char* system_cats[] = {
123
static const char* audiovideo_cats[] ={
139
static const char* game_cats[] = {
145
static const char* education_cats[] = {
150
static const char* utility_cats[] = {
155
static const CatInfo known_cats[]=
157
{N_("Other"), "Other", "gnome-other", NULL},
158
{N_("Game"), "Games", "gnome-joystick", game_cats},
159
{N_("Education"), "Education", "gnome-amusements", education_cats},
160
{N_("Development"), "Development", "gnome-devel", development_cats},
161
{N_("Audio & Video"), "Multimedia", "gnome-multimedia", audiovideo_cats},
162
{N_("Graphics"), "Graphics", "gnome-graphics", graphics_cats},
163
{N_("Settings"), "Settings", "gnome-settings", settings_cats},
164
{N_("System Tools"), "System-Tools", "gnome-system", system_cats},
165
{N_("Network"), "Internet", "gnome-globe", network_cats},
166
{N_("Office"), "Office", "gnome-applications", office_cats},
167
{N_("Accessories"), "Accessories", "gnome-util", utility_cats}
170
static int find_cat( char** cats )
173
for( cat = cats; *cat; ++cat )
177
for( i = 1; i < G_N_ELEMENTS(known_cats); ++i )
179
const char** sub_cats = known_cats[i].sub_cats;
182
if( 0 == strncmp(*cat, "X-", 2) ) /* Desktop specific*/
184
if( 0 == strcmp( *sub_cats, *cat ) )
193
static void app_dirs_foreach( GFunc func, gpointer user_data );
195
static int compare_menu_item_titles( gpointer a, gpointer b )
197
const gchar *title_a, *title_b;
198
title_a = gtk_label_get_text( GTK_LABEL(gtk_bin_get_child(GTK_BIN(a))) );
199
title_b = gtk_label_get_text( GTK_LABEL(gtk_bin_get_child(GTK_BIN(b))) );
200
return g_ascii_strcasecmp(title_a, title_b);
203
static int find_menu_item_by_name( gpointer a, gpointer b )
205
PtkAppMenuItem* data = g_object_get_qdata( G_OBJECT(a), PTK_APP_MENU_ITEM_ID );
206
const char* name = (char*)b;
207
return strcmp(data->name, name);
210
/* Moved to misc.c of lxpanel to be used in other plugins */
212
static char* translate_exec( const char* exec, const char* icon,
213
const char* title, const char* fpath )
215
GString* cmd = g_string_sized_new( 256 );
216
for( ; *exec; ++exec )
218
if( G_UNLIKELY(*exec == '%') )
226
g_string_append( cmd, title );
231
g_string_append( cmd, "--icon " );
232
g_string_append( cmd, icon );
237
char* uri = g_filename_to_uri( fpath, NULL, NULL );
238
g_string_append( cmd, uri );
243
g_string_append_c( cmd, '%' );
248
g_string_append_c( cmd, *exec );
250
return g_string_free( cmd, FALSE );
254
static char* load_cat_title( GKeyFile* kf, CatInfo* inf )
257
char* fn = g_strconcat( "desktop-directories/", inf->directory_file, ".directory", NULL );
258
if( g_key_file_load_from_data_dirs( kf, fn, NULL, 0, NULL ) )
259
ret = g_key_file_get_locale_string( kf, desktop_ent, "Name", NULL, NULL );
264
static void unload_old_icons( GtkWidget* menu )
266
GList* items = gtk_container_get_children( GTK_CONTAINER(menu) );
268
for( l = items; l; l = l->next )
270
GtkWidget* sub_menu = gtk_menu_item_get_submenu( GTK_MENU_ITEM(l->data) );
272
if( ! GTK_IS_IMAGE_MENU_ITEM(l->data) )
274
img = gtk_image_menu_item_get_image( (GtkImageMenuItem*)l->data );
275
if( ! g_object_get_qdata( G_OBJECT(l->data), PTK_APP_MENU_ITEM_ID ) )
278
gtk_widget_destroy( img );
280
unload_old_icons( sub_menu );
282
g_list_free( items );
285
static void on_menu_item_size_request( GtkWidget* item,
289
int min_height = ICON_SIZE + (GTK_CONTAINER(item)->border_width +
290
item->style->ythickness) * 2;
291
if( req->height < min_height ) {
292
req->height = min_height;
294
if( req->width < ICON_SIZE )
295
req->width = ICON_SIZE;
298
static gboolean on_menu_item_expose( GtkWidget* item,
304
PtkAppMenuItem* data = (PtkAppMenuItem*)user_data;
307
if( !GTK_IS_IMAGE_MENU_ITEM(item) )
309
img = GTK_WIDGET(gtk_image_menu_item_get_image((GtkImageMenuItem *) item));
312
if( G_UNLIKELY(!data) || G_UNLIKELY(!data->icon) )
315
if( data->icon[0] == '/' )
317
pix = gdk_pixbuf_new_from_file_at_size(data->icon, ICON_SIZE, ICON_SIZE, NULL);
322
inf = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), data->icon, ICON_SIZE, 0);
325
pix = gdk_pixbuf_new_from_file_at_size( gtk_icon_info_get_filename(inf), ICON_SIZE, ICON_SIZE, NULL);
326
gtk_icon_info_free ( inf );
331
img = gtk_image_new_from_pixbuf( pix );
333
g_object_unref( pix );
337
img = gtk_image_new();
338
gtk_image_set_pixel_size( GTK_IMAGE(img), ICON_SIZE );
340
gtk_image_menu_item_set_image( (GtkImageMenuItem *) item, img );
344
static void on_app_menu_item_activate( GtkMenuItem* item, PtkAppMenuItem* data )
347
/* FIXME: support startup notification */
348
g_debug("run command: %s", data->exec);
349
if( !g_spawn_command_line_async( data->exec, &err ) )
351
/* FIXME: show error message */
356
static void ptk_app_menu_item_free( PtkAppMenuItem* data )
358
g_free( data->name );
359
g_free( data->icon );
360
g_free( data->exec );
361
g_slice_free( PtkAppMenuItem, data );
364
static void do_load_dir( int prefix_len,
368
GDir* dir = g_dir_open( path, 0, NULL );
372
if( G_UNLIKELY( ! dir ) )
375
file = g_key_file_new();
377
while( (name = g_dir_read_name( dir )) )
385
fpath = g_build_filename( path, name, NULL );
386
if( g_file_test(fpath, G_FILE_TEST_IS_DIR) )
388
do_load_dir( prefix_len, fpath, sub_menus );
392
if( ! g_str_has_suffix( name, ".desktop" ) )
397
if( ! g_key_file_load_from_file( file, fpath, 0, NULL ) )
402
if( g_key_file_get_boolean( file, desktop_ent, "NoDisplay", NULL ) )
407
only_show_in = g_key_file_get_string_list( file, desktop_ent, "OnlyShowIn", NULL, NULL );
411
g_strfreev( only_show_in );
414
cats = g_key_file_get_string_list( file, desktop_ent, "Categories", NULL, NULL );
417
int i = find_cat( cats );
420
GtkWidget* menu_item;
421
char *title, *exec, *icon;
422
/* FIXME: processing duplicated items */
423
exec = g_key_file_get_string( file, desktop_ent, "Exec", NULL);
426
title = g_key_file_get_locale_string( file, desktop_ent, "Name", NULL, NULL);
429
PtkAppMenuItem* data;
431
prev =g_list_find_custom( sub_menus[i], (fpath + prefix_len),
432
(GCompareFunc) find_menu_item_by_name );
435
menu_item = gtk_image_menu_item_new_with_label( title );
436
data = g_slice_new0(PtkAppMenuItem);
441
menu_item = GTK_WIDGET(prev->data);
442
label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(menu_item)));
443
data = (PtkAppMenuItem*)g_object_get_qdata( G_OBJECT(menu_item), PTK_APP_MENU_ITEM_ID );
444
gtk_label_set_text( label, title );
445
g_free( data->name );
446
g_free( data->exec );
447
g_free( data->icon );
449
data->name = g_strdup( fpath + prefix_len );
450
data->exec = exec ? translate_exec_to_cmd( exec, data->icon, title, fpath ) : NULL;
452
g_signal_connect( menu_item, "expose-event",
453
G_CALLBACK(on_menu_item_expose), data );
454
g_signal_connect( menu_item,
456
G_CALLBACK(on_menu_item_size_request), data );
457
icon = g_strdup( g_key_file_get_string( file, desktop_ent, "Icon", NULL) );
460
char* dot = strchr( icon, '.' );
461
if( icon[0] !='/' && dot )
467
g_signal_connect( menu_item, "activate",
468
G_CALLBACK(on_app_menu_item_activate), data );
469
g_object_set_qdata_full( G_OBJECT(menu_item), PTK_APP_MENU_ITEM_ID, data,
470
(GDestroyNotify) ptk_app_menu_item_free );
471
sub_menus[i] = g_list_insert_sorted( sub_menus[i],
472
(gpointer) menu_item,
473
(GCompareFunc) compare_menu_item_titles );
483
g_key_file_free( file );
487
static void load_dir( const char* path, GList** sub_menus )
489
do_load_dir( strlen( path ) + 1, path, sub_menus );
492
#if defined( PTK_APP_MENU_DEMO )
493
static GtkWidget* app_menu = NULL;
494
static void on_menu( GtkWidget* btn, gpointer user_data )
496
if( ptk_app_menu_need_reload() )
499
gtk_widget_destroy( app_menu );
500
app_menu = ptk_app_menu_new();
503
app_menu = ptk_app_menu_new();
504
gtk_menu_popup(GTK_MENU(app_menu), NULL, NULL, NULL, NULL, 0, 0 );
508
static void on_app_menu_destroy( gpointer user_data, GObject* menu )
510
g_signal_handler_disconnect( gtk_icon_theme_get_default(),
511
GPOINTER_TO_INT(user_data) );
514
gboolean ptk_app_menu_item_has_data( GtkMenuItem* item )
516
return (g_object_get_qdata( G_OBJECT(item), PTK_APP_MENU_ITEM_ID ) != NULL);
520
* Insert application menus into specified menu
521
* menu: The parent menu to which the items should be inserted
522
* pisition: Position to insert items.
523
Passing -1 in this parameter means append all items
526
void ptk_app_menu_insert_items( GtkMenu* menu, int position )
528
GList* sub_menus[ G_N_ELEMENTS(known_cats) ] = {0};
530
GList *sub_items, *l;
531
guint change_handler;
534
if( G_UNLIKELY( PTK_APP_MENU_ITEM_ID == 0 ) )
535
PTK_APP_MENU_ITEM_ID = g_quark_from_static_string( "PtkAppMenuItem" );
537
app_dirs_foreach( (GFunc) load_dir, sub_menus );
539
kf = g_key_file_new();
541
for( i = 0; i < G_N_ELEMENTS(known_cats); ++i )
544
GtkWidget* menu_item;
545
PtkAppMenuItem* data;
548
if( ! (sub_items = sub_menus[i]) )
550
sub_menu = GTK_MENU(gtk_menu_new());
552
for( l = sub_items; l; l = l->next )
553
gtk_menu_shell_append( GTK_MENU_SHELL(sub_menu), GTK_WIDGET(l->data) );
554
g_list_free( sub_items );
556
title = load_cat_title( kf, &known_cats[i] );
557
menu_item = gtk_image_menu_item_new_with_label( title ? title : _(known_cats[i].title) );
560
data = g_slice_new0( PtkAppMenuItem );
561
data->icon = g_strdup(known_cats[i].icon);
562
g_object_set_qdata_full( G_OBJECT(menu_item), PTK_APP_MENU_ITEM_ID, data, (GDestroyNotify) ptk_app_menu_item_free );
563
g_signal_connect( menu_item, "expose-event", G_CALLBACK(on_menu_item_expose), data );
564
g_signal_connect( menu_item, "size-request", G_CALLBACK(on_menu_item_size_request), data );
565
on_menu_item_expose( menu_item, NULL, data );
566
gtk_menu_item_set_submenu( GTK_MENU_ITEM(menu_item), GTK_WIDGET(sub_menu) );
569
gtk_menu_shell_append( GTK_MENU_SHELL(menu), menu_item );
572
gtk_menu_shell_insert( GTK_MENU_SHELL(menu), menu_item, position );
577
g_key_file_free( kf );
579
gtk_widget_show_all(GTK_WIDGET(menu));
580
change_handler = g_signal_connect_swapped( gtk_icon_theme_get_default(), "changed", G_CALLBACK(unload_old_icons), menu );
581
g_object_weak_ref( G_OBJECT(menu), on_app_menu_destroy, GINT_TO_POINTER(change_handler) );
584
GtkWidget* ptk_app_menu_new()
587
menu = gtk_menu_new();
588
ptk_app_menu_insert_items( GTK_MENU(menu), -1 );
592
static void app_dirs_foreach( GFunc func, gpointer user_data )
594
const char** sys_dirs = (const char**)g_get_system_data_dirs();
597
struct stat dir_stat;
599
len = g_strv_length((gchar **) sys_dirs);
601
times = g_new0( time_t, len + 2 );
602
for( i = 0; i < len; ++i )
604
path = g_build_filename( sys_dirs[i], app_dir_name, NULL );
605
if( stat( path, &dir_stat) == 0 )
607
times[i] = dir_stat.st_mtime;
608
func( path, user_data );
612
path = g_build_filename( g_get_user_data_dir(), app_dir_name, NULL );
613
times[i] = dir_stat.st_mtime;
614
if( stat( path, &dir_stat) == 0 )
616
times[i] = dir_stat.st_mtime;
617
func( path, user_data );
622
#if defined( PTK_APP_MENU_DEMO )
623
int main( int argc, char** argv )
625
gtk_init(&argc, &argv);
626
GtkWidget* window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
627
gtk_window_set_title(GTK_WINDOW( window ), "Show Applications" );
628
GtkWidget* button = gtk_button_new_with_label("Application Menu");
629
g_signal_connect(button, "clicked", G_CALLBACK(on_menu), NULL );
630
gtk_container_add( GTK_CONTAINER(window), button );
631
g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL );
632
gtk_widget_show_all(window);
634
gtk_widget_destroy( app_menu );
640
gboolean ptk_app_menu_need_reload( GtkWidget* menu )
642
const char** sys_dirs = (const char**)g_get_system_data_dirs();
645
struct stat dir_stat;
649
len = g_strv_length((gchar **) sys_dirs);
650
for( i = 0; i < len; ++i )
652
path = g_build_filename( sys_dirs[i], app_dir_name, NULL );
653
if( stat( path, &dir_stat) == 0 )
655
if( times[i] != dir_stat.st_mtime )
663
path = g_build_filename( g_get_user_data_dir(), app_dir_name, NULL );
664
if( stat( path, &dir_stat) == 0 )
666
if( times[i] != dir_stat.st_mtime )