#!/usr/bin/env python3
# coding: utf-8
# vim:et:sta:sts=4:sw=4:ts=8:tw=79:

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

import configparser as cp

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk

xopts = {
    'seticon': False,
    'iconsize': 16,
    'nosvg': False,
    'desktop': False,
    'submenu': True,
    'pekwmdynamic': False,
    'twmtitles': False,
    'max_icon_size': False
}

# the following line gets changed by the Makefile. If it is set to
# 'not_set' it looks in the currect directory tree for the .directory
# files. If it is actually set to something else, it looks under there
# for them, where they should be if this was installed properly
prefix = 'not_set'
if prefix == 'not_set':
    desktop_dir = '../desktop-directories/'
else:
    desktop_dir = '{}/share/desktop-directories/'.format(prefix)

if not os.path.isdir(desktop_dir):
    sys.exit('ERROR: Could not find {}'.format(desktop_dir))


class App:
    '''
    A class to keep individual app details in.
    '''

    def __init__(self, name, icon, command, path):
        self.name = name
        self.icon = icon
        self.command = command
        self.path = path

    def __repr__(self):
        return repr((self.name, self.icon, self.command,
                     self.path))


class MenuEntry:
    '''
    A class for each menu entry. Includes the class category and app details
    from the App class.
    '''

    def __init__(self, category, app):
        self.category = category
        self.app = app

    def __repr__(self):
        return repr((self.category, self.app.name, self.app.icon,
                     self.app.command, self.app.path))


class MenuCategory:
    '''
    A class for each menu category. Keeps the category name and the list of
    apps that go in that category.
    '''

    def __init__(self, category, applist):
        self.category = category
        self.applist = applist


de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-applications.directory')
applications = de.getName().encode('utf-8')
apps_name = applications.decode()
applications_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-accessories.directory')
accessories = de.getName().encode('utf-8')
accessories_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-development.directory')
development = de.getName().encode('utf-8')
development_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-education.directory')
education = de.getName().encode('utf-8')
education_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir + 'xdgmenumaker-games.directory')
games = de.getName().encode('utf-8')
games_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-graphics.directory')
graphics = de.getName().encode('utf-8')
graphics_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-multimedia.directory')
multimedia = de.getName().encode('utf-8')
multimedia_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-network.directory')
network = de.getName().encode('utf-8')
network_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-office.directory')
office = de.getName().encode('utf-8')
office_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-settings.directory')
settings = de.getName().encode('utf-8')
settings_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir +
                         'xdgmenumaker-system.directory')
