license_gui.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #!/usr/bin/env python3
  2. """License generator GUI for regInfo."""
  3. import subprocess
  4. import json
  5. import os
  6. import sys
  7. import tempfile
  8. from datetime import date
  9. from tkinter import Tk, Frame, Label, Entry, Button, StringVar, messagebox, filedialog
  10. from tkinter import font as tkfont
  11. from tkinter.ttk import Combobox, Style
  12. # Resolve the regInfo binary: try same dir, then parent dir (dev layout: gui/ -> build/)
  13. def _find_binary():
  14. if getattr(sys, 'frozen', False):
  15. # PyInstaller --onefile extracts bundled binaries to _MEIPASS
  16. base = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(sys.executable)))
  17. else:
  18. base = os.path.dirname(os.path.abspath(__file__))
  19. candidates = [
  20. os.path.join(base, "regInfo"),
  21. os.path.join(base, "regInfo.exe"),
  22. os.path.join(base, "..", "regInfo"),
  23. os.path.join(base, "..", "build", "Release", "regInfo.exe"),
  24. os.path.join(base, "..", "build", "regInfo"),
  25. ]
  26. for p in candidates:
  27. if os.path.isfile(p) and os.access(p, os.X_OK):
  28. return p
  29. return candidates[0] # default, will fail with clear error
  30. BINARY = _find_binary()
  31. def get_machine_code():
  32. try:
  33. result = subprocess.run([BINARY, "machinecode"], capture_output=True, text=True, check=True)
  34. return result.stdout.strip()
  35. except Exception as e:
  36. messagebox.showerror("错误", f"获取机器码失败:\n{e}\n\n请确认 {BINARY} 存在且可执行")
  37. return ""
  38. class LicenseGUI:
  39. def __init__(self):
  40. self.root = Tk()
  41. self.root.title("注册文件生成器")
  42. self.root.resizable(False, False)
  43. # Font: 宋体 for Chinese, with platform fallbacks
  44. families = ["宋体", "SimSun", "Noto Serif CJK SC", "AR PL UMing CN", "serif"]
  45. available = [f for f in families if f in tkfont.families()]
  46. self._font = tkfont.Font(family=available[0] if available else "TkDefaultFont", size=10)
  47. self.root.option_add("*Font", self._font)
  48. # -- machine code --
  49. machine_code = get_machine_code()
  50. if not machine_code:
  51. self.root.destroy()
  52. return
  53. pad = {"padx": 10, "pady": 6}
  54. # Row 0: machine code
  55. Label(self.root, text="机器码:").grid(row=0, column=0, sticky="e", **pad)
  56. self.mc_var = StringVar(value=machine_code)
  57. Entry(self.root, textvariable=self.mc_var, width=68).grid(row=0, column=1, columnspan=2, sticky="w", **pad)
  58. # Row 1: name
  59. Label(self.root, text="名称:").grid(row=1, column=0, sticky="e", **pad)
  60. self.name_var = StringVar()
  61. Entry(self.root, textvariable=self.name_var, width=35).grid(row=1, column=1, sticky="w", **pad)
  62. # Row 2: phone
  63. Label(self.root, text="电话:").grid(row=2, column=0, sticky="e", **pad)
  64. self.phone_var = StringVar()
  65. Entry(self.root, textvariable=self.phone_var, width=35).grid(row=2, column=1, sticky="w", **pad)
  66. # Row 3: unit
  67. Label(self.root, text="单位:").grid(row=3, column=0, sticky="e", **pad)
  68. self.unit_var = StringVar()
  69. Entry(self.root, textvariable=self.unit_var, width=35).grid(row=3, column=1, sticky="w", **pad)
  70. # Row 4: auth datetime (auto today)
  71. today_str = date.today().strftime("%Y-%m-%d")
  72. Label(self.root, text="授权日期:").grid(row=4, column=0, sticky="e", **pad)
  73. self.auth_var = StringVar(value=today_str)
  74. auth_entry = Entry(self.root, textvariable=self.auth_var, width=20, state="readonly",
  75. readonlybackground="#f0f0f0")
  76. auth_entry.grid(row=4, column=1, sticky="w", **pad)
  77. # Row 5: over datetime (date picker)
  78. Label(self.root, text="到期日期:").grid(row=5, column=0, sticky="e", **pad)
  79. over_frame = Frame(self.root)
  80. over_frame.grid(row=5, column=1, columnspan=2, sticky="w", **pad)
  81. self.over_year = StringVar(value=str(date.today().year + 1))
  82. self.over_month = StringVar(value="01")
  83. self.over_day = StringVar(value="01")
  84. self._build_date_picker(over_frame, self.over_year, self.over_month, self.over_day)
  85. # Row 6: update datetime (date picker)
  86. Label(self.root, text="升级到期:").grid(row=6, column=0, sticky="e", **pad)
  87. update_frame = Frame(self.root)
  88. update_frame.grid(row=6, column=1, columnspan=2, sticky="w", **pad)
  89. self.update_year = StringVar(value=str(date.today().year + 1))
  90. self.update_month = StringVar(value="01")
  91. self.update_day = StringVar(value="01")
  92. self._build_date_picker(update_frame, self.update_year, self.update_month, self.update_day)
  93. # Row 7: save path
  94. Label(self.root, text="输出目录:").grid(row=7, column=0, sticky="e", **pad)
  95. self.path_var = StringVar(value=os.getcwd())
  96. Entry(self.root, textvariable=self.path_var, width=50).grid(row=7, column=1, sticky="w", **pad)
  97. Button(self.root, text="选择...", command=self._choose_dir).grid(row=7, column=2, sticky="w", **pad)
  98. # Row 8: filename preview
  99. Label(self.root, text="文件名:").grid(row=8, column=0, sticky="e", **pad)
  100. self.filename_var = StringVar()
  101. self.filename_label = Entry(self.root, textvariable=self.filename_var, width=60,
  102. state="readonly", readonlybackground="#f0f0f0")
  103. self.filename_label.grid(row=8, column=1, columnspan=2, sticky="w", **pad)
  104. self._update_filename()
  105. self.name_var.trace_add("write", lambda *a: self._update_filename())
  106. # Row 9: buttons
  107. btn_frame = Frame(self.root)
  108. btn_frame.grid(row=9, column=0, columnspan=3, pady=15)
  109. self.gen_btn = Button(btn_frame, text="生成激活", font=("", 14, "bold"),
  110. width=15, height=2, command=self._generate)
  111. self.gen_btn.pack(side="left", padx=5)
  112. self.open_btn = Button(btn_frame, text="打开授权文件", font=("", 14),
  113. width=15, height=2, command=self._open_license)
  114. self.open_btn.pack(side="left", padx=5)
  115. self.root.bind("<Return>", lambda e: self._generate())
  116. self.root.mainloop()
  117. def _build_date_picker(self, parent, year_var, month_var, day_var):
  118. years = [str(y) for y in range(2020, 2101)]
  119. months = [f"{m:02d}" for m in range(1, 13)]
  120. days = [f"{d:02d}" for d in range(1, 32)]
  121. cy = Combobox(parent, textvariable=year_var, values=years, width=6, state="readonly")
  122. cy.pack(side="left")
  123. Label(parent, text="年").pack(side="left")
  124. cm = Combobox(parent, textvariable=month_var, values=months, width=4, state="readonly")
  125. cm.pack(side="left")
  126. Label(parent, text="月").pack(side="left")
  127. cd = Combobox(parent, textvariable=day_var, values=days, width=4, state="readonly")
  128. cd.pack(side="left")
  129. Label(parent, text="日").pack(side="left")
  130. def _update_filename(self):
  131. name = self.name_var.get().strip() or "unknown"
  132. mc_short = (self.mc_var.get() or "00000000")[:8]
  133. self.filename_var.set(f"license_{name}_{mc_short}.xlts")
  134. def _choose_dir(self):
  135. d = filedialog.askdirectory(initialdir=self.path_var.get())
  136. if d:
  137. self.path_var.set(d)
  138. def _generate(self):
  139. name = self.name_var.get().strip()
  140. if not name:
  141. messagebox.showwarning("提示", "请输入名称")
  142. return
  143. phone = self.phone_var.get().strip()
  144. if not phone.isdigit() or len(phone) != 11:
  145. messagebox.showwarning("提示", "请输入正确的11位手机号")
  146. return
  147. license_data = {
  148. "name": name,
  149. "machinecode": self.mc_var.get(),
  150. "phone": phone,
  151. "unit": self.unit_var.get().strip(),
  152. "authdatetime": self.auth_var.get(),
  153. "overdatetime": f"{self.over_year.get()}-{self.over_month.get()}-{self.over_day.get()}",
  154. "updatedatetime": f"{self.update_year.get()}-{self.update_month.get()}-{self.update_day.get()}",
  155. }
  156. with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as f:
  157. json.dump(license_data, f, ensure_ascii=False)
  158. json_path = f.name
  159. outfile = os.path.join(self.path_var.get(), self.filename_var.get())
  160. try:
  161. result = subprocess.run(
  162. [BINARY, "generate", "-f", json_path, outfile],
  163. capture_output=True, text=True, check=True)
  164. messagebox.showinfo("成功", f"授权文件已生成:\n{outfile}")
  165. except subprocess.CalledProcessError as e:
  166. messagebox.showerror("失败", f"生成失败:\n{e.stderr}")
  167. finally:
  168. os.unlink(json_path)
  169. def _parse_date(self, date_str, year_var, month_var, day_var):
  170. """Parse 'YYYY-MM-DD' into year/month/day vars. Ignores on failure."""
  171. parts = date_str.strip().split("-")
  172. if len(parts) == 3:
  173. year_var.set(parts[0])
  174. month_var.set(parts[1])
  175. day_var.set(parts[2])
  176. def _open_license(self):
  177. f = filedialog.askopenfilename(
  178. title="选择授权文件",
  179. filetypes=[("授权文件", "*.xlts"), ("所有文件", "*.*")])
  180. if not f:
  181. return
  182. try:
  183. result = subprocess.run([BINARY, "read", f], capture_output=True, text=True, check=True)
  184. data = json.loads(result.stdout)
  185. except subprocess.CalledProcessError as e:
  186. messagebox.showerror("失败", f"读取失败:\n{e.stderr}")
  187. return
  188. except json.JSONDecodeError:
  189. messagebox.showerror("失败", "解析授权文件输出失败")
  190. return
  191. self.name_var.set(data.get("name", ""))
  192. self.mc_var.set(data.get("machinecode", ""))
  193. self.phone_var.set(data.get("phone", ""))
  194. self.unit_var.set(data.get("unit", ""))
  195. self.auth_var.set(data.get("authdatetime", ""))
  196. self._parse_date(data.get("overdatetime", ""), self.over_year, self.over_month, self.over_day)
  197. self._parse_date(data.get("updatedatetime", ""), self.update_year, self.update_month, self.update_day)
  198. self.path_var.set(os.path.dirname(f) or ".")
  199. self._update_filename()
  200. messagebox.showinfo("成功", f"已加载授权文件:\n{f}")
  201. if __name__ == "__main__":
  202. LicenseGUI()