#!/usr/bin/python3
#Version: 3.0
#This script generates FreeDesktop application menu for any window manager with a template.
#Original copy Written by Konstantin Korikov <lostclus@ua.fm>, put in the public domain
#adapted to use for fluxbox by anticapitalista <antiX@operamail.com>
#adapted to use for jwm by Dave <david@daveserver.info>
#adapted to auto-detect or specify the session and use the resulting template by Dave <david@daveserver.info>
#adapted to use the gtk theme with --use-gtk by Dave <david@daveserver.info>
#adapted to utilize the xdg separator key by Dave <david@daveserver.info>
#adapted to have no icons by Dave <david@daveserver.info>
#adapted to write out to the applications file directly by Dave <david@daveserver.info>
#adapted to detect when the file has very little to no contents and adds a retry button by Dave <david@daveserver.info>
#updated to python3

#Requires pyxdg http://cvs.freedesktop.org/cgi-bin/viewcvs.cgi/pyxdg/

#USAGE EXAMPLE FOR antiX 
#desktop-menu > ~/.WM/applications
#Then link your main WM menu to this application menu.

#To Do:
#Preliminary testing then Rewrite / Clean the code

import sys
import locale
import getopt
import re
import os
import shlex
from subprocess import Popen, PIPE
import xdg.Menu
import xdg.DesktopEntry
import xdg.IconTheme
import xdg.Config
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
        


version = "3.0"


def print_usage(exit_code = 1):
  print("""Usage: %s [options]
Options:
  Locale:
  --locale=locale               set output language and encoding
                                (This uses your session locale as the default)
  
  Filtering:
  --root-folder folder          folder to generate (for example: /Games)
  --menu-programs               Make a list of menu program links
  
  Specify Menu Type:
  --menu-file                   Specify the menu file to use
  --desktop-code                Specify which WM you are using 
                                (for example: icewm, fluxbox, jwm, etc...)
                                (Requires matching template!)
                                
  Utilities:
  --terminal command            set terminal emulator command 
                                (default: xterm -e %%s)
  --write-out                   Write the output to the applications
                                file from the appropriate template
  --write-out-file              Specify the name of the file rather than 
                                using the template.
  
  Icon Setting:
  --no-gtk                      turn off using gtk to find the menu icons
                                (includes --with-theme-paths function)
  --theme theme                 set icon theme 
                                (Disables gtk theme)
  --with-theme-paths            convert icon base names to icon absolute paths
                                using specified icon theme
  --icon-size                   set default icon size
                                (This is 16 by default)
  --default-folder-icon icon    icon for folders that not provide Icon option
                                (This is blank by default)
  --default-entry-icon icon     icon for entries that not provide Icon option
                                (This is blank by default)
  --no-icons                    Turns off Icons
  
  Help and Information:
  --help                        print this help and exit
  --version                     print version and exit
""" % sys.argv[0])
  sys.exit(exit_code)
  
def print_version():
  print("%s version %s" % (os.path.basename(sys.argv[0]), version))
  sys.exit(0)
  
def write_to_file(STRING):
    text = open((USER_HOME+"/"+Var.Conf_Dir+"/"+Var.App_File), "a")
    text.write (str(STRING)+"\n")
    text.close()

def find_icon(entry):
    appicon = entry.getIcon()
    if os.path.isfile(appicon):
        icon = appicon
    else:
        icon_theme = Gtk.IconTheme.get_default()
        if icon_theme.lookup_icon(appicon, icon_size, 0):
            icon_info = icon_theme.lookup_icon(appicon, icon_size, 0)
            icon = icon_info.get_filename()
        else:
            if os.path.exists("/usr/share/pixmaps/%s" % appicon):
                icon = "/usr/share/pixmaps/"+appicon
            else:
                icon = missing_icon
            #Section to crudely try and find an icon by using locate. Replaces above icon =
            #    p =  subprocess.Popen(["locate "+appicon],stdout=subprocess.PIPE, shell=True)
            #    (output, err) = p.communicate()
            #    last_attempt = output.partition('\n')[0]
            #    if os.path.isfile(last_attempt):
            #        icon = last_attempt
                
        icon = re.sub(r' ','\ ', icon)
        return icon

