diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..872256e
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include wireguide/logo.png
diff --git a/build/lib/wireguide/logo.png b/build/lib/wireguide/logo.png
new file mode 100644
index 0000000..9ada82d
Binary files /dev/null and b/build/lib/wireguide/logo.png differ
diff --git a/build/lib/wireguide/wireguide b/build/lib/wireguide/wireguide
new file mode 100755
index 0000000..5e85216
--- /dev/null
+++ b/build/lib/wireguide/wireguide
@@ -0,0 +1,392 @@
+#!/usr/bin/env python3
+"""
+This is a program that can manage Wireguard Configuration graphically
+"""
+import configparser
+import os
+import re
+import sys
+import uuid
+from socket import AF_INET
+
+import gi
+import wx
+import wx.adv
+
+gi.require_version("NM", "1.0")
+from gi.repository import NM # pylint: disable=wrong-import-position
+
+
+class WireFrame(wx.Frame): # pylint: disable=too-many-ancestors,too-many-instance-attributes
+ """
+ The WireGUIde wx.Frame
+ """
+ def __init__(self, *args, **kw):
+ super().__init__(*args, **kw)
+
+ self.version = 0.1
+
+ # Get active conns from NetworkManager
+ self.client = NM.Client.new(None)
+ self.conns = self.get_wg_conns()
+ self.active_conns = self.get_wg_aconns()
+
+ # Set up for loaded configs
+ self.inactive_conns = get_inactive_conns()
+
+ # create a panel in the frame
+ self.pnl = wx.Panel(self)
+
+ # and create a sizer to manage the layout of child widgets
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
+ self.write_to_sizer()
+ self.pnl.SetSizer(self.sizer)
+
+ # create a menu bar
+ self.make_menu_bar()
+
+ # and a status bar
+ self.statusbar = self.CreateStatusBar(style=wx.BORDER_NONE)
+ self.set_status()
+
+ self.timer = wx.Timer(self)
+ self.count = 0
+
+ self.Bind(wx.EVT_TIMER, self.do_on_timer)
+ self.Bind(wx.EVT_PAINT, self.timing)
+ self.Show()
+
+ def about_clicked(self, event): # pylint: disable=unused-argument
+ """Display an About Dialog"""
+ about = "WireGUIde is a GUI for WireGuard."
+ lic_text = """
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see ."""
+ #wx.MessageBox(about, "About WireGUIde" ,wx.OK | wx.ICON_INFORMATION)
+ info = wx.adv.AboutDialogInfo()
+ info.SetIcon(wx.Icon('logo.png', wx.BITMAP_TYPE_PNG))
+ info.SetName('WireGUIde')
+ info.SetVersion(str(self.version))
+ info.SetDescription(about)
+ info.SetCopyright('(C) 2020 Mikael Nordin')
+ info.SetWebSite('https://github.com/mickenordin')
+ info.SetLicence(lic_text)
+ info.AddDeveloper('Mikael Nordin')
+ info.AddDocWriter('Mikael Nordin')
+ info.AddArtist('Mikael Nordin')
+
+ wx.adv.AboutBox(info)
+
+ def activate_button_clicked(self, event, conn): # pylint: disable=unused-argument
+ """
+ This activates an imported config
+ """
+ print(conn.get_id())
+ self.client.add_connection_async(conn, False, None, self.add_callback,
+ None)
+ self.remove_inactive(conn)
+
+ def add_callback(self, client, result, data): # pylint: disable=unused-argument
+ """
+ This is here to let us know if the connection was successful or not
+ """
+ try:
+ client.add_connection_finish(result)
+ print(
+ "The connection profile has been successfully added to NetworkManager."
+ )
+ except Exception as exception: # pylint: disable=broad-except
+ sys.stderr.write("Error: %s\n" % exception)
+ self.active_conns = self.get_wg_aconns()
+ self.conns = self.get_wg_conns()
+ self.write_to_sizer()
+
+ def create_conn_from_file(self, pathname):
+ """
+ Read a WireGuardUI config file and convert it in to
+ an object that can be user by NetworkManager
+ """
+ filename = os.path.basename(pathname)
+ try:
+ config = configparser.ConfigParser()
+ config.read(pathname)
+ iname = self.get_next_int_name()
+ profile = NM.SimpleConnection.new()
+ s_con = NM.SettingConnection.new()
+ s_con.set_property(NM.SETTING_CONNECTION_ID, iname)
+ s_con.set_property(NM.SETTING_CONNECTION_INTERFACE_NAME, iname)
+ s_con.set_property(NM.SETTING_CONNECTION_UUID, str(uuid.uuid4()))
+ s_con.set_property(NM.SETTING_CONNECTION_TYPE,
+ NM.SETTING_WIREGUARD_SETTING_NAME)
+
+ s_wireguard = NM.SettingWireGuard.new()
+ s_wireguard.set_property(NM.SETTING_WIREGUARD_PRIVATE_KEY,
+ config['Interface']['PrivateKey'])
+ s_peer = NM.WireGuardPeer.new()
+ s_peer.set_endpoint(config['Peer']['Endpoint'], False)
+ s_peer.set_public_key(config['Peer']['PublicKey'], False)
+ s_peer.append_allowed_ip(config['Peer']['AllowedIPs'], False)
+ s_wireguard.append_peer(s_peer)
+
+ s_ip4 = NM.SettingIP4Config.new()
+ s_ip4_address = NM.IPAddress(AF_INET,
+ config['Interface']['Address'],
+ int(32))
+ s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, "manual")
+ s_ip4.add_address(s_ip4_address)
+ s_ip4.add_dns(config['Interface']['DNS'])
+
+ profile.add_setting(s_con)
+ profile.add_setting(s_ip4)
+ profile.add_setting(s_wireguard)
+ return profile
+
+ except IOError:
+ wx.LogError("Cannot open file '%s'." % filename)
+ return None
+
+ def deactivate_button_clicked(self, event, conn): # pylint: disable=unused-argument
+ """
+ This deactivates an active config
+ """
+ print(conn.get_id())
+ self.client.deactivate_connection_async(conn, None, self.de_callback,
+ conn)
+ conn.get_connection().delete_async(None, None, None)
+
+ def de_callback(self, client, result, data): # pylint: disable=unused-argument
+ """
+ This is here to let us know if the deactivation was successful or not
+ """
+ try:
+ client.deactivate_connection_finish(result)
+ print(
+ "The connection profile has been successfully removed from NetworkManager."
+ )
+ except Exception as exception: # pylint: disable=broad-except
+ sys.stderr.write("Error: %s\n" % exception)
+ self.active_conns = self.get_wg_aconns()
+ self.conns = self.get_wg_conns()
+ self.write_to_sizer()
+
+ def do_on_timer(self, event): # pylint: disable=unused-argument
+ """
+ Do stuff to redraw the window when the timer times out
+ Ths is because connections might change outside of WireGUIde
+ """
+ self.count += 1
+ if self.count == 5:
+ self.client.reload_connections_async()
+ self.active_conns = self.get_wg_aconns()
+ self.conns = self.get_wg_conns()
+ self.set_status()
+ self.write_to_sizer()
+ self.count = 0
+
+ def exit_clicked(self, event): # pylint: disable=unused-argument
+ """
+ Close the frame, terminating the application.
+ """
+ self.Close(True)
+
+ def file_chooser_clicked(self, event): # pylint: disable=unused-argument
+ """
+ This is what happens when you click on the file chooser
+ """
+ with wx.FileDialog(self,
+ "Open WireGuard config file",
+ wildcard="WireGuard files (*.conf)|*.conf",
+ style=wx.FD_OPEN
+ | wx.FD_FILE_MUST_EXIST) as file_dialog:
+
+ if file_dialog.ShowModal() == wx.ID_CANCEL:
+ return # the user changed their mind
+
+ # Proceed loading the file chosen by the user
+ pathname = file_dialog.GetPath()
+ new_conn = self.create_conn_from_file(pathname)
+ self.inactive_conns.append(new_conn)
+ self.write_to_sizer()
+
+ def get_next_int_name(self):
+ """
+ Determins what we should call the next interface
+ """
+ temp = []
+ for conn in self.conns:
+ temp.append(re.findall(r'\d+', conn.get_interface_name()))
+ for conn in self.inactive_conns:
+ temp.append(re.findall(r'\d+', conn.get_interface_name()))
+ numbers = [int(item) for sublist in temp for item in sublist]
+ if not numbers:
+ num = 0
+ else:
+ numbers.sort(reverse=True)
+ num = numbers[0] + 1
+ return "wg" + str(num)
+
+ def get_wg_aconns(self):
+ """
+ Reads all active wireguard connections from NetworkManager
+ and returns them as objects in an array
+ """
+ mconns = []
+ wgconns = self.client.get_active_connections()
+ for conn in wgconns:
+ if conn.get_connection_type() == NM.SETTING_WIREGUARD_SETTING_NAME:
+ mconns.append(conn)
+ return mconns
+
+ def get_wg_conns(self):
+ """
+ Reads all current wireguard connections from NetworkManager
+ and returns them as objects in an array
+ """
+ mconns = []
+ wgconns = self.client.get_connections()
+ for conn in wgconns:
+ if conn.get_connection_type() == NM.SETTING_WIREGUARD_SETTING_NAME:
+ mconns.append(conn)
+ return mconns
+
+ def make_menu_bar(self):
+ """
+ A menu bar is composed of menus, which are composed of menu items.
+ This method builds a set of menus and binds handlers to be called
+ when the menu item is selected.
+ """
+
+ file_menu = wx.Menu()
+ file_chooser_item = file_menu.Append(-1, "&Open...\tCtrl-O",
+ "Select WireGuard config file")
+ file_menu.AppendSeparator()
+ exit_item = file_menu.Append(wx.ID_EXIT)
+
+ help_menu = wx.Menu()
+ about_item = help_menu.Append(wx.ID_ABOUT)
+
+ menu_bar = wx.MenuBar()
+ menu_bar.Append(file_menu, "&File")
+ menu_bar.Append(help_menu, "&Help")
+
+ self.SetMenuBar(menu_bar)
+
+ self.Bind(wx.EVT_MENU, self.file_chooser_clicked, file_chooser_item)
+ self.Bind(wx.EVT_MENU, self.exit_clicked, exit_item)
+ self.Bind(wx.EVT_MENU, self.about_clicked, about_item)
+
+ def remove_inactive(self, conn):
+ """
+ Remove the inactive connection from the array
+ """
+ for i in range(len(self.inactive_conns)):
+ if self.inactive_conns[i].get_id() == conn.get_id():
+ self.inactive_conns.pop(i)
+
+ def set_status(self):
+ """
+ Update the status bar
+ """
+ status = str(len(self.active_conns)) + " active connection(s)"
+ self.statusbar.SetStatusText(status)
+
+ def timing(self, event): # pylint: disable=unused-argument
+ """
+ Start a timer
+ """
+ self.timer.Start(20)
+
+ def write_to_sizer(self):
+ """
+ We use the BoxSizer to hold our configs
+ This method redraws the sizer
+ """
+ self.sizer.Clear(delete_windows=True)
+ if len(self.active_conns) > 0:
+ hd1 = wx.StaticText(self.pnl)
+ hd1.SetLabelMarkup("Active connections")
+ self.sizer.Add(hd1, 0, wx.ALIGN_CENTER)
+ for conn in self.active_conns:
+ statstr = wx.StaticText(self.pnl)
+ info = get_info_as_text(conn)
+ statstr.SetLabelMarkup(info)
+ self.sizer.Add(statstr,
+ wx.SizerFlags().Border(wx.TOP | wx.LEFT, 25))
+ dbtn = wx.Button(self.pnl, -1, "Deactivate")
+ self.sizer.Add(dbtn, 0, wx.ALIGN_CENTER)
+ self.Bind(wx.EVT_BUTTON,
+ lambda event, mconn=conn: self.
+ deactivate_button_clicked(event, mconn),
+ dbtn)
+ if len(self.inactive_conns) > 0:
+ hd2 = wx.StaticText(self.pnl)
+ hd2.SetLabelMarkup("Imported configs")
+ self.sizer.Add(hd2, 0, wx.ALIGN_CENTER)
+ if self.inactive_conns:
+ for conn in self.inactive_conns:
+ statstr = wx.StaticText(self.pnl)
+ info = get_info_as_text(conn)
+ statstr.SetLabelMarkup(info)
+ self.sizer.Add(
+ statstr,
+ wx.SizerFlags().Border(wx.TOP | wx.LEFT, 25))
+ btn = wx.Button(self.pnl, -1, "Activate")
+ self.sizer.Add(btn, 0, wx.ALIGN_CENTER)
+ self.Bind(wx.EVT_BUTTON,
+ lambda event, mconn=conn: self.
+ activate_button_clicked(event, mconn),
+ btn)
+ if (len(self.active_conns) == 0) and (len(self.inactive_conns) == 0):
+ hd0 = wx.StaticText(self.pnl)
+ hd0.SetLabelMarkup("No configs available")
+ missingstr = wx.StaticText(self.pnl)
+ missingstr.SetLabelMarkup(
+ "Please choose a config file from the file menu (CTRL+O).")
+ self.sizer.Add(hd0, 0, wx.ALIGN_CENTER)
+ self.sizer.Add(missingstr, 0, wx.ALIGN_LEFT)
+ self.sizer.Layout()
+
+
+def get_inactive_conns():
+ """
+ TODO: Not implemented yet
+ """
+ minactive_conns = []
+ #return empty array for now, we cant read configs from stash yet
+ return minactive_conns
+
+
+def get_info_as_text(aconn):
+ """
+ Returns info about a connection as text
+ """
+ try:
+ conn = aconn.get_connection()
+ except Exception: # pylint: disable=broad-except
+ conn = aconn
+ mid = conn.get_id()
+ miname = conn.get_interface_name()
+ muuid = conn.get_uuid()
+ info = "id: " + mid + '\n'
+ info += "interface name: " + miname + '\n'
+ info += "uuid: " + muuid + '\n'
+ return info
+
+
+if __name__ == '__main__':
+ # When this module is run (not imported) then create the app, the
+ # frame, show it, and start the event loop.
+ app = wx.App()
+ frm = WireFrame(None, title='WireGUIde')
+ frm.Show()
+ app.MainLoop()
diff --git a/dist/wireguide-0.0.1-py3-none-any.whl b/dist/wireguide-0.0.1-py3-none-any.whl
index d95d76f..cfb132a 100644
Binary files a/dist/wireguide-0.0.1-py3-none-any.whl and b/dist/wireguide-0.0.1-py3-none-any.whl differ
diff --git a/dist/wireguide-0.0.1.tar.gz b/dist/wireguide-0.0.1.tar.gz
index 6e100a8..404b7c7 100644
Binary files a/dist/wireguide-0.0.1.tar.gz and b/dist/wireguide-0.0.1.tar.gz differ
diff --git a/setup.py b/setup.py
index 0bc2c08..4b84a05 100644
--- a/setup.py
+++ b/setup.py
@@ -5,10 +5,11 @@ with open("README.md", "r") as fh:
setuptools.setup(
name="wireguide",
- version="0.0.1",
+ version="0.0.2",
author="Mikael Nordin",
author_email="mik@elnord.in",
description="A WireGuard GUI for GNU/Linux",
+ include_package_data=True,
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/mickenordin/wireguide",
diff --git a/wireguide.egg-info/PKG-INFO b/wireguide.egg-info/PKG-INFO
index 5651c5e..f55e21c 100644
--- a/wireguide.egg-info/PKG-INFO
+++ b/wireguide.egg-info/PKG-INFO
@@ -22,12 +22,9 @@ Description: # WireGUIde
Thanks to Jan Bodnar of zetcode.com for the valuable tutorial on wxPython dialogs:
* http://zetcode.com/wxpython/dialogs/
- ## Usage
+ ## Installation
```
- sudo apt get install libnm0 wxpython gir1.2-nm-1.0
- git clone https://github.com/mickenordin/wireguide.git
- cd wireguide/
- ./wireguide
+ pip install wireguide
```
## Screenshots
![No config](https://raw.githubusercontent.com/mickenordin/wireguide/main/screenshots/scrot0.png)
diff --git a/wireguide.egg-info/SOURCES.txt b/wireguide.egg-info/SOURCES.txt
index cfe19e8..d44b329 100644
--- a/wireguide.egg-info/SOURCES.txt
+++ b/wireguide.egg-info/SOURCES.txt
@@ -1,6 +1,8 @@
+MANIFEST.in
README.md
setup.py
wireguide/__init__.py
+wireguide/logo.png
wireguide/wireguide
wireguide.egg-info/PKG-INFO
wireguide.egg-info/SOURCES.txt
diff --git a/wireguide/wireguide b/wireguide/wireguide
index 5e85216..b02a6df 100755
--- a/wireguide/wireguide
+++ b/wireguide/wireguide
@@ -24,7 +24,7 @@ class WireFrame(wx.Frame): # pylint: disable=too-many-ancestors,too-many-instan
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
- self.version = 0.1
+ self.version = 0.0.2
# Get active conns from NetworkManager
self.client = NM.Client.new(None)