A command line tool that generates XDG menus for several window managers
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

497 lines
14 KiB

13 years ago
#!/usr/bin/env python
import os
import sys
import getopt
import gtk
13 years ago
import xdg.DesktopEntry as dentry
import xdg.Exceptions as exc
import xdg.BaseDirectory as bd
import ConfigParser
13 years ago
from operator import attrgetter
seticon = False
iconsize = 16
13 years ago
desktop = False
submenu = True
pekwmdynamic = False
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-applications.directory')
applications = de.getName().encode('utf-8')
applications_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-accessories.directory')
accessories = de.getName().encode('utf-8')
accessories_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-development.directory')
development = de.getName().encode('utf-8')
development_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-education.directory')
education = de.getName().encode('utf-8')
education_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-games.directory')
games = de.getName().encode('utf-8')
games_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-graphics.directory')
graphics = de.getName().encode('utf-8')
graphics_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-multimedia.directory')
multimedia = de.getName().encode('utf-8')
multimedia_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-network.directory')
network = de.getName().encode('utf-8')
network_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-office.directory')
office = de.getName().encode('utf-8')
office_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-settings.directory')
settings = de.getName().encode('utf-8')
settings_icon = de.getIcon()
13 years ago
de = dentry.DesktopEntry(filename = '/usr/share/desktop-directories/xdgmenumaker-system.directory')
system = de.getName().encode('utf-8')
system_icon = de.getIcon()
13 years ago
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'
13 years ago
def main(argv):
global desktop
global seticon
global iconsize
global submenu
global pekwmdynamic
13 years ago
try:
opts, args = getopt.getopt(argv, "hins:f:", ["help", "icons" ,
"no-submenu", "pekwm-dynamic", "size=", "format="])
13 years ago
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 ("-s", "--size"):
try:
iconsize = int(arg)
except ValueError:
usage()
sys.exit('ERROR: size must be a number')
elif opt in ("-n", "--no-submenu"):
submenu = False
elif opt in ("--pekwm-dynamic",):
pekwmdynamic = True
13 years ago
elif opt in ("-f", "--format"):
desktop = arg
if not desktop:
13 years ago
usage()
sys.exit('ERROR: You must specify the output format with -f')
13 years ago
elif desktop == "fluxbox":
fluxboxmenu()
elif desktop == "windowmaker":
seticon = False
13 years ago
windowmakermenu()
elif desktop == "icewm":
icewmmenu()
elif desktop == "pekwm":
pekwmmenu()
elif desktop == "jwm":
jwmmenu()
13 years ago
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.'
print ' Valid options are fluxbox, icewm,'
print ' jwm, windowmaker and pekwm'
print ' -i, --icons enable support for icons in the'
print ' menus. Does not work with windowmaker'
print ' -s, --size preferred icon size in pixels (default: 16)'
print ' -n, --no-submenu do not create a submenu. Does not work with'
print ' 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.'
13 years ago
print
print 'EXAMPLES:'
print ' xdgmenumaker -f windowmaker'
print ' xdgmenumaker -i -f fluxbox'
13 years ago
class MenuEntry:
def __init__(self, category, name, icon, command, path):
13 years ago
self.category = category
self.name = name
self.icon = icon
self.command = command
self.path = path
13 years ago
def __repr__(self):
return repr((self.category, self.name, self.icon, self.command,
self.path))
13 years ago
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):
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, iconsize, gtk.ICON_LOOKUP_NO_SVG)
if icon:
icon = icon.get_filename()
return icon
13 years ago
def get_entry_info(desktopfile):
show = True
de = dentry.DesktopEntry(filename = desktopfile)
name = de.getName().encode('utf-8')
if seticon:
# strip the directory and extension from the icon name
icon = de.getIcon()
icon = icon_full_path(icon)
13 years ago
else:
icon = None
hidden = de.getHidden()
if hidden:
13 years ago
show = False
nodisplay = de.getNoDisplay()
if nodisplay:
13 years ago
show = False
# removing any %U or %F from the exec line
command = de.getExec().partition('%')[0]
terminal = de.getTerminal()
if terminal:
command = terminal_app+' -e '+command
path = de.getPath()
if not path:
path = None
#cleaning up categories and keeping only registered freedesktop.org main categories
13 years ago
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:
return [category, name, icon, command, path]
13 years ago
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)
13 years ago
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)
13 years ago
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]))
13 years ago
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])
13 years ago
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
13 years ago
def fluxboxmenu():
if submenu:
spacing = ' '
if seticon:
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 = ''
13 years ago
for i in menu():
category = i[0]
if seticon:
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+')'
13 years ago
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(')', '\)')
13 years ago
icon = j[1]
command = j[2]
path = j[3]
if path is not None:
command = 'cd '+path+' ; '+command
13 years ago
if icon is None:
print spacing+' [exec] ('+name+') {'+command+'}'
13 years ago
else:
print spacing+' [exec] ('+name+') {'+command+'} <'+icon+'>'
print spacing+'[end] # ('+category+')'
if submenu:
print '[end] # ('+applications+')'
13 years ago
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():
if submenu:
spacing = ' '
if seticon:
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 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 and icon is not None:
print spacing+' prog "'+name+'" '+icon+' '+command
else:
print spacing+' prog "'+name+'" _none_ '+command
print spacing+'}'
if submenu:
print '}'
def pekwmmenu():
if pekwmdynamic:
print "Dynamic {"
dspacing = ' '
else:
dspacing = ''
if submenu:
spacing = ' '
if seticon:
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 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 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:
print dspacing+'}'
if pekwmdynamic:
print "}"
def jwmmenu():
print '<?xml version="1.0"?>'
print '<JWM>'
if submenu:
spacing = ' '
if seticon:
app_icon = icon_full_path(applications_icon)
if app_icon is None:
print '<Menu label="'+applications+'">'
else:
print '<Menu icon="'+app_icon+'" label="'+applications+'">'
else:
print '<Menu label="'+applications+'">'
else:
spacing = ''
for i in menu():
category = i[0]
cat_icon = category_icon(category)
cat_icon = icon_full_path(cat_icon)
if seticon and cat_icon is not None:
print spacing+'<Menu icon="'+cat_icon+'" label="'+category+'">'
else:
print spacing+'<Menu label="'+category+'">'
for j in i[1]:
name = j[0]
icon = j[1]
command = j[2]
path = j[3]
if path is not None:
command = 'cd '+path+' ; '+command
if seticon and icon is not None:
print spacing+' <Program icon="'+icon+'" label="'+name+'">'+command+'</Program>'
else:
print spacing+' <Program label="'+name+'">'+command+'</Program>'
print spacing+'</Menu>'
if submenu:
print '</Menu>'
print '</JWM>'
13 years ago
if __name__ == "__main__":
main(sys.argv[1:])