#!/usr/bin/env python

import os
import sys
import getopt
import gtk
import xdg.DesktopEntry as dentry
import xdg.Exceptions as exc
import xdg.BaseDirectory as bd
import ConfigParser
from operator import attrgetter

seticon = False
desktop = False
submenu = True
pekwmdynamic = False

de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-applications.directory')
applications = de.getName().encode('utf-8')
applications_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-accessories.directory')
accessories = de.getName().encode('utf-8')
accessories_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-development.directory')
development = de.getName().encode('utf-8')
development_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-education.directory')
education = de.getName().encode('utf-8')
education_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-games.directory')
games = de.getName().encode('utf-8')
games_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-graphics.directory')
graphics = de.getName().encode('utf-8')
graphics_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-multimedia.directory')
multimedia = de.getName().encode('utf-8')
multimedia_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-network.directory')
network = de.getName().encode('utf-8')
network_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-office.directory')
office = de.getName().encode('utf-8')
office_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-settings.directory')
settings = de.getName().encode('utf-8')
settings_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-system.directory')
system = de.getName().encode('utf-8')
system_icon = de.getIcon()
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-other.directory')
other = de.getName().encode('utf-8')
other_icon = de.getIcon()
# Find out which terminal emulator to use for apps that need to be
# launched in a terminal.
# First see if there is a user specified terminal emulator in the
# xdgmenumaker.cfg file.
try:
	config = ConfigParser.SafeConfigParser()
	config.read(os.path.expanduser('~/.config/xdgmenumaker.cfg'))
	terminal_app = config.get('Terminal', 'terminal')
# if there isn't, on debian and debian-likes, use the alternatives
# system, otherwise default to xterm
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e:
	if os.path.exists('/etc/alternatives/x-terminal-emulator') and os.path.exists('/usr/bin/x-terminal-emulator'):
		terminal_app = '/usr/bin/x-terminal-emulator'
	else:
		terminal_app = 'xterm'

def main(argv):
	global desktop
	global seticon
	global submenu
	global pekwmdynamic
	try:
		opts, args = getopt.getopt(argv, "hinf:", ["help", "icons" ,
			"no-submenu", "pekwm-dynamic", "format="])
	except getopt.GetoptError:
		usage()
		sys.exit(2)
	for opt, arg in opts:
		if opt in ("-h", "--help"):
			usage()
			sys.exit(0)
		elif opt in ("-i", "--icons"):
			seticon = True
		elif opt in ("-n", "--no-submenu"):
			submenu = False
		elif opt in ("--pekwm-dynamic"):
			pekwmdynamic = True
		elif opt in ("-f", "--format"):
			desktop = arg
	if desktop is False:
		usage()
		sys.exit('ERROR: You must specify the output format with -f')
	elif desktop == "fluxbox":
		fluxboxmenu()
	elif desktop == "windowmaker":
		seticon = False
		windowmakermenu()
	elif desktop == "icewm":
		icewmmenu()
	elif desktop == "pekwm":
		pekwmmenu()
	else:
		usage()
		sys.exit(2)

def usage():
	print 'USAGE:', os.path.basename(sys.argv[0]), '[OPTIONS]'
	print
	print 'OPTIONS:'
	print '    -f, --format         the output format to use. Valid options are fluxbox, icewm, windowmaker and pekwm'
	print '    -i, --icons          enable support for icons in the menus. Only works with fluxbox, icewm'
	print '    -n, --no-submenu     do not create a submenu. Does not work with windowmaker'
	print '        --pekwm-dynamic  generate dynamic menus for pekwm'
	print '    -h, --help           show this help message'
	print '  You have to specify the output format using the -f switch.'
	print
	print 'EXAMPLES:'
	print '    xdgmenumaker -f windowmaker'
	print '    xdgmenumaker -i -f fluxbox'

class MenuEntry:
	def __init__(self, category, name, icon, command, path):
		self.category = category
		self.name = name
		self.icon = icon
		self.command = command
		self.path = path
	
	def __repr__(self):
		return repr((self.category, self.name, self.icon, self.command,
			self.path))

def icon_full_path(icon):
	# If the icon path is absolute and exists, leave it alone.
	# This takes care of software that has its own icons stored
	# in non-standard directories
	if os.path.exists(icon) == True:
		return icon
	else:
		icon = os.path.basename(icon)
		if icon.endswith('.png'):
			icon = icon.replace('.png', '')
		elif icon.endswith('.svg'):
			icon = icon.replace('.svg', '')
		elif icon.endswith('.xpm'):
			icon = icon.replace('.xpm', '')
		icon_theme = gtk.icon_theme_get_default()
		icon = icon_theme.lookup_icon(icon, 16, gtk.ICON_LOOKUP_NO_SVG)
		if icon:
			icon = icon.get_filename()
		return icon

