Page 1 of 1

Menu right click adding/deleting submenus and launchers hack

Posted: Fri Jul 10, 2009 3:28 pm
by paxman
I've made some ugly hack to implement adding/deleting submenus/launchers from menus, to conform (to some extent) to this.
The main code is as follows:

Code: Select all

//some constants

const static char *  header = "<!DOCTYPE Menu PUBLIC \"-//freedesktop//DTD Menu 1.0//EN\" \"http://www.freedesktop.org/standards/menu-spec/1.0/menu.dtd\">\n<Menu>\n\t<Name>Applications</Name>\n<MergeFile type=\"parent\">/etc/xdg/menus/lxde-applications.menu</MergeFile>\n";
const static char *  footer = "\n</Menu>";
const static char *  menu_start = "\n\t\t<Menu>";
const static char *  menu_end = "\n\t\t</Menu>";
const static char *  name_start = "\n\t\t\t<Name>";
const static char *  name_end = "</Name>";
const static char *  inc_exc = "\n\t\t\t<Include>\n\t\t\t</Include>\n\t\t\t<Exclude>\n\t\t\t</Exclude>";
const static char *  menu_inc_exc = "\n\t\t\t<Include>\n\t\t\t<Filename>empty.desktop</Filename>\n\t\t\t</Include>\n\t\t\t<Exclude>\n\t\t\t<Filename>empty.desktop</Filename>\n\t\t\t</Exclude>";
const static char menus[12][17] =  { "Accessories",
									"Universal Access",
									"Development",
									"Education",
									"Games",
									"Graphics",
									"Internet",
									"Multimedia",
									"Office",
									"System",
									"Other",
									"DesktopSettings"
								  };
//hack for showing empty menu
const static char * empty_file = "[Desktop Entry]\nType=Application\nName=\nExec=\n";

/*
 * <Menu>
 * <Include>
 * desktop_file.desktop
 * </Include>
 * <Menu>
 *
 * -find menu_name in string
 * -if (found)
 *     find <Include>
 *     insert "<Filename>desktop_file</Filename>" after first <Include>
 * else:
 *    error!
 *
*/
static int add_launcher_to_menu(char * desktop_file, char * menu_name)
{
	GString * file;
	char * menu_pos;
	char * include_pos;
	char * user_file;
	char * include_string;

	user_file = get_user_file_path();

	if(user_file_exists(user_file))
	{
		file = read_user_file();
	}
	else
	{
		file = create_user_file();
	}

	if(file)
	{
		//menu position
		if((menu_pos= g_strstr_len(file->str,-1,menu_name)))
		{
			//include position
			if((include_pos = g_strstr_len(menu_pos,-1,"<Include>")))
			{
				include_string = g_strconcat("<Filename>",desktop_file,".desktop","</Filename>",NULL);
				file = g_string_insert(file,include_pos-(file->str)+9,include_string);

				return write_user_file(file);
			}
			else
			{
				g_debug("Menu hasn't got <Include> section!");
			}
		}
		else
		{
			g_debug("Menu name %s does not exist!",menu_name);
		}
	}
	else
	{
		g_debug("File read eror!");
	}

	return 0;
}

/*
 * <Menu>
 * <Exclude>
 * desktop_file.desktop
 * </Exclude>
 *
 * -find menu_name in string
 * - find <Include> & </Include>
 * - if (desktop_file) between <Inc> & </Inc>
 *		remove <Filename>desktop_file</Filename>
 * else:
 * 		find <Exclude> and </Exclude>
 * 		if (found):
 * 			insert <Filename>"desktop_file"</Filename> after <Exclude>
 *      else
 *      	error!
 *
*/
static int remove_launcher_from_menu(char* desktop_file, char * menu_name)
{
	GString * file;
	char * user_file;
	gchar * include_string;

	//positions
	char * menu_pos;
	char * include_start;
	char * include_end;

	char * filename_pos;
	char * filename_start;
	char * filename_end;

	char * exclude_start;

	user_file = get_user_file_path();

	if(user_file_exists(user_file))
	{

		file = read_user_file();
	}
	else
	{
		file = create_user_file();
	}

	if(file)
	{
		//menu position
		if((menu_pos = g_strstr_len(file->str,-1,menu_name)))
		{
			//include_start position
			if((include_start = g_strstr_len(menu_pos,-1,"<Include>")))
			{
				//include end position
				include_end = g_strstr_len(include_start,-1,"</Include>");

				//if already under includes
				if((filename_pos = g_strstr_len(include_start,(include_end-include_start),desktop_file)))
				{
					//just erase it from <Include>
					if((filename_start = g_strrstr_len(include_start,(filename_pos-include_start),"<Filename>")))
					{
						if((filename_end = g_strstr_len(filename_pos,-1,"</Filename>")))
						{
							//erase it
							file = g_string_erase(file,filename_start-(file->str),(filename_end-filename_start)+11);
							return write_user_file(file);
						}
						else
						{
							g_debug("No Filename end tag!");
						}
					}
					else
					{
						g_debug("Desktop file not under <Filename> section!");
					}
				}
				else
				{
					//find excludes and add it
					exclude_start = g_strstr_len(menu_pos,-1,"<Exclude>");

					include_string = g_strconcat("<Filename>",desktop_file,"</Filename>\n",NULL);
					file = g_string_insert(file,exclude_start-(file->str)+10,include_string);
					printf("%s",file->str);
					return write_user_file(file);
				}

			}
			else
			{
				g_debug("Menu hasn't got <Include> section!");
			}
		}
		else
		{
			g_debug("Menu name %s does not exist!",menu_name);
		}

	}

	return 0;
}