system = de.getName().encode('utf-8')
system_icon = de.getIcon()
de = dentry.DesktopEntry(filename=desktop_dir + '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 check if the XDGMENUMAKERTERM environment variable is set and use it if
# it is.
# Then see if there is a user specified terminal emulator in the
# xdgmenumaker.cfg file.
terminal_app = os.getenv("XDGMENUMAKERTERM")
if not terminal_app:
    try:
        config = cp.ConfigParser()
        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 (cp.NoSectionError, cp.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 xopts
    try:
        opts, args = getopt.getopt(argv, "hins:f:", ["help", "icons",
                                                     "no-submenu",
                                                     "pekwm-dynamic",
                                                     "twm-titles",
                                                     "max-icon-size",
                                                     "no-svg",
                                                     "size=",
                                                     "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"):
            xopts['seticon'] = True
        elif opt in ("-s", "--size"):
            try:
                xopts['iconsize'] = int(arg)
            except ValueError:
                usage()
                sys.exit('ERROR: size must be a number')
        elif opt in ("-n", "--no-submenu"):
            xopts['submenu'] = False
        elif opt in ("--pekwm-dynamic",):
            xopts['pekwmdynamic'] = True
        elif opt in ("--twm-titles",):
            xopts['twmtitles'] = True
        elif opt in ("--max-icon-size",):
            try:
                # Pillow is optional and loaded only if we want to restrict the
                # icon sizes (useful for Fvwm). Yeah, I know it's not a good
                # idea to load a module in here, but I really don't want to
                # load it by default at the top. It would make xdgmenumaker a
                # bit slower to run even if it is not needed. This way it only
                # slows down when it is actually needed.
                global Image
                from PIL import Image
                xopts['max_icon_size'] = True
            except ImportError:
                usage()
                sys.exit('ERROR: --max-icon-size requires Pillow')
        elif opt == "--no-svg":
            xopts['nosvg'] = True
        elif opt in ("-f", "--format"):
            xopts['desktop'] = arg
    if not xopts['desktop']:
        usage()
        sys.exit('ERROR: You must specify the output format with -f')
    elif xopts['desktop'] == "blackbox":
        blackbox()
    elif xopts['desktop'] == "fluxbox":
        fluxbox()
    elif xopts['desktop'] == "fvwm":
        fvwm()
    elif xopts['desktop'] == "windowmaker":
        xopts['seticon'] = False
        windowmaker()
    elif xopts['desktop'] == "icewm":
        icewm()
    elif xopts['desktop'] == "pekwm":
        pekwmmenu()
    elif xopts['desktop'] == "jwm":
        jwm()
    elif xopts['desktop'] == "compizboxmenu":
        compizboxmenu()
    elif xopts['desktop'] == "twm":
        twm()
    elif xopts['desktop'] == "amiwm":
        xopts['seticon'] = False
        amiwm()
    elif xopts['desktop'] == "openbox":
        openbox()
    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 amiwm, blackbox, compizboxmenu,')
    print('                         fluxbox, fvwm, twm, icewm, jwm, openbox,')
    print('                         windowmaker and pekwm')
    print('    -i, --icons          enable support for icons in the')
    print('                         menus. Does not work with windowmaker or amiwm')
    print('        --no-svg         Do not use SVG icons even for WMs that support it')
    print('    -s, --size           preferred icon size in pixels (default: 16)')
    print('    -n, --no-submenu     do not create a submenu. Does not work with')
    print('                         openbox and windowmaker')
    print('        --max-icon-size  restrict the icon sizes to the specified size')
    print('        --pekwm-dynamic  generate dynamic menus for pekwm')
    print('        --twm-titles     show menu titles in twm menus')
    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')


def icon_strip(icon):
    # strip the directory and extension from the icon name
    icon = os.path.basename(icon)
    main, ext = os.path.splitext(icon)
    ext = ext.lower()
    if ext == '.png' or ext == '.svg' or ext == '.svgz' or ext == '.xpm':
        return main
    return icon


def icon_max_size(icon):
    # Checks if the icon size is bigger than the requested size and discards
    # the icon if it is, only allowing sizes smaller or equal to the requested
    # size
    try:
        img = Image.open(icon)
    except:
        # if there is any error reading the icon, just discard it
        return None
    if img.size[0] <= xopts['iconsize']:
        return icon
    return None


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.
    ext = os.path.splitext(icon)[1].lower()
    if os.path.exists(icon):
        if ext == ".svg" or ext == ".svgz":
            if wm_supports_icons(xopts['desktop']) and not xopts['nosvg']:
                return icon
        else:
            # icon is not svg
            if xopts['max_icon_size']:
                return icon_max_size(icon)
            else:
                return icon
    # fall back to looking for the icon elsewhere in the system
    icon = icon_strip(icon)
    icon_theme = Gtk.IconTheme.get_default()
    try:
        # Only some WMs support svg icons
        if wm_supports_icons(xopts['desktop']) and not xopts['nosvg']:
            icon = icon_theme.lookup_icon(icon, xopts['iconsize'],
                    Gtk.IconLookupFlags.FORCE_SVG)
        # but none of the other WMs does
        else:
            icon = icon_theme.lookup_icon(icon, xopts['iconsize'],
                    Gtk.IconLookupFlags.NO_SVG)
    except AttributeError:
        sys.exit('ERROR: You need to run xdgmenumaker inside an X session.')
    if icon:
        icon = icon.get_filename()
        # icon size only matters for non-SVG icons
        if icon and xopts['max_icon_size'] and (ext != ".svg" or ext != ".svgz"):
            icon = icon_max_size(icon)
    return icon

def remove_command_keys(command, desktopfile, icon):
    # replace the %i (icon key) if it's there. This is what freedesktop has to
    # say about it: "The Icon key of the desktop entry expanded as two
    # arguments, first --icon and then the value of the Icon key. Should not
    # expand to any arguments if the Icon key is empty or missing."
    if icon:
        command = command.replace('"%i"', '--icon {}'.format(icon))
        command = command.replace("'%i'", '--icon {}'.format(icon))
        command = command.replace('%i', '--icon {}'.format(icon))
    # some KDE apps have this "-caption %c" in a few variations. %c is "The
    # translated name of the application as listed in the appropriate Name key
    # in the desktop entry" according to freedesktop. All apps launch without a
    # problem without it as far as I can tell, so it's better to remove it than
    # have to deal with extra sets of nested quotes which behave differently in
    # each WM. This is not 100% failure-proof. There might be other variations
    # of this out there, but we can't account for every single one. If someone
    # finds another one, I can always add it later.
    command = command.replace('-caption "%c"', '')
    command = command.replace("-caption '%c'", '')
    command = command.replace('-caption %c', '')
    # same as before, although with -qwindowtitle (typical for Qt 5 apps).
    command = command.replace('-qwindowtitle "%c"', '')
    command = command.replace("-qwindowtitle '%c'", '')
    command = command.replace('-qwindowtitle %c', '')
    # replace the %k key. This is what freedesktop says about it: "The
    # location of the desktop file as either a URI (if for example gotten from
    # the vfolder system) or a local filename or empty if no location is
    # known."
    command = command.replace('"%k"', desktopfile)
    command = command.replace("'%k'", desktopfile)
    command = command.replace('%k', desktopfile)
    # removing any remaining keys from the command. That can potentially remove
    # any other trailing options after the keys,
    command = command.partition('%')[0]
    return command

def clean_up_categories(categories):
    # cleaning up categories and keeping only registered freedesktop.org main
    # categories
    category_menus = {
        "AudioVideo":   multimedia,
        "Audio":        multimedia,
        "Video":        multimedia,
        "Development":  development,
        "Education":    education,
        "Game":         games,
        "Graphics":     graphics,
        "Network":      network,
        "Office":       office,
        "System":       system,
        "Settings":     settings,
        "Utility":      accessories
    }
    category = other
    for candidate in categories:
        if candidate in category_menus:
            category = category_menus.get(candidate)
            break
    return category

def get_entry_info(desktopfile, ico_paths=True):
    de = dentry.DesktopEntry(filename=desktopfile)

    # skip processing the rest of the desktop entry if the item is to not be
    # displayed anyway
    onlyshowin = de.getOnlyShowIn()
    notshowin = de.getNotShowIn()
    hidden = de.getHidden()
    nodisplay = de.getNoDisplay()
    # none of the freedesktop registered environments are supported by
    # OnlyShowIn but it might be worth using some extra logic here.
    # http://standards.freedesktop.org/menu-spec/latest/apb.html
    if (onlyshowin != [] and not (xopts['desktop'].lower() in (name.lower() for name in onlyshowin))) \
        or (xopts['desktop'].lower() in (name.lower() for name in notshowin)) \
        or hidden or nodisplay:
        return None

    name = de.getName().encode('utf-8')

    if xopts['seticon']:
        icon = de.getIcon()
        if ico_paths:
            icon = icon_full_path(icon)
        else:
            icon = icon_strip(icon)
    else:
        icon = None

    command = de.getExec()
    command = remove_command_keys(command, desktopfile, icon)

    terminal = de.getTerminal()
    if terminal:
        command = '{term} -e {cmd}'.format(term=terminal_app, cmd=command)

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

    categories = de.getCategories()
    category = clean_up_categories(categories)

    app = App(name, icon, command, path)
    mentry = MenuEntry(category, app)
    return mentry


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


def wm_supports_icons(wm):
    # only jwm, icewm and openbox support svg icons
    if wm == "icewm" or wm == "jwm" or wm == "openbox":
        return True
    return False


def desktopfilelist():
    # if this env variable is set to 1, then only read .desktop files from the
    # tests directory, not systemwide. This gives a standard set of .desktop
    # files to compare against for testing.
    testing = os.getenv('XDGMENUMAKER_TEST')
    if testing == "1":
        dirs = ['../tests']
    else:
        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 = '{}/applications'.format(d)
        if os.path.isdir(xdgdir):
            for root, dirnames, filenames in os.walk(xdgdir):
                    for i in fnmatch.filter(filenames, '*.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(os.path.join(root, i))
    return filelist


def menu(ico_paths=True):
    applist = []
    for desktopfile in desktopfilelist():
        try:
            entry = get_entry_info(desktopfile, ico_paths=ico_paths)
            if entry is not None:
                applist.append(entry)
        except exc.ParsingError:
            pass

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

    menu = []
    for c in sortedcategories(applist):
        appsincategory = []
        for i in sortedapplist:
            if i.category == c:
                appsincategory.append(i.app)
        menu_category = MenuCategory(c, appsincategory)
        menu.append(menu_category)
    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 blackbox():
    # Blackbox menus are the same as Fluxbox menus. They just don't support
    # icons.
    global xopts
    xopts['seticon'] = False
    fluxbox()


def fluxbox():
    if xopts['submenu']:
        spacing = '  '
        if xopts['seticon']:
            app_icon = icon_full_path(applications_icon)

            if app_icon is None:
                print('[submenu] ({})'.format(apps_name))
            else:
                print('[submenu] ({}) <{}>'.format(apps_name, app_icon))
        else:
            print('[submenu] ({})'.format(apps_name))
    else:
        spacing = ''
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        if xopts['seticon']:
            cat_icon = category_icon(category)
            cat_icon = icon_full_path(cat_icon)
            if cat_icon:
                print('{s}[submenu] ({c}) <{i}>'.format(s=spacing, c=cat_name,
                                                        i=cat_icon))
            else:
                print('{s}[submenu] ({c})'.format(s=spacing, c=cat_name))
        else:
            print('{s}[submenu] ({c})'.format(s=spacing, c=cat_name))
        for app in menu_category.applist:
            # closing parentheses need to be escaped, otherwise they are
            # cropped out, along with everything that comes after them
            name = app.name.decode().replace(')', r'\)')
            icon = app.icon
            command = app.command
            path = app.path
            if path is not None:
                command = 'cd {p} ; {c}'.format(p=path, c=command)
            if icon is None:
                print('{s}  [exec] ({n}) {{{c}}}'.format(s=spacing, n=name,
                                                         c=command))
            else:
                print('{s}  [exec] ({n}) {{{c}}} <{i}>'.format(s=spacing,
                                                               n=name,
                                                               c=command,
                                                               i=icon))
        print('{s}[end] # ({c})'.format(s=spacing, c=cat_name))
    if xopts['submenu']:
        print('[end] # ({})'.format(apps_name))


def fvwm():
    if xopts['submenu']:
        print('DestroyMenu "xdgmenu"')
        print('AddToMenu "xdgmenu"')
        if xopts['seticon']:
            app_icon = icon_full_path(applications_icon)
            if app_icon:
                print('+ "{a}%{i}% " Title'.format(a=apps_name, i=app_icon))
            else:
                print('+ "{a}" Title'.format(a=apps_name))
            app_icon = icon_full_path(applications_icon)
            for menu_category in menu():
                category = menu_category.category
                cat_name = category.decode()
                cat_icon = category_icon(category)
                cat_icon = icon_full_path(cat_icon)
                if cat_icon:
                    print('+ "{c}%{i}%" Popup "{c}"'.format(c=cat_name,
                                                            i=cat_icon))
                else:
                    print('+ "{c}" Popup "{c}"'.format(c=cat_name))
        else:
            for menu_category in menu():
                category = menu_category.category
                cat_name = category.decode()
                print('+ "{c}" Popup "{c}"'.format(c=cat_name))
        print()
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        print('DestroyMenu "{}"'.format(cat_name))
        print('AddToMenu "{c}"'.format(c=cat_name))
        if xopts['seticon']:
            cat_icon = category_icon(category)
            cat_icon = icon_full_path(cat_icon)
            if cat_icon:
                print('+ "{c}%{i}%" Title'.format(c=cat_name, i=cat_icon))
            else:
                print('+ "{c}" Title'.format(c=cat_name))
        else:
            print('+ "{c}" Title'.format(c=cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            icon = app.icon
            command = app.command
            path = app.path
            if path is not None:
                command = 'cd {p} ; {c}'.format(p=path, c=command)
            if icon is None:
                print('+ "{n}" Exec {c}'.format(n=name, c=command))
            else:
                print('+ "{n}%{i}%" Exec {c}'.format(n=name,
                                                            c=command,
                                                            i=icon))
        print()


def windowmaker():
    print('"{}" MENU'.format(apps_name))
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        print(' "{}" MENU'.format(cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            command = app.command
            print('  "{n}" EXEC {c}'.format(n=name, c=command))
        print(' "{}" END'.format(cat_name))
    print('"{}" END'.format(apps_name))


def icewm():
    if xopts['submenu']:
        spacing = '  '
        if xopts['seticon']:
            app_icon = icon_full_path(applications_icon)
            if app_icon is None:
                app_icon = "_none_"
            print('menu "{a}" {i} {{'.format(a=apps_name, i=app_icon))
        else:
            print('menu "{}" _none_ {{'.format(apps_name))
    else:
        spacing = ''
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        cat_icon = category_icon(category)
        cat_icon = icon_full_path(cat_icon)
        if xopts['seticon'] and cat_icon is not None:
            print('{s}menu "{c}" {i} {{'.format(s=spacing, c=cat_name,
                                                i=cat_icon))
        else:
            print('{s}menu "{c}" _none_ {{'.format(s=spacing, c=cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            icon = app.icon
            command = app.command
            if xopts['seticon'] and icon is not None:
                print('{s}  prog "{n}" {i} {c}'.format(s=spacing, n=name,
                                                       i=icon, c=command))
            else:
                print('{s}  prog "{n}" _none_ {c}'.format(s=spacing, n=name,
                                                          c=command))
        print('{}}}'.format(spacing))
    if xopts['submenu']:
        print('}')


def pekwmmenu():
    if xopts['pekwmdynamic']:
        print("Dynamic {")
        dspacing = '  '
    else:
        dspacing = ''
    if xopts['submenu']:
        spacing = '  '
        if xopts['seticon']:
            app_icon = icon_full_path(applications_icon)
            print('{s}Submenu = "{a}" {{ Icon = "{i}"'.format(s=dspacing,
                                                              a=apps_name,
                                                              i=app_icon))
        else:
            print('{s}Submenu = "{a}" {{'.format(s=dspacing, a=apps_name))
    else:
        spacing = ''
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        cat_icon = category_icon(category)
        cat_icon = icon_full_path(cat_icon)
        if xopts['seticon'] and cat_icon is not None:
            print('{d}{s}Submenu = "{c}" {{ Icon = "{i}"'.format(d=dspacing,
                                                                 s=spacing,
                                                                 c=cat_name,
                                                                 i=cat_icon))
        else:
            print('{d}{s}Submenu = "{c}" {{'.format(d=dspacing, s=spacing,
                                                    c=cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            icon = app.icon
            # 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 = app.command.replace('"', '')
            path = app.path
            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 {p} && {c} || {c}'.format(p=path, c=command)
            if xopts['seticon'] and icon is not None:
                print('{d}{s}  Entry = "{n}" {{ Icon = "{i}"; Actions = "Exec {c} &" }}'
                      .format(d=dspacing, s=spacing, n=name, i=icon,
                              c=command))
            else:
                print('{d}{s}  Entry = "{n}" {{ Actions = "Exec {c} &" }}'
                      .format(d=dspacing, s=spacing, n=name, c=command))
        print('{d}{s}}}'.format(d=dspacing, s=spacing))
    if xopts['submenu']:
        print('{}}}'.format(dspacing))
    if xopts['pekwmdynamic']:
        print("}")


def jwm():
    print('<?xml version="1.0"?>')
    print('<JWM>')
    if xopts['submenu']:
        spacing = '  '
        if xopts['seticon']:
            app_icon = icon_full_path(applications_icon)
            if app_icon is None:
                print('<Menu label="{}">'.format(apps_name))
            else:
                print('<Menu icon="{i}" label="{a}">'.format(i=app_icon,
                                                             a=apps_name))
        else:
            print('<Menu label="{}">'.format(apps_name))
    else:
        spacing = ''
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        cat_icon = category_icon(category)
        cat_icon = icon_full_path(cat_icon)
        if xopts['seticon'] and cat_icon is not None:
            print('{s}<Menu icon="{i}" label="{c}">'.format(s=spacing,
                                                            i=cat_icon,
                                                            c=cat_name))
        else:
            print('{s}<Menu label="{c}">'.format(s=spacing, c=cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            icon = app.icon
            command = app.command
            path = app.path
            if path is not None:
                command = 'cd {p} ; {c}'.format(p=path, c=command)
            if xopts['seticon'] and icon is not None:
                print('{s}  <Program icon="{i}" label="{n}">{c}</Program>'
                      .format(s=spacing, i=icon, n=name, c=command))
            else:
                print('{s}  <Program label="{n}">{c}</Program>'
                      .format(s=spacing, n=name, c=command))
        print('{}</Menu>'.format(spacing))
    if xopts['submenu']:
        print('</Menu>')
    print('</JWM>')


def compizboxmenu():
    if xopts['submenu']:
        spacing = '  '
        if xopts['seticon']:
            app_icon = icon_strip(applications_icon)
            print('<menu icon="{i}" name="{a}">'.format(i=app_icon,
                                                        a=apps_name))
        else:
            print('<menu name="{}">'.format(apps_name))
    else:
        spacing = ''
    for menu_category in menu(ico_paths=False):
        category = menu_category.category
        cat_name = category.decode()
        cat_icon = category_icon(category)
        if xopts['seticon'] and cat_icon is not None:
            print('{s}<menu icon="{i}" name="{c}">'.format(
                s=spacing, i=cat_icon, c=cat_name))
        else:
            print('{s}<menu name="{c}">'.format(s=spacing, c=cat_name))
        for app in menu_category.applist:
            name = app.name.decode().replace('&', '&amp;')
            icon = app.icon
            command = app.command.replace("'", "'\\''").replace('&', '&amp;')
            path = app.path
            if path is not None:
                path = path.replace("'", "'\\''")
                command = 'sh -c \'cd "{p}" ;{c}\''.format(p=path, c=command)
            if xopts['seticon'] and icon is not None:
                print(('{s}  <item type="launcher"><name>{n}</name>'
                       '<icon>{i}</icon>'
                       '<command>{c}</command></item>').format(s=spacing,
                                                               n=name, i=icon,
                                                               c=command))
            else:
                print(('{s}  <item type="launcher"><name>{n}</name>'
                       '<command>{c}</command></item>').format(s=spacing,
                                                               n=name,
                                                               c=command))
        print('{}</menu>'.format(spacing))
    if xopts['submenu']:
        print('</menu>')

def twm():
    if xopts['submenu']:
        print('menu "xdgmenu"')
        print("{")
        if xopts['twmtitles']:
            print('    "{a}" f.title'.format(a=apps_name))
        for menu_category in menu():
            category = menu_category.category
            cat_name = category.decode()
            print('    "{c}" f.menu "{c}"'.format(c=cat_name))
        print("}")
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        print('menu "{}"'.format(cat_name))
        print("{")
        if xopts['twmtitles']:
            print('    "{c}" f.title'.format(c=cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            # for some apps (like netbeans) the command is launched with
            # /bin/sh "command"
            # and the quotes get mixed up with the quotes twm puts
            # around the command, so we're just stripping the quotes
            command = app.command.replace('"', '')
            path = app.path
            if path is not None:
                print('    "{n}" f.exec "cd {p} ; {c} &"'.format(n=name, p=path, c=command))
            else:
                print('    "{n}" f.exec "{c} &"'.format(n=name, c=command))
        print("}")

def amiwm():
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        print('ToolItem "{}" {{'.format(cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            # for some apps (like netbeans) the command is launched with
            # /bin/sh "command"
            # and the quotes get mixed up with the quotes amim needs
            # around the command, so we're just stripping the quotes
            command = app.command.replace('"', '')
            path = app.path
            if path is not None:
                print('  ToolItem "{n}" "cd {p} ; {c}" ""'.format(n=name, p=path, c=command))
            else:
                print('  ToolItem "{n}" "{c}" ""'.format(n=name, c=command))
        print("}")

def openbox():
    print('<?xml version="1.0" encoding="UTF-8"?>')
    print('<openbox_pipe_menu>')
    spacing = '  '
    for menu_category in menu():
        category = menu_category.category
        cat_name = category.decode()
        cat_id = 'apps-{}-menu'.format(cat_name.lower())
        cat_icon = category_icon(category)
        cat_icon = icon_full_path(cat_icon)
        if xopts['seticon'] and cat_icon is not None:
            print('{s}<menu id="{d}" label="{c}" icon="{i}">'
                  .format(s=spacing, d=cat_id, c=cat_name, i=cat_icon))
        else:
            print('{s}<menu id="{d}" label="{c}">'
                  .format(s=spacing, d=cat_id, c=cat_name))
        for app in menu_category.applist:
            name = app.name.decode()
            command = app.command
            icon = app.icon
            if xopts['seticon'] and icon is not None:
                print('{s}{s}<item label="{n}" icon="{i}">'
                      .format(s=spacing, n=name, i=icon))
            else:
                print('{s}{s}<item label="{n}">'
                      .format(s=spacing, n=name))
            print('{s}{s}{s}<action name="Execute">'.format(s=spacing))
            print('{s}{s}{s}{s}<command>{c}</command>'.format(s=spacing,
                                                           c=command))
            print('{s}{s}{s}</action>'.format(s=spacing))
            print('{s}{s}</item>'.format(s=spacing))
        print('{s}</menu>'.format(s=spacing))
    print('</openbox_pipe_menu>')


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