Hi, Recently, I encoutered this bug. So I decided to look at the code. I seems that obamenu code is poor. The translation to python3 has been very basic. It don't follow the python3 coding style. First, there is a mix of tab and spaces.
One can use f-string instead of the % operator for formating string. There are many too long lines. The fix for the bug regarding broken links is easy. In the function `process_dtfile`, it must frame the whole code of the function in a `try: exception`. I decided to rewrite the code. I made many changes to get a cleaner code. I believe this will be easier to maintain. I don't know what is the best meethod to suggest my improvements. See the attachement for the entire code. If I must proceed by other way, let me know how. Best regards. -- Gilles Crèvecoeur.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import glob import sys # Version 1.1.7 # ---- config --- applications_dirs = ("/usr/share/applications", ) image_dir_base = "/usr/share" # without "pixmaps" -/usr/local/share in FreeBSD, /usr/share on linux icon_Theme = "Humanity" image_cat_prefix = "applications-" # if empty will create no icon text only menu application_groups = ("Office", "Development", "Graphics", "Internet", "Games", "System", "Multimedia", "Utilities", "Settings") group_aliases = {"Audio": "Multimedia", "AudioVideo": "Multimedia", "Network": "Internet", "Game": "Games", "Utility": "Utilities", "GTK": "", "GNOME": ""} ignoreList = ("evince-previewer", "Ted", "wingide3.2", "python3.4", "feh", "xfce4-power-manager-settings") terminal_string = "evte -e" # your favourites terminal exec string simpleOBheader = False # print full xml style OB header # --- End of user config --- class dtItem(object): def __init__(self, fName): self.fileName = fName self.Name = "" self.Comment = "" self.Exec = "" self.Terminal = None self.Type = "" self.Icon = "" self.Categories = () def addName(self, data): self.Name = xescape(data) def addComment(self, data): self.Comment = data def addExec(self, data): if len(data) > 3 and data[-2] == '%': # get rid of filemanager arguments in dt files data = data[:-2].strip() self.Exec = data def addIcon(self, data): self.Icon = "" if image_cat_prefix == "": return image_dir = image_dir_base + "/pixmaps/" di = data.strip() if len(di) < 3: # "Error in %s: Invalid or no icon '%s'" % (self.fileName, di) return dix = di.find("/") # is it a full path? if dix >= 0 and dix <= 2: # yes, its a path (./path or ../path or /path ...) self.Icon = di return # else a short name like "myapp" tmp = image_dir + di + ".*" tmp = glob.glob(tmp) if len(tmp) > 0: self.Icon = tmp[0] return def addTerminal(self, data): if data == "True" or data == "true": self.Terminal = True else: self.Terminal = False def addType(self, data): self.Type = data def addCategories(self, data): self.Categories = data def getCatIcon(cat): iconDir = image_dir_base + "/icons/" + icon_Theme + "/categories/24/" cat = image_cat_prefix + cat.lower() tmp = glob.glob(iconDir + cat + ".*") if len(tmp) > 0: return tmp[0] return "" def xescape(s): Rep = {"&": "&", "<": "<", ">": ">", "'": "'", "\"": """} for p in ("&", "<", ">", "'", "\""): sl = len(s) last = -1 while last < sl: i = s.find(p, last+1) if i < 0: # done = True # never used break last = i x = s[:i] r = s[i+1:] s = x + Rep[p] + r return s def process_category(cat, curCats, appGroups=application_groups, aliases=group_aliases ): # first process aliases if cat in aliases: if aliases[cat] == "": return "" # ignore this one cat = aliases[cat] if cat in appGroups and cat not in curCats: # valid categories only and no doublettes, please curCats.append(cat) return cat return "" def process_dtfile(dtf, catDict): # process this file & extract relevant info try: with open(dtf, "r") as lines: active = False # parse only after "[Desktop Entry]" line this = dtItem(dtf) for line in lines: line = line.strip() if line == "[Desktop Entry]": active = True continue if active is False: # we don't care about licenses or other comments continue if line is None or len(line) < 1 or line[0] == '#': continue if line[0] == '[' and line != "[Desktop Entry]": active = False continue # else eqi = line.split('=') if len(eqi) < 2: print("Error: Invalid .desktop line'" + line + "'") continue # Check what it is ... if eqi[0] == "Name": this.addName(eqi[1]) elif eqi[0] == "Comment": this.addComment(eqi[1]) elif eqi[0] == "Exec": this.addExec(eqi[1]) elif eqi[0] == "Icon": this.addIcon(eqi[1]) elif eqi[0] == "Terminal": this.addTerminal(eqi[1]) elif eqi[0] == "Type": if eqi[1] != "Application": continue this.addType(eqi[1]) elif eqi[0] == "Categories": if eqi[1] and eqi[1][-1] == ';': eqi[1] = eqi[1][0:-1] cats = [] # DEBUG dtCats = eqi[1].split(';') for cat in dtCats: # result = process_category(cat, cats) # result is never used process_category(cat, cats) this.addCategories(cats) else: continue # add to catDict # this.dprint() if len(this.Categories) > 0: # don't care about stuff w/o category for cat in this.Categories: catDict[cat].append(this) except FileNotFoundError: prog_name = sys.argv[0] print(F"{prog_name}: file not found: {dtf}", file=sys.stderr) categoryDict = {} if __name__ == "__main__": # init the application group dict (which will contain list of apps) for appGroup in application_groups: categoryDict[appGroup] = [] # now let's look into the app dirs ... for appDir in applications_dirs: appDir += "/*.desktop" dtFiles = glob.glob(appDir) # process each .desktop file in dir for dtf in dtFiles: skipFlag = False for ifn in ignoreList: if dtf.find(ifn) >= 0: skipFlag = True if skipFlag is False: process_dtfile(dtf, categoryDict) # now, generate jwm menu include if simpleOBheader is True: print('<openbox_pipe_menu>') # magic header else: print('<?xml version="1.0" encoding="UTF-8" ?><openbox_pipe_menu xmlns="http://openbox.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://openbox.org/" >') # magic header appGroupLen = len(application_groups) for ag in range(appGroupLen): catList = categoryDict[application_groups[ag]] if len(catList) < 1: continue # don't create empty menus s = application_groups[ag] catStr = F"<menu id=\"openbox-{s}\" label=\"{s}\" " tmp = getCatIcon(application_groups[ag]) if tmp != "": catStr += F"icon=\"{tmp}\"" print(catStr, ">") for app in catList: progStr = "<item " progStr += F"label=\"{app.Name}\" " if app.Icon != "": progStr += F"icon=\"{app.Icon}\" " progStr += "><action name=\"Execute\"><command><![CDATA[" if app.Terminal is True: progStr += terminal_string + " " progStr += F"{app.Exec}]]></command></action></item>" print(progStr) print("</menu>") print("</openbox_pipe_menu>") # magic footer pass # done/debug break