/*
 *
 * - find menu_name
 * - if (found):
 * 		-add <Menu><Name>submenu_name</Name><Directory>...</Directory><Include></Include>..</Menu> after first occurence of </Exclude>
 *
*/
static int add_submenu_to_menu(char * submenu, char * menu_name)
{
	GString * file;
	char * user_file;
	gchar * include_string;

	//positions
	char * menu_pos;
	char * exclude_end;

	user_file = get_user_file_path();

	if(user_file_exists(user_file))
	{
		file = read_user_file();
	}
	else
	{
		file = create_user_file();
	}

	if(file)
	{
		//menu position
		//check if menu already exists?
		if((menu_pos= g_strstr_len(file->str,-1,menu_name)))
		{
			//find exc
			if((exclude_end = g_strstr_len(menu_pos,-1,"</Exclude>")))
			{
				include_string = g_strconcat("<Menu>\n<Name>",submenu,"</Name>\n","<Directory>",submenu,".directory","</Directory>",menu_inc_exc,"</Menu>\n",NULL);
				file = g_string_insert(file,exclude_end-(file->str)+10,include_string);

				if(create_empty_desktop_file())
				{
					return write_user_file(file);
				}
				else
				{
					g_debug("Couldn't create empty desktop file!");
				}
			}
			else
			{
				g_debug("Menu hasn't got Excludes end tag!");
			}
		}
		else
		{
			g_debug("Menu name %s does not exist!",menu_name);
		}
	}
	else
	{
		g_debug("Error reading file!");
	}

	return FALSE;
}

/*
 *
 * <Menu>
 * <Menu>
 *   <Deleted/>
 * </Menu>
 *
 * find menu_name
 * - if (found):
 *      find submenu name
 *      if(found)
 * 			-add <Deleted/> after first occurence of </Name>
 *
*/

static int remove_submenu_from_menu(char * submenu, char * menu_name)
{
	GString * file;
	char * menu_pos;
	char * submenu_pos;

	char * name_pos;
	char * user_file;

	user_file = get_user_file_path();

	if(user_file_exists(user_file))
	{
		file = read_user_file();
	}
	else
	{
		file = create_user_file();
	}

	if(file)
	{
		//menu position
		if((menu_pos= g_strstr_len(file->str,-1,menu_name)))
		{
			//submenu position
			if((submenu_pos= g_strstr_len(menu_pos,-1,submenu)))
			{
				//end of Name tag
				if((name_pos = g_strstr_len(submenu_pos,-1,"</Name>")))
				{
					//include_string = g_strconcat("<Filename>",desktop_file,".desktop","</Filename>",NULL);
					file = g_string_insert(file,name_pos-(file->str)+8,"<Deleted/>");

					return write_user_file(file);
				}
				else
				{
					g_debug("Menu hasn't got end Name tag!");
				}
			}
			else
			{
				g_debug("Menu %s has no submenu %s!",menu_name,submenu);
			}
		}
		else
		{
			g_debug("Menu name %s does not exist!",menu_name);
		}
	}
	else
	{
		g_debug("File read eror!");
	}

	return 0;
}


static char * get_user_file_path()
{
	return g_build_filename(g_get_user_config_dir(), "menus", "lxde-applications.menu", NULL );
}

static int user_file_exists(char * filename)
{
	return g_file_test (filename,G_FILE_TEST_EXISTS);
}


