#!/usr/bin/env python # Read a menu description in a common format, and write out menus for various # environments. # Adam Sampson import os, re class MenuError(Exception): pass def xml_encode(s): s = s.replace("&", "&") s = s.replace("<", "<") s = s.replace(">", ">") s = s.replace("'", "'") s = s.replace('"', """) return s def read_menu(fn): f = open(fn) root = [] stack = [root] for line in f.readlines(): line = line.strip() if line == "" or line[0] == "#": continue m = re.match(r'^item "(.*)" "(.*)"$', line) if m is not None: stack[-1].append((m.group(1), m.group(2))) continue m = re.match(r'^submenu "(.*)"$', line) if m is not None: menu = [] stack[-1].append((m.group(1), menu)) stack.append(menu) continue if line == "end": stack = stack[:-1] continue if line == "separator": stack[-1].append((None, None)) continue raise MenuError("Bad line: " + line) f.close() return root class Writer: def __init__(self, fn): self.fn = fn self.tempname = fn + ".new-" + str(os.getpid()) self.f = open(self.tempname, "w") self.id_counter = 0 def close(self): self.f.close() os.rename(self.tempname, self.fn) def write(self, menu): self.write_begin() self.write_menu(menu) self.write_end() def write_item(self, name, value): if value == "=workspaces": self.write_workspaces(name) elif value == "=configure": self.write_configure(name) elif value == "=themesmenu": self.write_themesmenu(name) elif value == "=reconfigure": self.write_reconfigure(name) elif value == "=restart": self.write_restart(name) elif value == "=quit": self.write_quit(name) else: self.write_exec(name, value) def generate_id(self): self.id_counter += 1 return "id" + str(self.id_counter) # These are mandatory: def write_begin(self): pass def write_end(self): pass def write_exec(self, name, value): pass # These are optional: def write_separator(self): pass def write_workspaces(self, name): pass def write_configure(self, name): pass def write_themesmenu(self, name): pass def write_reconfigure(self, name): pass def write_restart(self, name): pass def write_quit(self, name): pass class NestedWriter(Writer): def write_menu(self, menu): for (name, value) in menu: if name is None: self.write_separator() elif isinstance(value, list): self.write_menu_start(name) self.write_menu(value) self.write_menu_end(name) else: self.write_item(name, value) # These are mandatory: def write_menu_start(self, name): pass def write_menu_end(self, name): pass class OrderedWriter(Writer): def write_menu(self, menu, is_root = True): items = [] for (name, value) in menu: if isinstance(value, list): newid = self.generate_id() self.write_menu_start(newid, name) self.write_menu(value, False) self.write_menu_end(newid, name) items.append((name, True, newid)) else: items.append((name, False, value)) if is_root: self.write_rootmenu_start() for (name, is_menu, value) in items: if is_menu: self.write_submenu(name, value) elif name is None: self.write_separator() else: self.write_item(name, value) if is_root: self.write_rootmenu_end() # These are mandatory: def write_rootmenu_start(self): pass def write_rootmenu_end(self): pass def write_menu_start(self, id, name): pass def write_menu_end(self, id, name): pass def write_submenu(self, name, id): pass class Openbox2Writer(NestedWriter): def write_begin(self): self.f.write("[begin] (Menu)\n") def write_end(self): self.f.write("[end]\n") def write_exec(self, name, value): self.f.write("[exec] (" + name + ") {" + value + "}\n") def write_menu_start(self, name): self.f.write("[submenu] (" + name + ")\n") def write_menu_end(self, name): self.f.write("[end]\n") def write_workspaces(self, name): self.f.write("[workspaces] (" + name + ")\n") def write_configure(self, name): self.f.write("[config] (" + name + ")\n") # FIXME ob2 does themesmenu, but needs the directories def write_reconfigure(self, name): self.f.write("[reconfig] (" + name + ")\n") def write_restart(self, name): self.f.write("[restart] (" + name + ")\n") # FIXME quit? class XFCE4Writer(NestedWriter): def write_begin(self): self.f.write(""" """) def write_end(self): self.f.write("\n") def write_exec(self, name, value): self.f.write('\n') def write_menu_start(self, name): self.f.write('\n') def write_menu_end(self, name): self.f.write('\n') def write_separator(self): self.f.write('\n') def write_configure(self, name): self.write_exec(name, "xfce-setting-show") # FIXME full menu would be nicer class Openbox3Writer(OrderedWriter): def write_begin(self): self.f.write(""" """) def write_end(self): self.f.write("\n") def write_ob_item(self, label, action, param = None): label = xml_encode(label) action = xml_encode(action) self.f.write('') if param is None: self.f.write('') else: param = xml_encode(param) el = action.lower() self.f.write('<' + el + '>' + param + '') self.f.write('\n') def write_exec(self, name, value): self.write_ob_item(name, "Execute", value) def write_configure(self, name): self.write_exec(name, "obconf") def write_reconfigure(self, name): self.write_ob_item(name, "Reconfigure") # FIXME more actions def write_workspaces(self, name): self.write_submenu(name, "client-list-menu") def write_rootmenu_start(self): self.write_menu_start("root-menu", "Menu") def write_rootmenu_end(self): self.write_menu_end("root-menu", "Menu") def write_menu_start(self, id, name): self.f.write('\n') def write_menu_end(self, id, name): self.f.write('\n') def write_submenu(self, name, id): self.f.write('\n') def write_separator(self): self.f.write('\n') class XDGWriter(Writer): def __init__(self, fn, appdir): Writer.__init__(self, fn) self.appdir = appdir self.context = [] def write_begin(self): # Nuke any existing sanemenus-generated .desktop files. try: os.makedirs(self.appdir) except OSError: pass for fn in os.listdir(self.appdir): if fn.startswith("sanemenus-"): os.unlink(self.appdir + "/" + fn) self.f.write('\n') def write_menu(self, menu, name = None): category = "X-sanemenus-" + self.generate_id() self.context.append(category) self.f.write('\n') if name is None: self.f.write('Applications\n') self.f.write('\n') self.f.write('\n') else: self.f.write('' + xml_encode(name) + '\n') self.f.write('\n' + xml_encode(category) + '\n\n') self.f.write('\n') for (name, value) in menu: if value is None: self.write_separator() elif isinstance(value, list): self.f.write('' + xml_encode(name) + '\n') else: self.write_item(name, value) self.f.write('\n') for (name, value) in menu: if isinstance(value, list): self.write_menu(value, name) self.f.write('\n') self.context.pop() def write_exec(self, name, value): fn = "sanemenus-%s.desktop" % self.generate_id() f = open(self.appdir + "/" + fn, "w") f.write("""[Desktop Entry] Version=1.0 Type=Application Encoding=UTF-8 Name=""" + name + """ Exec=""" + value + """ StartupNotify=false Categories=""" + self.context[-1] + """; """) f.close() self.f.write('' + fn + '\n') def write_separator(self): self.f.write('\n') def write_configure(self, name): self.write_exec(name, "xfce4-settings-manager") def fvwm_escape(s): return s.replace('$', '$$') def fvwm_quote(s): return '"' + fvwm_escape(s).replace('"', '\\"') + '"' class FvwmWriter(OrderedWriter): def write_fvwm_item(self, name, command): self.f.write('+ %s %s\n' % (fvwm_quote(name), command)) def write_fvwm_menu_start(self, name): self.f.write("DestroyMenu %s\n" % name) self.f.write("AddToMenu %s\n" % name) def write_fvwm_menu_end(self): self.f.write("\n") def write_exec(self, name, value): self.write_fvwm_item(name, "Exec exec " + fvwm_escape(value)) def write_separator(self): self.write_fvwm_item("", "Nop") def write_restart(self, name): self.write_fvwm_item(name, "Restart") def write_quit(self, name): self.write_fvwm_item(name, "Quit") def write_rootmenu_start(self): self.write_fvwm_menu_start("MenuFvwmRoot") def write_rootmenu_end(self): self.write_fvwm_menu_end() def write_menu_start(self, id, name): self.write_fvwm_menu_start("MenuSanemenu" + id) def write_menu_end(self, id, name): self.write_fvwm_menu_end() def write_submenu(self, name, id): self.write_fvwm_item(name, "Popup MenuSanemenu" + id) if __name__ == "__main__": home = os.getenv("HOME") menu = read_menu(home + "/.sanemenusrc") writers = [ XFCE4Writer(home + "/.config/xfce4/desktop/menu.xml"), Openbox3Writer(home + "/.config/openbox/menu.xml"), Openbox2Writer(home + "/.openbox/menu"), XDGWriter(home + "/.config/menus/xfce-applications.menu", home + "/.local/share/applications"), FvwmWriter(home + "/.config/fvwm/menus"), ] for w in writers: w.write(menu) w.close()