#!/usr/bin/python3 # # generates menus for e16 from .desktop files # import os, sys import getopt import subprocess dbg = 0 N = 0 dtis = {} dt_seen = {} class DTI: # .desktop item def __init__(self, file, name, exec, icon, cats, type): self.file = file self.name = name self.exec = exec self.icon = icon self.cats = cats self.type = type def D(str): if dbg > 0: print(str) def D2(str): if dbg >= 2: print(str) def D3(str): if dbg >= 3: print(str) def getenv(env): if env in os.environ: return os.environ[env] else: return '' def FileExt(file): return os.path.splitext(file)[1] # Make directory list, check that they exist def MkDirList(pfxs, sufs, sep=''): r = [] for pfx in pfxs: for suf in sufs: dir = f'{pfx}{sep}{suf}' if os.path.isdir(dir): r += [dir] return r # Remove duplicates and nulls in dir list def RemoveDuplicates(arr): r = [] for itm in arr: if itm == '' or itm in r: continue r.append(itm) return r # Make dir if non-existing def MkDir(dir): if not os.path.isdir(dir): os.makedirs(dir, 0o755) D(f'mkdir {dir}') # Make simple menus def MakeMenu(name, tmpl): file = EdirMenus + '/' + name D(f'Generating {name} -> {file}') if os.path.exists(file): return f = open(file, 'w') for line in tmpl: (t, n, p) = line.split(':') if t == 't': f.write(f'"{n}"\n') elif t == 'm': f.write(f'"{n}" NULL menu "{p}"\n') elif t == 'x': f.write(f'"{n}" NULL exec "{p}"\n') elif t == 'c': f.write(f'"{n}" NULL "{p}"\n') f.close() # Make the Epplets menu def MakeEppsMenu(name): dirs = [] file = EdirMenus + '/' + name D(f'Generating {name} -> {file}') f = open(file, 'w') f.write('"Enlightenment Epplets"\n') for dir in os.environ['PATH'].split(':'): # D(f'P = {dir}') if dir in dirs: continue if not os.path.isdir(dir): continue dirs.append(dir) D2(f'Looking for epplets in {dir}') for file in os.listdir(dir): if not file.endswith('.epplet'): continue epp = file.removesuffix('.epplet') f.write(f'"{epp}" "{EdirRoot}/epplet_icons/{epp}.icon" exec "{dir}/{file}"\n') f.close() # Process a .desktop file def ProcessFile(file, cats, type): global N global dtis D(f'- File {file}') if not os.path.isfile(file): print(f'Not found: {file}') exit(1) # Global ref no N += 1 Name = Exec = Icon = Ndis = '' Nam1 = Nam2 = Nam3 = '' Cats = [] if cats: Cats.append(cats) Type = type f = open(file, 'r') for line in f: line = line.strip() # print(line) if len(line) == 0: continue if line.startswith('#'): continue if line.startswith('['): if line != '[Desktop Entry]': D2(f'Break: {line}') break continue (tok, val) = line.split('=', 1) if tok.startswith('Name'): if tok == 'Name': Name = val elif loc1 and tok == f'Name[{loc1}]': Nam1 = val elif loc2 and tok == f'Name[{loc2}]': Nam2 = val elif loc3 and tok == f'Name[{loc3}]': Nam3 = val if Nam1 or Nam2 or Nam3: if Nam1: Name = Nam1 elif Nam2: Name = Nam2 else: Name = Nam3 elif tok == 'Exec': Exec = val elif tok == 'Icon': Icon = val elif tok == 'OnlyShowIn': Type = val.split(';')[0] elif tok == 'Categories': if cats: continue for cat in val.split(';'): if cat == 'KDE': Type = 'KDE' continue if cat in CatsRemove: continue if cat.startswith('X-'): continue Cats.append(cat) elif tok == 'Type': if val == 'Application': continue Name = '' break elif tok == 'NoDisplay': Ndis = val f.close() if Ndis == 'true' or Name == '' or Exec == '' or len(Cats) == 0: D3('Skipped: %-24s %-4s %-24s %-20s %-20s %s' % (file, Name, Ndis, Exec, Icon, Cats)) return # Basename File = os.path.basename(file) D3('%-24s: %-24s %-20s %-20s %s\n' % (File, Name, Exec, Icon, Cats)) if not Type: if File.startswith('gnome'): Type = 'GNOME' elif File.startswith('kde'): Type = 'KDE' else: Type = 'Other' ndti = f'{Name}-{N}' # Make key unique ndti = ndti.lower() # To lower case (for sorting) # $Exec =~ s/\s*%(f|F|i|k|m|n|N|u|U|v)//g; # Strip unwanted args # $Exec =~ s/\s*-\w+\s*"%c"//g; # Strip option with caption ## $Exec =~ s/"%c"/'$Name'/g; # Alternatively - Substitute caption Exec = Exec.split('%', 1)[0].strip() # Strip all args dti = DTI(File, Name, Exec, Icon, Cats, Type) dtis[ndti] = dti # Process all .desktop files in a directory def ProcessDir(dir, cats, type): global dt_seen for name in os.listdir(dir): if not name.endswith('.desktop'): continue if name in dt_seen: D(f'Skip duplicate {name} (in {dir})') continue dt_seen[name] = 1 file = f'{dir}/{name}' ProcessFile(file, cats, type) # Find that $#@! thing def FindIcon(icon): if not icon: return icon if icon.startswith('/'): if os.path.isfile(icon): return icon return '' for dir in IconDirs: file = f'{dir}/{icon}' D2(f'Check icon: {icon} : {file}') if os.path.isfile(file): return file if not FileExt(icon) in ['png', 'xpm', 'svg']: for ext in ['png', 'xpm']: fil2 = f'{file}.{ext}' D2(f'Check icon: {icon} : {fil2}') if os.path.isfile(fil2): return fil2 for dir in IconDirs2: for sz in IconSizes: sdir = f'{dir}/{sz}' if not os.path.isdir(sdir): continue D2(f'Check icon: {icon} in {sdir}') if icon.startswith('stock'): glob = f'{sdir}/stock/*/{icon}.png' D2(f'Check icon: {icon} in {glob}') # FIXME # $ii = glob("$i"); # return $ii if (-f $ii); else: for icat in IconCats: idir = f'{sdir}/{icat}' if not os.path.isdir(idir): continue file = f'{idir}/{icon}' if FileExt(icon) in ['png', 'xpm', 'svg']: D2(f'Check icon: {icon}: {file}') if os.path.isfile(file): return file else: for ext in ['png', 'xpm', 'svg']: fil2 = f'{file}.{ext}' D2(f'Check icon: {icon}: {fil2}') if os.path.isfile(fil2): return fil2 D(f'Icon not found: {icon}') return icon # Make the menu for a given app type def MakeAppsMenu(type): mdir = f'menus_{type}' dir = f'{EdirMenus}/{mdir}' D(f'Generating menu: {type} in {dir}') MkDir(dir) # Sort the apps into categories menus = {} for ndti in sorted(dtis): dti = dtis[ndti] # if dti.type != type: continue cat = dti.cats[0] # First category if cat not in menus: menus[cat] = [] menus[cat].append(ndti) # Make top- and sub-menus ftop = open(f'{dir}/index.menu', 'w') ftop.write(f'"{type} Menu"\n') for cat in sorted(menus): fsub = open(f'{dir}/{cat}.menu', 'w') D(f'- Submenu: {cat}') ftop.write(f'"{cat}" "" menu "{mdir}/{cat}.menu"\n') fsub.write(f'"{cat}"\n') for ndti in sorted(menus[cat]): dti = dtis[ndti] D2(f' - Item: {ndti}: {dti.file}') icon = FindIcon(dti.icon) fsub.write('"%s" "%s" exec "%s"\n' % (dti.name, icon, dti.exec)) fsub.close() ftop.close() def EeshCall(cmd): # os.system(f'eesh "{cmd}" >/dev/null') subprocess.run(['eesh'] + cmd.split(), stdout=subprocess.DEVNULL) # Close all windows named "Message" (we assume they are E dialogs) def EeshCloseMessageWindows(): EeshCall('wop Message* close') ############################################################################## # Here we go ############################################################################## opts, args = getopt.getopt(sys.argv[1:], 'd', ['debug']) for opt, val in opts: if opt in ['-d', '--debug']: dbg += 1 # Likely prefixes Prefixes = ['/usr/local', '/usr', '/opt'] Prefixes += ['/opt/kde', '/opt/kde3', getenv('KDEDIR')] Prefixes += ['/opt/gnome'] # SUSE Prefixes += [getenv('HOME') + '/.local'] D(f'Prefixes = "{Prefixes}"') Prefixes = RemoveDuplicates(Prefixes) D(f'Prefixes = "{Prefixes}"') SufDirs = ['/share/applications', '/share/applications/kde', '/share/applications/kde4'] # Where to look for GNOME/KDE stuff AppDirs = MkDirList(Prefixes, SufDirs) D(f'AppDirs = "{AppDirs}"') IconDirs = MkDirList(Prefixes, ['/share/pixmaps', '/share/icons']) IconDirs2 = MkDirList(Prefixes, ['/share/icons']) Themes = ['default.kde', 'gnome', 'hicolor', 'mate', 'Adwaita'] #Themes += ['HighContrast'] IconDirs2 = MkDirList(IconDirs2, Themes, sep='/') IconCats = ['apps', 'filesystems', 'actions', 'devices', 'categories', 'places', 'mimetypes'] IconCats += ['legacy'] #IconCats += ['stock'] IconSizes = ['48x48', '32x32', '24x24', '128x128', '16x16'] IconSizes += ['scalable'] D(f'IconDirs = "{IconDirs}"') D(f'IconDirs2 = "{IconDirs2}"') # Pick up env vars EdirUser = getenv('ECONFDIR') EdirRoot = getenv('EROOT') EdirBin = getenv('EBIN') if EdirUser == '': EdirUser = getenv('HOME') + '/.e16' if EdirRoot == '': EdirRoot = '/usr/share/e16' if EdirBin == '': EdirBin = '/usr/bin' D(f'EdirUser = "{EdirUser}"') D(f'EdirRoot = "{EdirRoot}"') D(f'EdirBin = "{EdirBin}"') EdirMenus = EdirUser + '/menus' D(f'EdirMenus = "{EdirMenus}"') # Localization bits. There may be better ways to do this. Lang = getenv('LANG') loc1 = Lang loc2 = Lang.split('.')[0].split('@')[0] loc3 = Lang.split('_')[0] if loc1 == loc2: loc1 = '' D(f'Locale = "{Lang}:{loc1}:{loc2}:{loc3}"') # Put EBIN first in path os.environ['PATH'] = f"{EdirBin}:{os.environ['PATH']}" CatsRemove = [ 'ConsoleOnly', 'Qt', 'QT', 'GTK', 'GNOME', 'KDE', 'UtilityApplication', 'Applications', 'Application', #'X-.*', ] MainMenu = [ 't:User menus:', 'm:User application list:user_apps.menu', 'm:Applications:menus_apps/index.menu', 'm:Epplets:epplets.menu', 'c:Restart:exit restart', 'c:Log out:exit logout' ] UserAppsMenu = [ 't:User Application List:', 'x:XTerm:xterm', 'x:urxvt:urxvt', 'x:Firefox:firefox', 'x:Thunderbird:thunderbird', 'x:Seamonkey:seamonkey', 'x:Shotwell:shotwell', 'x:Pidgin:pidgin', 'x:Gmplayer:gmplayer', 'x:Xine:xine', 'x:The GIMP:gimp', 'x:Geeqie:geeqie', 'x:XV:xv', 'x:XMag:xmag', 'x:Grip:grip', 'x:Audacious:audacious', ] EeshCloseMessageWindows() EeshCall('dialog_ok Menus are being generated... Please Wait') # Process new style (GNOME2, KDE2/3) directories for dir in AppDirs: D(f'Processing directory: {dir}') if not os.path.isdir(dir): D(f'- Not found') continue ProcessDir(dir, None, None) # Make menu dir and scaled icon dir MkDir(EdirMenus) # Make the menus MakeMenu('file.menu', MainMenu) MakeMenu('user_apps.menu', UserAppsMenu) MakeEppsMenu('epplets.menu') MakeAppsMenu('apps') EeshCloseMessageWindows() EeshCall('menus reload') EeshCall('dialog_ok Menu generation complete')