def get_entry_info(desktopfile):
	global desktop
	global seticon
	show = True
	de = dentry.DesktopEntry(filename = desktopfile)
	name = de.getName().encode('utf-8')

	if seticon == True:
		# strip the directory and extension from the icon name
		icon = de.getIcon()
		icon = icon_full_path(icon)
	else:
		icon = None

	hidden = de.getHidden()
	if hidden == True:
		show = False
	nodisplay = de.getNoDisplay()
	if nodisplay == True:
		show = False

	# removing any %U or %F from the exec line
	command = de.getExec().partition('%')[0]

	terminal = de.getTerminal()
	if terminal is True:
		command = terminal_app+' -e '+command

	path = de.getPath()
	if not path:
		path = None

	#cleaning up categories and keeping only registered freedesktop.org main categories
	categories = de.getCategories()
	if 'AudioVideo' in categories:
		category = multimedia
	elif 'Audio' in categories:
		category = multimedia
	elif 'Video' in categories:
		category = multimedia
	elif 'Development' in categories:
		category = development
	elif 'Education' in categories:
		category = education
	elif 'Game' in categories:
		category = games
	elif 'Graphics' in categories:
		category = graphics
	elif 'Network' in categories:
		category = network
	elif 'Office' in categories:
		category = office
	elif 'System' in categories:
		category = system
	elif 'Settings' in categories:
		category = settings
	elif 'Utility' in categories:
		category = accessories
	else:
		category = other

	onlyshowin = de.getOnlyShowIn()
	notshowin = de.getNotShowIn()
	# none of the freedesktop registered environments are supported by this anyway
	# http://standards.freedesktop.org/menu-spec/latest/apb.html
	if onlyshowin != []:
		show = False
	if desktop in notshowin:
		show = False
	if show == True:
		return [category, name, icon, command, path]
	else:
		return None

def sortedcategories(applist):
	categories = []
	for e in applist:
		categories.append(e.category)
	categories = sorted(set(categories))
	return categories

def desktopfilelist():
	dirs = []
	# some directories are mentioned twice in bd.xdg_data_dirs, once
	# with and once without a trailing /
	for i in bd.xdg_data_dirs:
		i = i.rstrip('/')
		if i not in dirs:
			dirs.append(i)
	filelist = []
	df_temp = []
	for d in dirs:
		xdgdir = d+'/applications'
		if os.path.isdir(xdgdir):
			for i in os.listdir(xdgdir):
				if i.endswith('.desktop'):
					# for duplicate .desktop files that exist in more
					# than one locations, only keep the first occurence.
					# That one should have precedence anyway (e.g.
					# ~/.local/share/applications has precedence over
					# /usr/share/applications
					if i not in df_temp:
						df_temp.append(i)
						filelist.append(xdgdir+'/'+i)
	return filelist

def menu():
	applist = []
	for desktopfile in desktopfilelist():
		try:
			e = get_entry_info(desktopfile)
			if e is not None:
				applist.append(MenuEntry(e[0], e[1], e[2], e[3], e[4]))
		except exc.ParsingError:
			pass

	sortedapplist = sorted(applist, key=attrgetter('category', 'name'))

	menu = []
	for c in sortedcategories(applist):
		appsincategory = []
		for i in sortedapplist:
			if i.category == c:
				appsincategory.append([i.name, i.icon, i.command,
						i.path])
		menu.append([c, appsincategory])
	return menu

def category_icon(category):
	if category == accessories:
		icon = accessories_icon
	elif category == development:
		icon = development_icon
	elif category == education:
		icon = education_icon
	elif category == games:
		icon = games_icon
	elif category == graphics:
		icon = graphics_icon
	elif category == multimedia:
		icon = multimedia_icon
	elif category == network:
		icon = network_icon
	elif category == office:
		icon = office_icon
	elif category == settings:
		icon = settings_icon
	elif category == system:
		icon = system_icon
	elif category == other:
		icon = other_icon
	else:
		icon = None
	return icon