static GString * create_user_file()
{
	GString * out;

	out = g_string_new("");

	//header
	out = g_string_append(out,header);

	//menus'n'stuff
	int i;
	for (i = 0; i < 12; i++)
	{
		//append menu start -> <Menu>
		out = g_string_append(out,menu_start);

		//append menu name -> <Name>
		out = g_string_append(out,name_start);

		//menu name
		out = g_string_append(out,menus[i]);

		//append menu name -> </Name>
		out = g_string_append(out,name_end);

		//append includes/excludes
		out = g_string_append(out,inc_exc);

		//append menu end -> </Menu>
		out = g_string_append(out,menu_end);
	}

	//footer
	out = g_string_append(out,footer);

	return out;
}

static int create_empty_desktop_file()
{
	char * empty_filename;

	empty_filename = g_build_filename( g_get_user_data_dir(), "applications", "empty.desktop",NULL );

	if(!user_file_exists(empty_filename))
	{
		return g_file_set_contents (empty_filename,empty_file,-1,NULL);
	}
	else
		return TRUE;
}

static GString * read_user_file()
{
	char * file;
	char * in;
	GString * out;

	file = get_user_file_path();

	if(g_file_get_contents (file,&in,NULL,NULL))
	{
		out = g_string_new(in);
	}

	g_free(in);
	g_free(file);

	return out;
}

static int write_user_file(GString * in)
{
	char * filename;

	filename = get_user_file_path();

	return g_file_set_contents (filename,in->str,-1,NULL);
}
These are the common functions, that are then used in menu plugin. The deletes are managed from the menu plugin itself:

Code: Select all


//added
static void on_menu_item_delete(GtkMenuItem* item, MenuCacheApp* app)
{
	char * menu_name;
	char * desktop_id;
	MenuCacheItem * parent;

	parent = MENU_CACHE_ITEM(menu_cache_item_get_parent(MENU_CACHE_ITEM(app)));

	menu_name = menu_cache_item_get_id(parent);
	desktop_id = menu_cache_item_get_id(MENU_CACHE_ITEM(app));

	g_debug("Removing launcher %s from menu %s.",desktop_id,menu_name);

	/*
	 * FIXME:
	 * - backup user file?
	 * - error handling
	 */
	delete_launcher_from_menu(desktop_id,menu_name);

	g_free(menu_name);
	g_free(desktop_id);
}

//doesn't work yet
static void on_menu_delete(GtkMenuItem* item, MenuCacheApp* app)
{
	char * menu_name;
	char * menu_id;
	MenuCacheItem * parent;

	parent = MENU_CACHE_ITEM(menu_cache_item_get_parent(MENU_CACHE_ITEM(app)));

	menu_name = menu_cache_item_get_id(parent);
	menu_id = menu_cache_item_get_id(MENU_CACHE_ITEM(app));

	g_debug("Removing submenu %s from menu %s.",menu_id,menu_name);

	/*
	 * FIXME:
	 * - backup user file?
	 * - error handling
	 */
 	 delete_submenu_from_menu(menu_id,menu_name);

	//g_free(menu_name);
	//g_free(menu_id);
}
while the addes are made by modified LXshortcut:

Code: Select all

//changed
if( ! ifile && ! ofile && !menu && !desktop)
        return 1;

//two added arguments
static char* menu = NULL;
static char* desktop = NULL;

static GOptionEntry opt_entries[] =
{
    {"no-display", 'n', 0, G_OPTION_ARG_NONE, &no_display, NULL, NULL},
    {"input", 'i', 0, G_OPTION_ARG_FILENAME, &ifile, N_("input file"), N_("file name or desktop id")},
    {"output", 'o', 0, G_OPTION_ARG_FILENAME, &ofile, N_("file name"), NULL},
    //added
    {"desktop", 'd', 0, G_OPTION_ARG_STRING, &desktop, N_("create desktop file and add it to the given menu name"), NULL},
    {"menu", 'm', 0, G_OPTION_ARG_STRING, &menu, N_("create menu file and add it to the given menu name"), NULL},
    { NULL }
};
then in the main function in "if( gtk_dialog_run( GTK_DIALOG(dlg) ) == GTK_RESPONSE_OK ) clause":

Code: Select all

 //for LXPanel's 'Add new Item','Add new submenu' shortcut output filename
        if(desktop)
        {
        	ofile = g_build_filename( g_get_user_data_dir(), "applications", g_strconcat(gtk_entry_get_text( GTK_ENTRY(name)),".desktop",NULL),NULL );
        }
        else
        if(menu)
        {
        	ofile = g_build_filename( g_get_user_data_dir(), "desktop-directories",g_strconcat(gtk_entry_get_text( GTK_ENTRY(name)),".directory",NULL), NULL );
        }
