tinc-gui 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. #!/usr/bin/python
  2. # tinc-gui -- GUI for controlling a running tincd
  3. # Copyright (C) 2009-2014 Guus Sliepen <guus@tinc-vpn.org>
  4. # 2014 Dennis Joachimsthaler <dennis@efjot.de>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License along
  17. # with this program; if not, write to the Free Software Foundation, Inc.,
  18. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19. import string
  20. import socket
  21. import wx
  22. import sys
  23. import os
  24. import platform
  25. import time
  26. from wx.lib.mixins.listctrl import ColumnSorterMixin
  27. from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
  28. if platform.system() == 'Windows':
  29. import _winreg
  30. # Classes to interface with a running tinc daemon
  31. REQ_STOP = 0
  32. REQ_RELOAD = 1
  33. REQ_RESTART = 2
  34. REQ_DUMP_NODES = 3
  35. REQ_DUMP_EDGES = 4
  36. REQ_DUMP_SUBNETS = 5
  37. REQ_DUMP_CONNECTIONS = 6
  38. REQ_DUMP_GRAPH = 7
  39. REQ_PURGE = 8
  40. REQ_SET_DEBUG = 9
  41. REQ_RETRY = 10
  42. REQ_CONNECT = 11
  43. REQ_DISCONNECT = 12
  44. ID = 0
  45. ACK = 4
  46. CONTROL = 18
  47. class Node:
  48. def parse(self, args):
  49. self.name = args[0]
  50. self.address = args[1]
  51. self.port = args[3]
  52. self.cipher = int(args[4])
  53. self.digest = int(args[5])
  54. self.maclength = int(args[6])
  55. self.compression = int(args[7])
  56. self.options = int(args[8], 0x10)
  57. self.status = int(args[9], 0x10)
  58. self.nexthop = args[10]
  59. self.via = args[11]
  60. self.distance = int(args[12])
  61. self.pmtu = int(args[13])
  62. self.minmtu = int(args[14])
  63. self.maxmtu = int(args[15])
  64. self.last_state_change = float(args[16])
  65. self.subnets = {}
  66. class Edge:
  67. def parse(self, args):
  68. self.fr = args[0]
  69. self.to = args[1]
  70. self.address = args[2]
  71. self.port = args[4]
  72. self.options = int(args[5], 16)
  73. self.weight = int(args[6])
  74. class Subnet:
  75. def parse(self, args):
  76. if args[0].find('#') >= 0:
  77. (address, self.weight) = args[0].split('#', 1)
  78. else:
  79. self.weight = 10
  80. address = args[0]
  81. if address.find('/') >= 0:
  82. (self.address, self.prefixlen) = address.split('/', 1)
  83. else:
  84. self.address = address
  85. self.prefixlen = '48'
  86. self.owner = args[1]
  87. class Connection:
  88. def parse(self, args):
  89. self.name = args[0]
  90. self.address = args[1]
  91. self.port = args[3]
  92. self.options = int(args[4], 0x10)
  93. self.socket = int(args[5])
  94. self.status = int(args[6], 0x10)
  95. self.weight = 123
  96. class VPN:
  97. confdir = '/etc/tinc'
  98. piddir = '/var/run/'
  99. def connect(self):
  100. # read the pidfile
  101. f = open(self.pidfile)
  102. info = string.split(f.readline())
  103. f.close()
  104. # check if there is a UNIX socket as well
  105. if self.pidfile.endswith(".pid"):
  106. unixfile = self.pidfile.replace(".pid", ".socket");
  107. else:
  108. unixfile = self.pidfile + ".socket";
  109. if os.path.exists(unixfile):
  110. # use it if it exists
  111. print(unixfile + " exists!");
  112. s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  113. s.connect(unixfile)
  114. else:
  115. # otherwise connect via TCP
  116. print(unixfile + " does not exist.");
  117. if ':' in info[2]:
  118. af = socket.AF_INET6
  119. else:
  120. af = socket.AF_INET
  121. s = socket.socket(af, socket.SOCK_STREAM)
  122. s.connect((info[2], int(info[4])))
  123. self.sf = s.makefile()
  124. s.close()
  125. hello = string.split(self.sf.readline())
  126. self.name = hello[1]
  127. self.sf.write('0 ^' + info[1] + ' 17\r\n')
  128. self.sf.flush()
  129. resp = string.split(self.sf.readline())
  130. self.port = info[4]
  131. self.nodes = {}
  132. self.edges = {}
  133. self.subnets = {}
  134. self.connections = {}
  135. self.refresh()
  136. def refresh(self):
  137. self.sf.write('18 3\r\n18 4\r\n18 5\r\n18 6\r\n')
  138. self.sf.flush()
  139. for node in self.nodes.values():
  140. node.visited = False
  141. for edge in self.edges.values():
  142. edge.visited = False
  143. for subnet in self.subnets.values():
  144. subnet.visited = False
  145. for connections in self.connections.values():
  146. connections.visited = False
  147. while True:
  148. resp = string.split(self.sf.readline())
  149. if len(resp) < 2:
  150. break
  151. if resp[0] != '18':
  152. break
  153. if resp[1] == '3':
  154. if len(resp) < 19:
  155. continue
  156. node = self.nodes.get(resp[2]) or Node()
  157. node.parse(resp[2:])
  158. node.visited = True
  159. self.nodes[resp[2]] = node
  160. elif resp[1] == '4':
  161. if len(resp) < 9:
  162. continue
  163. edge = self.nodes.get((resp[2], resp[3])) or Edge()
  164. edge.parse(resp[2:])
  165. edge.visited = True
  166. self.edges[(resp[2], resp[3])] = edge
  167. elif resp[1] == '5':
  168. if len(resp) < 4:
  169. continue
  170. subnet = self.subnets.get((resp[2], resp[3])) or Subnet()
  171. subnet.parse(resp[2:])
  172. subnet.visited = True
  173. self.subnets[(resp[2], resp[3])] = subnet
  174. self.nodes[subnet.owner].subnets[resp[2]] = subnet
  175. elif resp[1] == '6':
  176. if len(resp) < 9:
  177. break
  178. connection = self.connections.get((resp[2], resp[3], resp[5])) or Connection()
  179. connection.parse(resp[2:])
  180. connection.visited = True
  181. self.connections[(resp[2], resp[3], resp[5])] = connection
  182. else:
  183. break
  184. for key, subnet in self.subnets.items():
  185. if not subnet.visited:
  186. del self.subnets[key]
  187. for key, edge in self.edges.items():
  188. if not edge.visited:
  189. del self.edges[key]
  190. for key, node in self.nodes.items():
  191. if not node.visited:
  192. del self.nodes[key]
  193. else:
  194. for key, subnet in node.subnets.items():
  195. if not subnet.visited:
  196. del node.subnets[key]
  197. for key, connection in self.connections.items():
  198. if not connection.visited:
  199. del self.connections[key]
  200. def close(self):
  201. self.sf.close()
  202. def disconnect(self, name):
  203. self.sf.write('18 12 ' + name + '\r\n')
  204. self.sf.flush()
  205. resp = string.split(self.sf.readline())
  206. def debug(self, level = -1):
  207. self.sf.write('18 9 ' + str(level) + '\r\n')
  208. self.sf.flush()
  209. resp = string.split(self.sf.readline())
  210. return int(resp[2])
  211. def __init__(self, netname = None, pidfile = None):
  212. if platform.system() == 'Windows':
  213. sam = _winreg.KEY_READ
  214. if platform.machine().endswith('64'):
  215. sam = sam | _winreg.KEY_WOW64_64KEY
  216. try:
  217. reg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
  218. try:
  219. key = _winreg.OpenKey(reg, "SOFTWARE\\tinc", 0, sam)
  220. except WindowsError:
  221. key = _winreg.OpenKey(reg, "SOFTWARE\\Wow6432Node\\tinc", 0, sam)
  222. VPN.confdir = _winreg.QueryValue(key, None)
  223. except WindowsError:
  224. pass
  225. if netname:
  226. self.netname = netname
  227. self.confbase = os.path.join(VPN.confdir, netname)
  228. else:
  229. self.confbase = VPN.confdir
  230. self.tincconf = os.path.join(self.confbase, 'tinc.conf')
  231. if pidfile != None:
  232. self.pidfile = pidfile
  233. else:
  234. if platform.system() == 'Windows':
  235. self.pidfile = os.path.join(self.confbase, 'pid')
  236. else:
  237. if netname:
  238. self.pidfile = os.path.join(VPN.piddir, 'tinc.' + netname + '.pid')
  239. else:
  240. self.pidfile = os.path.join(VPN.piddir, 'tinc.pid')
  241. # GUI starts here
  242. argv0 = sys.argv[0]
  243. del sys.argv[0]
  244. netname = None
  245. pidfile = None
  246. def usage(exitcode = 0):
  247. print('Usage: ' + argv0 + ' [options]')
  248. print('\nValid options are:')
  249. print(' -n, --net=NETNAME Connect to net NETNAME.')
  250. print(' --pidfile=FILENAME Read control cookie from FILENAME.')
  251. print(' --help Display this help and exit.')
  252. print('\nReport bugs to tinc@tinc-vpn.org.')
  253. sys.exit(exitcode)
  254. while sys.argv:
  255. if sys.argv[0] in ('-n', '--net'):
  256. del sys.argv[0]
  257. netname = sys.argv[0]
  258. elif sys.argv[0] in ('--pidfile'):
  259. del sys.argv[0]
  260. pidfile = sys.argv[0]
  261. elif sys.argv[0] in ('--help'):
  262. usage(0)
  263. else:
  264. print(argv0 + ': unrecognized option \'' + sys.argv[0] + '\'')
  265. usage(1)
  266. del sys.argv[0]
  267. if netname == None:
  268. netname = os.getenv("NETNAME")
  269. if netname == ".":
  270. netname = None
  271. vpn = VPN(netname, pidfile)
  272. vpn.connect()
  273. class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
  274. def __init__(self, parent, style):
  275. wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
  276. ListCtrlAutoWidthMixin.__init__(self)
  277. ColumnSorterMixin.__init__(self, 16)
  278. def GetListCtrl(self):
  279. return self
  280. class SettingsPage(wx.Panel):
  281. def OnDebugLevel(self, event):
  282. vpn.debug(self.debug.GetValue())
  283. def __init__(self, parent, id):
  284. wx.Panel.__init__(self, parent, id)
  285. grid = wx.FlexGridSizer(cols = 2)
  286. grid.AddGrowableCol(1, 1)
  287. namelabel = wx.StaticText(self, -1, 'Name:')
  288. self.name = wx.TextCtrl(self, -1, vpn.name)
  289. grid.Add(namelabel)
  290. grid.Add(self.name, 1, wx.EXPAND)
  291. portlabel = wx.StaticText(self, -1, 'Port:')
  292. self.port = wx.TextCtrl(self, -1, vpn.port)
  293. grid.Add(portlabel)
  294. grid.Add(self.port)
  295. debuglabel = wx.StaticText(self, -1, 'Debug level:')
  296. self.debug = wx.SpinCtrl(self, min = 0, max = 5, initial = vpn.debug())
  297. self.debug.Bind(wx.EVT_SPINCTRL, self.OnDebugLevel)
  298. grid.Add(debuglabel)
  299. grid.Add(self.debug)
  300. modelabel = wx.StaticText(self, -1, 'Mode:')
  301. self.mode = wx.ComboBox(self, -1, style = wx.CB_READONLY, value = 'Router', choices = ['Router', 'Switch', 'Hub'])
  302. grid.Add(modelabel)
  303. grid.Add(self.mode)
  304. self.SetSizer(grid)
  305. class ConnectionsPage(wx.Panel):
  306. def __init__(self, parent, id):
  307. wx.Panel.__init__(self, parent, id)
  308. self.list = SuperListCtrl(self, id)
  309. self.list.InsertColumn(0, 'Name')
  310. self.list.InsertColumn(1, 'Address')
  311. self.list.InsertColumn(2, 'Port')
  312. self.list.InsertColumn(3, 'Options')
  313. self.list.InsertColumn(4, 'Weight')
  314. hbox = wx.BoxSizer(wx.HORIZONTAL)
  315. hbox.Add(self.list, 1, wx.EXPAND)
  316. self.SetSizer(hbox)
  317. self.refresh()
  318. class ContextMenu(wx.Menu):
  319. def __init__(self, item):
  320. wx.Menu.__init__(self)
  321. self.item = item
  322. disconnect = wx.MenuItem(self, -1, 'Disconnect')
  323. self.AppendItem(disconnect)
  324. self.Bind(wx.EVT_MENU, self.OnDisconnect, id=disconnect.GetId())
  325. def OnDisconnect(self, event):
  326. vpn.disconnect(self.item[0])
  327. def OnContext(self, event):
  328. i = event.GetIndex()
  329. self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition())
  330. def refresh(self):
  331. sortstate = self.list.GetSortState()
  332. self.list.itemDataMap = {}
  333. i = 0
  334. for key, connection in vpn.connections.items():
  335. if self.list.GetItemCount() <= i:
  336. self.list.InsertStringItem(i, connection.name)
  337. else:
  338. self.list.SetStringItem(i, 0, connection.name)
  339. self.list.SetStringItem(i, 1, connection.address)
  340. self.list.SetStringItem(i, 2, connection.port)
  341. self.list.SetStringItem(i, 3, str(connection.options))
  342. self.list.SetStringItem(i, 4, str(connection.weight))
  343. self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight)
  344. self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext)
  345. self.list.SetItemData(i, i)
  346. i += 1
  347. while self.list.GetItemCount() > i:
  348. self.list.DeleteItem(self.list.GetItemCount() - 1)
  349. self.list.SortListItems(sortstate[0], sortstate[1])
  350. class NodesPage(wx.Panel):
  351. def __init__(self, parent, id):
  352. wx.Panel.__init__(self, parent, id)
  353. self.list = SuperListCtrl(self, id)
  354. self.list.InsertColumn( 0, 'Name')
  355. self.list.InsertColumn( 1, 'Address')
  356. self.list.InsertColumn( 2, 'Port')
  357. self.list.InsertColumn( 3, 'Cipher')
  358. self.list.InsertColumn( 4, 'Digest')
  359. self.list.InsertColumn( 5, 'MACLength')
  360. self.list.InsertColumn( 6, 'Compression')
  361. self.list.InsertColumn( 7, 'Options')
  362. self.list.InsertColumn( 8, 'Status')
  363. self.list.InsertColumn( 9, 'Nexthop')
  364. self.list.InsertColumn(10, 'Via')
  365. self.list.InsertColumn(11, 'Distance')
  366. self.list.InsertColumn(12, 'PMTU')
  367. self.list.InsertColumn(13, 'Min MTU')
  368. self.list.InsertColumn(14, 'Max MTU')
  369. self.list.InsertColumn(15, 'Since')
  370. hbox = wx.BoxSizer(wx.HORIZONTAL)
  371. hbox.Add(self.list, 1, wx.EXPAND)
  372. self.SetSizer(hbox)
  373. self.refresh()
  374. def refresh(self):
  375. sortstate = self.list.GetSortState()
  376. self.list.itemDataMap = {}
  377. i = 0
  378. for key, node in vpn.nodes.items():
  379. if self.list.GetItemCount() <= i:
  380. self.list.InsertStringItem(i, node.name)
  381. else:
  382. self.list.SetStringItem(i, 0, node.name)
  383. self.list.SetStringItem(i, 1, node.address)
  384. self.list.SetStringItem(i, 2, node.port)
  385. self.list.SetStringItem(i, 3, str(node.cipher))
  386. self.list.SetStringItem(i, 4, str(node.digest))
  387. self.list.SetStringItem(i, 5, str(node.maclength))
  388. self.list.SetStringItem(i, 6, str(node.compression))
  389. self.list.SetStringItem(i, 7, format(node.options, "x"))
  390. self.list.SetStringItem(i, 8, format(node.status, "04x"))
  391. self.list.SetStringItem(i, 9, node.nexthop)
  392. self.list.SetStringItem(i, 10, node.via)
  393. self.list.SetStringItem(i, 11, str(node.distance))
  394. self.list.SetStringItem(i, 12, str(node.pmtu))
  395. self.list.SetStringItem(i, 13, str(node.minmtu))
  396. self.list.SetStringItem(i, 14, str(node.maxmtu))
  397. if node.last_state_change:
  398. since = time.strftime("%Y-%m-%d %H:%M", time.localtime(node.last_state_change))
  399. else:
  400. since = "never"
  401. self.list.SetStringItem(i, 15, since)
  402. self.list.itemDataMap[i] = (node.name, node.address, node.port, node.cipher, node.digest, node.maclength, node.compression, node.options, node.status, node.nexthop, node.via, node.distance, node.pmtu, node.minmtu, node.maxmtu, since)
  403. self.list.SetItemData(i, i)
  404. i += 1
  405. while self.list.GetItemCount() > i:
  406. self.list.DeleteItem(self.list.GetItemCount() - 1)
  407. self.list.SortListItems(sortstate[0], sortstate[1])
  408. class EdgesPage(wx.Panel):
  409. def __init__(self, parent, id):
  410. wx.Panel.__init__(self, parent, id)
  411. self.list = SuperListCtrl(self, id)
  412. self.list.InsertColumn(0, 'From')
  413. self.list.InsertColumn(1, 'To')
  414. self.list.InsertColumn(2, 'Address')
  415. self.list.InsertColumn(3, 'Port')
  416. self.list.InsertColumn(4, 'Options')
  417. self.list.InsertColumn(5, 'Weight')
  418. hbox = wx.BoxSizer(wx.HORIZONTAL)
  419. hbox.Add(self.list, 1, wx.EXPAND)
  420. self.SetSizer(hbox)
  421. self.refresh()
  422. def refresh(self):
  423. sortstate = self.list.GetSortState()
  424. self.list.itemDataMap = {}
  425. i = 0
  426. for key, edge in vpn.edges.items():
  427. if self.list.GetItemCount() <= i:
  428. self.list.InsertStringItem(i, edge.fr)
  429. else:
  430. self.list.SetStringItem(i, 0, edge.fr)
  431. self.list.SetStringItem(i, 1, edge.to)
  432. self.list.SetStringItem(i, 2, edge.address)
  433. self.list.SetStringItem(i, 3, edge.port)
  434. self.list.SetStringItem(i, 4, format(edge.options, "x"))
  435. self.list.SetStringItem(i, 5, str(edge.weight))
  436. self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight)
  437. self.list.SetItemData(i, i)
  438. i += 1
  439. while self.list.GetItemCount() > i:
  440. self.list.DeleteItem(self.list.GetItemCount() - 1)
  441. self.list.SortListItems(sortstate[0], sortstate[1])
  442. class SubnetsPage(wx.Panel):
  443. def __init__(self, parent, id):
  444. wx.Panel.__init__(self, parent, id)
  445. self.list = SuperListCtrl(self, id)
  446. self.list.InsertColumn(0, 'Subnet', wx.LIST_FORMAT_RIGHT)
  447. self.list.InsertColumn(1, 'Weight', wx.LIST_FORMAT_RIGHT)
  448. self.list.InsertColumn(2, 'Owner')
  449. hbox = wx.BoxSizer(wx.HORIZONTAL)
  450. hbox.Add(self.list, 1, wx.EXPAND)
  451. self.SetSizer(hbox)
  452. self.refresh()
  453. def refresh(self):
  454. sortstate = self.list.GetSortState()
  455. self.list.itemDataMap = {}
  456. i = 0
  457. for key, subnet in vpn.subnets.items():
  458. if self.list.GetItemCount() <= i:
  459. self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen)
  460. else:
  461. self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen)
  462. self.list.SetStringItem(i, 1, subnet.weight)
  463. self.list.SetStringItem(i, 2, subnet.owner)
  464. self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner)
  465. self.list.SetItemData(i, i)
  466. i += 1
  467. while self.list.GetItemCount() > i:
  468. self.list.DeleteItem(self.list.GetItemCount() - 1)
  469. self.list.SortListItems(sortstate[0], sortstate[1])
  470. class StatusPage(wx.Panel):
  471. def __init__(self, parent, id):
  472. wx.Panel.__init__(self, parent, id)
  473. class GraphPage(wx.Window):
  474. def __init__(self, parent, id):
  475. wx.Window.__init__(self, parent, id)
  476. class NetPage(wx.Notebook):
  477. def __init__(self, parent, id):
  478. wx.Notebook.__init__(self, parent)
  479. self.settings = SettingsPage(self, id)
  480. self.connections = ConnectionsPage(self, id)
  481. self.nodes = NodesPage(self, id)
  482. self.edges = EdgesPage(self, id)
  483. self.subnets = SubnetsPage(self, id)
  484. self.graph = GraphPage(self, id)
  485. self.status = StatusPage(self, id)
  486. self.AddPage(self.settings, 'Settings')
  487. #self.AddPage(self.status, 'Status')
  488. self.AddPage(self.connections, 'Connections')
  489. self.AddPage(self.nodes, 'Nodes')
  490. self.AddPage(self.edges, 'Edges')
  491. self.AddPage(self.subnets, 'Subnets')
  492. #self.AddPage(self.graph, 'Graph')
  493. class MainWindow(wx.Frame):
  494. def OnQuit(self, event):
  495. app.ExitMainLoop()
  496. def OnTimer(self, event):
  497. vpn.refresh()
  498. self.np.nodes.refresh()
  499. self.np.subnets.refresh()
  500. self.np.edges.refresh()
  501. self.np.connections.refresh()
  502. def __init__(self, parent, id, title):
  503. wx.Frame.__init__(self, parent, id, title)
  504. menubar = wx.MenuBar()
  505. file = wx.Menu()
  506. file.Append(1, '&Quit\tCtrl-X', 'Quit tinc GUI')
  507. menubar.Append(file, '&File')
  508. #nb = wx.Notebook(self, -1)
  509. #nb.SetPadding((0, 0))
  510. self.np = NetPage(self, -1)
  511. #nb.AddPage(np, 'VPN')
  512. self.timer = wx.Timer(self, -1)
  513. self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
  514. self.timer.Start(1000)
  515. self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
  516. self.SetMenuBar(menubar)
  517. self.Show()
  518. app = wx.App()
  519. mw = MainWindow(None, -1, 'Tinc GUI')
  520. #def OnTaskBarIcon(event):
  521. # mw.Raise()
  522. #
  523. #icon = wx.Icon("tincgui.ico", wx.BITMAP_TYPE_PNG)
  524. #taskbaricon = wx.TaskBarIcon()
  525. #taskbaricon.SetIcon(icon, 'Tinc GUI')
  526. #wx.EVT_TASKBAR_RIGHT_UP(taskbaricon, OnTaskBarIcon)
  527. app.MainLoop()
  528. vpn.close()