def process_menu(menu):
  for entry in menu.getEntries():
    if isinstance(entry, xdg.Menu.Separator):
      if write_out:
        write_to_file(Var.Separator)
      else:
        print(Var.Separator)
        
    elif isinstance(entry, xdg.Menu.Menu):
      name = entry.getName() or entry.DesktopFileID
      if not no_icons:
        icon = find_icon(entry) or default_folder_icon
        #print (icon)

      if menu_programs:
        print ((Var.Menu_Program % (name, icon, sys.argv[0])) +
          (" --root-folder \"%s\"" % entry.getPath(org=True)) +
          (" --terminal \"%s\"" % terminal)).encode(encoding),
        if not no_icons:
          print ((" --default-folder-icon \"%s\"" % default_folder_icon) +
          (" --default-entry-icon \"%s\"" % default_entry_icon) +
          (" --theme \"%s\"" % xdg.Config.icon_theme) +
          (" --icon-size \"%d\"" % icon_size) +
          (with_theme_paths and " --with-theme-paths" or "")).encode(encoding),
        if locale_str:
          print (" --locale \"%s\"" % locale_str).encode(encoding),
        print()
      else:
        if no_icons:
          SUB_ORDER('menu',name,'','')
        else:
          SUB_ORDER('menu',name,icon,'')
        if write_out:
          if Var.Menu_ID == '1':
            write_to_file(Var.Menu_Start % (SUB_ORDER.sub0, SUB_ORDER.sub1, SUB_ORDER.sub2))
          else:
            write_to_file(Var.Menu_Start % (SUB_ORDER.sub0, SUB_ORDER.sub1))
        else:
          if Var.Menu_ID == '1':
            print (Var.Menu_Start % (SUB_ORDER.sub0, SUB_ORDER.sub1, SUB_ORDER.sub2))
          else:
            print (Var.Menu_Start % (SUB_ORDER.sub0, SUB_ORDER.sub1))
        process_menu(entry)
        if write_out:
          write_to_file(Var.Menu_End)
        else:
          print (Var.Menu_End)
          
    elif isinstance(entry, xdg.Menu.MenuEntry):
      de = entry.DesktopEntry
      desktopFile=str(entry)
      OnlyShowIn = de.getOnlyShowIn()
      if not os.path.isfile("/usr/share/applications/antix/"+desktopFile) and not OnlyShowIn:
        name = de.getName() or entry.DesktopFileID
        if not no_icons:
          icon = find_icon(de) or default_entry_icon
        execute = exec_clean2_re.sub('%', exec_clean1_re.sub('', de.getExec()))
        if de.getTerminal(): execute = terminal % execute      
        if no_icons:
          SUB_ORDER('prog',name,'',execute)
        else:
          SUB_ORDER('prog',name,icon,execute)
        if write_out:
          write_to_file(Var.Program % (SUB_ORDER.sub0, SUB_ORDER.sub1, SUB_ORDER.sub2))
        else:
          print (Var.Program % (SUB_ORDER.sub0, SUB_ORDER.sub1, SUB_ORDER.sub2))

class Var: 
  def read(self):
    var = Var
    for line in open("/usr/share/desktop-menu/templates/"+DESKTOP+".template", "r"):
      if "#" not in line:
        if re.search(r'^.*=', line):
          pieces = line.split(' = ')
          var.VARIABLE=(pieces[0])
          var.VARIABLE = re.sub(r'\n', '', var.VARIABLE)
          OBJECT=(pieces[1])
          OBJECT = re.sub(r'\n', '', OBJECT)
          setattr(var, var.VARIABLE, OBJECT)
          
class SUB_ORDER: 
  def __init__(self,menuORprog,NAME,ICON,EXECUTE):
    var = SUB_ORDER
    count=0
    if menuORprog == "menu":
      SUB = Var.Menu_Sub_Order.split(',')
    elif menuORprog == "prog":
      SUB = Var.Program_Sub_Order.split(',')
      
    for value in SUB:
      str_count=str(count)
      var.VARIABLE="sub"+str_count
      if re.search(r'icon', value):
        setattr(var, var.VARIABLE, ICON)
      elif re.search(r'execute', value):
        setattr(var, var.VARIABLE, EXECUTE)
      elif re.search(r'name', value):
        setattr(var, var.VARIABLE, NAME)
      count=count+1
      
root_folder = ""
menu_file = "/etc/xdg/menus/applications.menu"
terminal = "desktop-defaults-run -t %s"
default_folder_icon = "folder"
default_entry_icon = "-"
menu_programs = False
with_theme_paths = False
write_out = False
write_out_global = False
write_out_file = ""
no_gtk = False
no_icons = False
icon_size = 32
missing_icon = "gtk-missing-image"
DESKTOP_CODE = ""

exec_clean1_re = re.compile(r'%[a-zA-Z]')
exec_clean2_re = re.compile(r'%%')
encoding = None
locale_str = None

try: opts, args = getopt.getopt(sys.argv[1:], "", 
  ("help", "version", "locale=",
   "root-folder=", "terminal=", "default-folder-icon=", 
   "default-entry-icon=", "menu-programs", "theme=", "with-theme-paths",
   "icon-size=", "write-out", "write-out-global", "write-out-file=", "desktop-code=", "no-gtk","no-icons", "menu-file="))