//and just before the end of the clause
if(desktop)
        {
        	add_new_launcher_to_menu(gtk_entry_get_text( GTK_ENTRY(name)),desktop);
        }
        else
        if(menu)
        {
        	add_submenu_to_menu(gtk_entry_get_text(GTK_ENTRY(name)),menu);
        }
which is invoked from menu with the right argument:

Code: Select all

static void on_menu_item_add(GtkMenuItem* item, MenuCacheApp* app)
{
	char* desktop;
	char* menu_name;
	char t[200];

	menu_name = menu_cache_item_get_id(MENU_CACHE_ITEM(app));

	g_debug("%s",menu_name);

	char* argv[] = {
			"lxshortcut",
			"-d",
			NULL,
			NULL};

	argv[2] = menu_name;

	g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL );

	//g_free( cats );
}
from the added right click menu (on_menu_button_press):

Code: Select all

//modified
static gboolean on_menu_button_press(GtkWidget* mi, GdkEventButton* evt, MenuCacheItem* data)
{
    if( evt->button == 3 )  /* right */
    {
        char* tmp;
        GtkWidget* item;
        GtkMenu* p = GTK_MENU(gtk_menu_new());

        tmp = g_find_program_in_path("lxshortcut");

        if(menu_cache_item_get_type(data)==MENU_CACHE_TYPE_APP)
        {
        	item = gtk_menu_item_new_with_label(_("Add to desktop"));
        	g_signal_connect(item, "activate", G_CALLBACK(on_add_menu_item_to_desktop), data);
        	gtk_menu_shell_append(GTK_MENU_SHELL(p), item);

        	item = gtk_menu_item_new_with_label(_("Delete launcher"));
        	g_signal_connect(item, "activate", G_CALLBACK(on_menu_item_delete), data);
        	gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
        }
        else
	    if(menu_cache_item_get_type(data)==MENU_CACHE_TYPE_DIR)
		{
	    	if( tmp )
	    	{
	    		item = gtk_menu_item_new_with_label(_("Add launcher"));
	    		g_signal_connect(item, "activate", G_CALLBACK(on_menu_item_add), data);
	    		gtk_menu_shell_append(GTK_MENU_SHELL(p), item);

	    		item = gtk_menu_item_new_with_label(_("Add menu"));
	    		g_signal_connect(item, "activate", G_CALLBACK(on_menu_add), data);
	    		gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
	    	}

	    	item = gtk_menu_item_new_with_label(_("Delete menu"));
	    	g_signal_connect(item, "activate", G_CALLBACK(on_menu_delete), data);
	    	gtk_menu_shell_append(GTK_MENU_SHELL(p), item);

		}

        if( tmp )
        {
			item = gtk_separator_menu_item_new();
			gtk_menu_shell_append(GTK_MENU_SHELL(p), item);

			item = gtk_menu_item_new_with_label(_("Properties"));
			g_signal_connect(item, "activate", G_CALLBACK(on_menu_item_properties), data);
			gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
        }

        g_free(tmp);

        g_signal_connect(p, "selection-done", G_CALLBACK(gtk_widget_destroy), NULL);
        g_signal_connect(p, "deactivate", G_CALLBACK(restore_grabs), mi);

        gtk_widget_show_all(GTK_WIDGET(p));
        gtk_menu_popup(p, NULL, NULL, NULL, NULL, 0, evt->time);
        return TRUE;
    }
    return FALSE;
}
EDIT: for exclusion to work, menu-cache-gen.c must also be modified, like this:

Code: Select all

//in write_dir(...) function:
else if( type == GMENU_TREE_ITEM_ENTRY )
        {
               //edited
        	if(!gmenu_tree_entry_get_is_excluded((GMenuTreeEntry*)item))
        		write_entry( of, (GMenuTreeEntry*)item );
        }
Any comments are welcomed.
NOTE: this is only a POC - it needs more error checkng, cleaning up, testing and optimizations. Should it be better to use XML parser? (Have looked at GMarkup but I think it is a bit overkill, for simple inserting/deleting of a short string) Use only basic char arrays? (GString has simple erase/insert functions). Memory usage? (Many needles in haystack searches, (eventually?) long GStrings).
EDIT: added some new functions, more cleanup, changed right click menu.
EDIT2: deleting submenu now works, fixed a launcher delete bug, cleaned code to mostly use only glib functions.