def fluxboxmenu():
	global seticon
	global submenu
	if submenu is True:
		spacing = '  '
		if seticon == True:
			app_icon = icon_full_path(applications_icon)
			if app_icon is None:
				print '[submenu] ('+applications+')'
			else:
				print '[submenu] ('+applications+') <'+app_icon+'>'
		else:
			print '[submenu] ('+applications+')'
	else:
		spacing = ''
	for i in menu():
		category = i[0]
		if seticon == True:
			cat_icon = category_icon(category)
			cat_icon = icon_full_path(cat_icon)
			if cat_icon:
				print spacing+'[submenu] ('+category+') <'+cat_icon+'>'
			else:
				print spacing+'[submenu] ('+category+')'
		else:
			print spacing+'[submenu] ('+category+')'
		for j in i[1]:
			# closing parentheses need to be escaped, otherwise they are
			# cropped out, along with everything that comes after them
			name = j[0].replace(')', '\)')
			icon = j[1]
			command = j[2]
			path = j[3]
			if path is not None:
				command = 'cd '+path+' ; '+command
			if icon is None:
				print spacing+'  [exec] ('+name+') {'+command+'}'
			else:
				print spacing+'  [exec] ('+name+') {'+command+'} <'+icon+'>'
		print spacing+'[end] # ('+category+')'
	if submenu is True:
		print '[end] # ('+applications+')'

def windowmakermenu():
	print '"'+applications+'" MENU'
	for i in menu():
		category = i[0]
		print ' "'+category+'" MENU'
		for j in i[1]:
			name = j[0]
			command = j[2]
			print '  "'+name+'" EXEC '+command
		print ' "'+category+'" END'
	print '"'+applications+'" END'

def icewmmenu():
	global seticon
	global submenu
	if submenu is True:
		spacing = '  '
		if seticon == True:
			app_icon = icon_full_path(applications_icon)
			if app_icon is None:
				app_icon = "_none_"
			print 'menu "'+applications+'" '+app_icon+' {'
		else:
			print 'menu "'+applications+'" _none_ {'
	else:
		spacing = ''
	for i in menu():
		category = i[0]
		cat_icon = category_icon(category)
		cat_icon = icon_full_path(cat_icon)
		if seticon is True and cat_icon is not None:
			print spacing+'menu "'+category+'" '+cat_icon+' {'
		else:
			print spacing+'menu "'+category+'" _none_ {'
		for j in i[1]:
			name = j[0]
			icon = j[1]
			command = j[2]
			if seticon is True and icon is not None:
				print spacing+'  prog "'+name+'" '+icon+' '+command
			else:
				print spacing+'  prog "'+name+'" _none_ '+command
		print spacing+'}'
	if submenu is True:
		print '}'

def pekwmmenu():
	global seticon
	global submenu
	global pekwmdynamic
	if pekwmdynamic is True:
		print "Dynamic {"
		dspacing = '  '
	else:
		dspacing = ''
	if submenu is True:
		spacing = '  '
		if seticon == True:
			app_icon = icon_full_path(applications_icon)
			print dspacing+'Submenu = "'+applications+'" { Icon = "'+app_icon+'"'
		else:
			print dspacing+'Submenu = "'+applications+'" {'
	else:
		spacing = ''
	for i in menu():
		category = i[0]
		cat_icon = category_icon(category)
		cat_icon = icon_full_path(cat_icon)
		if seticon is True and cat_icon is not None:
			print dspacing+spacing+'Submenu = "'+category+'" { Icon = "'+cat_icon+'"'
		else:
			print dspacing+spacing+'Submenu = "'+category+'" {'
		for j in i[1]:
			name = j[0]
			icon = j[1]
			# for some apps (like netbeans) the command is launched with
			# /bin/sh "command"
			# and the quotes get mixed up with the quotes pekwm puts
			# around Actions, so we're just stripping the quotes
			command = j[2].replace('"', '')
			path = j[3]
			if path is not None:
				# pekwm doesn't like "cd path ; command", but it works
				# with "&&" and "||", so we'll launch the command even if the
				# path does not exist
				command = 'cd '+path+' && '+command+' || '+command
			if seticon is True and icon is not None:
				print dspacing+spacing+'  Entry = "'+name+'" { Icon = "'+icon+'"; Actions = "Exec '+command+' &" }'
			else:
				print dspacing+spacing+'  Entry = "'+name+'" { Actions = "Exec '+command+' &" }'
		print dspacing+spacing+'}'
	if submenu is True:
		print dspacing+'}'
	if pekwmdynamic is True:
		print "}"

if __name__ == "__main__":
	main(sys.argv[1:])
