openwrt-build-gui.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #!/usr/bin/env python3
  2. import tkinter as tk
  3. from tkinter import filedialog
  4. from tkinter import ttk
  5. import os.path
  6. import csv
  7. import json
  8. import gzip
  9. import urllib.request
  10. #####################################################################
  11. ######################## VARIABLES ##################################
  12. #####################################################################
  13. # Enable file caching and some dev output
  14. debug=True
  15. #####################################################################
  16. ######################## FUNCTIONS ##################################
  17. #####################################################################
  18. def get_toh():
  19. """
  20. Retrieves the openwrt.org table of hardware and returns it as a list of dicts
  21. """
  22. toh_cache_file = "sources/toh.tsv"
  23. if debug is True and os.path.isfile(toh_cache_file):
  24. with open(toh_cache_file) as infile:
  25. toh = json.load(infile)
  26. else:
  27. toh_gz_handle = urllib.request.urlopen("https://openwrt.org/_media/toh_dump_tab_separated.gz")
  28. toh_handle = gzip.open(toh_gz_handle, mode='rt', encoding='ISO-8859-1')
  29. toh_dict = csv.DictReader(toh_handle, delimiter='\t')
  30. # Convert the DictReader object to a native list of dicts
  31. toh = list(toh_dict)
  32. # Sanitize it
  33. junk = ['', 'nan', 'NULL', '-', '?', '¿', ' ']
  34. toh = [d for d in toh if d['target'] not in junk and d['subtarget'] not in junk]
  35. # Save to cache file
  36. with open(toh_cache_file, 'w') as outfile:
  37. json.dump(toh, outfile)
  38. return toh
  39. #####################################################################
  40. ######################## CLASSES ####################################
  41. #####################################################################
  42. class DeviceFrame(tk.Frame):
  43. """This class renders the device information frame"""
  44. def __init__(self, parent, *args, **kwargs):
  45. super().__init__(parent, *args, **kwargs)
  46. # Make a dict to hold our widgets
  47. self.widgets = {}
  48. # Get the Table of Hardware (toh) data frame from openwrt.org
  49. self.toh = get_toh()
  50. # Create device frame and widgets
  51. self.device_frame = tk.LabelFrame(self, text="Select Device")
  52. self.device_frame.grid(row=0, column=0, sticky=(tk.W + tk.E))
  53. self.info_frame = tk.LabelFrame(self, text="Info")
  54. self.info_frame.grid(row=1, column=0, sticky=(tk.W + tk.E))
  55. self.version_frame = tk.LabelFrame(self, text="Version")
  56. self.version_frame.grid(row=2, column=0, sticky=(tk.W + tk.E))
  57. self.targets_widget(parent)
  58. self.subtargets_widget(parent)
  59. self.info_widget(parent)
  60. self.version_widget(parent)
  61. # Create some traces to repopulate the subtarget menu and info widget
  62. parent.target.trace("w", lambda var_name, var_index, operation: self.subtargets_widget(parent, regen=True))
  63. parent.subtarget.trace("w", lambda var_name, var_index, operation: self.info_widget(parent, regen=True))
  64. # Place the widgets in the device frame
  65. self.widgets['Target'].grid(row=0, column=0)
  66. self.widgets['Subtarget'].grid(row=1, column=0)
  67. self.widgets['Version'].grid(row=0, column=0)
  68. self.widgets['Info'].grid(row=0, column=1)
  69. def targets_widget(self, parent):
  70. """A Combobox of targets from the ToH"""
  71. targets = [k['target'] for k in self.toh]
  72. targets = sorted(set(targets))
  73. parent.target.set(targets[0])
  74. self.widgets['Target'] = LabelInput(
  75. self.device_frame,
  76. label='Target:',
  77. input_class=ttk.Combobox,
  78. input_var=parent.target,
  79. options_list=targets
  80. )
  81. def subtargets_widget(self, parent, regen=None):
  82. """A Combobox of subtargets of the current target"""
  83. regen = regen or False
  84. subtargets = [d['subtarget'] for d in self.toh if d['target'] == parent.target.get()]
  85. subtargets = sorted(set(subtargets))
  86. parent.subtarget.set(subtargets[0])
  87. if regen is True:
  88. self.widgets['Subtarget'].input['values'] = subtargets
  89. return
  90. self.widgets['Subtarget'] = LabelInput(
  91. self.device_frame,
  92. label='Subtarget:',
  93. input_class=ttk.Combobox,
  94. input_var=parent.subtarget,
  95. options_list=subtargets
  96. )
  97. def info_widget(self, parent, regen=None):
  98. """An InfoBox of info about the current subtarget"""
  99. regen = regen or False
  100. # Add any stat that you wish to display to this list
  101. stats = {'devicetype': 'Type', 'brand': 'Brand', 'model': 'Model', 'cpu': 'CPU', 'supportedsincerel': 'First Release',
  102. 'supportedcurrentrel': 'Current Release', 'packagearchitecture': 'Arch', 'wikideviurl': 'Wiki'}
  103. for device in self.toh:
  104. if device['target'] == parent.target.get() and device['subtarget'] == parent.subtarget.get():
  105. break
  106. # Stringify
  107. stats_str = '\n'.join('{}: {}'.format(stats[k], v) for k, v in device.items() if k in stats)
  108. parent.subtarget_info.set(stats_str)
  109. if regen is True:
  110. return
  111. self.widgets['Info'] = LabelInput(
  112. self.device_frame,
  113. label='',
  114. input_class=ttk.Label,
  115. input_var=parent.subtarget_info,
  116. textvariable=parent.subtarget_info,
  117. )
  118. def version_widget(self, parent):
  119. """A Combobox of version numbers"""
  120. # A space-delim'd string of version numbers TODO: make automatic from scraped data (repo tags?)
  121. versions = 'snapshot 12.09'
  122. parent.version.set('snapshot')
  123. self.widgets['Version'] = LabelInput(
  124. self.version_frame,
  125. label='',
  126. input_class=ttk.Combobox,
  127. input_var=parent.version,
  128. options_list=versions
  129. )
  130. class LabelInput(tk.Frame):
  131. """A widget containing a label and input together."""
  132. def __init__(self, parent, *args, label='', input_class=ttk.Entry,
  133. input_var=None, input_args=None, label_args=None, options_list=None,
  134. **kwargs):
  135. super().__init__(parent)
  136. input_args = input_args or {}
  137. label_args = label_args or {}
  138. options_list = options_list or []
  139. self.variable = input_var
  140. if input_class in (ttk.Checkbutton, ttk.Button, ttk.Radiobutton):
  141. input_args["text"] = label
  142. input_args["variable"] = input_var
  143. else:
  144. self.label = ttk.Label(self, text=label, **label_args)
  145. self.label.grid(row=0, column=0, sticky=(tk.W + tk.E))
  146. input_args["textvariable"] = input_var
  147. if input_class in (ttk.OptionMenu, ttk.Combobox):
  148. input_args["values"] = options_list
  149. if input_class is ttk.OptionMenu:
  150. self.input = input_class(self, self.variable, *options_list)
  151. else:
  152. self.input = input_class(self, **input_args)
  153. self.input.grid(row=1, column=0, sticky=(tk.W + tk.E))
  154. self.columnconfigure(0, weight=1)
  155. def grid(self, sticky=(tk.E + tk.W), **kwargs):
  156. super().grid(sticky=sticky, **kwargs)
  157. def get(self):
  158. if self.variable:
  159. return self.variable.get()
  160. elif type(self.input) == tk.Text:
  161. return self.input.get('1.0', tk.END)
  162. else:
  163. return self.input.get()
  164. def set(self, value, *args, **kwargs):
  165. if type(self.variable) == tk.BooleanVar:
  166. self.variable.set(bool(value))
  167. elif self.variable:
  168. self.variable.set(value, *args, **kwargs)
  169. elif type(self.input).__name__.endswith('button'):
  170. if value:
  171. self.input.select()
  172. else:
  173. self.input.deselect()
  174. elif type(self.input) == tk.Text:
  175. self.input.delete('1.0', tk.END)
  176. self.input.insert('1.0', value)
  177. else:
  178. self.input.delete(0, tk.END)
  179. self.input.insert(0, value)
  180. class GUI(tk.Tk):
  181. """Application root window"""
  182. def __init__(self, *args, **kwargs):
  183. super().__init__(*args, **kwargs)
  184. # Set the window properties
  185. self.title("openwrt-build")
  186. self.geometry("800x600")
  187. self.resizable(width=False, height=False)
  188. ttk.Label(
  189. self,
  190. text="OpenWRT Image Builder",
  191. font=("TkDefaultFont", 16)
  192. ).grid(row=0)
  193. # Let's store the global tk vars in this top-level class
  194. self.target = tk.StringVar()
  195. self.subtarget = tk.StringVar()
  196. self.subtarget_info = tk.StringVar()
  197. self.version = tk.StringVar()
  198. # Add each frame class
  199. self.device_frame = DeviceFrame(self)
  200. self.device_frame.grid(row=1, column=0, sticky=(tk.W + tk.E))
  201. if __name__ == '__main__':
  202. app = GUI()
  203. app.mainloop()