SwitchHosts.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. # -*- coding: utf-8 -*-
  2. u"""
  3. 本程序用于快速切换 hosts 文件
  4. @author: oldj
  5. @blog: http://oldj.net
  6. @email: [email protected]
  7. """
  8. import os
  9. import sys
  10. import glob
  11. import wx
  12. import libs.common_operations as co
  13. import libs.ui as ui
  14. from libs.cls_Hosts import Hosts, DEFAULT_HOSTS_FN
  15. VERSION = "0.2.0.1763"
  16. SELECTED_FLAG = u"√"
  17. class TaskBarIcon(wx.TaskBarIcon):
  18. ID_About = wx.NewId()
  19. ID_Exit = wx.NewId()
  20. ID_MainFrame = wx.NewId()
  21. def __init__(self, frame):
  22. wx.TaskBarIcon.__init__(self)
  23. # super(wx.TaskBarIcon, self).__init__()
  24. self.frame = frame
  25. self.SetIcon(co.GetMondrianIcon(), "SwitchHosts! %s" % VERSION)
  26. self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarLeftDClick)
  27. self.Bind(wx.EVT_MENU, self.frame.OnAbout, id=self.ID_About)
  28. self.Bind(wx.EVT_MENU, self.OnExit, id=self.ID_Exit)
  29. self.Bind(wx.EVT_MENU, self.OnMainFrame, id=self.ID_MainFrame)
  30. self.current_hosts = None
  31. self.font_bold = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
  32. self.font_bold.SetWeight(wx.BOLD)
  33. def OnTaskBarLeftDClick(self, event):
  34. if self.frame.IsIconized():
  35. self.frame.Iconize(False)
  36. if not self.frame.IsShown():
  37. self.frame.Show(True)
  38. self.frame.Raise()
  39. def OnExit(self, event):
  40. self.frame.Destroy()
  41. self.Destroy()
  42. sys.exit()
  43. def OnMainFrame(self, event):
  44. u"""显示主面板"""
  45. if not self.frame.IsShown():
  46. self.frame.Show(True)
  47. self.frame.Raise()
  48. # override
  49. def CreatePopupMenu(self):
  50. self.hosts = {}
  51. hosts_list = listLocalHosts()
  52. menu = wx.Menu()
  53. menu.Append(self.ID_MainFrame, u"SwitchHosts!")
  54. menu.AppendSeparator()
  55. for fn in hosts_list:
  56. oh = self.frame.getOHostsFromFn(fn)
  57. if oh:
  58. self.addHosts(menu, oh)
  59. menu.AppendSeparator()
  60. menu.Append(self.ID_About, "About")
  61. menu.Append(self.ID_Exit, "Exit")
  62. return menu
  63. def addHosts(self, menu, ohost):
  64. u"""在菜单项中添加一个 hosts"""
  65. title = ohost.getTitle()
  66. item_id = wx.NewId()
  67. mitem = wx.MenuItem(menu, item_id, title, kind=wx.ITEM_RADIO)
  68. mitem.SetBitmap(co.GetMondrianBitmap(ohost.icon_idx))
  69. menu.AppendItem(mitem)
  70. menu.Check(item_id, self.current_hosts == ohost.path)
  71. if self.current_hosts == ohost.path:
  72. mitem.SetFont(self.font_bold)
  73. self.hosts[item_id] = title
  74. self.Bind(wx.EVT_MENU, self.switchHost, id=item_id)
  75. def switchHost(self, event):
  76. hosts_id = event.GetId()
  77. title = self.hosts[hosts_id]
  78. oh = self.frame.getOHostsFromTitle(title)
  79. if oh:
  80. co.switchHost(self, oh.path)
  81. self.frame.updateListCtrl()
  82. class Frame(ui.Frame):
  83. ID_RENAME = wx.NewId()
  84. def __init__(
  85. self, parent=None, id=wx.ID_ANY, title="SwitchHosts! %s" % VERSION, pos=wx.DefaultPosition,
  86. size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
  87. sys_hosts_title=None
  88. ):
  89. ui.Frame.__init__(self, parent, id, title, pos, size, style, cls_TaskBarIcon=TaskBarIcon)
  90. self.Bind(wx.EVT_CLOSE, self.OnClose)
  91. self.init2(sys_hosts_title)
  92. def init2(self, sys_hosts_title):
  93. self.Bind(wx.EVT_MENU, self.newHosts, id=wx.ID_NEW)
  94. self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT)
  95. self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
  96. self.Bind(wx.EVT_BUTTON, self.OnHide, id=wx.ID_CLOSE)
  97. self.Bind(wx.EVT_BUTTON, self.applyHost, id=wx.ID_APPLY)
  98. self.Bind(wx.EVT_TEXT, self.hostsContentChange, id=self.ID_HOSTS_TEXT)
  99. self.Bind(wx.EVT_BUTTON, self.newHosts, id=wx.ID_ADD)
  100. self.Bind(wx.EVT_BUTTON, self.deleteHosts, id=wx.ID_DELETE)
  101. hosts_cols = (
  102. (u"hosts", 130),
  103. (u"", 20),
  104. )
  105. for col, (txt, width) in enumerate(hosts_cols):
  106. self.m_list.InsertColumn(col, txt)
  107. self.m_list.SetColumnWidth(col, width)
  108. self.current_selected_hosts_index = -1
  109. self.current_selected_hosts_fn = None
  110. self.current_use_hosts_index = -1
  111. self.current_max_hosts_index = -1
  112. self.latest_stable_version = None
  113. self.updateHostsList(sys_hosts_title)
  114. self.hosts_item_menu = wx.Menu()
  115. self.hosts_item_menu.Append(wx.ID_APPLY, u"切换到当前hosts")
  116. # self.hosts_item_menu.Append(wx.ID_EDIT, u"编辑")
  117. self.hosts_item_menu.Append(self.ID_RENAME, u"重命名")
  118. self.hosts_item_menu.AppendMenu(-1, u"图标", self.mkSubIconMenu())
  119. self.hosts_item_menu.AppendSeparator()
  120. self.hosts_item_menu.Append(wx.ID_DELETE, u"删除")
  121. self.m_btn_apply.Disable()
  122. co.checkLatestStableVersion(self)
  123. self.Bind(wx.EVT_MENU, self.menuApplyHost, id=wx.ID_APPLY)
  124. self.Bind(wx.EVT_MENU, self.deleteHosts, id=wx.ID_DELETE)
  125. self.Bind(wx.EVT_MENU, self.renameHosts, id=self.ID_RENAME)
  126. self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnHostsItemRClick, self.m_tree)
  127. self.Bind(wx.EVT_TREE_ITEM_MENU, self.OnHostsItemBeSelected, self.m_tree) # todo selected 方法?
  128. self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.applyHost, self.m_tree)
  129. def setLatestStableVersion(self, version):
  130. self.latest_stable_version = version
  131. # print(version)
  132. def mkSubIconMenu(self):
  133. u"""生成图标子菜单"""
  134. menu = wx.Menu()
  135. def _f(i):
  136. return lambda e: self.setHostIcon(e, i)
  137. icons_length = len(co.ICONS)
  138. for i in range(icons_length):
  139. item_id = wx.NewId()
  140. mitem = wx.MenuItem(menu, item_id, u"图标#%d" % (i + 1))
  141. mitem.SetBitmap(co.GetMondrianBitmap(i))
  142. menu.AppendItem(mitem)
  143. self.Bind(wx.EVT_MENU, _f(i), id=item_id)
  144. return menu
  145. def setHostIcon(self, event=None, i=0):
  146. index = self.current_selected_hosts_index
  147. ohosts = self.hosts_objects[index]
  148. ohosts.setIcon(i)
  149. self.m_list.SetItemImage(index, ohosts.icon_idx, ohosts.icon_idx)
  150. ch = self.taskbar_icon.current_hosts
  151. if ch == ohosts.path or (not ch and DEFAULT_HOSTS_FN == ohosts.fn):
  152. # 如果当前设置图片的 hosts 正是正在使用的 hosts,则
  153. self.SetIcon(co.GetMondrianIcon(i))
  154. self.taskbar_icon.SetIcon(co.GetMondrianIcon(i), u"当前 hosts 方案:%s" % ohosts.getTitle())
  155. def updateHostsList(self, selected_title=None):
  156. u"""更新 hosts 列表"""
  157. hosts_list = listLocalHosts()
  158. # hosts_list.insert(0, co.getSysHostsPath())
  159. hosts_list = [list(os.path.split(fn)) + [fn] for fn in hosts_list]
  160. self.hosts_lists = hosts_list
  161. self.hosts_objects = []
  162. self.m_list.DeleteAllItems()
  163. ch = self.taskbar_icon.current_hosts
  164. c_idx = -1
  165. il = wx.ImageList(16, 16, True)
  166. icons_count = len(co.ICONS)
  167. for i in xrange(icons_count):
  168. il.Add(co.GetMondrianBitmap(i))
  169. self.m_list.AssignImageList(il, wx.IMAGE_LIST_SMALL)
  170. for idx, (folder, fn, fn2) in enumerate(hosts_list):
  171. icon_idx = idx if idx < icons_count else icons_count - 1
  172. ohosts = Hosts(idx, fn2, icon_idx)
  173. self.hosts_objects.append(ohosts)
  174. i, t, t2 = fn.partition(".")
  175. if i.isdigit():
  176. i = int(i)
  177. if i > self.current_max_hosts_index:
  178. self.current_max_hosts_index = i
  179. c = ""
  180. index = self.m_list.InsertStringItem(sys.maxint, ohosts.getTitle())
  181. # 如果指定了当前选中的 hosts
  182. if ohosts.getTitle() == selected_title:
  183. ch = self.taskbar_icon.current_hosts = fn2
  184. self.SetIcon(co.GetMondrianIcon(ohosts.icon_idx))
  185. self.taskbar_icon.SetIcon(co.GetMondrianIcon(ohosts.icon_idx), u"当前 hosts 方案:%s" % ohosts.getTitle())
  186. self.m_list.SetItemFont(idx, self.font_bold)
  187. if (ch and ch == fn2) or \
  188. (not selected_title and not ch and co.decode(fn) == DEFAULT_HOSTS_FN):
  189. c = SELECTED_FLAG
  190. if c:
  191. c_idx = index
  192. self.m_list.SetStringItem(index, 1, c)
  193. self.m_list.SetItemImage(index, ohosts.icon_idx, ohosts.icon_idx)
  194. if self.current_selected_hosts_index >= 0:
  195. c_idx = self.current_selected_hosts_index
  196. while c_idx >= len(self.hosts_objects) and c_idx > 0:
  197. c_idx -= 1
  198. ohosts = self.hosts_objects[c_idx]
  199. self.m_list.Select(c_idx)
  200. self.current_selected_hosts_index = c_idx
  201. self.current_selected_hosts_fn = self.hosts_objects[c_idx].path
  202. self.m_textCtrl_content.Value = ohosts.getContent()
  203. def hostsContentChange(self, event):
  204. c = self.m_textCtrl_content.Value.rstrip()
  205. ohosts = self.getOHostsFromFn()
  206. old_c = ohosts.getContent()
  207. if ohosts and c != old_c:
  208. # 内容改变
  209. # print ohosts.getTitle()
  210. # print("%s, changed!" % self.current_selected_hosts_fn)
  211. self.saveCurrentHost(ohosts, c)
  212. self.textStyle(old_c)
  213. else:
  214. # 新切换
  215. self.textStyle()
  216. self.m_btn_apply.Enable()
  217. def menuApplyHost(self, event):
  218. self.applyHost(event)
  219. def mkNewHostsPath(self):
  220. global g_local_hosts_dir
  221. self.current_max_hosts_index += 1
  222. return os.path.join(g_local_hosts_dir, "%d.hosts" % self.current_max_hosts_index)
  223. def newHosts_test(self):
  224. print(123)
  225. dlg = ui.Dlg_addHosts(None)
  226. if dlg.ShowModal() == wx.ID_OK:
  227. print("OK!")
  228. print(dlg.m_radioBtn_local.Value)
  229. print(dlg.m_textCtrl_title.Value)
  230. print(dlg.m_textCtrl_url.Value)
  231. else:
  232. print("Cancel!")
  233. def newHosts(self, event=None, default=""):
  234. u"""新建一个 hosts"""
  235. global g_local_hosts_dir
  236. repeat = False
  237. title = default
  238. self.newHosts_test()
  239. return
  240. dlg = wx.TextEntryDialog(None, u"新建 hosts", u"输入 hosts 名:", title,
  241. style=wx.OK | wx.CANCEL
  242. )
  243. if dlg.ShowModal() == wx.ID_OK:
  244. title = dlg.GetValue().strip()
  245. if title:
  246. oh = self.getOHostsFromTitle(title)
  247. if oh:
  248. repeat = True
  249. self.alert(u"命名失败!", u"名为 '%s' 的 hosts 已经存在了!" % title)
  250. else:
  251. # 保存新文件
  252. path = self.mkNewHostsPath()
  253. c = u"# %s" % title
  254. oh = Hosts(path=path)
  255. oh.setTitle(title)
  256. oh.setContent(c)
  257. self.updateHostsList()
  258. dlg.Destroy()
  259. if repeat:
  260. self.newHosts(event, default=title)
  261. def renameHosts(self, event):
  262. u"""重命名一个 hosts"""
  263. ohosts = self.hosts_objects[self.current_selected_hosts_index]
  264. if ohosts.fn == DEFAULT_HOSTS_FN:
  265. self.alert(u"不可操作", u"默认 hosts 不可重命名!")
  266. return
  267. old_title = ohosts.getTitle()
  268. repeat = False
  269. dlg = wx.TextEntryDialog(None, u"重命名 hosts", u"输入新的 hosts 名:", old_title,
  270. style=wx.OK | wx.CANCEL
  271. )
  272. if dlg.ShowModal() == wx.ID_OK:
  273. # 改名
  274. new_title = dlg.GetValue().strip()
  275. if new_title and new_title != old_title:
  276. oh2 = self.getOHostsFromTitle(new_title)
  277. if oh2:
  278. repeat = True
  279. self.alert(u"重命名失败!", u"'%s' 已存在,请先将它删除!" % new_title)
  280. else:
  281. ohosts.setTitle(new_title)
  282. if self.taskbar_icon.current_hosts == ohosts.path:
  283. self.applyHost()
  284. self.updateHostsList()
  285. dlg.Destroy()
  286. if repeat:
  287. self.renameHosts(event)
  288. def deleteHosts(self, event):
  289. u"""删除 hosts"""
  290. fn = DEFAULT_HOSTS_FN.encode()
  291. if self.current_selected_hosts_fn:
  292. folder, fn = os.path.split(self.current_selected_hosts_fn)
  293. # fn = co.decode(fn)
  294. ohosts = self.getOHostsFromFn(fn)
  295. # print(self.current_selected_hosts_fn, self.taskbar_icon.current_hosts)
  296. if not self.current_selected_hosts_fn or \
  297. self.current_selected_hosts_fn == self.taskbar_icon.current_hosts or \
  298. (self.taskbar_icon.current_hosts is None and fn == DEFAULT_HOSTS_FN):
  299. self.alert(u"不可删除", u"当前 hosts 正在使用中,不可删除!")
  300. return
  301. dlg = wx.MessageDialog(None, u"确定要删除 hosts '%s'?" % ohosts.getTitle(), u"删除 hosts",
  302. wx.YES_NO | wx.ICON_QUESTION
  303. )
  304. ret_code = dlg.ShowModal()
  305. if ret_code == wx.ID_YES:
  306. # 删除当前 hosts
  307. try:
  308. os.remove(ohosts.path)
  309. except Exception:
  310. pass
  311. self.updateHostsList()
  312. dlg.Destroy()
  313. def saveCurrentHost(self, ohosts=None, content=None):
  314. u"""保存当前 hosts"""
  315. c = content or self.m_textCtrl_content.Value.rstrip()
  316. ohosts = ohosts or self.getOHostsFromFn()
  317. if ohosts:
  318. ohosts.setContent(c)
  319. ohosts.save()
  320. def applyHost(self, event=None, ohosts=None):
  321. u"""应用某个 hosts"""
  322. # 保存当前 hosts 的内容
  323. self.saveCurrentHost()
  324. # 切换 hosts
  325. co.switchHost(self.taskbar_icon, self.current_selected_hosts_fn)
  326. self.updateListCtrl()
  327. self.m_btn_apply.Disable()
  328. def getOHostsFromTitle(self, title):
  329. for oh in self.hosts_objects:
  330. if oh.getTitle() == title:
  331. return oh
  332. return None
  333. def getOHostsFromFn(self, fn=None):
  334. u"""从 hosts 的文件名取得它的 id"""
  335. if not fn:
  336. fn = self.current_selected_hosts_fn or DEFAULT_HOSTS_FN.encode()
  337. fn = co.decode(fn)
  338. for oh in self.hosts_objects:
  339. if oh.fn == fn or oh.dc_path == fn:
  340. return oh
  341. return None
  342. def updateListCtrl(self):
  343. for idx in range(len(self.hosts_lists)):
  344. c = ""
  345. font = self.font_normal
  346. if self.hosts_lists[idx][2] == self.taskbar_icon.current_hosts:
  347. c = SELECTED_FLAG
  348. font = self.font_bold
  349. self.m_list.SetStringItem(idx, 1, c)
  350. self.m_list.SetItemFont(idx, font)
  351. def OnHostsItemBeSelected(self, event):
  352. item = event.GetItem()
  353. host_title = self.m_tree.GetItemText(item)
  354. idx = event.GetIndex()
  355. fn = self.hosts_lists[idx][2]
  356. ohosts = self.hosts_objects[idx]
  357. # c = open(fn, "rb").read() if os.path.isfile(fn) else ""
  358. self.current_selected_hosts_index = idx
  359. self.current_selected_hosts_fn = fn
  360. self.m_textCtrl_content.Value = ohosts.getContent()
  361. self.m_btn_apply.Enable()
  362. def OnHostsItemRClick(self, event):
  363. u""""""
  364. item = event.GetItem()
  365. host_title = self.m_tree.GetItemText(item)
  366. # idx = event.GetIndex()
  367. # fn = self.hosts_lists[idx][2]
  368. fn = self.getOHostsFromTitle(host_title)
  369. self.current_selected_hosts_index = idx
  370. self.current_selected_hosts_fn = fn
  371. self.m_list.PopupMenu(self.hosts_item_menu, event.GetPosition())
  372. def OnAbout(self, event):
  373. dlg = ui.AboutBox(version=VERSION, latest_stable_version=self.latest_stable_version)
  374. dlg.ShowModal()
  375. dlg.Destroy()
  376. def editHost(self, event):
  377. u"""编辑一个 hosts 文件"""
  378. print(1)
  379. def textStyle(self, old_content=None):
  380. u"""更新文本区的样式"""
  381. from libs.highLight import highLight
  382. self.m_textCtrl_content.SetFont(self.font_mono)
  383. highLight(self.m_textCtrl_content, old_content=old_content)
  384. def OnHide(self, event):
  385. self.Hide()
  386. def OnIconfiy(self, event):
  387. wx.MessageBox("Frame has been iconized!", "Prompt")
  388. event.Skip()
  389. def OnExit(self, event):
  390. # self.taskbar_icon.Destroy()
  391. # self.Destroy()
  392. # event.Skip()
  393. self.taskbar_icon.OnExit(event)
  394. def OnClose(self, event):
  395. self.Hide()
  396. return False
  397. def listLocalHosts():
  398. u"""列出指定目录下的 host 文件列表"""
  399. global g_local_hosts_dir
  400. fns = [fn for fn in glob.glob(os.path.join(g_local_hosts_dir, "*.hosts")) if\
  401. os.path.isfile(fn) and not fn.startswith(".")\
  402. and not fn.startswith("_")
  403. ]
  404. return fns
  405. def getSysHostsTitle():
  406. global g_local_hosts_dir
  407. sys_hosts = co.getSysHostsPath()
  408. path = os.path.join(g_local_hosts_dir, DEFAULT_HOSTS_FN)
  409. ohosts_sys = Hosts(path=sys_hosts)
  410. sys_hosts_title = ohosts_sys.getTitle()
  411. is_title_valid = False
  412. fns = listLocalHosts()
  413. for fn in fns:
  414. fn2 = os.path.split(fn)[1]
  415. i, t, t2 = fn2.partition(".")
  416. if not i.isdigit():
  417. continue
  418. ohosts = Hosts(path=fn)
  419. if ohosts.getTitle() == sys_hosts_title:
  420. is_title_valid = True
  421. break
  422. if not is_title_valid:
  423. open(path, "wb").write(open(sys_hosts, "rb").read())
  424. # else:
  425. # if os.path.isfile(path):
  426. # os.remove(path)
  427. return sys_hosts_title if is_title_valid else None
  428. def init():
  429. global g_local_hosts_dir
  430. base_dir = os.getcwd()
  431. g_local_hosts_dir = os.path.join(base_dir, "hosts")
  432. if not os.path.isdir(g_local_hosts_dir):
  433. os.makedirs(g_local_hosts_dir)
  434. return getSysHostsTitle()
  435. def main():
  436. sys_hosts_title = init()
  437. app = wx.App()
  438. # app = wx.PySimpleApp()
  439. frame = Frame(size=(640, 480), sys_hosts_title=sys_hosts_title)
  440. frame.Centre()
  441. frame.Show()
  442. app.MainLoop()
  443. if __name__ == "__main__":
  444. main()