2 Implementation of the XDG Menu Specification Version 1.0.draft-1
3 http://standards.freedesktop.org/menu-spec/
6 from __future__ import generators
7 import locale, os, xml.dom.minidom
9 from xdg.BaseDirectory import *
10 from xdg.DesktopEntry import *
11 from xdg.Exceptions import *
16 ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE
37 # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True
41 # Private stuff, only needed for parsing
43 self.DefaultLayout = None
44 self.Deleted = "notset"
46 self.DirectoryDirs = []
50 self.OnlyUnallocated = "notset"
57 def __add__(self, other):
58 for dir in other.AppDirs:
59 self.AppDirs.append(dir)
61 for dir in other.DirectoryDirs:
62 self.DirectoryDirs.append(dir)
64 for directory in other.Directories:
65 self.Directories.append(directory)
67 if other.Deleted != "notset":
68 self.Deleted = other.Deleted
70 if other.OnlyUnallocated != "notset":
71 self.OnlyUnallocated = other.OnlyUnallocated
74 self.Layout = other.Layout
76 if other.DefaultLayout:
77 self.DefaultLayout = other.DefaultLayout
79 for rule in other.Rules:
80 self.Rules.append(rule)
82 for move in other.Moves:
83 self.Moves.append(move)
85 for submenu in other.Submenus:
86 self.addSubmenu(submenu)
90 # FIXME: Performance: cache getName()
91 def __cmp__(self, other):
92 return locale.strcoll(self.getName(), other.getName())
94 def __eq__(self, other):
95 if self.Name == str(other):
101 def getEntries(self, hidden=False):
102 for entry in self.Entries:
105 elif entry.Show == True:
108 # FIXME: Add searchEntry/seaqrchMenu function
109 # search for name/comment/genericname/desktopfileide
110 # return multiple items
112 def getMenuEntry(self, desktopfileid, deep = False):
113 for menuentry in self.MenuEntries:
114 if menuentry.DesktopFileID == desktopfileid:
117 for submenu in self.Submenus:
118 submenu.getMenuEntry(desktopfileid, deep)
120 def getMenu(self, path):
121 array = path.split("/", 1)
122 for submenu in self.Submenus:
123 if submenu.Name == array[0]:
125 return submenu.getMenu(array[1])
129 def getPath(self, org=False, toplevel=False):
134 names.append(parent.Name)
136 names.append(parent.getName())
138 parent = parent.Parent
143 if toplevel == False:
146 path = os.path.join(path, name)
151 return self.Directory.DesktopEntry.getName()
152 except AttributeError:
155 def getGenericName(self):
157 return self.Directory.DesktopEntry.getGenericName()
158 except AttributeError:
161 def getComment(self):
163 return self.Directory.DesktopEntry.getComment()
164 except AttributeError:
169 return self.Directory.DesktopEntry.getIcon()
170 except AttributeError:
173 """ PRIVATE STUFF """
174 def addSubmenu(self, newmenu):
175 for submenu in self.Submenus:
176 if submenu == newmenu:
180 self.Submenus.append(newmenu)
181 newmenu.Parent = self
182 newmenu.Depth = self.Depth + 1
186 def __init__(self, node=None):
193 def __cmp__(self, other):
194 return cmp(self.Old, other.Old)
196 def parseNode(self, node):
197 for child in node.childNodes:
198 if child.nodeType == ELEMENT_NODE:
199 if child.tagName == "Old":
201 self.parseOld(child.childNodes[0].nodeValue)
203 raise ValidationError('Old cannot be empty', '??')
204 elif child.tagName == "New":
206 self.parseNew(child.childNodes[0].nodeValue)
208 raise ValidationError('New cannot be empty', '??')
210 def parseOld(self, value):
212 def parseNew(self, value):
218 def __init__(self, node=None):
221 self.show_empty = node.getAttribute("show_empty") or "false"
222 self.inline = node.getAttribute("inline") or "false"
223 self.inline_limit = node.getAttribute("inline_limit") or 4
224 self.inline_header = node.getAttribute("inline_header") or "true"
225 self.inline_alias = node.getAttribute("inline_alias") or "false"
226 self.inline_limit = int(self.inline_limit)
229 self.show_empty = "false"
230 self.inline = "false"
231 self.inline_limit = 4
232 self.inline_header = "true"
233 self.inline_alias = "false"
234 self.order.append(["Merge", "menus"])
235 self.order.append(["Merge", "files"])
237 def parseNode(self, node):
238 for child in node.childNodes:
239 if child.nodeType == ELEMENT_NODE:
240 if child.tagName == "Menuname":
243 child.childNodes[0].nodeValue,
244 child.getAttribute("show_empty") or "false",
245 child.getAttribute("inline") or "false",
246 child.getAttribute("inline_limit") or 4,
247 child.getAttribute("inline_header") or "true",
248 child.getAttribute("inline_alias") or "false" )
250 raise ValidationError('Menuname cannot be empty', "")
251 elif child.tagName == "Separator":
252 self.parseSeparator()
253 elif child.tagName == "Filename":
255 self.parseFilename(child.childNodes[0].nodeValue)
257 raise ValidationError('Filename cannot be empty', "")
258 elif child.tagName == "Merge":
259 self.parseMerge(child.getAttribute("type") or "all")
261 def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"):
262 self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias])
263 self.order[-1][4] = int(self.order[-1][4])
265 def parseSeparator(self):
266 self.order.append(["Separator"])
268 def parseFilename(self, value):
269 self.order.append(["Filename", value])
271 def parseMerge(self, type="all"):
272 self.order.append(["Merge", type])
276 "Inlcude / Exclude Rules Class"
277 def __init__(self, type, node=None):
278 # Type is Include or Exclude
280 # Rule is a python expression
283 # Private attributes, only needed for parsing
298 def do(menuentries, type, run):
299 for menuentry in menuentries:
300 if run == 2 and ( menuentry.MatchedInclude == True \
301 or menuentry.Allocated == True ):
304 if type == "Include":
306 menuentry.MatchedInclude = True
308 menuentry.Add = False
310 """ % self.Rule) in self.__dict__
312 def parseNode(self, node):
313 for child in node.childNodes:
314 if child.nodeType == ELEMENT_NODE:
315 if child.tagName == 'Filename':
317 self.parseFilename(child.childNodes[0].nodeValue)
319 raise ValidationError('Filename cannot be empty', "???")
320 elif child.tagName == 'Category':
322 self.parseCategory(child.childNodes[0].nodeValue)
324 raise ValidationError('Category cannot be empty', "???")
325 elif child.tagName == 'All':
327 elif child.tagName == 'And':
329 elif child.tagName == 'Or':
331 elif child.tagName == 'Not':
334 def parseNew(self, set=True):
336 self.Rule += " " + self.Expr[self.Depth] + " "
342 def parseFilename(self, value):
344 self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'")
346 def parseCategory(self, value):
348 self.Rule += "'%s' in menuentry.Categories" % value.strip()
354 def parseAnd(self, node):
358 self.Expr.append("and")
364 def parseOr(self, node):
368 self.Expr.append("or")
374 def parseNot(self, node):
378 self.Expr.append("or")
386 "Wrapper for 'Menu Style' Desktop Entries"
387 def __init__(self, filename, dir="", prefix=""):
389 self.DesktopEntry = DesktopEntry(os.path.join(dir,filename))
390 self.setAttributes(filename, dir, prefix)
392 # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True
400 self.Allocated = False
402 self.MatchedInclude = False
405 self.Categories = self.DesktopEntry.getCategories()
408 if self.DesktopEntry.tainted == True:
409 self.DesktopEntry.write()
412 return self.DesktopEntry.filename.replace(self.Filename, '')
415 # Can be one of System/User/Both
416 if xdg.Config.root_mode == False:
419 elif xdg_data_dirs[0] in self.DesktopEntry.filename:
426 def setAttributes(self, filename, dir="", prefix=""):
427 self.Filename = filename
429 self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-")
431 if not os.path.isabs(self.DesktopEntry.filename):
434 def updateAttributes(self):
435 if self.getType() == "System":
436 self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix)
439 def __setFilename(self):
440 if xdg.Config.root_mode == False:
441 path = xdg_data_dirs[0]
443 path= xdg_data_dirs[1]
445 if self.DesktopEntry.getType() == "Application":
446 dir = os.path.join(path, "applications")
448 dir = os.path.join(path, "desktop-directories")
450 self.DesktopEntry.filename = os.path.join(dir, self.Filename)
452 def __cmp__(self, other):
453 return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName())
455 def __eq__(self, other):
456 if self.DesktopFileID == str(other):
462 return self.DesktopFileID
466 "Just a dummy class for Separators"
467 def __init__(self, parent):
473 "Class for Inline Headers"
474 def __init__(self, name, generic_name, comment):
476 self.GenericName = generic_name
477 self.Comment = comment
485 def __getFileName(filename):
486 dirs = xdg_config_dirs[:]
487 if xdg.Config.root_mode == True:
491 menuname = os.path.join (dir, "menus" , filename)
492 if os.path.isdir(dir) and os.path.isfile(menuname):
495 def parse(filename=None):
496 # conver to absolute path
497 if filename and not os.path.isabs(filename):
498 filename = __getFileName(filename)
500 # use default if no filename given
502 candidate = os.environ.get('XDG_MENU_PREFIX', '') + "applications.menu"
503 filename = __getFileName(candidate)
506 raise ParsingError('File not found', "/etc/xdg/menus/%s" % candidate)
508 # check if it is a .menu file
509 if not os.path.splitext(filename)[1] == ".menu":
510 raise ParsingError('Not a .menu file', filename)
514 doc = xml.dom.minidom.parse(filename)
515 except xml.parsers.expat.ExpatError:
516 raise ParsingError('Not a valid .menu file', filename)
520 tmp["mergeFiles"] = []
521 tmp["DirectoryDirs"] = []
522 tmp["cache"] = MenuEntryCache()
524 __parse(doc, filename, tmp["Root"])
525 __parsemove(tmp["Root"])
526 __postparse(tmp["Root"])
528 tmp["Root"].Doc = doc
529 tmp["Root"].Filename = filename
532 __genmenuNotOnlyAllocated(tmp["Root"])
533 __genmenuOnlyAllocated(tmp["Root"])
541 def __parse(node, filename, parent=None):
542 for child in node.childNodes:
543 if child.nodeType == ELEMENT_NODE:
544 if child.tagName == 'Menu':
545 __parseMenu(child, filename, parent)
546 elif child.tagName == 'AppDir':
548 __parseAppDir(child.childNodes[0].nodeValue, filename, parent)
550 raise ValidationError('AppDir cannot be empty', filename)
551 elif child.tagName == 'DefaultAppDirs':
552 __parseDefaultAppDir(filename, parent)
553 elif child.tagName == 'DirectoryDir':
555 __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent)
557 raise ValidationError('DirectoryDir cannot be empty', filename)
558 elif child.tagName == 'DefaultDirectoryDirs':
559 __parseDefaultDirectoryDir(filename, parent)
560 elif child.tagName == 'Name' :
562 parent.Name = child.childNodes[0].nodeValue
564 raise ValidationError('Name cannot be empty', filename)
565 elif child.tagName == 'Directory' :
567 parent.Directories.append(child.childNodes[0].nodeValue)
569 raise ValidationError('Directory cannot be empty', filename)
570 elif child.tagName == 'OnlyUnallocated':
571 parent.OnlyUnallocated = True
572 elif child.tagName == 'NotOnlyUnallocated':
573 parent.OnlyUnallocated = False
574 elif child.tagName == 'Deleted':
575 parent.Deleted = True
576 elif child.tagName == 'NotDeleted':
577 parent.Deleted = False
578 elif child.tagName == 'Include' or child.tagName == 'Exclude':
579 parent.Rules.append(Rule(child.tagName, child))
580 elif child.tagName == 'MergeFile':
582 if child.getAttribute("type") == "parent":
583 __parseMergeFile("applications.menu", child, filename, parent)
585 __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent)
587 raise ValidationError('MergeFile cannot be empty', filename)
588 elif child.tagName == 'MergeDir':
590 __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent)
592 raise ValidationError('MergeDir cannot be empty', filename)
593 elif child.tagName == 'DefaultMergeDirs':
594 __parseDefaultMergeDirs(child, filename, parent)
595 elif child.tagName == 'Move':
596 parent.Moves.append(Move(child))
597 elif child.tagName == 'Layout':
598 if len(child.childNodes) > 1:
599 parent.Layout = Layout(child)
600 elif child.tagName == 'DefaultLayout':
601 if len(child.childNodes) > 1:
602 parent.DefaultLayout = Layout(child)
603 elif child.tagName == 'LegacyDir':
605 __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent)
607 raise ValidationError('LegacyDir cannot be empty', filename)
608 elif child.tagName == 'KDELegacyDirs':
609 __parseKDELegacyDirs(filename, parent)
611 def __parsemove(menu):
612 for submenu in menu.Submenus:
615 # parse move operations
616 for move in menu.Moves:
617 move_from_menu = menu.getMenu(move.Old)
619 move_to_menu = menu.getMenu(move.New)
621 menus = move.New.split("/")
623 while len(menus) > 0:
626 newmenu = oldparent.getMenu(menus[0])
629 newmenu.Name = menus[0]
631 newmenu.NotInXml = True
632 oldparent.addSubmenu(newmenu)
636 newmenu += move_from_menu
637 move_from_menu.Parent.Submenus.remove(move_from_menu)
639 def __postparse(menu):
640 # unallocated / deleted
641 if menu.Deleted == "notset":
643 if menu.OnlyUnallocated == "notset":
644 menu.OnlyUnallocated = False
647 if not menu.Layout or not menu.DefaultLayout:
648 if menu.DefaultLayout:
649 menu.Layout = menu.DefaultLayout
652 menu.DefaultLayout = menu.Parent.DefaultLayout
654 menu.DefaultLayout = Layout()
657 menu.Layout = menu.Parent.DefaultLayout
658 menu.DefaultLayout = menu.Parent.DefaultLayout
660 menu.Layout = Layout()
661 menu.DefaultLayout = Layout()
663 # add parent's app/directory dirs
665 menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs
666 menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs
669 menu.Directories = __removeDuplicates(menu.Directories)
670 menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs)
671 menu.AppDirs = __removeDuplicates(menu.AppDirs)
673 # go recursive through all menus
674 for submenu in menu.Submenus:
677 # reverse so handling is easier
678 menu.Directories.reverse()
679 menu.DirectoryDirs.reverse()
680 menu.AppDirs.reverse()
682 # get the valid .directory file out of the list
683 for directory in menu.Directories:
684 for dir in menu.DirectoryDirs:
685 if os.path.isfile(os.path.join(dir, directory)):
686 menuentry = MenuEntry(directory, dir)
687 if not menu.Directory:
688 menu.Directory = menuentry
689 elif menuentry.getType() == "System":
690 if menu.Directory.getType() == "User":
691 menu.Directory.Original = menuentry
697 def __parseMenu(child, filename, parent):
699 __parse(child, filename, m)
706 def __check(value, filename, type):
707 path = os.path.dirname(filename)
709 if not os.path.isabs(value):
710 value = os.path.join(path, value)
712 value = os.path.abspath(value)
714 if type == "dir" and os.path.exists(value) and os.path.isdir(value):
716 elif type == "file" and os.path.exists(value) and os.path.isfile(value):
721 # App/Directory Dir Stuff
722 def __parseAppDir(value, filename, parent):
723 value = __check(value, filename, "dir")
725 parent.AppDirs.append(value)
727 def __parseDefaultAppDir(filename, parent):
728 for dir in reversed(xdg_data_dirs):
729 __parseAppDir(os.path.join(dir, "applications"), filename, parent)
731 def __parseDirectoryDir(value, filename, parent):
732 value = __check(value, filename, "dir")
734 parent.DirectoryDirs.append(value)
736 def __parseDefaultDirectoryDir(filename, parent):
737 for dir in reversed(xdg_data_dirs):
738 __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent)
741 def __parseMergeFile(value, child, filename, parent):
742 if child.getAttribute("type") == "parent":
743 for dir in xdg_config_dirs:
744 rel_file = filename.replace(dir, "").strip("/")
745 if rel_file != filename:
746 for p in xdg_config_dirs:
749 if os.path.isfile(os.path.join(p,rel_file)):
750 __mergeFile(os.path.join(p,rel_file),child,parent)
753 value = __check(value, filename, "file")
755 __mergeFile(value, child, parent)
757 def __parseMergeDir(value, child, filename, parent):
758 value = __check(value, filename, "dir")
760 for item in os.listdir(value):
762 if os.path.splitext(item)[1] == ".menu":
763 __mergeFile(os.path.join(value, item), child, parent)
764 except UnicodeDecodeError:
767 def __parseDefaultMergeDirs(child, filename, parent):
768 basename = os.path.splitext(os.path.basename(filename))[0]
769 for dir in reversed(xdg_config_dirs):
770 __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent)
772 def __mergeFile(filename, child, parent):
773 # check for infinite loops
774 if filename in tmp["mergeFiles"]:
776 raise ParsingError('Infinite MergeFile loop detected', filename)
780 tmp["mergeFiles"].append(filename)
784 doc = xml.dom.minidom.parse(filename)
787 raise ParsingError('File not found', filename)
790 except xml.parsers.expat.ExpatError:
792 raise ParsingError('Not a valid .menu file', filename)
797 for child in doc.childNodes:
798 if child.nodeType == ELEMENT_NODE:
799 __parse(child,filename,parent)
803 def __parseLegacyDir(dir, prefix, filename, parent):
804 m = __mergeLegacyDir(dir,prefix,filename,parent)
808 def __mergeLegacyDir(dir, prefix, filename, parent):
809 dir = __check(dir,filename,"dir")
810 if dir and dir not in tmp["DirectoryDirs"]:
811 tmp["DirectoryDirs"].append(dir)
814 m.AppDirs.append(dir)
815 m.DirectoryDirs.append(dir)
816 m.Name = os.path.basename(dir)
819 for item in os.listdir(dir):
821 if item == ".directory":
822 m.Directories.append(item)
823 elif os.path.isdir(os.path.join(dir,item)):
824 m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent))
825 except UnicodeDecodeError:
828 tmp["cache"].addMenuEntries([dir],prefix, True)
829 menuentries = tmp["cache"].getMenuEntries([dir], False)
831 for menuentry in menuentries:
832 categories = menuentry.Categories
833 if len(categories) == 0:
835 r.parseFilename(menuentry.DesktopFileID)
838 if not dir in parent.AppDirs:
839 categories.append("Legacy")
840 menuentry.Categories = categories
844 def __parseKDELegacyDirs(filename, parent):
845 f=os.popen3("kde-config --path apps")
846 output = f[1].readlines()
848 for dir in output[0].split(":"):
849 __parseLegacyDir(dir,"kde", filename, parent)
853 # remove duplicate entries from a list
854 def __removeDuplicates(list):
857 list = [set.setdefault(e,e) for e in list if e not in set]
861 # Finally generate the menu
862 def __genmenuNotOnlyAllocated(menu):
863 for submenu in menu.Submenus:
864 __genmenuNotOnlyAllocated(submenu)
866 if menu.OnlyUnallocated == False:
867 tmp["cache"].addMenuEntries(menu.AppDirs)
869 for rule in menu.Rules:
870 menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1)
871 for menuentry in menuentries:
872 if menuentry.Add == True:
873 menuentry.Parents.append(menu)
874 menuentry.Add = False
875 menuentry.Allocated = True
876 menu.MenuEntries.append(menuentry)
878 def __genmenuOnlyAllocated(menu):
879 for submenu in menu.Submenus:
880 __genmenuOnlyAllocated(submenu)
882 if menu.OnlyUnallocated == True:
883 tmp["cache"].addMenuEntries(menu.AppDirs)
885 for rule in menu.Rules:
886 menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2)
887 for menuentry in menuentries:
888 if menuentry.Add == True:
889 menuentry.Parents.append(menu)
890 # menuentry.Add = False
891 # menuentry.Allocated = True
892 menu.MenuEntries.append(menuentry)
899 for submenu in menu.Submenus:
905 for order in menu.Layout.order:
906 if order[0] == "Filename":
907 tmp_e.append(order[1])
908 elif order[0] == "Menuname":
909 tmp_s.append(order[1])
911 for order in menu.Layout.order:
912 if order[0] == "Separator":
913 separator = Separator(menu)
914 if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator):
915 separator.Show = False
916 menu.Entries.append(separator)
917 elif order[0] == "Filename":
918 menuentry = menu.getMenuEntry(order[1])
920 menu.Entries.append(menuentry)
921 elif order[0] == "Menuname":
922 submenu = menu.getMenu(order[1])
924 __parse_inline(submenu, menu)
925 elif order[0] == "Merge":
926 if order[1] == "files" or order[1] == "all":
927 menu.MenuEntries.sort()
928 for menuentry in menu.MenuEntries:
929 if menuentry not in tmp_e:
930 menu.Entries.append(menuentry)
931 elif order[1] == "menus" or order[1] == "all":
933 for submenu in menu.Submenus:
934 if submenu.Name not in tmp_s:
935 __parse_inline(submenu, menu)
937 # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec
938 for entry in menu.Entries:
941 if isinstance(entry, Menu):
942 if entry.Deleted == True:
943 entry.Show = "Deleted"
945 elif isinstance(entry.Directory, MenuEntry):
946 if entry.Directory.DesktopEntry.getNoDisplay() == True:
947 entry.Show = "NoDisplay"
949 elif entry.Directory.DesktopEntry.getHidden() == True:
950 entry.Show = "Hidden"
952 elif isinstance(entry, MenuEntry):
953 if entry.DesktopEntry.getNoDisplay() == True:
954 entry.Show = "NoDisplay"
956 elif entry.DesktopEntry.getHidden() == True:
957 entry.Show = "Hidden"
959 elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()):
960 entry.Show = "NoExec"
962 elif xdg.Config.windowmanager:
963 if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \
964 or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn():
965 entry.Show = "NotShowIn"
967 elif isinstance(entry,Separator):
970 # remove separators at the beginning and at the end
971 if len(menu.Entries) > 0:
972 if isinstance(menu.Entries[0], Separator):
973 menu.Entries[0].Show = False
974 if len(menu.Entries) > 1:
975 if isinstance(menu.Entries[-1], Separator):
976 menu.Entries[-1].Show = False
979 for entry in menu.Entries:
980 if isinstance(entry,Menu) and entry.Layout.show_empty == "false" and entry.Visible == 0:
983 if entry.NotInXml == True:
984 menu.Entries.remove(entry)
986 def __try_exec(executable):
987 paths = os.environ['PATH'].split(os.pathsep)
988 if not os.path.isfile(executable):
990 f = os.path.join(p, executable)
991 if os.path.isfile(f):
992 if os.access(f, os.X_OK):
995 if os.access(executable, os.X_OK):
1000 def __parse_inline(submenu, menu):
1001 if submenu.Layout.inline == "true":
1002 if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true":
1003 menuentry = submenu.Entries[0]
1004 menuentry.DesktopEntry.set("Name", submenu.getName(), locale = True)
1005 menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale = True)
1006 menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale = True)
1007 menu.Entries.append(menuentry)
1008 elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0:
1009 if submenu.Layout.inline_header == "true":
1010 header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment())
1011 menu.Entries.append(header)
1012 for entry in submenu.Entries:
1013 menu.Entries.append(entry)
1015 menu.Entries.append(submenu)
1017 menu.Entries.append(submenu)
1019 class MenuEntryCache:
1020 "Class to cache Desktop Entries"
1022 self.cacheEntries = {}
1023 self.cacheEntries['legacy'] = []
1026 def addMenuEntries(self, dirs, prefix="", legacy=False):
1028 if not self.cacheEntries.has_key(dir):
1029 self.cacheEntries[dir] = []
1030 self.__addFiles(dir, "", prefix, legacy)
1032 def __addFiles(self, dir, subdir, prefix, legacy):
1033 for item in os.listdir(os.path.join(dir,subdir)):
1034 if os.path.splitext(item)[1] == ".desktop":
1036 menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix)
1037 except ParsingError:
1040 self.cacheEntries[dir].append(menuentry)
1042 self.cacheEntries['legacy'].append(menuentry)
1043 elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False:
1044 self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy)
1046 def getMenuEntries(self, dirs, legacy=True):
1049 # handle legacy items
1052 appdirs.append("legacy")
1053 # cache the results again
1054 key = "".join(appdirs)
1056 return self.cache[key]
1060 for menuentry in self.cacheEntries[dir]:
1062 if menuentry.DesktopFileID not in ids:
1063 ids.append(menuentry.DesktopFileID)
1064 list.append(menuentry)
1065 elif menuentry.getType() == "System":
1066 # FIXME: This is only 99% correct, but still...
1067 i = list.index(menuentry)
1069 if e.getType() == "User":
1070 e.Original = menuentry
1071 except UnicodeDecodeError:
1073 self.cache[key] = list