Menu right click adding/deleting submenus and launchers hack

Discussion on LXDE releases and Development. This forum is not the best way to contact the developers, please use the Development mailing list and Sourceforge to interact with them.
Locked
paxman
Posts: 10
Joined: Sat May 02, 2009 6:00 pm

Menu right click adding/deleting submenus and launchers hack

Post 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.
Locked