except getopt.GetoptError: print_usage()

locale.setlocale(locale.LC_ALL, "")

for o, v in opts:
  if o == "--locale":
    locale_str = v
    locale.setlocale(locale.LC_ALL, locale_str)
  if o == "--root-folder": root_folder = v
  elif o == "--menu-file": menu_file = v
  elif o == "--terminal": terminal = v
  elif o == "--default-folder-icon": default_folder_icon = v
  elif o == "--default-entry-icon": default_entry_icon = v
  elif o == "--menu-programs" : menu_programs = True
  elif o == "--write-out" : write_out = True
  elif o == "--write-out-global" : write_out = True; write_out_global = True; 
  elif o == "--write-out-file" : write_out_file = v
  elif o == "--with-theme-paths" : with_theme_paths = True
  elif o == "--no-gtk" : no_gtk = True
  elif o == "--no-icons" : no_icons = True
  elif o == "--icon-size": icon_size = int(v)
  elif o == "--theme" : xdg.Config.setIconTheme(v)
  elif o == "--desktop-code" : DESKTOP_CODE = v
  elif o in ("-h", "-?", "--help"): print_usage(0)
  elif o in ("-v", "--version"): print_version()

def build_menu():
    Var().read()
    if write_out_file:
        Var.App_File = write_out_file
    
    if write_out:
        if not os.path.isdir(USER_HOME+"/"+Var.Conf_Dir+"/"):
            os.system("mkdir -p %s" % (USER_HOME+"/"+Var.Conf_Dir+"/"))
            os.system("touch %s" % (USER_HOME+"/"+Var.Conf_Dir+"/"+Var.App_File))
        else:
            #open((USER_HOME+"/"+Var.Conf_Dir+"/"+Var.App_File), 'w').close()  
            text = open((USER_HOME+"/"+Var.Conf_Dir+"/"+Var.App_File), "w")
            text.write("")
            text.close()
        

    menu = xdg.Menu.parse(menu_file)
    #menu = xdg.Menu.parse("/etc/xdg/menus/TCM-MENU.menu")
    if root_folder: menu = menu.getMenu(root_folder)
  
    if write_out:
        write_to_file(Var.File_Start)
    else:
        print(Var.File_Start)
  
    process_menu(menu)
  
    if write_out:
        write_to_file(Var.File_End)
    else:
        print (Var.File_End)
  
    if not (os.path.getsize((USER_HOME+"/"+Var.Conf_Dir+"/"+Var.App_File)) > 50):
      SUB_ORDER('prog',"Retry",'',"desktop-menu --write-out")
      write_to_file(Var.Program % (SUB_ORDER.sub0, SUB_ORDER.sub1, SUB_ORDER.sub2))
      
    if Var.Restart_Command and 'DISPLAY' in os.environ:
        os.system("%s" % (Var.Restart_Command))

encoding = locale.getlocale()[1] or 'UTF-8'
if write_out_global:
    USER_HOME="/usr/share/desktop-menu/"
    for i in os.listdir('/usr/share/desktop-menu/templates'):
        if i.endswith(".template"):
            checkwmname = "which " + i.split('.',1)[0]
            myprocess = Popen( shlex.split(checkwmname), stdout=PIPE)
            mystdout = myprocess.communicate()[0]
            ### if the corresponding wm is not currently installed, skip this template
            ###  (to avoid logspam e.g. "Writing Menu: jwm... sh: 1: jwm: not found")
            if mystdout and mystdout[0] != '':
                i = re.sub(r'\..*', '', i)
                print ('Writing Menu: %s' % i)
                DESKTOP = i
                build_menu()
else:
    USER_HOME = os.environ['HOME']
    if 'DISPLAY' in os.environ and not write_out_global:
        DISPLAY = os.environ['DISPLAY']
        DISPLAY = re.sub(r':', '', DISPLAY)
        DISPLAY_SPLIT = DISPLAY.split('.')
        DISPLAY = DISPLAY_SPLIT[0]
        if not DESKTOP_CODE:
            with open(USER_HOME+"/.desktop-session/desktop-code."+DISPLAY, "r") as f:
                DESKTOP_CODE = f.readline()
                DESKTOP_CODE = re.sub(r'\n', '', DESKTOP_CODE)
            
    if not DESKTOP_CODE:
      print ('No Desktop Code. Please specify a desktop code with --desktop-code')
      exit
    else:  
      DESKTOP = re.sub(r'.*-', '', DESKTOP_CODE)
      build_menu()
