""" CLass to edit XDG Menus """ from xdg.Menu import * from xdg.BaseDirectory import * from xdg.Exceptions import * from xdg.DesktopEntry import * from xdg.Config import * import xml.dom.minidom import os import re # XML-Cleanups: Move / Exclude # FIXME: proper reverte/delete # FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions # FIXME: catch Exceptions # FIXME: copy functions # FIXME: More Layout stuff # FIXME: unod/redo function / remove menu... # FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile # Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs class MenuEditor: def __init__(self, menu=None, filename=None, root=False): self.menu = None self.filename = None self.doc = None self.parse(menu, filename, root) # fix for creating two menus with the same name on the fly self.filenames = [] def parse(self, menu=None, filename=None, root=False): if root == True: setRootMode(True) if isinstance(menu, Menu): self.menu = menu elif menu: self.menu = parse(menu) else: self.menu = parse() if root == True: self.filename = self.menu.Filename elif filename: self.filename = filename else: self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1]) try: self.doc = xml.dom.minidom.parse(self.filename) except IOError: self.doc = xml.dom.minidom.parseString('Applications'+self.menu.Filename+'') except xml.parsers.expat.ExpatError: raise ParsingError('Not a valid .menu file', self.filename) self.__remove_whilespace_nodes(self.doc) def save(self): self.__saveEntries(self.menu) self.__saveMenu() def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None): menuentry = MenuEntry(self.__getFileName(name, ".desktop")) menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal) self.__addEntry(parent, menuentry, after, before) sort(self.menu) return menuentry def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None): menu = Menu() menu.Parent = parent menu.Depth = parent.Depth + 1 menu.Layout = parent.DefaultLayout menu.DefaultLayout = parent.DefaultLayout menu = self.editMenu(menu, name, genericname, comment, icon) self.__addEntry(parent, menu, after, before) sort(self.menu) return menu def createSeparator(self, parent, after=None, before=None): separator = Separator(parent) self.__addEntry(parent, separator, after, before) sort(self.menu) return separator def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None): self.__deleteEntry(oldparent, menuentry, after, before) self.__addEntry(newparent, menuentry, after, before) sort(self.menu) return menuentry def moveMenu(self, menu, oldparent, newparent, after=None, before=None): self.__deleteEntry(oldparent, menu, after, before) self.__addEntry(newparent, menu, after, before) root_menu = self.__getXmlMenu(self.menu.Name) if oldparent.getPath(True) != newparent.getPath(True): self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name)) sort(self.menu) return menu def moveSeparator(self, separator, parent, after=None, before=None): self.__deleteEntry(parent, separator, after, before) self.__addEntry(parent, separator, after, before) sort(self.menu) return separator def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None): self.__addEntry(newparent, menuentry, after, before) sort(self.menu) return menuentry def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None): deskentry = menuentry.DesktopEntry if name: if not deskentry.hasKey("Name"): deskentry.set("Name", name) deskentry.set("Name", name, locale = True) if comment: if not deskentry.hasKey("Comment"): deskentry.set("Comment", comment) deskentry.set("Comment", comment, locale = True) if genericname: if not deskentry.hasKey("GnericNe"): deskentry.set("GenericName", genericname) deskentry.set("GenericName", genericname, locale = True) if command: deskentry.set("Exec", command) if icon: deskentry.set("Icon", icon) if terminal == True: deskentry.set("Terminal", "true") elif terminal == False: deskentry.set("Terminal", "false") if nodisplay == True: deskentry.set("NoDisplay", "true") elif nodisplay == False: deskentry.set("NoDisplay", "false") if hidden == True: deskentry.set("Hidden", "true") elif hidden == False: deskentry.set("Hidden", "false") menuentry.updateAttributes() if len(menuentry.Parents) > 0: sort(self.menu) return menuentry def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None): # Hack for legacy dirs if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory": xml_menu = self.__getXmlMenu(menu.getPath(True, True)) self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory") menu.Directory.setAttributes(menu.Name + ".directory") # Hack for New Entries elif not isinstance(menu.Directory, MenuEntry): if not name: name = menu.Name filename = self.__getFileName(name, ".directory").replace("/", "") if not menu.Name: menu.Name = filename.replace(".directory", "") xml_menu = self.__getXmlMenu(menu.getPath(True, True)) self.__addXmlTextElement(xml_menu, 'Directory', filename) menu.Directory = MenuEntry(filename) deskentry = menu.Directory.DesktopEntry if name: if not deskentry.hasKey("Name"): deskentry.set("Name", name) deskentry.set("Name", name, locale = True) if genericname: if not deskentry.hasKey("GenericName"): deskentry.set("GenericName", genericname) deskentry.set("GenericName", genericname, locale = True) if comment: if not deskentry.hasKey("Comment"): deskentry.set("Comment", comment) deskentry.set("Comment", comment, locale = True) if icon: deskentry.set("Icon", icon) if nodisplay == True: deskentry.set("NoDisplay", "true") elif nodisplay == False: deskentry.set("NoDisplay", "false") if hidden == True: deskentry.set("Hidden", "true") elif hidden == False: deskentry.set("Hidden", "false") menu.Directory.updateAttributes() if isinstance(menu.Parent, Menu): sort(self.menu) return menu def hideMenuEntry(self, menuentry): self.editMenuEntry(menuentry, nodisplay = True) def unhideMenuEntry(self, menuentry): self.editMenuEntry(menuentry, nodisplay = False, hidden = False) def hideMenu(self, menu): self.editMenu(menu, nodisplay = True) def unhideMenu(self, menu): self.editMenu(menu, nodisplay = False, hidden = False) xml_menu = self.__getXmlMenu(menu.getPath(True,True), False) for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu): node.parentNode.removeChild(node) def deleteMenuEntry(self, menuentry): if self.getAction(menuentry) == "delete": self.__deleteFile(menuentry.DesktopEntry.filename) for parent in menuentry.Parents: self.__deleteEntry(parent, menuentry) sort(self.menu) return menuentry def revertMenuEntry(self, menuentry): if self.getAction(menuentry) == "revert": self.__deleteFile(menuentry.DesktopEntry.filename) menuentry.Original.Parents = [] for parent in menuentry.Parents: index = parent.Entries.index(menuentry) parent.Entries[index] = menuentry.Original index = parent.MenuEntries.index(menuentry) parent.MenuEntries[index] = menuentry.Original menuentry.Original.Parents.append(parent) sort(self.menu) return menuentry def deleteMenu(self, menu): if self.getAction(menu) == "delete": self.__deleteFile(menu.Directory.DesktopEntry.filename) self.__deleteEntry(menu.Parent, menu) xml_menu = self.__getXmlMenu(menu.getPath(True, True)) xml_menu.parentNode.removeChild(xml_menu) sort(self.menu) return menu def revertMenu(self, menu): if self.getAction(menu) == "revert": self.__deleteFile(menu.Directory.DesktopEntry.filename) menu.Directory = menu.Directory.Original sort(self.menu) return menu def deleteSeparator(self, separator): self.__deleteEntry(separator.Parent, separator, after=True) sort(self.menu) return separator """ Private Stuff """ def getAction(self, entry): if isinstance(entry, Menu): if not isinstance(entry.Directory, MenuEntry): return "none" elif entry.Directory.getType() == "Both": return "revert" elif entry.Directory.getType() == "User" \ and (len(entry.Submenus) + len(entry.MenuEntries)) == 0: return "delete" elif isinstance(entry, MenuEntry): if entry.getType() == "Both": return "revert" elif entry.getType() == "User": return "delete" else: return "none" return "none" def __saveEntries(self, menu): if not menu: menu = self.menu if isinstance(menu.Directory, MenuEntry): menu.Directory.save() for entry in menu.getEntries(hidden=True): if isinstance(entry, MenuEntry): entry.save() elif isinstance(entry, Menu): self.__saveEntries(entry) def __saveMenu(self): if not os.path.isdir(os.path.dirname(self.filename)): os.makedirs(os.path.dirname(self.filename)) fd = open(self.filename, 'w') fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*\n', ''))) fd.close() def __getFileName(self, name, extension): postfix = 0 while 1: if postfix == 0: filename = name + extension else: filename = name + "-" + str(postfix) + extension if extension == ".desktop": dir = "applications" elif extension == ".directory": dir = "desktop-directories" if not filename in self.filenames and not \ os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)): self.filenames.append(filename) break else: postfix += 1 return filename def __getXmlMenu(self, path, create=True, element=None): if not element: element = self.doc if "/" in path: (name, path) = path.split("/", 1) else: name = path path = "" found = None for node in self.__getXmlNodesByName("Menu", element): for child in self.__getXmlNodesByName("Name", node): if child.childNodes[0].nodeValue == name: if path: found = self.__getXmlMenu(path, create, node) else: found = node break if found: break if not found and create == True: node = self.__addXmlMenuElement(element, name) if path: found = self.__getXmlMenu(path, create, node) else: found = node return found def __addXmlMenuElement(self, element, name): node = self.doc.createElement('Menu') self.__addXmlTextElement(node, 'Name', name) return element.appendChild(node) def __addXmlTextElement(self, element, name, text): node = self.doc.createElement(name) text = self.doc.createTextNode(text) node.appendChild(text) return element.appendChild(node) def __addXmlFilename(self, element, filename, type = "Include"): # remove old filenames for node in self.__getXmlNodesByName(["Include", "Exclude"], element): if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename: element.removeChild(node) # add new filename node = self.doc.createElement(type) node.appendChild(self.__addXmlTextElement(node, 'Filename', filename)) return element.appendChild(node) def __addXmlMove(self, element, old, new): node = self.doc.createElement("Move") node.appendChild(self.__addXmlTextElement(node, 'Old', old)) node.appendChild(self.__addXmlTextElement(node, 'New', new)) return element.appendChild(node) def __addXmlLayout(self, element, layout): # remove old layout for node in self.__getXmlNodesByName("Layout", element): element.removeChild(node) # add new layout node = self.doc.createElement("Layout") for order in layout.order: if order[0] == "Separator": child = self.doc.createElement("Separator") node.appendChild(child) elif order[0] == "Filename": child = self.__addXmlTextElement(node, "Filename", order[1]) elif order[0] == "Menuname": child = self.__addXmlTextElement(node, "Menuname", order[1]) elif order[0] == "Merge": child = self.doc.createElement("Merge") child.setAttribute("type", order[1]) node.appendChild(child) return element.appendChild(node) def __getXmlNodesByName(self, name, element): for child in element.childNodes: if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name: yield child def __addLayout(self, parent): layout = Layout() layout.order = [] layout.show_empty = parent.Layout.show_empty layout.inline = parent.Layout.inline layout.inline_header = parent.Layout.inline_header layout.inline_alias = parent.Layout.inline_alias layout.inline_limit = parent.Layout.inline_limit layout.order.append(["Merge", "menus"]) for entry in parent.Entries: if isinstance(entry, Menu): layout.parseMenuname(entry.Name) elif isinstance(entry, MenuEntry): layout.parseFilename(entry.DesktopFileID) elif isinstance(entry, Separator): layout.parseSeparator() layout.order.append(["Merge", "files"]) parent.Layout = layout return layout def __addEntry(self, parent, entry, after=None, before=None): if after or before: if after: index = parent.Entries.index(after) + 1 elif before: index = parent.Entries.index(before) parent.Entries.insert(index, entry) else: parent.Entries.append(entry) xml_parent = self.__getXmlMenu(parent.getPath(True, True)) if isinstance(entry, MenuEntry): parent.MenuEntries.append(entry) entry.Parents.append(parent) self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include") elif isinstance(entry, Menu): parent.addSubmenu(entry) if after or before: self.__addLayout(parent) self.__addXmlLayout(xml_parent, parent.Layout) def __deleteEntry(self, parent, entry, after=None, before=None): parent.Entries.remove(entry) xml_parent = self.__getXmlMenu(parent.getPath(True, True)) if isinstance(entry, MenuEntry): entry.Parents.remove(parent) parent.MenuEntries.remove(entry) self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude") elif isinstance(entry, Menu): parent.Submenus.remove(entry) if after or before: self.__addLayout(parent) self.__addXmlLayout(xml_parent, parent.Layout) def __deleteFile(self, filename): try: os.remove(filename) except OSError: pass try: self.filenames.remove(filename) except ValueError: pass def __remove_whilespace_nodes(self, node): remove_list = [] for child in node.childNodes: if child.nodeType == xml.dom.minidom.Node.TEXT_NODE: child.data = child.data.strip() if not child.data.strip(): remove_list.append(child) elif child.hasChildNodes(): self.__remove_whilespace_nodes(child) for node in remove_list: node.parentNode.removeChild(node)