#!/usr/local/bin/python2.7
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 vincent_delft@yahoo.com
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
import sys
import xdg, xdg.BaseDirectory, xdg.DesktopEntry, xdg.IconTheme
import os.path, glob
import gettext
gettext.install('openboxMenuCreate', './po', unicode=True)
#TODO: i18n must be finalized
#######################
# configuration items #
#######################
THEME="Adwaita" #verify your have such package on your system (/usr/local/share/icons/Awaita)
CONFIG_FILE="~/.config/openbox/menu.xml" #this is the default output file
TERM = "xterm" #to start some .desktop applications
FAVORITE_APPS=[{"exec":"firefox"}, {"name":"_Xterm","exec":"xterm"}, {"exec":"pcmanfm"}] #name is optional, "_" is for shortcodes
#the sequence is important because some .desktop file have several categories.
#we take the first one matching
menuCategories = [[_('multimedia'), ('AudioVideo','Audio','Video')],
(_('development'), ('Development','TextEditor')),
(_('office'),('Office',)),
(_('education'), ('Education',)),
(_('games'), ('Game',)),
(_('graphics'), ('Graphics',)),
(_('network'), ('Network',)),
(_('system'),('System',)),
(_('accessories'),('Settings','Utility')),
(_('others'),()),
]
#layout proposed for the global menus
#parameters are: favorites, commands, applications, system
XML_LAYOUT = """
"""
##############################
# end of configuration items #
##############################
#parameters are: label, icon, command
ITEM_tmpl="""
-
%(command)s
"""
#parameters are: id, label, content
MENU_tmpl="""
"""
#parameters are: id, label, command
PIPE_tmpl="""
"""
#############################################
# methods to collect the .desktop available #
# on the machine #
#############################################
def reformatIcon(icon):
""" in some .desktop the icon entry is not correctly formated. we remove the extension"""
ret = icon
#remove the extension
if icon and (icon[0] not in ["/","."]):
ret = os.path.splitext(icon)[0]
return ret
def getDesktopFolders():
"return a list of all folders containing .desktop file"
desktopDirs=[]
for elem in xdg.BaseDirectory.xdg_data_dirs:
folder = elem.rstrip("/") + "/applications"
if folder not in desktopDirs:
if os.path.isdir(folder):
desktopDirs.append(folder + "/*.desktop")
print "we perform the following search:", desktopDirs
return desktopDirs
def getDesktopItems(filename):
"""return a dictionary containing the .desktop data"""
xdgData={}
if os.path.isfile(filename):
entryObject = xdg.DesktopEntry.DesktopEntry(filename = filename)
xdgData['name'] = entryObject.getName()
icon = reformatIcon(entryObject.getIcon())
fpicon = xdg.IconTheme.getIconPath(icon, theme=THEME)
if fpicon == None:
fpicon = xdg.IconTheme.getIconPath("application-x-executable", theme=THEME)
xdgData['icon'] = fpicon
xdgData['hidden'] = entryObject.getHidden()
xdgData['nodisplay'] = entryObject.getNoDisplay() #boolean
exe = entryObject.getExec() #remove %f, %u, ... and add termnial if required
pos = exe.find("%")
if pos >0: #we want to remove any %F, %f, %U, %u, ....
exe = exe [:pos]
if entryObject.getTerminal():
exe = "%s -e \"%s\" " % (TERM, exe)
xdgData['exec'] = exe
xdgData['path'] = entryObject.getPath()
xdgData['categories'] = entryObject.getCategories() #is a list
xdgData['onlyshowin'] = entryObject.getOnlyShowIn() #is a list
xdgData['noshowin'] = entryObject.getNotShowIn() #is a list
#print "Filename: ", filename
#print xdgData
#print "-"*20
return xdgData
def getAllXDGData():
"""return a dictonary of the type:
{: { : # can be string or list }
keys and values are coming from getDesktopItems
"""
allData = {}
for folder in getDesktopFolders():
for elem in glob.glob(folder):
allData[elem] = getDesktopItems(elem)
return allData
def getMenuCategory(xdgData):
"""for xdgdata allowed, we return one of the know categories or others"""
if ((len(xdgData['onlyshowin'])>0) and ("Openbox" not in xdgData['onlyshowin'])) or ("Openbox" in xdgData["noshowin"]) or ( xdgData["nodisplay"] == True):
return None
for menuName, categories in menuCategories:
for cat in categories:
if cat in xdgData['categories']:
return menuName
return "others"
###############################################
# Objects to properly create the required xml #
###############################################
def pretty_xml(text, indent=0):
res = ""
for line in text.split('\n'):
if line.strip():
res += " "*indent + line + "\n"
return res
##############################################
# Methods to build our openbox menu.xml file #
##############################################
def menu_exit():
"This return an XML with the exit possibilities"
#TODO: i18n the different labels
content ="""-
yes
-
Are you sure you want to reboot?
doas /sbin/shutdown -r now
-
Are you sure you want to shutdown?
doas /sbin/halt -p
""" % {'exit-icon': xdg.IconTheme.getIconPath("application-exit", theme=THEME)}
return content
def menu_applications(dataDict ):
"return an xml with all applications found on your system"
openboxElems = {}
for filename in dataDict.keys():
menuCategory = getMenuCategory(dataDict[filename])
if menuCategory:
openboxElems.setdefault(menuCategory, {})
openboxElems[menuCategory]['name'] = menuCategory
openboxElems[menuCategory].setdefault('entries', [])
openboxElems[menuCategory]['entries'].append([dataDict[filename]['name'], dataDict[filename]['exec'], dataDict[filename]['icon']])
content = ""
for menuName, categories in menuCategories:
if openboxElems.has_key(menuName):
menu_category = openboxElems[menuName]['name']
items = ""
for elems in openboxElems[menuName]['entries']:
if elems[0] and elems[2]:
raw_xml = ITEM_tmpl % {'label':elems[0], 'icon':elems[2], 'command':elems[1]}
items += raw_xml
if items:
p_items = pretty_xml(items,1)
content += MENU_tmpl % {'id':menu_category,'label':menu_category,'content':p_items.lstrip()}
return content
def menu_favoriteApplications(dataDict):
"Build the xml with few favorites applications as specified in FAVORITE_APPS"
#reorganise the data dictionary
dataDictExecs = {}
for fn in dataDict.keys():
exe = dataDict[fn].get('exec','').strip()
if exe:
dataDictExecs[exe] = dataDict[fn]
content = ""
for app in FAVORITE_APPS:
if app.get("exec","") and app.get("name","") and app['name']!="_":
#we take what you propose in FAVORITE_APPS
icon = app.get('icon','')
#if you do not provide an icon in FAVORITA_APPS,we try to find one
if not icon and dataDictExecs.get(app['exec'],''):
icon = dataDictExecs[app["exec"]].get('icon','')
raw_xml = ITEM_tmpl % {'label':app['name'],'icon':icon,'command': app["exec"]}
content += raw_xml
elif app.get('exec','') and dataDictExecs.get(app["exec"],'') and dataDictExecs[app["exec"]].get('name',''):
#no "name" in FAVORIT_APPS (or name is just the underscore (for key-bindings)
raw_xml = ITEM_tmpl % {'label':app.get('name','') + dataDictExecs[app["exec"]]["name"], 'icon':dataDictExecs[app["exec"]]['icon'], 'command':app["exec"]}
content += raw_xml
else:
print """ERROR !!!!. The favorite is not correctly defined. It must have either "exec" either "name" and "exec" and optionaly "icon" """
print "We have:", app
execs = dataDictExecs.keys()
execs.sort()
print "Available exec are:", execs
return content
def menu_commands():
"Build an xml with few users commands, if they are present on the system"
#the network part
content = ""
net_items = ""
if os.path.isfile(os.path.expanduser("~/.config/openbox/wireless.py")):
net_items = PIPE_tmpl % {'id':"wifi-list",'label':_("Wifi confidured"),'command':"~/.config/openbox/wireless.py --list"}
net_items += PIPE_tmpl % {'id':"wifi-scan",'label':_("Wifi scan"),'command':"~/.config/openbox/wireless.py --scan"}
if os.path.isfile(os.path.expanduser("~/.config/openbox/nfs_status.sh")):
if net_items:
net_items += ""
net_items += PIPE_tmpl % {'id':"NFS-status", 'label':_("NFS status"), 'command':"~/.config/openbox/nfs_status.sh"}
net_items += ITEM_tmpl % {'label':_("NFS toggle u/mount"),'icon':"",'command':"~/.config/openbox/toggle_mount_nas.sh"}
if net_items:
net_items = pretty_xml(net_items.lstrip(),1)
content += MENU_tmpl % {'id':"network-commands",'label':_("Network"),'content':net_items.lstrip()}
#the battery part
if os.path.isfile(os.path.expanduser("~/.config/openbox/battery.sh")):
content += PIPE_tmpl % {'id':"battery-menu", 'label':_("Battery status"),'command':"~/.config/openbox/battery.sh"}
return content
def menu_system():
"we return an xml with some system commands we can find on the system"
items = ""
if os.path.isfile(os.path.expanduser("/usr/local/bin/pcmanfm")):
items += ITEM_tmpl % {"label":_("Desktop preferences"), "icon": "", "command":"/usr/local/bin/pcmanfm --desktop-pref"}
if os.path.isfile(os.path.expanduser("~/.config/openbox/wallpaper.sh")):
items += ITEM_tmpl % {"label":_("Change wallpaper"), "icon":"", "command":"~/.config/openbox/wallpaper.sh"}
if os.path.isfile(os.path.expanduser("~/.config/openbox/switchmonitor.sh")):
items += ITEM_tmpl % {"label":_("Switch monitor"), "icon":"", "command":"~/.config/openbox/switchmonitor.sh"}
if os.path.isfile(os.path.expanduser("~/.config/openbox/togglemonitor2.sh")):
items += ITEM_tmpl % {"label":_("Toggle extra monitor"), "icon":"", "command":"~/.config/openbox/togglemonitor2.sh"}
if os.path.isfile(os.path.expanduser("/usr/loca/bin/openboxMenuCreate")):
items += ITEM_tmpl % {"label":_("Generate Openbox menus"),"icon":"", "command":"openboxMenuCreate"}
if items:
items += ""
if os.path.isfile(os.path.expanduser("/usr/local/bin/obconf")):
items += ITEM_tmpl % {"label":_("Openbox configuration manager"),"icon":"", "command":"obconf"}
items += """- \n \n
""" % (_("Reload Openbox configs(W-r)"))
content = MENU_tmpl % {'id':'system-commnds',"label":"system commands","content":pretty_xml(items,1).lstrip()}
return content
def build_whole_xml(allData):
"Concatenation of all elements in order to build a complete xml file for openbox"
favorites = pretty_xml(menu_favoriteApplications(allData), 3)
commands = pretty_xml(menu_commands().lstrip(),3)
applications = pretty_xml(menu_applications(allData),3)
system = pretty_xml(menu_system().lstrip(),3)
exit = menu_exit()
xml_content = XML_LAYOUT % {'favorites':favorites.lstrip(),'commands':commands.lstrip(), 'applications':applications.lstrip(), 'system':system.lstrip(),'exit':exit}
return xml_content
def return_existing_categories(data):
for filename in data.keys():
menuCategory = getMenuCategory(data[filename])
if menuCategory:
print "%s\t%s\t%s" % (filename, data[filename]['exec'], menuCategory)
if __name__=="__main__":
import argparse
parser = argparse.ArgumentParser(description=_("Create menus for openbox"))
parser.add_argument('--dryrun','-d', action="store_true", help=_("dry run and do not overwrite the config file, but present the result on the standard output"))
parser.add_argument('--config','-c', help=_("overwrite the default config file"))
parser.add_argument('--categories','-C', action="store_true", help=_("present all application and their assocaited category"))
args = parser.parse_args()
allData = getAllXDGData()
if args.categories:
return_existing_categories(allData)
sys.exit(0)
xml_content = build_whole_xml(allData)
if xml_content:
if CONFIG_FILE:
conf_file = os.path.expanduser(CONFIG_FILE)
if args.config:
conf_file = args.config
if conf_file and not args.dryrun:
try:
fid = open(conf_file,"w")
fid.write(xml_content.encode('utf8'))
fid.close()
except Exception, e:
print "ERROR: failed to create the file:", conf_file
raise
else:
print "The following menu file has been created:", conf_file
if args.dryrun:
print xml_content