diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e0b46ee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+debug.log
+.venv
+.venv.bak
+.idea
+.vscode
+__pycache__
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index d5e9910..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index cca3bd8..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]/shelved.patch
deleted file mode 100644
index 99be84b..0000000
--- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]/shelved.patch
+++ /dev/null
@@ -1,76 +0,0 @@
-Index: .idea/workspace.xml
-IDEA additional info:
-Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
-<+>\n\n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {\n "associatedIndex": 3\n}\n \n \n \n \n \n \n \n \n {\n "keyToString": {\n "ASKED_ADD_EXTERNAL_FILES": "true",\n "Python.main.executor": "Run",\n "RunOnceActivity.ShowReadmeOnStart": "true",\n "git-widget-placeholder": "wire-py-reformat-14-08-2024",\n "last_opened_file_path": "/home/punix/Downloads/tkinter-bitcoin_price_converter_objectoriented(1).py"\n }\n}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 1723279982210\n \n \n 1723279982210\n \n \n \n 1723666001005\n \n \n \n 1723666001005\n \n \n \n 1723666477079\n \n \n \n 1723666477079\n \n \n \n 1723847456806\n \n \n \n 1723847456806\n \n \n \n 1724013210356\n \n \n \n 1724013210356\n \n \n \n 1724013251954\n \n \n \n 1724013251954\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
-Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
-<+>UTF-8
-===================================================================
-diff --git a/.idea/workspace.xml b/.idea/workspace.xml
---- a/.idea/workspace.xml
-+++ b/.idea/workspace.xml
-@@ -4,10 +4,8 @@
-
-
-
--
-+
-
--
--
-
-
-
-@@ -129,10 +127,33 @@
-
- 1724013251954
-
--
-+
-+
-+ 1724016284774
-+
-+
-+
-+ 1724016284774
-+
-+
-
-
-
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+
-
-
-
-@@ -143,7 +164,7 @@
-
-
-
--
-+
-
-
-
-@@ -161,6 +182,7 @@
-
-
-
--
-+
-+
-
-
-\ No newline at end of file
diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]1/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]1/shelved.patch
deleted file mode 100644
index e69de29..0000000
diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24__06_49__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24__06_49__Changes_.xml
deleted file mode 100644
index 85897b2..0000000
--- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24__06_49__Changes_.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/wire-py.iml b/.idea/wire-py.iml
deleted file mode 100644
index 2c80e12..0000000
--- a/.idea/wire-py.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 5c6d238..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,606 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "associatedIndex": 3
-}
-
-
-
-
-
-
-
-
- {
- "keyToString": {
- "ASKED_ADD_EXTERNAL_FILES": "true",
- "Python.INSTALL.executor": "Run",
- "Python.install.executor": "Run",
- "Python.main.executor": "Run",
- "Python.messagebox.executor": "Run",
- "Python.start_wg.executor": "Run",
- "Python.testtheme.executor": "Run",
- "Python.wg_func.executor": "Run",
- "Python.wg_main.executor": "Run",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "Shell Script.install.executor": "Run",
- "Shell Script.run_as.executor": "Run",
- "git-widget-placeholder": "1.11.1024",
- "last_opened_file_path": "/home/punix/Pyapps/wire-py",
- "settings.editor.selected.configurable": "reference.settingsdialog.IDE.editor.colors"
- }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1723279982210
-
-
- 1723279982210
-
-
-
- 1725991610908
-
-
-
- 1725991610908
-
-
-
- 1726349168248
-
-
-
- 1726349168248
-
-
-
- 1726359012150
-
-
-
- 1726359012150
-
-
-
- 1726599446537
-
-
-
- 1726599446538
-
-
-
- 1726599588155
-
-
-
- 1726599588155
-
-
-
- 1726650691719
-
-
-
- 1726650691719
-
-
-
- 1726652747322
-
-
-
- 1726652747322
-
-
-
- 1726691611936
-
-
-
- 1726691611936
-
-
-
- 1726734843529
-
-
-
- 1726734843529
-
-
-
- 1726764877546
-
-
-
- 1726764877546
-
-
-
- 1726770649542
-
-
-
- 1726770649542
-
-
-
- 1726777434040
-
-
-
- 1726777434040
-
-
-
- 1726836930251
-
-
-
- 1726836930251
-
-
-
- 1726841190285
-
-
-
- 1726841190285
-
-
-
- 1726860371820
-
-
-
- 1726860371820
-
-
-
- 1726915238475
-
-
-
- 1726915238475
-
-
-
- 1726959423800
-
-
-
- 1726959423800
-
-
-
- 1727015078922
-
-
-
- 1727015078922
-
-
-
- 1727018233930
-
-
-
- 1727018233930
-
-
-
- 1727028762875
-
-
-
- 1727028762875
-
-
-
- 1727028915701
-
-
-
- 1727028915701
-
-
-
- 1727118598759
-
-
-
- 1727118598760
-
-
-
- 1727288788988
-
-
-
- 1727288788988
-
-
-
- 1727347126769
-
-
-
- 1727347126769
-
-
-
- 1727378355274
-
-
-
- 1727378355275
-
-
-
- 1727379755537
-
-
-
- 1727379755537
-
-
-
- 1727380793216
-
-
-
- 1727380793216
-
-
-
- 1727525609727
-
-
-
- 1727525609728
-
-
-
- 1728059870005
-
-
-
- 1728059870005
-
-
-
- 1729103964804
-
-
-
- 1729103964804
-
-
-
- 1729283656386
-
-
-
- 1729283656387
-
-
-
- 1729283719951
-
-
-
- 1729283719951
-
-
-
- 1729353898829
-
-
-
- 1729353898830
-
-
-
- 1729541504291
-
-
-
- 1729541504292
-
-
-
- 1729541561434
-
-
-
- 1729541561434
-
-
-
- 1729593628907
-
-
-
- 1729593628908
-
-
-
- 1729938941026
-
-
-
- 1729938941027
-
-
-
- 1731097309468
-
-
-
- 1731097309468
-
-
-
- 1731097969343
-
-
-
- 1731097969344
-
-
-
- 1731098372497
-
-
-
- 1731098372497
-
-
-
- 1731690583059
-
-
-
- 1731690583060
-
-
-
- 1731836942211
-
-
-
- 1731836942212
-
-
-
- 1731840048762
-
-
-
- 1731840048763
-
-
-
- 1731840089956
-
-
-
- 1731840089956
-
-
-
- 1731840188277
-
-
-
- 1731840188278
-
-
-
- 1731840383592
-
-
-
- 1731840383592
-
-
-
- 1731841930614
-
-
-
- 1731841930615
-
-
-
- 1731844213239
-
-
-
- 1731844213239
-
-
-
- 1731844339039
-
-
-
- 1731844339039
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- file://$PROJECT_DIR$/wg_main.py
- 1128
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Changelog b/Changelog
index 0384163..b3414ab 100644
--- a/Changelog
+++ b/Changelog
@@ -3,27 +3,30 @@ My standard System: Linux Mint 22 Cinnamon
## [Unreleased]
- - os import in cls_mth_fc.py replaced by other methods
+ - os import in common_tools.py replaced by other methods
- If Wire-Py already runs, prevent further start
- for loops with lists replaced by List Comprehensions
-
### Added
-13-04-0725
+03-06-2025
+
+ -
+ ### Added
+13-04-20255
- Installer update for Open Suse Tumbleweed and Leap
- add symbolic link wirepy.py
### Added
-09-04-0725
+09-04-2025
- Installer now with query and remove
- Icons merged
### Added
-07-04-0725
+07-04-2025
- Installers will support other systems again
- Installer is now finished clean with wrong password
@@ -41,7 +44,7 @@ My standard System: Linux Mint 22 Cinnamon
- Fix ipv6 in Config File on import
- Wirepy run now as user
- - settings, keys and Config Files now in ~/.config/wire_py
+ - settings, AppConfig.KEYS_FILE and Config Files now in ~/.config/wire_py
- For new users, the required files are created and autostart service is started.
- Tunnels are now read from the directory to view them in the list.
To display only own tunnels, and read errors are minimized.
diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc
deleted file mode 100644
index 18c17c4..0000000
Binary files a/__pycache__/cls_mth_fc.cpython-312.pyc and /dev/null differ
diff --git a/__pycache__/message.cpython-312.pyc b/__pycache__/message.cpython-312.pyc
deleted file mode 100644
index f8ad592..0000000
Binary files a/__pycache__/message.cpython-312.pyc and /dev/null differ
diff --git a/__pycache__/start_wg.cpython-312.pyc b/__pycache__/start_wg.cpython-312.pyc
deleted file mode 100644
index d7729fa..0000000
Binary files a/__pycache__/start_wg.cpython-312.pyc and /dev/null differ
diff --git a/__pycache__/wg_main.cpython-312.pyc b/__pycache__/wg_main.cpython-312.pyc
deleted file mode 100644
index ca880e7..0000000
Binary files a/__pycache__/wg_main.cpython-312.pyc and /dev/null differ
diff --git a/cls_mth_fc.py b/cls_mth_fc.py
deleted file mode 100755
index fa16c27..0000000
--- a/cls_mth_fc.py
+++ /dev/null
@@ -1,429 +0,0 @@
-""" Classes Method and Functions for lx Apps """
-
-import gettext
-import locale
-import os
-import shutil
-import subprocess
-import tkinter as tk
-import zipfile
-from datetime import datetime
-from pathlib import Path
-from subprocess import check_call
-from tkinter import ttk
-
-import requests
-
-APP = "wirepy"
-LOCALE_DIR = "/usr/share/locale/"
-locale.bindtextdomain(APP, LOCALE_DIR)
-gettext.bindtextdomain(APP, LOCALE_DIR)
-gettext.textdomain(APP)
-_ = gettext.gettext
-
-wg_set = Path(Path.home() / ".config/wire_py/settings")
-
-
-class Create:
- """
- This class is for the creation of the folders and files
- required by Wire-Py, as well as for decryption
- the tunnel from the user's home directory
- """
-
- @staticmethod
- def dir_and_files():
- """
- check and create folders and files if not present
- """
-
- pth = Path.home() / ".config/wire_py"
- pth.mkdir(parents=True, exist_ok=True)
- sett = Path.home() / ".config/wire_py/settings"
- ks = Path.home() / ".config/wire_py/keys"
-
- if sett.exists():
- pass
-
- else:
- sett.touch()
- sett.write_text(
- "[UPDATES]\non\n[THEME]\nlight\n[TOOLTIP]\nTrue\n[AUTOSTART ON]\noff\n"
- )
-
- if ks.exists():
- pass
-
- else:
- ks.touch()
-
- @staticmethod
- def files_for_autostart():
- """
- check and create file for auto start if not present and enable the service
- """
-
- pth2 = Path.home() / ".config/systemd/user"
- pth2.mkdir(parents=True, exist_ok=True)
- wg_ser = Path.home() / ".config/systemd/user/wg_start.service"
-
- if wg_ser.exists():
- pass
-
- else:
- wg_ser.touch()
- wg_ser.write_text(
- "[Unit]\nDescription=Automatic Tunnel Start\nAfter=network-online.target"
- "\n\n[Service]\nType=oneshot\nExecStartPre=/bin/sleep 5\nExecStart=/usr/"
- "local/bin/start_wg.py\n[Install]\nWantedBy=default.target"
- )
- check_call(["systemctl", "--user", "enable", "wg_start.service"])
-
- @staticmethod
- def make_dir():
- """Dirname "tlecdewg" = Tunnel Encrypt Decrypt Wireguard"""
-
- dirname = Path("/tmp/tlecdcwg/")
- if dirname.exists():
- pass
- else:
- dirname.mkdir()
-
- @staticmethod
- def decrypt():
- """
- This start ssl_decrypt file
- """
- process = subprocess.run(
- ["pkexec", "/usr/local/bin/ssl_decrypt.py"],
- stdout=subprocess.PIPE,
- text=True,
- check=True,
- )
- # print(process.stdout)
- if process.returncode == 0:
- print("File successfully decrypted...")
- else:
- print(f"Error with the following code... {process.returncode}")
-
- @staticmethod
- def encrypt():
- """
- this start ssl_encrypt file
- """
- process = subprocess.run(
- ["pkexec", "/usr/local/bin/ssl_encrypt.py"],
- stdout=subprocess.PIPE,
- text=True,
- check=True,
- )
- print(process.stdout)
- if process.returncode == 0:
- print("All Files successfully encrypted...")
- else:
- print(f"Error with the following code... {process.returncode}")
-
-
-def uos():
- """
-
- uos = LOGIN USERNAME
-
- This method displays the user name of the logged-in user,
- even if you are rooted in a shell
- """
- logname = str(Path.home())[6:]
- file = Path.home() / "/tmp/.loguser"
- with open(file, "w", encoding="utf-8") as f:
- f.write(logname)
-
-
-class GiteaUpdate:
- """
- Calling download requests the download URL of the running script,
- the taskbar image for the “Download OK” window, the taskbar image for the
- “Download error” window and the variable res
- """
-
- @staticmethod
- def api_down(update_api_url, version):
- """
- Calling api_down requests the URL and the version of the running script.
- Example: version = 'v. 1.1.1.1' GiteaUpdate.api_down(http://example.de, version)
- """
- try:
- response = requests.get(update_api_url, timeout=10)
- response_dict = response.json()
- response_dict = response_dict[0]
- with open(wg_set, "r", encoding="utf-8") as set_file:
- set_file = set_file.read()
- if "on\n" in set_file:
- if version[3:] != response_dict["tag_name"]:
- req = response_dict["tag_name"]
- else:
- req = "No Updates"
- else:
- req = "False"
- return req
- except requests.exceptions.RequestException:
- req = "No Internet Connection!"
- return req
-
- @staticmethod
- def download(urld, down_ok_image, down_not_ok_image, res):
- """
- this is for download new Version of wirepy
- """
- try:
- to_down = "wget -qP " + str(Path.home()) + " " + urld
- result = subprocess.call(to_down, shell=True)
- if result == 0:
- shutil.chown(str(Path.home()) + f"/{res}.zip", 1000, 1000)
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = down_ok_image
- wt = _("Download Successful")
- msg_t = _("Your zip file is in home directory")
- msg_window(iw, ii, wt, msg_t)
-
- else:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/error.png"
- ii = down_not_ok_image
- wt = _("Download error")
- msg_t = _("Download failed! Please try again")
- msg_window(iw, ii, wt, msg_t)
- except subprocess.CalledProcessError:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/error.png"
- ii = down_not_ok_image
- wt = _("Download error")
- msg_t = _("Download failed! No internet connection!")
- msg_window(iw, ii, wt, msg_t)
-
-
-def msg_window(img_w, img_i, w_title, w_txt, txt2=None, com=None):
- """
- Function for different message windows for the user. with 4 arguments to be passed.
- To create messages with your own images, icons, and titles.
- As an alternative to Python Messagebox.
- Paths to images must be specified: r'/usr/share/icons/lx-icons/64/info.png'
- img_w = Image for Tk Window
- img_i = Image for Icon
- w_title = Windows Title
- w_txt = Text for Tk Window
- txt2 = Text for Button two
- com = function for Button command
- """
- msg = tk.Toplevel()
- msg.resizable(width=False, height=False)
- msg.title(w_title)
- msg.configure(pady=15, padx=15)
- msg.img = tk.PhotoImage(file=img_w)
- msg.i_window = tk.Label(msg, image=msg.img)
-
- label = tk.Label(msg, text=w_txt)
-
- label.grid(column=1, row=0)
-
- if txt2 is not None and com is not None:
- label.config(font=("Ubuntu", 11), padx=15, justify="left")
- msg.i_window.grid(column=0, row=0, sticky="nw")
- button2 = ttk.Button(msg, text=f"{txt2}", command=com, padding=4)
- button2.grid(column=0, row=1, sticky="e", columnspan=2)
- button = ttk.Button(msg, text="OK", command=msg.destroy, padding=4)
- button.grid(column=0, row=1, sticky="w", columnspan=2)
-
- else:
- label.config(font=("Ubuntu", 11), padx=15)
- msg.i_window.grid(column=0, row=0)
- button = ttk.Button(msg, text="OK", command=msg.destroy, padding=4)
- button.grid(column=0, columnspan=2, row=1)
-
- img_i = tk.PhotoImage(file=img_i)
- msg.iconphoto(True, img_i)
- msg.columnconfigure(0, weight=1)
- msg.rowconfigure(0, weight=1)
- msg.winfo_toplevel()
-
-
-class Tunnel:
- """
- Class of Methods for Wire-Py
- """
-
- @classmethod
- def con_to_dict(cls, file):
- """
- The config file is packed into a dictionary,
- to display the values Address , DNS and Peer in the labels
- """
-
- dictlist = []
- for lines in file.readlines():
- line_plit = lines.split()
- dictlist = dictlist + line_plit
- dictlist.remove("[Interface]")
- dictlist.remove("[Peer]")
- for items in dictlist:
- if items == "=":
- dictlist.remove(items)
- if items == "::/0":
- dictlist.remove(items)
-
- # Here is the beginning (Loop) of convert List to Dictionary
- for _ in dictlist:
- a = [dictlist[0], dictlist[1]]
- b = [dictlist[2], dictlist[3]]
- c = [dictlist[4], dictlist[5]]
- d = [dictlist[6], dictlist[7]]
- e = [dictlist[8], dictlist[9]]
- f = [dictlist[10], dictlist[11]]
- g = [dictlist[12], dictlist[13]]
- h = [dictlist[14], dictlist[15]]
- new_list = [a, b, c, d, e, f, g, h]
- final_dict = {}
- for elements in new_list:
- final_dict[elements[0]] = elements[1]
-
- # end... result a Dictionary
-
- address = final_dict["Address"]
- dns = final_dict["DNS"]
- if "," in dns:
- dns = dns[:-1]
- endpoint = final_dict["Endpoint"]
- pre_key = final_dict.get("PresharedKey")
- if pre_key is None:
- pre_key = final_dict.get("PreSharedKey")
- return address, dns, endpoint, pre_key
-
- @staticmethod
- def active():
- """
- Shows the Active Tunnel
- """
- active = (
- os.popen('nmcli con show --active | grep -iPo "(.*)(wireguard)"')
- .read()
- .split()
- )
- if not active:
- active = ""
- else:
- active = active[0]
-
- return active
-
- @staticmethod
- def list():
- """
- Shows all existing Wireguard tunnels a login user
- """
- dirname = Path("/tmp/tlecdcwg/")
- wg_s = os.listdir(dirname)
-
- return wg_s
-
- @staticmethod
- def export():
- """
- This will export the tunnels.
- A zipfile with current date and time is created
- in the user's home directory with correct right
- """
- now_time = datetime.now()
- now_datetime = now_time.strftime("wg-exp-" + "%m-%d-%Y" + "-" + "%H:%M")
- tl = Tunnel.list()
-
- try:
- if len(tl) != 0:
- wg_tar = str(Path.home()) + "/" + now_datetime
- shutil.copytree("/tmp/tlecdcwg/", "/tmp/wire_py", dirs_exist_ok=True)
- source = Path("/tmp/wire_py")
- shutil.make_archive(wg_tar, "zip", source)
- shutil.rmtree(source)
- with zipfile.ZipFile((wg_tar + ".zip"), "r") as zf:
- if len(zf.namelist()) != 0:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_vpn.png"
- wt = _("Export Successful")
- msg_t = _("Your zip file is in home directory")
- msg_window(iw, ii, wt, msg_t)
-
- else:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/error.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Export error")
- msg_t = _("Export failed! Please try again")
- msg_window(iw, ii, wt, msg_t)
-
- else:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Select tunnel")
- msg_t = _("Please first import tunnel")
- msg_window(iw, ii, wt, msg_t)
-
- except TypeError:
- pass
-
-
-def if_tip(path):
- """
- method that writes in file whether tooltip is displayed or not
- """
- with open(path, "r", encoding="utf-8") as set_file2:
- lines2 = set_file2.readlines()
- if "False\n" in lines2:
- tip = False
- else:
- tip = True
- return tip
-
-class Tooltip:
- """
- class for Tooltip
-
- imoprt Tooltip
-
- example: Tooltip(label, "Show tooltip on label")
- examble: Tooltip(button, "Show tooltip on button")
- info: label and button is parrent.
- """
- def __init__(self, widget, text):
- self.widget = widget
- self.text = text
- self.tooltip_window = None
- self.widget.bind("", self.show_tooltip)
- self.widget.bind("", self.hide_tooltip)
-
- def show_tooltip(self, event=None):
- if self.tooltip_window or not self.text:
- return
-
- x, y, _, _ = self.widget.bbox("insert")
- x += self.widget.winfo_rootx() + 25
- y += self.widget.winfo_rooty() + 20
- self.tooltip_window = tw = tk.Toplevel(self.widget)
- tw.wm_overrideredirect(True)
- tw.wm_geometry(f"+{x}+{y}")
-
- label = tk.Label(tw, text=self.text, relief="solid", borderwidth=1, padx=5, pady=5)
- label.grid()
-
- def hide_tooltip(self, event=None):
- if self.tooltip_window:
- self.tooltip_window.destroy()
- self.tooltip_window = None
-
-
\ No newline at end of file
diff --git a/install b/install
deleted file mode 100755
index 1b88d2b..0000000
--- a/install
+++ /dev/null
@@ -1,228 +0,0 @@
-#!/bin/bash
-NORMAL='\033[0m'
-GREEN='\033[1;32m'
-RED='\033[31;1;42m'
-BLUE='\033[30;1;34m'
-
-install_file_with(){
- clear
- mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
- mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
- systemctl --user enable wg_start.service >/dev/null 2>&1
- sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
- if [ $? -ne 0 ]
- then
- systemctl --user disable wg_start.service
- rm -r ~/.config/wire_py && rm -r ~/.config/systemd
- exit 0
- else
- sudo apt install python3-tk && \
- sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
- sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
- sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
- sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
- sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
- sudo mkdir -p /usr/local/etc/ssl
- if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
- then
- sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
- fi
- fi
- }
-
-install_arch_d(){
- clear
- mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
- mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
- systemctl --user enable wg_start.service >/dev/null 2>&1
- sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
- if [ $? -ne 0 ]
- then
- systemctl --user disable wg_start.service
- rm -r ~/.config/wire_py && rm -r ~/.config/systemd
- exit 0
- else
- sudo pacman -S --noconfirm tk python3 python-requests && \
- sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
- sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
- sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
- sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
- sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
- sudo mkdir -p /usr/local/etc/ssl
- if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
- then
- sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
- fi
-
- fi
- }
-
-install(){
- if grep -i 'debian' /etc/os-release > /dev/null 2>&1
- then
- groups > /tmp/isgroup
- if grep 'sudo' /tmp/isgroup
- then
- install_file_with
- else
- echo -e "$BLUE"The installer found that they are not in the group sudo.""
- echo -e "with "$RED"su -"$BLUE" "they can enter the root shell in which they then""
- echo -e "enter "$GREEN""usermod -aG sudo $USER.""$BLUE""
- echo -e ""after logging in from the system, they can then run Wire-Py install again." $NORMAL"
- read -n 1 -s -r -p $"Press Enter to exit"
- clear
- exit 0
-
- fi
-
- elif grep -i 'mint\|ubuntu\|pop|' /etc/os-release > /dev/null 2>&1
- then
- install_file_with
-
- elif grep -i 'arch' /etc/os-release > /dev/null 2>&1
- then
- groups > /tmp/isgroup
- clear
- if grep 'wheel' /tmp/isgroup
- then
- install_arch_d
- else
- echo "The installer found that they are not in the group sudo."
- echo "The sudoers file must be edited with"
- echo -e "$RED""su -""$NORMAL"
- echo -e "$GREEN"""EDITOR=nano visudo"""$NORMAL"
- echo "Find the line:"
- echo "## Uncomment to allow members of group wheel to execute any command"
- echo "remove '#' on # %wheel ALL=(ALL) ALL and save the file"
- echo -e "then enter "$GREEN"gpasswd -a $USER wheel.""$NORMAL"
- echo "after logging in from the system, they can then run Wire-Py install again."
- read -n 1 -s -r -p $"Press Enter to exit"
- clear
- exit 0
-
- fi
-
- elif grep -i '|manjaro\|garuda\|endeavour|' /etc/os-release > /dev/null 2>&1
- then
- install_arch_d
-
- elif grep -i 'fedora' /etc/os-release > /dev/null 2>&1
- then
- clear
- mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
- mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
- systemctl --user enable wg_start.service >/dev/null 2>&1
- sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
- if [ $? -ne 0 ]
- then
- systemctl --user disable wg_start.service
- rm -r ~/.config/wire_py && rm -r ~/.config/systemd
- exit 0
- else
- sudo dnf install python3-tkinter -y
- sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
- sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
- sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
- sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
- sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
- sudo mkdir -p /usr/local/etc/ssl
- if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
- then
- sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
- fi
-
- fi
- elif grep -i 'suse' /etc/os-release > /dev/null 2>&1
- then
- clear
- mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \
- mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \
- systemctl --user enable wg_start.service >/dev/null 2>&1
- sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/
- if [ $? -ne 0 ]
- then
- systemctl --user disable wg_start.service
- rm -r ~/.config/wire_py && rm -r ~/.config/systemd
- exit 0
- else
- sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \
- sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \
- sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \
- sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \
- sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
- sudo mkdir -p /usr/local/etc/ssl
- if [ ! -f /usr/local/etc/ssl/pwgk.pem ]
- then
- sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096
- fi
- if grep -i 'Tumbleweed' /etc/os-release > /dev/null 2>&1
- then
- sudo zypper install python313-tk
- else
- sudo zypper install python36-tk
- fi
-
- fi
-
- else
- clear
- echo $"Your System could not be determined."
- echo
- read -n 1 -s -r -p $"Press Enter to exit"
- clear
- exit 0
-
- fi
- #clear
- read -n 1 -s -r -p $"Press Enter to exit"
- clear
-
- }
-
-remove(){
- sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \
- /usr/local/bin/cls_mth_fc.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py
- if [ $? -ne 0 ]
- then
- exit 0
- else
- systemctl --user disable wg_start.service
- rm -r ~/.config/wire_py && rm -r ~/.config/systemd
- sudo rm /usr/share/applications/Wire-Py.desktop
- sudo rm /usr/share/locale/de/LC_MESSAGES/languages/de/wirepy.mo
- sudo rm -r /usr/local/etc/ssl
- which syncpy >/dev/null
- if [ $? -ne 0 ]
- then
- sudo rm -r /usr/share/icons/lx-icons && sudo rm -r /usr/share/TK-Themes
-
- fi
-
- echo
- read -p "Press Enter to exit..."
-
- fi
-
- }
-
-which wirepy >/dev/null
-if [ $? -eq 0 ]
- then
- echo "Do you want to update/reinstall or uninstall wirepy?"
- echo
- echo "Update/reinstall: press y, uninstall press r"
- echo
- read -n 1 -s -r -p "Cancel with any other key..." result
- case $result in
- [y]* ) clear; install; exit;;
- [Y]* ) clear; install; exit;;
- [j]* ) clear; install; exit;;
- [J]* ) clear; install; exit;;
- [r]* ) clear; remove; exit;;
- [R]* ) clear; remove; exit;;
- esac
- clear
-else
- install
-
-fi
\ No newline at end of file
diff --git a/lx-icons/128/download.png b/lx-icons/128/download.png
new file mode 100644
index 0000000..1589350
Binary files /dev/null and b/lx-icons/128/download.png differ
diff --git a/lx-icons/128/download_error.png b/lx-icons/128/download_error.png
new file mode 100644
index 0000000..011c99e
Binary files /dev/null and b/lx-icons/128/download_error.png differ
diff --git a/lx-icons/128/log.png b/lx-icons/128/log.png
new file mode 100644
index 0000000..06de63c
Binary files /dev/null and b/lx-icons/128/log.png differ
diff --git a/lx-icons/256/download.png b/lx-icons/256/download.png
new file mode 100644
index 0000000..614bf40
Binary files /dev/null and b/lx-icons/256/download.png differ
diff --git a/lx-icons/256/download_error.png b/lx-icons/256/download_error.png
new file mode 100644
index 0000000..e75bb67
Binary files /dev/null and b/lx-icons/256/download_error.png differ
diff --git a/lx-icons/256/log.png b/lx-icons/256/log.png
new file mode 100644
index 0000000..3921edb
Binary files /dev/null and b/lx-icons/256/log.png differ
diff --git a/lx-icons/32/download.png b/lx-icons/32/download.png
new file mode 100644
index 0000000..e209fbd
Binary files /dev/null and b/lx-icons/32/download.png differ
diff --git a/lx-icons/32/download_error.png b/lx-icons/32/download_error.png
new file mode 100644
index 0000000..595d04d
Binary files /dev/null and b/lx-icons/32/download_error.png differ
diff --git a/lx-icons/32/log.png b/lx-icons/32/log.png
new file mode 100644
index 0000000..ebc7be1
Binary files /dev/null and b/lx-icons/32/log.png differ
diff --git a/lx-icons/32/wg_vpn.png b/lx-icons/32/wg_vpn.png
deleted file mode 100644
index 55df4dd..0000000
Binary files a/lx-icons/32/wg_vpn.png and /dev/null differ
diff --git a/lx-icons/48/download.png b/lx-icons/48/download.png
new file mode 100644
index 0000000..4302a7c
Binary files /dev/null and b/lx-icons/48/download.png differ
diff --git a/lx-icons/48/download_error.png b/lx-icons/48/download_error.png
new file mode 100644
index 0000000..96ec900
Binary files /dev/null and b/lx-icons/48/download_error.png differ
diff --git a/lx-icons/48/log.png b/lx-icons/48/log.png
new file mode 100644
index 0000000..971a013
Binary files /dev/null and b/lx-icons/48/log.png differ
diff --git a/lx-icons/64/download.png b/lx-icons/64/download.png
new file mode 100644
index 0000000..cc12d8a
Binary files /dev/null and b/lx-icons/64/download.png differ
diff --git a/lx-icons/64/download_error.png b/lx-icons/64/download_error.png
new file mode 100644
index 0000000..0cd4161
Binary files /dev/null and b/lx-icons/64/download_error.png differ
diff --git a/lx-icons/64/log.png b/lx-icons/64/log.png
new file mode 100644
index 0000000..e1eb8dc
Binary files /dev/null and b/lx-icons/64/log.png differ
diff --git a/match_found.py b/match_found.py
new file mode 100755
index 0000000..e80954b
--- /dev/null
+++ b/match_found.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python3
+
+import argparse
+from pathlib import Path
+
+
+directorys: list[str] = [
+ "/etc/netplan/",
+ "/etc/NetworkManager/system-connections/",
+ "/var/lib/NetworkManager/user-connections/",
+]
+
+
+def search_string_in_directory(
+ directories: list[str] = directorys, # Use the predefined list as default
+ search_string: str = "", # Default is empty string
+) -> bool:
+
+ if len(search_string) == 0:
+ return False
+
+ result = False
+ for directory in directories:
+ in_paths = Path(directory)
+ if not in_paths.exists() or not in_paths.is_dir():
+ continue
+
+ files = [file for file in in_paths.iterdir() if file.is_file()]
+ if not files:
+ continue
+
+ # Search for the string in each file
+ for file in files:
+ try:
+ with open(file, "r", errors="ignore") as f:
+ for line in f:
+ if search_string in line:
+ result = True # String found
+ break
+ if result:
+ break # No need to check further
+ except Exception:
+ continue # Skip files that cause errors
+
+ return result
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="Script only for use to compare the private key in the"
+ "Network configurations to avoid errors with the network manager."
+ )
+ parser.add_argument("search_string", help="Search string")
+ args = parser.parse_args()
+
+ result = search_string_in_directory(search_string=args.search_string)
+ print(result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/org.sslcrypt.policy b/org.sslcrypt.policy
index 6a2c47a..84fff56 100644
--- a/org.sslcrypt.policy
+++ b/org.sslcrypt.policy
@@ -25,6 +25,7 @@ License along with this library. If not, see
auth_admin_keep
+ auth_admin_keep
yes
/usr/local/bin/ssl_encrypt.py
@@ -37,6 +38,14 @@ License along with this library. If not, see
yes
/usr/local/bin/ssl_decrypt.py
-
+
+
+
+
+ auth_admin_keep
+ auth_admin_keep
+ yes
+
+ /usr/local/bin/match_found.py
\ No newline at end of file
diff --git a/settings b/settings
deleted file mode 100644
index c0aaccd..0000000
--- a/settings
+++ /dev/null
@@ -1,8 +0,0 @@
-[UPDATES]
-on
-[THEME]
-light
-[TOOLTIP]
-True
-[AUTOSTART ON]
-off
diff --git a/ssl_decrypt.py b/ssl_decrypt.py
index 7cb4630..c3cef08 100755
--- a/ssl_decrypt.py
+++ b/ssl_decrypt.py
@@ -1,59 +1,84 @@
#!/usr/bin/python3
-""" This Script decrypt Wireguardfiles for Wirepy users """
-
-import os
-import shutil
+""" This Script decrypt Wireguard files for Wirepy users """
+import argparse
from pathlib import Path
-from subprocess import check_call
+import pwd
+import shutil
+from subprocess import CompletedProcess, run
+from shared_libs.wp_app_config import AppConfig, logging
-uname = Path("/tmp/.loguser")
+parser = argparse.ArgumentParser()
+parser.add_argument("--user", required=True, help="Username of the target file system")
+args = parser.parse_args()
-with open(uname, "r", encoding="utf-8") as f:
- logname = f.readline()
+try:
+ # Retrieve UID and GID
+ user_info = pwd.getpwnam(args.user)
+ uid = user_info.pw_uid # User ID (e.g., 1000)
+ gid = user_info.pw_gid # Group ID (e.g., 1000)
+except KeyError:
+ logging.error(f"User '{args.user}' not found.", exc_info=True)
+ exit(1)
-# Dirname "tlecdewg" = Tunnel Encrypt Decrypt Wireguard
-dirname = Path("/tmp/tlecdcwg/")
-keyfile = Path(f"/home/{logname}/.config/wire_py/pbwgk.pem")
-PKEYFILE = "/usr/local/etc/ssl/pwgk.pem"
+keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem")
+path_of_crypted_tunnel: Path = Path(f"/home/{args.user}/.config/wire_py")
if not keyfile.is_file():
-
- check_call(
+ process: CompletedProcess[str] = run(
[
"openssl",
"rsa",
"-in",
- PKEYFILE,
+ AppConfig.SYSTEM_PATHS["pkey_path"],
"-out",
keyfile,
"-outform",
"PEM",
"-pubout",
- ]
+ ],
+ capture_output=True,
+ text=True,
+ check=False,
)
- shutil.chown(keyfile, 1000, 1000)
-dirname2 = "/home/" + logname + "/.config/wire_py/"
-detl = os.listdir(dirname2)
-os.chdir(dirname2)
-detl.remove("keys")
-detl.remove("settings")
-if os.path.exists(dirname2 + "pbwgk.pem"):
- detl.remove("pbwgk.pem")
- for detunnels in detl:
- tlname2 = detunnels[:-4] + ".conf"
- extpath = str(dirname) + "/" + tlname2
- check_call(
+ if process.returncode == 0:
+ logging.info("Public key generated successfully.", exc_info=True)
+ else:
+ logging.error(
+ f"Error with the following code... {process.returncode}", exc_info=True
+ )
+ shutil.chown(keyfile, uid, gid)
+
+if AppConfig.PUBLICKEY.exists():
+
+ crypted__tunnel = [str(file) for file in path_of_crypted_tunnel.glob("*.dat")]
+
+ for tunnel_path in crypted__tunnel:
+
+ base_name = Path(tunnel_path).stem
+
+ process: CompletedProcess[str] = run(
[
"openssl",
"pkeyutl",
"-decrypt",
"-inkey",
- PKEYFILE,
+ AppConfig.SYSTEM_PATHS["pkey_path"],
"-in",
- detunnels,
+ tunnel_path, # full path to the file
"-out",
- extpath,
- ]
+ f"{AppConfig.TEMP_DIR}/{base_name}.conf",
+ ],
+ capture_output=True,
+ text=True,
+ check=False,
)
- shutil.chown(extpath, 1000, 1000)
+ shutil.chown(f"{AppConfig.TEMP_DIR}/{base_name}.conf", uid, gid)
+ logging.info(f"Processing of the file: {tunnel_path}", exc_info=True)
+
+ # Output from Openssl Error
+ if process.stderr:
+ logging.error(
+ f"{process.stderr} Error by [{tunnel_path}] Code: {process.returncode}",
+ exc_info=True,
+ )
diff --git a/ssl_encrypt.py b/ssl_encrypt.py
index 4f05032..0a00e7f 100755
--- a/ssl_encrypt.py
+++ b/ssl_encrypt.py
@@ -1,83 +1,82 @@
#!/usr/bin/python3
""" This Script encrypt Wireguardfiles for Wirepy users for more Security """
-import os
-import shutil
+import argparse
from pathlib import Path
-from subprocess import check_call
+import pwd
+import shutil
+from subprocess import CompletedProcess, run
+from shared_libs.wp_app_config import AppConfig, logging
-uname = Path("/tmp/.loguser")
+parser = argparse.ArgumentParser()
+parser.add_argument("--user", required=True, help="Username of the target file system")
+args = parser.parse_args()
-with open(uname, "r", encoding="utf-8") as f:
- logname = f.readline()
+try:
+ # Retrieve UID and GID
+ user_info = pwd.getpwnam(args.user)
+ uid = user_info.pw_uid # User ID (e.g., 1000)
+ gid = user_info.pw_gid # Group ID (e.g., 1000)
+except KeyError:
+ logging.error(f"User '{args.user}' not found.", exc_info=True)
+ exit(1)
-keyfile = Path(f"/home/{logname}/.config/wire_py/pbwgk.pem")
-dirname = Path("/tmp/tlecdcwg/")
-PKEYFILE = "/usr/local/etc/ssl/pwgk.pem"
+keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem")
+
+target: Path = Path(f"/home/{args.user}/.config/wire_py/")
if not keyfile.is_file():
- check_call(
+ process: CompletedProcess[str] = run(
[
"openssl",
"rsa",
"-in",
- PKEYFILE,
+ AppConfig.SYSTEM_PATHS["pkey_path"],
"-out",
keyfile,
"-outform",
"PEM",
"-pubout",
- ]
+ ],
+ capture_output=True,
+ text=True,
+ check=False,
)
- shutil.chown(keyfile, 1000, 1000)
- if dirname.exists():
- tl = os.listdir(str(dirname))
- CPTH = str(keyfile)
- CRYPTFILES = CPTH[:-9]
+ # Output from Openssl Error
+ if process.stderr:
+ logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True)
- if keyfile.exists() and len(tl) != 0:
- for tunnels in tl:
- sourcetl = str(dirname) + "/" + tunnels
- tlname = CRYPTFILES + tunnels[:-5] + ".dat"
- check_call(
- [
- "openssl",
- "pkeyutl",
- "-encrypt",
- "-inkey",
- keyfile,
- "-pubin",
- "-in",
- sourcetl,
- "-out",
- tlname,
- ]
- )
+ if process.returncode == 0:
+ logging.info("Public key generated successfully.", exc_info=True)
-else:
+ shutil.chown(keyfile, uid, gid)
- if dirname.exists():
- tl = os.listdir(str(dirname))
- CPTH = str(keyfile)
- CRYPTFILES = CPTH[:-9]
+# any() get True when directory is not empty
+if AppConfig.TEMP_DIR.exists() and any(AppConfig.TEMP_DIR.iterdir()):
+ clear_files = [str(file) for file in AppConfig.TEMP_DIR.glob("*.conf")]
- if keyfile.exists() and len(tl) != 0:
- for tunnels in tl:
- sourcetl = str(dirname) + "/" + tunnels
- tlname = CRYPTFILES + tunnels[:-5] + ".dat"
- check_call(
- [
- "openssl",
- "pkeyutl",
- "-encrypt",
- "-inkey",
- keyfile,
- "-pubin",
- "-in",
- sourcetl,
- "-out",
- tlname,
- ]
- )
+ for config_file in clear_files:
+ base_name = Path(config_file).stem
+ process: CompletedProcess[str] = run(
+ [
+ "openssl",
+ "pkeyutl",
+ "-encrypt",
+ "-inkey",
+ keyfile,
+ "-pubin",
+ "-in",
+ config_file,
+ "-out",
+ f"{target}/{base_name}.dat",
+ ],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+ # Output from Openssl Error
+ if process.stderr:
+ logging.error(process.stderr, exc_info=True)
diff --git a/start_wg.py b/start_wg.py
index 8f2636c..3336915 100755
--- a/start_wg.py
+++ b/start_wg.py
@@ -1,18 +1,24 @@
#!/usr/bin/python3
"""
- This script belongs to wirepy and is for the auto start of the tunnel
+ This script belongs to wirepy and is for the auto start of the tunnel
"""
-from pathlib import Path
-from subprocess import check_call
+import logging
+from subprocess import CompletedProcess, run
+from shared_libs.wp_app_config import AppConfig
+from shared_libs.common_tools import ConfigManager, LogConfig
-path_to_file = Path(Path.home() / ".config/wire_py/settings")
+ConfigManager.init(AppConfig.SETTINGS_FILE)
+LogConfig.logger(ConfigManager.get("logfile"))
+if ConfigManager.get("autostart") != "off":
+ process: CompletedProcess[str] = run(
+ ["nmcli", "connection", "up", ConfigManager.get("autostart")],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+ # Output from start_wg error
+ if process.stderr:
+ logging.error(process.stderr, exc_info=True)
-with open(path_to_file, "r", encoding="utf-8") as a_con:
-
- # This funtion is for the independent autostart of the previously selected tunnel
- lines = a_con.readlines()
- a_con = lines[7].strip()
- if a_con != "off":
- check_call(["nmcli", "connection", "up", a_con])
- else:
- pass
+else:
+ pass
diff --git a/tunnel.py b/tunnel.py
new file mode 100644
index 0000000..fe4cf9a
--- /dev/null
+++ b/tunnel.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python3
+import logging
+import getpass
+import zipfile
+from datetime import datetime
+from pathlib import Path
+import shutil
+from subprocess import run, CompletedProcess
+import secrets
+from shared_libs.wp_app_config import AppConfig, Msg
+from shared_libs.common_tools import LxTools, CryptoUtil
+
+# Translate
+_ = AppConfig.setup_translations()
+
+
+class Tunnel:
+ """
+ Class of Methods for Wire-Py
+ """
+
+ @staticmethod
+ def parse_files_to_dictionary(
+ directory: Path = None, filepath: str = None, content: str = None
+ ) -> tuple[dict, str] | dict | None:
+ data = {}
+
+ if filepath is not None:
+ filepath = Path(filepath)
+ try:
+ content = filepath.read_text()
+
+ # parse the content
+ address_line = next(
+ line for line in content.splitlines() if line.startswith("Address")
+ )
+ dns_line = next(
+ line for line in content.splitlines() if line.startswith("DNS")
+ )
+ endpoint_line = next(
+ line for line in content.splitlines() if line.startswith("Endpoint")
+ )
+ private_key_line = next(
+ line
+ for line in content.splitlines()
+ if line.startswith("PrivateKey")
+ )
+
+ content = secrets.token_bytes(len(content))
+
+ # extract the values
+ address = address_line.split("=")[1].strip()
+ dns = dns_line.split("=")[1].strip()
+ endpoint = endpoint_line.split("=")[1].strip()
+ private_key = private_key_line.split("=")[1].strip()
+
+ # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters.
+ original_stem = filepath.stem
+ truncated_stem = (
+ original_stem[-12:] if len(original_stem) > 12 else original_stem
+ )
+
+ # save in the dictionary
+ data[truncated_stem] = {
+ "Address": address,
+ "DNS": dns,
+ "Endpoint": endpoint,
+ "PrivateKey": private_key,
+ }
+
+ content = secrets.token_bytes(len(content))
+
+ except StopIteration:
+ pass
+
+ elif directory is not None:
+
+ if not directory.exists() or not directory.is_dir():
+ logging.error(
+ "Temp directory does not exist or is not a directory.",
+ exc_info=True,
+ )
+ return None
+
+ # Get a list of all files in the directory
+ files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()]
+
+ # Search for the string in the files
+ for file in files:
+ try:
+ content = file.read_text()
+ # parse the content
+ address_line = next(
+ line
+ for line in content.splitlines()
+ if line.startswith("Address")
+ )
+ dns_line = next(
+ line for line in content.splitlines() if line.startswith("DNS")
+ )
+ endpoint_line = next(
+ line
+ for line in content.splitlines()
+ if line.startswith("Endpoint")
+ )
+
+ # extract values
+ address = address_line.split("=")[1].strip()
+ dns = dns_line.split("=")[1].strip()
+ endpoint = endpoint_line.split("=")[1].strip()
+
+ # save values to dictionary
+ data[file.stem] = {
+ "Address": address,
+ "DNS": dns,
+ "Endpoint": endpoint,
+ }
+
+ except Exception:
+ # Ignore errors and continue to the next file
+ continue
+ if content is not None:
+ content = secrets.token_bytes(len(content))
+ if filepath is not None:
+ return data, truncated_stem
+ else:
+ return data
+
+ @staticmethod
+ def get_active() -> str:
+ """
+ Shows the Active Tunnel
+ """
+ active = None
+ try:
+ process: CompletedProcess[str] = run(
+ ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show", "--active"],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+ active = next(
+ line.split(":")[0].strip()
+ for line in process.stdout.splitlines()
+ if line.endswith("wireguard")
+ )
+
+ if process.stderr and "error" in process.stderr.lower():
+ logging.error(f"Error output on nmcli: {process.stderr}")
+
+ except StopIteration:
+ active = None
+ except Exception as e:
+ logging.error(f"Error on nmcli: {e}")
+ active = None
+
+ return active if active is not None else ""
+
+ @staticmethod
+ def export() -> bool | None:
+ """
+ This will export the tunnels.
+ A zipfile with the current date and time is created
+ in the user's home directory with the correct right
+ """
+ now_time: datetime = datetime.now()
+ now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M")
+
+ try:
+ AppConfig.ensure_directories()
+ CryptoUtil.decrypt(getpass.getuser())
+ if len([file.name for file in AppConfig.TEMP_DIR.glob("*.conf")]) == 0:
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["sel_tl"],
+ Msg.STR["tl_first"],
+ )
+ return False
+ else:
+ wg_tar: str = f"{AppConfig.BASE_DIR}/{now_datetime}"
+ try:
+ shutil.make_archive(wg_tar, "zip", AppConfig.TEMP_DIR)
+ with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf:
+ if zf.namelist():
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_vpn"],
+ Msg.STR["exp_succ"],
+ Msg.STR["exp_in_home"],
+ )
+ else:
+ logging.error(
+ "There was a mistake at creating the Zip file. File is empty."
+ )
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_error"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["exp_err"],
+ Msg.STR["exp_zip"],
+ )
+ return False
+ return True
+ except PermissionError:
+ logging.error(
+ f"Permission denied when creating archive in {wg_tar}"
+ )
+ return False
+
+ except zipfile.BadZipFile as e:
+ logging.error(f"Invalid ZIP file: {e}")
+ return False
+ except TypeError:
+ pass
+ except Exception as e:
+ logging.error(f"Export failed: {str(e)}")
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_error"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["exp_err"],
+ Msg.STR["exp_try"],
+ )
+ return False
+
+ finally:
+ LxTools.clean_files(AppConfig.TEMP_DIR)
+ AppConfig.ensure_directories()
diff --git a/wg_start.service b/wg_start.service
deleted file mode 100644
index 0352b4e..0000000
--- a/wg_start.service
+++ /dev/null
@@ -1,10 +0,0 @@
-[Unit]
-Description=Automatic Tunnel Start
-After=network-online.target
-
-[Service]
-Type=oneshot
-ExecStartPre=/bin/sleep 5
-ExecStart=/usr/local/bin/start_wg.py
-[Install]
-WantedBy=default.target
diff --git a/wirepy.py b/wirepy.py
index c073737..3edc8dc 100755
--- a/wirepy.py
+++ b/wirepy.py
@@ -2,117 +2,75 @@
"""
this script is a simple GUI for managing Wireguard Tunnels
"""
-import gettext
-import locale
-import webbrowser
-import os
-import sys
-import signal
-import subprocess
+import logging
+import getpass
import shutil
+import sys
import tkinter as tk
+import webbrowser
from pathlib import Path
-from subprocess import check_call
-from tkinter import filedialog, ttk, TclError
-from cls_mth_fc import Tooltip, Tunnel, Create, msg_window, if_tip, GiteaUpdate, uos
+from subprocess import CompletedProcess, run
+from tkinter import TclError, filedialog, ttk
+from tunnel import Tunnel
-uos()
-Create.dir_and_files()
-Create.make_dir()
-Create.decrypt()
-
-tcl_path = Path("/usr/share/TK-Themes")
-wg_set = Path(Path.home() / ".config/wire_py/settings")
-WG_TIPS = if_tip(wg_set)
-dirname = Path("/tmp/tlecdcwg/")
-
-# 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year
-VERSION = "v. 2.04.1725"
-
-res = GiteaUpdate.api_down(
- "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION
+from shared_libs.gitea import GiteaUpdate
+from shared_libs.common_tools import (
+ LxTools,
+ CryptoUtil,
+ LogConfig,
+ ConfigManager,
+ ThemeManager,
+ Tooltip,
)
-# Translate
-APP = "wirepy"
-LOCALE_DIR = "/usr/share/locale/"
-locale.bindtextdomain(APP, LOCALE_DIR)
-gettext.bindtextdomain(APP, LOCALE_DIR)
-gettext.textdomain(APP)
-_ = gettext.gettext
-
-
-def signal_handler(signum, frame):
- """
- Determine clear text names for signal numbers
- """
- signals_to_names_dict = dict(
- (getattr(signal, n), n)
- for n in dir(signal)
- if n.startswith("SIG") and "_" not in n
- )
- signame = signals_to_names_dict.get(signum, f"Unnamed signal: {signum}")
-
- # End program for certain signals, report to others only reception
- if signum in (signal.SIGINT, signal.SIGTERM):
- exit_code = 1
- print(
- f"\nSignal {signame} {(signum)} received. => Aborting with exit code {exit_code}."
- )
- shutil.rmtree(dirname)
- Path.unlink("/tmp/.loguser")
- print("Breakdown by user...")
- sys.exit(exit_code)
- else:
- print(f"Signal {signum} received and ignored.")
- shutil.rmtree(dirname)
- Path.unlink("/tmp/.loguser")
- print("Process unexpectedly ended...")
-
-
-signal.signal(signal.SIGINT, signal_handler)
-signal.signal(signal.SIGTERM, signal_handler)
-signal.signal(signal.SIGHUP, signal_handler)
+from shared_libs.wp_app_config import AppConfig, Msg
class Wirepy(tk.Tk):
"""
- Class Wirepy this is Main Window of wirepy
+ Class Wirepy this is the Main Window of wirepy
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ # Hide the window initially
+ self.withdraw()
+
self.my_tool_tip = None
- self.x_width = 600
- self.y_height = 383
- self.monitor_center_x = int(self.winfo_screenwidth() / 2 - (self.x_width / 2))
- self.monitor_center_y = int(self.winfo_screenheight() / 2 - (self.y_height / 2))
- self.resizable(width=False, height=False)
- self.title("Wire-Py")
- self.geometry(
- f"{self.x_width}x{self.y_height}+{self.monitor_center_x}+{self.monitor_center_y}"
+ self.x_width = AppConfig.UI_CONFIG["window_size"][0]
+ self.y_height = AppConfig.UI_CONFIG["window_size"][1]
+
+ # Set the window size
+ self.geometry(f"{self.x_width}x{self.y_height}")
+ self.resizable(
+ AppConfig.UI_CONFIG["resizable_window"][0],
+ AppConfig.UI_CONFIG["resizable_window"][1],
)
+ self.title(AppConfig.UI_CONFIG["window_title"])
+
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
+ self.tk.call("source", f"{AppConfig.SYSTEM_PATHS['tcl_path']}/water.tcl")
+ ConfigManager.init(AppConfig.SETTINGS_FILE)
+ theme = ConfigManager.get("theme")
+ ThemeManager.change_theme(self, theme)
- self.style = ttk.Style(self)
- self.tk.call("source", str(tcl_path) + "/water.tcl")
- with open(wg_set, "r", encoding="utf-8") as read_file:
- lines = read_file.readlines()
- if "light\n" in lines:
- self.tk.call("set_theme", "light")
- else:
- self.tk.call("set_theme", "dark")
-
- # Load the image file from disk
- self.wg_icon = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn.png")
+ # Load the image file from the disk
+ self.wg_icon = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_vpn"])
# Set it as the window icon
self.iconphoto(True, self.wg_icon)
+ # Add the widgets
FrameWidgets(self).grid()
+ # Center the window on the primary monitor
+ LxTools.center_window_cross_platform(self, self.x_width, self.y_height)
+
+ # Now show the window after it has been positioned
+ self.after(10, self.deiconify)
+
class FrameWidgets(ttk.Frame):
"""
@@ -128,271 +86,100 @@ class FrameWidgets(ttk.Frame):
self.dns = None
self.address = None
self.auto_con = None
- self.wg_vpn_start = tk.PhotoImage(
- file=r"/usr/share/icons/lx-icons/48/wg_vpn-start.png"
- )
- self.wg_vpn_stop = tk.PhotoImage(
- file=r"/usr/share/icons/lx-icons/48/wg_vpn-stop.png"
- )
- self.imp_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_import.png")
- self.tr_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_trash.png")
- self.exp_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_export.png")
- self.warning_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/64/error.png")
- self.wg_vpn_start = tk.PhotoImage(
- file=r"/usr/share/icons/lx-icons/48/wg_vpn-start.png"
- )
- self.wg_vpn_stop = tk.PhotoImage(
- file=r"/usr/share/icons/lx-icons/48/wg_vpn-stop.png"
- )
+ self.style = ttk.Style()
+ self.wg_vpn_start = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_start"])
+ self.wg_vpn_stop = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_stop"])
+ self.imp_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_import"])
+ self.tr_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_trash"])
+ self.exp_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_export"])
+ self.warning_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_error"])
- def update():
- """
- Set on or off in file
- """
- if set_update.get() == 1:
- with open(wg_set, "r", encoding="utf-8") as set_file2:
- lines2 = set_file2.readlines()
- lines2[1] = "off\n"
- with open(wg_set, "w", encoding="utf-8") as set_file2:
- set_file2.writelines(lines2)
+ # StringVar-Variables initialization
+ self.tooltip_state = tk.BooleanVar()
+ # Get value from configuration
+ state = ConfigManager.get("tooltips")
+ # NOTE: ConfigManager.get("tooltips") can return either a boolean value or a string,
+ # depending on whether the value was loaded from the file (bool) or the default value is used (string).
+ # The expression 'lines[5].strip() == "True"' in ConfigManager.load() converts the string to a boolean.
+ # Convert to boolean and set
+ if isinstance(state, bool):
+ # If it's already a boolean, use directly
+ self.tooltip_state.set(state)
+ else:
+ # If it's a string or something else
+ self.tooltip_state.set(str(state) == "True")
- if set_update.get() == 0:
- with open(wg_set, "r", encoding="utf-8") as set_file2:
- lines2 = set_file2.readlines()
- lines2[1] = "on\n"
- with open(wg_set, "w", encoding="utf-8") as set_file2:
- set_file2.writelines(lines2)
-
- def tooltip():
- """
- Set True or False in file
- """
- if set_tip.get():
- with open(wg_set, "r", encoding="utf-8") as set_file2:
- lines2 = set_file2.readlines()
- lines2[5] = "False\n"
- with open(wg_set, "w", encoding="utf-8") as set_file2:
- set_file2.writelines(lines2)
-
- else:
- with open(wg_set, "r", encoding="utf-8") as set_file2:
- lines2 = set_file2.readlines()
- lines2[5] = "True\n"
- with open(wg_set, "w", encoding="utf-8") as set_file2:
- set_file2.writelines(lines2)
-
- def theme_change_light():
- """
- Set light theme
- """
- if self.tk.call("ttk::style", "theme", "use") == "water-dark":
-
- self.tk.call("set_theme", "light")
- with open(wg_set, "r", encoding="utf-8") as theme_set2:
- lines3 = theme_set2.readlines()
- lines3[3] = "light\n"
- with open(wg_set, "w", encoding="utf-8") as theme_set2:
- theme_set2.writelines(lines3)
- self.color_label()
-
- def theme_change_dark():
- """
- Set dark theme
- """
- if not self.tk.call("ttk::style", "theme", "use") == "water-dark":
-
- self.tk.call("set_theme", "dark")
- with open(wg_set, "r", encoding="utf-8") as theme_set2:
- lines4 = theme_set2.readlines()
- lines4[3] = "dark\n"
- with open(wg_set, "w", encoding="utf-8") as theme_set2:
- theme_set2.writelines(lines4)
- self.color_label()
-
- def info():
- def link_btn():
- webbrowser.open("https://git.ilunix.de/punix/Wire-Py")
-
- # img_w, img_i, w_title, w_txt , txt2, com hand over
- iw = r"/usr/share/icons/lx-icons/48/wg_vpn.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Info")
- msg_t = _(
- "Wire-Py a simple Wireguard Gui for Linux systems.\n\n"
- "Wire-Py is open source software written in Python.\n\n"
- "Email: polunga40@unity-mail.de also likes for donation.\n\n"
- "Use without warranty!\n"
- )
- txt2 = _("Go to Wire-Py git")
- com = link_btn
-
- msg_window(iw, ii, wt, msg_t, txt2, com)
+ self.tooltip_label = (
+ tk.StringVar()
+ ) # StringVar-Variable for tooltip label for view Disabled/Enabled
+ self.tooltip_update_label()
+ self.update_label = tk.StringVar() # StringVar-Variable for update label
+ self.update_tooltip = (
+ tk.StringVar()
+ ) # StringVar-Variable for update tooltip please not remove!
+ self.update_foreground = tk.StringVar(value="red")
# Frame for Menu
self.menu_frame = ttk.Frame(self)
self.menu_frame.configure(relief="flat")
self.menu_frame.grid(column=0, row=0, columnspan=4, sticky="w")
- def version_enter(event):
- """
- The mouse moves into the Version widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, f"Version: {VERSION[2:]}"
- )
-
- def version_leave(_):
- """
- The mouse moves from the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
# App Menu
- self.version_lb = ttk.Label(self.menu_frame, text=VERSION)
+ self.version_lb = ttk.Label(self.menu_frame, text=AppConfig.VERSION)
self.version_lb.config(font=("Ubuntu", 11), foreground="#00c4ff")
self.version_lb.grid(column=0, row=0, rowspan=4, padx=10)
+ Tooltip(
+ self.version_lb, f"Version: {AppConfig.VERSION[2:]}", self.tooltip_state
+ )
+
self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options"))
self.options_btn.grid(column=1, columnspan=1, row=0)
- def sets_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("Click for Settings")
- )
+ Tooltip(self.options_btn, Msg.TTIP["settings"], self.tooltip_state)
- def sets_leave(_):
- """
- The mouse moves from the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.version_lb.bind("", version_enter)
- self.version_lb.bind("", version_leave)
- self.options_btn.bind("", sets_enter)
- self.options_btn.bind("", sets_leave)
-
- set_update = tk.IntVar()
- set_tip = tk.BooleanVar()
+ self.set_update = tk.IntVar()
self.settings = tk.Menu(self, relief="flat")
self.options_btn.configure(menu=self.settings, style="Toolbutton")
self.settings.add_checkbutton(
- label=_("Disable Updates"), command=update, variable=set_update
+ label=_("Disable Updates"),
+ command=lambda: self.update_setting(self.set_update.get()),
+ variable=self.set_update,
)
- self.settings.add_checkbutton(
- label=_("Disable Tooltips"), command=tooltip, variable=set_tip
- )
- self.settings.add_command(label=_("Light"), command=theme_change_light)
- self.settings.add_command(label=_("Dark"), command=theme_change_dark)
+ self.updates_lb = ttk.Label(self.menu_frame, textvariable=self.update_label)
+ self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10)
+ self.updates_lb.grid_remove()
+ self.update_label.trace_add("write", self.update_label_display)
+ self.update_foreground.trace_add("write", self.update_label_display)
+ res = GiteaUpdate.api_down(
+ AppConfig.UPDATE_URL, AppConfig.VERSION, ConfigManager.get("updates")
+ )
+ self.update_ui_for_update(res)
+
+ # Tooltip Menu
+ self.settings.add_command(
+ label=self.tooltip_label.get(), command=self.tooltips_toggle
+ )
+ # Label show dark or light
+ self.theme_label = tk.StringVar()
+ self.update_theme_label()
+ self.settings.add_command(
+ label=self.theme_label.get(), command=self.on_theme_toggle
+ )
+ # Logviewer Menu
+ self.settings.add_command(
+ label="Log Viewer",
+ command=lambda: run(["logviewer", "--modul=wp_app_config"]),
+ )
# About BTN Menu / Label
self.about_btn = ttk.Button(
- self.menu_frame, text=_("About"), style="Toolbutton", command=info
+ self.menu_frame, text=_("About"), style="Toolbutton", command=self.about
)
self.about_btn.grid(column=2, columnspan=2, row=0)
self.readme = tk.Menu(self)
- # Update and Tooltip Label
- self.updates_lb = ttk.Label(self.menu_frame)
- self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10)
-
- # View Checkbox for enable or disable Tooltip
- if WG_TIPS:
- set_tip.set(value=False)
- else:
- set_tip.set(value=True)
-
- # View Checkbox for enable or disable Updates
- if res == "False":
- set_update.set(value=1)
- self.updates_lb.configure(text=_("Update search off"))
-
- def disable_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("Updates you have disabled")
- )
-
- def disable_leave(_):
- """
- The mouse moves from the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.updates_lb.bind("", disable_enter)
- self.updates_lb.bind("", disable_leave)
- elif res == "No Internet Connection!":
- self.updates_lb.configure(text=_("No Server Connection!"), foreground="red")
- elif res == "No Updates":
- self.updates_lb.configure(text=_("No Updates"))
-
- def congratulations_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("Congratulations! Wire-Py is up to date"),
- )
-
- def congratulations_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.updates_lb.bind("", congratulations_enter)
- self.updates_lb.bind("", congratulations_leave)
- else:
- set_update.set(value=0)
- text = f"Update {res} " + _("available!")
-
- # Update BTN Menu
- self.update_btn = ttk.Menubutton(self.menu_frame, text=text)
- self.update_btn.grid(column=4, columnspan=3, row=0, padx=0)
-
- def download_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("Click to download new version")
- )
-
- def download_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.update_btn.bind("", download_enter)
- self.update_btn.bind("", download_leave)
-
- self.download = tk.Menu(self, relief="flat")
-
- self.update_btn.configure(menu=self.download, style="Toolbutton")
- self.download.add_command(
- label=_("Download"),
- command=lambda: GiteaUpdate.download(
- f"https://git.ilunix.de/punix/Wire-Py/archive/{res}.zip",
- r"/usr/share/icons/lx-icons/48/wg_vpn.png",
- r"/usr/share/icons/lx-icons/48/wg_msg.png",
- res,
- ),
- )
-
- # Show active Tunnel
- self.a = Tunnel.active()
+ self.a = Tunnel.get_active()
# Label Frame 1
self.lb_frame_btn_lbox = ttk.Frame(self)
@@ -441,23 +228,12 @@ class FrameWidgets(ttk.Frame):
self.peer.config(font=("Ubuntu", 9))
self.peer.grid(column=0, row=4, sticky="we", padx=130)
- def enable_check_box(_):
- """
- checkbox for enable autostart Tunnel
- """
- Create.files_for_autostart()
- if self.l_box.size() != 0:
- self.wg_autostart.configure(state="normal")
- self.lb_rename.config(state="normal")
- self.lb_rename.delete(0, tk.END)
- self.btn_rename.config(state="normal")
-
# Listbox with Scrollbar
self.l_box = tk.Listbox(self.lb_frame_btn_lbox, selectmode="single")
self.l_box.config(relief="ridge", font=("Ubuntu", 12, "bold"))
self.l_box.grid(column=1, rowspan=4, row=0, sticky="ns")
self.l_box.event_add("<>", "")
- self.l_box.bind("<>", enable_check_box)
+ self.l_box.bind("<>", self.enable_check_box)
self.scrollbar = ttk.Scrollbar(
self.lb_frame_btn_lbox, orient="vertical", command=self.l_box.yview
)
@@ -465,59 +241,18 @@ class FrameWidgets(ttk.Frame):
self.l_box.configure(yscrollcommand=self.scrollbar.set)
# Tunnel List
- self.tl = Tunnel.list()
- for tunnels in self.tl:
- self.l_box.insert("end", tunnels[:-5])
+ self.tl = Tunnel.parse_files_to_dictionary(directory=AppConfig.TEMP_DIR)
+ LxTools.clean_files(AppConfig.TEMP_DIR, file=None)
+ AppConfig.ensure_directories()
+
+ for tunnels, values in self.tl.items():
+ self.l_box.insert("end", tunnels)
self.l_box.update()
- def list_empty_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("You must first import\na Wireguard tunnel"),
- )
-
- def list_empty_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def list_not_empty_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("Select a Tunnel")
- )
-
- def list_not_empty_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- if self.l_box.size() == 0:
- self.l_box.bind("", list_empty_enter)
- self.l_box.bind("", list_empty_leave)
- else:
- self.l_box.bind("", list_not_empty_enter)
- self.l_box.bind("", list_not_empty_leave)
-
# Button Vpn
if self.a != "":
self.stop()
- wg_read = "/tmp/tlecdcwg/" + str(self.a + ".conf")
- with open(wg_read, "r", encoding="utf-8") as file:
- data = Tunnel.con_to_dict(file)
-
- # Address Label
- self.init_and_report(data)
+ self.handle_tunnel_data(self.a, self.tl)
self.show_data()
else:
self.start()
@@ -526,7 +261,7 @@ class FrameWidgets(ttk.Frame):
self.add = tk.StringVar()
self.DNS = tk.StringVar()
self.enp = tk.StringVar()
- self.label_empty()
+ self.reset_fields()
self.show_data()
# Button Import
@@ -538,223 +273,37 @@ class FrameWidgets(ttk.Frame):
)
self.btn_i.grid(column=0, row=1, padx=15, pady=8)
- def imp_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("Click to import a Wireguard Tunnel")
- )
-
- def imp_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.btn_i.bind("", imp_enter)
- self.btn_i.bind("", imp_leave)
-
- def delete():
- """
- delete Wireguard Tunnel
- """
- try:
- self.select_tunnel = self.l_box.curselection()
- select_tl = self.l_box.get(self.select_tunnel[0])
- with open(
- "/tmp/tlecdcwg/" + select_tl + ".conf", "r+", encoding="utf-8"
- ) as file2:
- key = Tunnel.con_to_dict(file2)
- pre_key = key[3]
- check_call(["nmcli", "connection", "delete", select_tl])
- self.l_box.delete(self.select_tunnel[0])
- with open(wg_set, "r", encoding="utf-8") as set_file6:
- lines6 = set_file6.readlines()
- if (
- select_tl == lines6[7].strip()
- and "off\n" not in lines6[7].strip()
- ):
- lines6[7] = "off\n"
- with open(wg_set, "w", encoding="utf-8") as set_file7:
- set_file7.writelines(lines6)
- self.selected_option.set(0)
- self.autoconnect_var.set(_("no Autoconnect"))
- is_encrypt = Path.home() / ".config/wire_py" / str(select_tl + ".dat")
- if is_encrypt.is_file():
- Path.unlink(
- str(Path.home()) + "/.config/wire_py/" + str(select_tl + ".dat")
- )
- Path.unlink(Path("/tmp/tlecdcwg") / str(select_tl + ".conf"))
- with open(
- str(Path.home()) + "/.config/wire_py/keys", "r", encoding="utf-8"
- ) as readfile:
- with open(
- str(Path.home()) + "/.config/wire_py/keys2",
- "w",
- encoding="utf-8",
- ) as writefile:
- for line in readfile:
- if pre_key not in line.strip("\n"):
- writefile.write(line)
- file_one = Path(str(Path.home()) + "/.config/wire_py/keys2")
- file_two = file_one.with_name("keys")
- file_one.replace(file_two)
- self.wg_autostart.configure(state="disabled")
-
- # for disable checkbox when Listbox empty
- def empty_list_start_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("No tunnels to start in the list")
- )
-
- def empty_list_start_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- if self.l_box.size() == 0:
- self.wg_autostart.configure(state="disabled")
- self.lb_rename.configure(state="disabled")
- self.l_box.bind("", list_empty_enter)
- self.l_box.bind("", list_empty_leave)
- self.wg_autostart.bind("", chk_enter)
- self.wg_autostart.bind("", chk_leave)
- self.btn_tr.bind("", empty_list_enter)
- self.btn_tr.bind("", empty_list_leave)
- self.btn_exp.bind("", empty_list_enter)
- self.btn_exp.bind("", empty_list_leave)
- self.btn_stst.bind("", empty_list_start_enter)
- self.btn_stst.bind("", empty_list_start_leave)
- self.lb_rename.bind("", rename_no_active_enter)
- self.lb_rename.bind("", rename_no_active_leave)
- self.lb_rename.insert(0, _("Max. 12 characters!"))
-
- if self.a != "" and self.a == select_tl:
- self.str_var.set(value="")
- self.start()
- self.l_box.update()
-
- # Address Label
- self.add.set("")
- self.DNS.set("")
- self.enp.set("")
- return select_tl
-
- except IndexError:
-
- if self.l_box.size() != 0:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Select tunnel")
- msg_t = _("Please select a tunnel from the list")
- msg_window(iw, ii, wt, msg_t)
-
- else:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Select tunnel")
- msg_t = _("Please first import tunnel")
- msg_window(iw, ii, wt, msg_t)
+ Tooltip(self.btn_i, Msg.TTIP["import_tl"], self.tooltip_state)
# Button Trash
self.btn_tr = ttk.Button(
self.lb_frame_btn_lbox,
image=self.tr_pic,
- command=delete,
+ command=self.delete,
padding=0,
style="CButton.TButton",
)
self.btn_tr.grid(column=0, row=2, padx=15, pady=8)
- def empty_list_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("No tunnels to delete in the list")
- )
-
- def empty_list_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def del_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("Click to delete a Wireguard Tunnel\nSelect from the list!"),
- )
-
- def del_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
if self.l_box.size() == 0:
- self.btn_tr.bind("", empty_list_enter)
- self.btn_tr.bind("", empty_list_leave)
+ Tooltip(self.btn_tr, Msg.TTIP["trash_tl_info"], self.tooltip_state)
else:
- self.btn_tr.bind("", del_enter)
- self.btn_tr.bind("", del_leave)
+ Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tooltip_state)
# Button Export
self.btn_exp = ttk.Button(
- self.lb_frame_btn_lbox, image=self.exp_pic, command=Tunnel.export, padding=0
+ self.lb_frame_btn_lbox,
+ image=self.exp_pic,
+ command=lambda: Tunnel.export(),
+ padding=0,
)
+
self.btn_exp.grid(column=0, row=3, padx=15, pady=8)
- def empty_list_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("No Tunnels in List for Export")
- )
-
- empty_list_leave(_)
-
- def exp_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _(" Click to export all\nWireguard Tunnel to Zipfile"),
- )
-
- def exp_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
if self.l_box.size() == 0:
- self.btn_exp.bind("", empty_list_enter)
- self.btn_exp.bind("", empty_list_leave)
+ Tooltip(self.btn_exp, Msg.TTIP["export_tl_info"], self.tooltip_state)
else:
- self.btn_exp.bind("", exp_enter)
- self.btn_exp.bind("", exp_leave)
+ Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state)
# Label Entry
self.lb_rename = ttk.Entry(self.lb_frame4, width=20)
@@ -762,141 +311,29 @@ class FrameWidgets(ttk.Frame):
self.lb_rename.insert(0, _("Max. 12 characters!"))
self.lb_rename.config(state="disable")
- def rename_no_active_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def rename_no_active_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("To rename a tunnel, at least one must be in the list"),
- )
-
- def rename_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("To rename a tunnel, you need to\nselect a tunnel from the list"),
- )
-
- def rename_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
if self.l_box.size() != 0:
- self.lb_rename.bind("", rename_enter)
- self.lb_rename.bind("", rename_leave)
+ Tooltip(
+ self.lb_rename,
+ Msg.TTIP["rename_tl"],
+ self.tooltip_state,
+ x_offset=-120,
+ y_offset=-70,
+ )
else:
- self.lb_rename.bind("", rename_no_active_enter)
- self.lb_rename.bind("", rename_no_active_leave)
-
- def tl_rename():
-
- special_characters = ["\\", "/", "{", "}", " "]
-
- if len(self.lb_rename.get()) > 12:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Renaming not possible")
- msg_t = _("The new name may contain only 12 characters")
- msg_window(iw, ii, wt, msg_t)
-
- elif len(self.lb_rename.get()) == 0:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Renaming not possible")
- msg_t = _("At least one character must be entered")
- msg_window(iw, ii, wt, msg_t)
-
- elif any(ch in special_characters for ch in self.lb_rename.get()):
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Renaming not possible")
- msg_t = _(
- "No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n"
- )
- msg_window(iw, ii, wt, msg_t)
-
- else:
-
- try:
- self.select_tunnel = self.l_box.curselection()
- select_tl = self.l_box.get(self.select_tunnel[0])
-
- # nmcli connection modify old connection.id iphone
- check_call(
- [
- "nmcli",
- "connection",
- "modify",
- select_tl,
- "connection.id",
- self.lb_rename.get(),
- ]
- )
- source = Path("/tmp/tlecdcwg") / str(select_tl + ".conf")
- destination = source.with_name(str(self.lb_rename.get() + ".conf"))
- source.replace(destination)
- Path.unlink(
- str(Path.home()) + "/.config/wire_py/" + str(select_tl + ".dat")
- )
- self.l_box.delete(self.select_tunnel[0])
- self.l_box.insert("end", self.lb_rename.get())
- self.l_box.update()
- new_a_connect = self.lb_rename.get()
- self.lb_rename.delete(0, tk.END)
- if self.a != "" and self.a == select_tl:
- self.a = Tunnel.active()
- self.str_var.set(value=self.a)
- with open(wg_set, "r", encoding="utf-8") as set_file5:
- lines5 = set_file5.readlines()
- if (
- select_tl == lines5[7].strip()
- and "off\n" not in lines5[7].strip()
- ):
- lines5[7] = new_a_connect
- with open(wg_set, "w", encoding="utf-8") as theme_set5:
- theme_set5.writelines(lines5)
- self.autoconnect_var.set(value=new_a_connect)
-
- Create.encrypt()
-
- return select_tl
-
- except IndexError:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Renaming not possible")
- msg_t = _("Please select a tunnel from the list")
- msg_window(iw, ii, wt, msg_t)
+ Tooltip(
+ self.lb_rename,
+ Msg.TTIP["rename_tl_info"],
+ self.tooltip_state,
+ x_offset=-180,
+ y_offset=-50,
+ )
# Button Rename
self.btn_rename = ttk.Button(
self.lb_frame4,
text=_("Rename"),
state="disable",
- command=tl_rename,
+ command=self.tl_rename,
padding=4,
style="RnButton.TButton",
)
@@ -905,7 +342,7 @@ class FrameWidgets(ttk.Frame):
# Check Buttons
self.selected_option = tk.IntVar()
self.autoconnect_var = tk.StringVar()
- self.autoconnect_var.set(self.auto_con)
+ self.autoconnect_var.set(f"{self.auto_con}")
# Frame for Labels, Entry and Button
self.autoconnect = ttk.Label(
@@ -921,415 +358,500 @@ class FrameWidgets(ttk.Frame):
)
self.wg_autostart.grid(column=0, row=0, pady=15, padx=15, sticky="nw")
- def chk_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _(
- "You must have at least one\n"
- "tunnel in the list,to use the autostart"
+ if self.l_box.size() >= 1 and len(self.l_box.curselection()) >= 1:
+ Tooltip(
+ self.wg_autostart,
+ Msg.TTIP["autostart"],
+ self.tooltip_state,
+ x_offset=-10,
+ y_offset=-40,
+ )
+
+ if self.l_box.size() == 0:
+ Tooltip(
+ self.wg_autostart,
+ Msg.TTIP["autostart_info"],
+ self.tooltip_state,
+ x_offset=30,
+ y_offset=-50,
+ )
+
+ else:
+
+ Tooltip(
+ self.wg_autostart,
+ Msg.TTIP["autostart"],
+ self.tooltip_state,
+ x_offset=-10,
+ y_offset=-40,
+ )
+
+ self.on_off()
+
+ # Method that is called when the variable changes
+ def update_label_display(self, *args):
+ # Set the foreground color
+ self.updates_lb.configure(foreground=self.update_foreground.get())
+
+ # Show or hide the label based on whether it contains text
+ if self.update_label.get():
+ # Make sure the label is in the correct position every time it's shown
+ self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10)
+ else:
+ self.updates_lb.grid_remove()
+
+ # Update the labels based on the result
+ def update_ui_for_update(self, res):
+ """Update UI elements based on an update check result"""
+ # First, remove the update button if it exists to avoid conflicts
+ if hasattr(self, "update_btn"):
+ self.update_btn.grid_forget()
+ delattr(self, "update_btn")
+
+ if res == "False":
+ self.set_update.set(value=1)
+ self.update_label.set(_("Update search off"))
+ self.update_tooltip.set(_("Updates you have disabled"))
+ # Clear the foreground color as requested
+ self.update_foreground.set("")
+ # Set the tooltip for the label
+ Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)
+
+ elif res == "No Internet Connection!":
+ self.update_label.set(_("No Server Connection!"))
+ self.update_foreground.set("red")
+ # Set the tooltip for "No Server Connection"
+ Tooltip(
+ self.updates_lb,
+ _("Could not connect to update server"),
+ self.tooltip_state,
+ )
+
+ elif res == "No Updates":
+ self.update_label.set(_("No Updates"))
+ self.update_tooltip.set(_("Congratulations! Wire-Py is up to date"))
+ self.update_foreground.set("")
+ # Set the tooltip for the label
+ Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)
+
+ else:
+ self.set_update.set(value=0)
+ update_text = f"Update {res} {_('available!')}"
+
+ # Clear the label text since we'll show the button instead
+ self.update_label.set("")
+
+ # Create the update button
+ self.update_btn = ttk.Menubutton(self.menu_frame, text=update_text)
+ self.update_btn.grid(column=4, columnspan=3, row=0, padx=0)
+ Tooltip(
+ self.update_btn, _("Click to download new version"), self.tooltip_state
+ )
+
+ self.download = tk.Menu(self, relief="flat")
+ self.update_btn.configure(menu=self.download, style="Toolbutton")
+ self.download.add_command(
+ label=_("Download"),
+ command=lambda: GiteaUpdate.download(
+ f"{AppConfig.DOWNLOAD_URL}/{res}.zip", res
),
)
- def chk_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
+ @staticmethod
+ def about() -> None:
+ """
+ a tk.Toplevel window
+ """
- if self.l_box.size() >= 1 and len(self.l_box.curselection()) >= 1:
+ def link_btn() -> None:
+ webbrowser.open("https://git.ilunix.de/punix/Wire-Py")
- def chk_a_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("To use the autostart, enable this Checkbox"),
+ msg_t = _(
+ "Wire-Py a simple Wireguard Gui for Linux systems.\n\n"
+ "Wire-Py is open source software written in Python.\n\n"
+ "Email: polunga40@unity-mail.de also likes for donation.\n\n"
+ "Use without warranty!\n"
+ )
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_vpn"],
+ AppConfig.IMAGE_PATHS["icon_vpn"],
+ _("Info"),
+ msg_t,
+ _("Go to Wire-Py git"),
+ link_btn,
+ )
+
+ def update_setting(self, update_res) -> None:
+ """write off or on in file
+ Args:
+ update_res (int): argument that is passed contains 0 or 1
+ """
+ if update_res == 1:
+ # Disable updates
+ ConfigManager.set("updates", "off")
+ # When updates are disabled, we know the result should be "False"
+ self.update_ui_for_update("False")
+ else:
+ # Enable updates
+ ConfigManager.set("updates", "on")
+ # When enabling updates, we need to actually check for updates
+ try:
+ # Force a fresh check by passing "on" as the update setting
+ res = GiteaUpdate.api_down(
+ AppConfig.UPDATE_URL, AppConfig.VERSION, "on"
)
- def chk_a_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
+ # Make sure the UI is updated regardless of the previous state
+ if hasattr(self, "update_btn"):
+ self.update_btn.grid_forget()
+ if hasattr(self, "updates_lb"):
+ self.updates_lb.grid_forget()
- self.wg_autostart.bind("", chk_a_enter)
- self.wg_autostart.bind("", chk_a_leave)
+ # Now update the UI with the fresh result
+ self.update_ui_for_update(res)
+ except Exception as e:
+ logging.error(f"Error checking for updates: {e}", exc_info=True)
+ # Fallback to a default message if there's an error
+ self.update_ui_for_update("No Internet Connection!")
+
+ def tooltip_update_label(self) -> None:
+ """Updates the tooltip menu label based on the current tooltip status"""
+ # Set the menu text based on the current status
+ if self.tooltip_state.get():
+ # If tooltips are enabled, the menu option should be to disable them
+ self.tooltip_label.set(_("Disable Tooltips"))
+ else:
+ # If tooltips are disabled, the menu option should be to enable them
+ self.tooltip_label.set(_("Enable Tooltips"))
+
+ def tooltips_toggle(self):
+ """
+ Toggles the visibility of tooltips (on/off) and updates
+ the corresponding menu label. Inverts the current tooltip state
+ (`self.tooltip_state`), saves the new value in the configuration,
+ and applies the change immediately. Updates the menu entry's label to
+ reflect the new tooltip status (e.g., "Tooltips: On" or "Tooltips: Off").
+ """
+ # Toggle the boolean state
+ new_bool_state = not self.tooltip_state.get()
+ # Save the converted value in the configuration
+ ConfigManager.set("tooltips", str(new_bool_state))
+ # Update the tooltip_state variable for immediate effect
+ self.tooltip_state.set(new_bool_state)
+
+ # Update the menu label
+ self.tooltip_update_label()
+
+ # Update the menu entry - find the correct index
+ # This assumes it's the third item (index 2) in your menu
+ self.settings.entryconfigure(1, label=self.tooltip_label.get())
+
+ def update_theme_label(self) -> None:
+ """Update the theme label based on the current theme"""
+ current_theme = ConfigManager.get("theme")
+ if current_theme == "light":
+ self.theme_label.set(_("Dark"))
+ else:
+ self.theme_label.set(_("Light"))
+
+ def on_theme_toggle(self) -> None:
+ """Toggle between light and dark theme"""
+ current_theme = ConfigManager.get("theme")
+ new_theme = "dark" if current_theme == "light" else "light"
+ ThemeManager.change_theme(self, new_theme, new_theme)
+ self.color_label()
+ self.update_theme_label() # Update the theme label
+ # Update Menulfield
+ self.settings.entryconfigure(2, label=self.theme_label.get())
+
+ def start(self) -> None:
+ """
+ Start Button
+ """
+ self.btn_stst = ttk.Button(
+ self.lb_frame_btn_lbox,
+ image=self.wg_vpn_start,
+ command=lambda: self.wg_switch("start"),
+ padding=0,
+ )
+ self.btn_stst.grid(column=0, row=0, padx=5, pady=8)
if self.l_box.size() == 0:
- self.wg_autostart.bind("", chk_enter)
- self.wg_autostart.bind("", chk_leave)
+ Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state)
else:
+ Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tooltip_state)
- def chk_a_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("To use the autostart, a tunnel must be selected from the list"),
- )
-
- def chk_a_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.wg_autostart.bind("", chk_a_enter)
- self.wg_autostart.bind("", chk_a_leave)
-
- self.on_off()
-
- def import_sl(self):
+ def color_label(self) -> None:
"""
- Import Methode for Wireguard config Files.
- Before importing, it is checked whether PrivateKey and PublicKey are in the file.
- If True then it is checked whether the PreSharedKey is already in the key file
- to avoid an import error so that no double wgconf are imported.
- Thus, tunnels can be renamed without the problems arise.
- If False then the key is written into the file.
- Furthermore, it is checked whether the name is longer than 12 characters.
- If True then the name is automatically shortened to 12 characters
- and then imported.
- If in each case false comes out, a corresponding window comes to
- inform the user that something is wrong.
+ View activ Tunnel in the color green or yellow
"""
+ if ConfigManager.get("theme") == "light":
- Create.dir_and_files()
-
- try:
- filepath = filedialog.askopenfilename(
- initialdir=str(Path.home()),
- title=_("Select Wireguard config File"),
- filetypes=[(_("WG config files"), "*.conf")],
+ self.lb_tunnel = ttk.Label(
+ self, textvariable=self.str_var, foreground="green"
)
- with open(filepath, "r", encoding="utf-8") as file:
- read = file.read()
- path_split = filepath.split("/")
- path_split1 = path_split[-1]
- self.a = Tunnel.active()
+ else:
+ self.lb_tunnel = ttk.Label(
+ self, textvariable=self.str_var, foreground="yellow"
+ )
- if (
- "PrivateKey = " in read
- and "PublicKey = " in read
- and "Endpoint =" in read
- ):
- with open(filepath, "r", encoding="utf-8") as file:
- key = Tunnel.con_to_dict(file)
- pre_key = key[3]
- if len(pre_key) != 0:
- with open(
- str(Path.home()) + "/.config/wire_py/keys",
- "r",
- encoding="utf-8",
- ) as readfile:
- p_key = readfile.readlines()
- if pre_key in p_key or pre_key + "\n" in p_key:
+ self.lb_tunnel.config(font=("Ubuntu", 11, "bold"))
+ self.lb_tunnel.grid(column=2, padx=10, row=1)
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/error.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Import Error")
- msg_t = _(
- "Tunnel already available!\nPlease use another file for import"
- )
- msg_window(iw, ii, wt, msg_t)
+ def stop(self) -> None:
+ """
+ Stop Button
+ """
+ self.btn_stst = ttk.Button(
+ self.lb_frame_btn_lbox,
+ image=self.wg_vpn_stop,
+ command=lambda: self.wg_switch("stop"),
+ padding=0,
+ )
+ self.btn_stst.grid(column=0, row=0, padx=5, pady=8)
- else:
+ Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], self.tooltip_state)
- with open(
- str(Path.home()) + "/.config/wire_py/keys",
- "a",
- encoding="utf-8",
- ) as keyfile:
- keyfile.write(pre_key + "\r")
- if len(path_split1) > 17:
- p1 = shutil.copy(filepath, "/tmp/tlecdcwg/")
- path_split = path_split1[
- len(path_split1) - 17 :
- ]
- os.rename(
- p1, "/tmp/tlecdcwg/" + str(path_split)
- )
- new_conf = "/tmp/tlecdcwg/" + path_split
- if self.a != "":
- check_call(
- [
- "nmcli",
- "connection",
- "down",
- Tunnel.active(),
- ]
- )
- self.label_empty()
+ def reset_fields(self) -> None:
+ """
+ reset data from labels
+ """
+ fields = [self.add, self.DNS, self.enp]
+ for field in fields:
+ field.set("")
- subprocess.check_output(
- [
- "nmcli",
- "connection",
- "import",
- "type",
- "wireguard",
- "file",
- new_conf,
- ],
- text=True,
- )
+ def import_sl(self) -> None:
+ """validity check of wireguard config files"""
- Create.encrypt()
+ AppConfig.ensure_directories()
+ try:
+ filepath = filedialog.askopenfilename(
+ initialdir=f"{Path.home()}",
+ title="Select Wireguard config File",
+ filetypes=[("WG config files", "*.conf")],
+ )
+ data_import, key_name = Tunnel.parse_files_to_dictionary(filepath=filepath)
- else:
- shutil.copy(filepath, "/tmp/tlecdcwg/")
- if self.a != "":
- check_call(
- [
- "nmcli",
- "connection",
- "down",
- Tunnel.active(),
- ]
- )
- self.label_empty()
-
- subprocess.check_output(
- [
- "nmcli",
- "connection",
- "import",
- "type",
- "wireguard",
- "file",
- filepath,
- ],
- text=True,
- )
-
- Create.encrypt()
-
- self.str_var.set("")
- self.a = Tunnel.active()
- self.l_box.insert(0, self.a)
- self.wg_autostart.configure(state="normal")
- self.l_box.selection_clear(0, tk.END)
- self.l_box.update()
- self.l_box.selection_set(0)
-
- def chk_a_enter(event):
- """The mouse moves into the entry widget"""
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _(
- "To use the autostart, enable this "
- "Checkbox"
- ),
- )
-
- def chk_a_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def list_info_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("List of available tunnels"),
- )
-
- def list_info_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def del_enter(event):
- """The mouse moves into the entry widget"""
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _(
- "Click to delete a Wireguard "
- "Tunnel\nSelect from the list!"
- ),
- )
-
- def del_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def exp_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _(
- " Click to export "
- "all\nWireguard Tunnel to Zipfile"
- ),
- )
-
- def exp_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def rename_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _(
- "To rename a tunnel, you need to\n"
- "select a tunnel from the list"
- ),
- )
-
- def rename_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.wg_autostart.bind("", chk_a_enter)
- self.wg_autostart.bind("", chk_a_leave)
- self.l_box.bind("", list_info_enter)
- self.l_box.bind("", list_info_leave)
- self.btn_tr.bind("", del_enter)
- self.btn_tr.bind("", del_leave)
- self.btn_exp.bind("", exp_enter)
- self.btn_exp.bind("", exp_leave)
- self.lb_rename.bind("", rename_enter)
- self.lb_rename.bind("", rename_leave)
- self.lb_rename.insert(0, "Max. 12 characters!")
- self.str_var = tk.StringVar()
- self.str_var.set(self.a)
- self.color_label()
- self.stop()
- wg_read = "/tmp/tlecdcwg/" + str(self.a + ".conf")
- with open(
- wg_read, "r", encoding="utf-8"
- ) as file_for_key:
- data = Tunnel.con_to_dict(file_for_key)
-
- # Address Label
- self.init_and_report(data)
- self.show_data()
- check_call(
- [
- "nmcli",
- "con",
- "mod",
- self.a,
- "connection.autoconnect",
- "no",
- ]
- )
- Path.chmod(wg_read, 0o600)
-
- if ("PrivateKey = " in read) and ("Endpoint = " in read):
- pass
- else:
-
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/error.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Import Error")
- msg_t = _(
- "Oh... no valid Wireguard File!\nPlease select a valid Wireguard File"
+ if CryptoUtil.find_key(f"{data_import[key_name]["PrivateKey"]}="):
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_error"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["imp_err"],
+ Msg.STR["tl_exist"],
)
- msg_window(iw, ii, wt, msg_t)
+ elif not CryptoUtil.is_valid_base64(
+ f"{data_import[key_name]["PrivateKey"]}="
+ ): # 2. Second check: Is it valid Base64?
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_error"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["imp_err"],
+ Msg.STR["invalid_base64"],
+ )
+ else:
+ filepath = Path(filepath)
+ # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters.
+ original_name = filepath.name
+ truncated_name = (
+ original_name[-17:] if len(original_name) > 17 else original_name
+ )
+ import_file = shutil.copy2(
+ filepath, AppConfig.TEMP_DIR / truncated_name
+ )
+ import_file = Path(import_file)
+
+ del data_import[key_name]["PrivateKey"]
+ self.tl.update(data_import)
+
+ if self.a != "":
+ process: CompletedProcess[str] = run(
+ ["nmcli", "connection", "down", self.a],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+ if process.stderr:
+ logging.error(f"{process.stderr}: Code {process.returncode}")
+
+ self.reset_fields()
+
+ process: CompletedProcess[str] = run(
+ [
+ "nmcli",
+ "connection",
+ "import",
+ "type",
+ "wireguard",
+ "file",
+ import_file,
+ ],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+ if process.stderr:
+ logging.error(
+ f"{process.stderr} Code: {process.returncode}", exc_info=True
+ )
+
+ CryptoUtil.encrypt(getpass.getuser())
+ LxTools.clean_files(AppConfig.TEMP_DIR, file=None)
+ AppConfig.ensure_directories()
+ self.str_var.set("")
+ self.a = Tunnel.get_active()
+ self.l_box.insert(0, self.a)
+ self.wg_autostart.configure(state="normal")
+ self.l_box.selection_clear(0, tk.END)
+ self.l_box.update()
+ self.l_box.selection_set(0)
+
+ Tooltip(
+ self.wg_autostart,
+ Msg.TTIP["autostart"],
+ self.tooltip_state,
+ x_offset=-10,
+ y_offset=-40,
+ )
+ Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tooltip_state)
+ Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state)
+ Tooltip(self.btn_rename, Msg.TTIP["rename_tl"], self.tooltip_state)
+
+ self.lb_rename.insert(0, "Max. 12 characters!")
+ self.str_var = tk.StringVar()
+ self.str_var.set(self.a)
+ self.color_label()
+ self.stop()
+ self.handle_tunnel_data(self.a, self.tl)
+ self.show_data()
+ process: CompletedProcess[str] = run(
+ ["nmcli", "con", "mod", self.a, "connection.autoconnect", "no"],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+ if process.stderr:
+ logging.error(process.stderr, exc_info=True)
+
+ if process.returncode == 0:
+ print(f">> {import_file.stem} << autostart is disabled by default")
+
+ except UnboundLocalError:
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_error"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["imp_err"],
+ Msg.STR["no_valid_file"],
+ )
+ except (IsADirectoryError, TypeError, FileNotFoundError):
+ print("File import: abort by user...")
except EOFError as e:
print(e)
- except TypeError as e:
- print(e)
- except FileNotFoundError as e:
- print(e)
- except subprocess.CalledProcessError:
- print("Tunnel exist!")
-
- def box_set(self):
+ def delete(self) -> None:
"""
- This Method will display the autostart label which
- Tunnel is automatically started regardless of the active tunnel.
- The selected tunnel is written into a file to read it after the start of the system.
+ delete Wireguard Tunnel
"""
try:
- select_tunnel = self.l_box.curselection()
- select_tl = self.l_box.get(select_tunnel[0])
+ self.select_tunnel = self.l_box.curselection()
+ select_tl = self.l_box.get(self.select_tunnel[0])
- if self.selected_option.get() == 0:
- with open(wg_set, "r", encoding="utf-8") as set_file3:
- lines3 = set_file3.readlines()
- lines3[7] = "off\n"
- with open(wg_set, "w", encoding="utf-8") as set_file3:
- set_file3.writelines(lines3)
+ process: CompletedProcess[str] = run(
+ ["nmcli", "connection", "delete", select_tl],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
- tl = Tunnel.list()
+ if process.stderr:
+ logging.error(
+ f"{process.stderr} Code: {process.returncode}", exc_info=True
+ )
- if len(tl) == 0:
- self.wg_autostart.configure(state="disabled")
+ self.l_box.delete(self.select_tunnel[0])
+ Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat")
- if self.selected_option.get() >= 1:
- with open(wg_set, "r", encoding="utf-8") as set_file3:
- lines3 = set_file3.readlines()
- lines3[7] = select_tl
- with open(wg_set, "w", encoding="utf-8") as set_file3:
- set_file3.writelines(lines3)
+ if select_tl == ConfigManager.get("autostart"):
+ ConfigManager.set("autostart", "off")
+ self.selected_option.set(0)
+ self.autoconnect_var.set(_("no Autoconnect"))
+
+ self.wg_autostart.configure(state="disabled")
+
+ # for disabling checkbox when Listbox empty
+ if self.l_box.size() == 0:
+ self.wg_autostart.configure(state="disabled")
+ self.lb_rename.configure(state="disabled")
+ Tooltip(
+ self.wg_autostart,
+ Msg.TTIP["autostart_info"],
+ self.tooltip_state,
+ x_offset=30,
+ y_offset=-50,
+ )
+
+ Tooltip(self.btn_exp, Msg.TTIP["export_tl_info"], self.tooltip_state)
+ Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state)
+ Tooltip(self.lb_rename, Msg.TTIP["rename_tl_info"], self.tooltip_state)
+ self.lb_rename.insert(0, _("Max. 12 characters!"))
+
+ if self.a != "" and self.a == select_tl:
+ self.str_var.set(value="")
+ self.start()
+ self.l_box.update()
+ self.reset_fields()
except IndexError:
- self.selected_option.set(1)
- self.on_off()
+ if self.l_box.size() != 0:
- def on_off(self):
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["sel_tl"],
+ Msg.STR["sel_list"],
+ )
+
+ else:
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["sel_tl"],
+ Msg.STR["tl_first"],
+ )
+
+ def enable_check_box(self, _) -> None:
"""
- Here it is checked whether the path to the file is there if not it is created.
+ checkbox for enable autostart Tunnel
+ """
+ AppConfig.get_autostart_content()
+ if self.l_box.size() != 0:
+ self.wg_autostart.configure(state="normal")
+ self.lb_rename.config(state="normal")
+ self.lb_rename.delete(0, tk.END)
+ self.btn_rename.config(state="normal")
+
+ def on_off(self) -> None:
+ """
+ Here it is checked whether the path to the file is there, if not, it is created.
Set (on), the selected tunnel is displayed in the label.
At (off) the label is first emptied then filled with No Autoconnect
"""
- with open(wg_set, "r", encoding="utf-8") as set_file4:
- lines4 = set_file4.readlines()
- if lines4[7] != "off\n":
- print(lines4[7])
+ if ConfigManager.get("autostart") != "off":
self.selected_option.set(1)
self.autoconnect_var.set("")
- self.auto_con = lines4[7]
+ self.auto_con = ConfigManager.get("autostart")
else:
self.selected_option.set(0)
self.auto_con = _("no Autoconnect")
-
self.autoconnect_var.set("")
self.autoconnect_var = tk.StringVar()
self.autoconnect_var.set(self.auto_con)
@@ -1343,33 +865,157 @@ class FrameWidgets(ttk.Frame):
self.autoconnect.config(font=("Ubuntu", 11))
self.autoconnect.grid(column=1, row=0, sticky="e", pady=19)
- def init_and_report(self, data=None):
- """
- Displays the value address, DNS and peer in the labels
- or empty it again
+ def box_set(self) -> None:
"""
+ Configures the autostart for a selected tunnel.
+ This method is called when the user changes the autostart checkbox.
+ It saves the selected tunnel in the configuration file so that it
+ will be automatically connected at system startup.
+
+ If the checkbox is deactivated, 'off' is written to the configuration file
+ to disable the autostart.
+ """
+ try:
+ select_tunnel = self.l_box.curselection()
+ select_tl = self.l_box.get(select_tunnel[0])
+
+ if self.selected_option.get() == 0:
+ ConfigManager.set("autostart", "off")
+
+ if self.l_box.size() == 0:
+ self.wg_autostart.configure(state="disabled")
+
+ if self.selected_option.get() >= 1:
+ ConfigManager.set("autostart", select_tl)
+
+ except IndexError:
+ self.selected_option.set(1)
+
+ self.on_off()
+
+ def tl_rename(self) -> None:
+ """
+ Method to rename a tunnel. Validates input for length,
+ special characters, and duplicate names,
+ performs the renaming via `nmcli` if valid, updates
+ the configuration file in the directory,
+ and adjusts UI elements such as listboxes and labels.
+ """
+ special_characters = ["\\", "/", "{", "}", " "]
+
+ if len(self.lb_rename.get()) > 12:
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["ren_err"],
+ Msg.STR["sign_len"],
+ )
+
+ elif len(self.lb_rename.get()) == 0:
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["ren_err"],
+ Msg.STR["zero_signs"],
+ )
+
+ elif any(ch in special_characters for ch in self.lb_rename.get()):
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["ren_err"],
+ Msg.STR["false_signs"],
+ )
+
+ elif self.lb_rename.get() in [
+ file.stem for file in AppConfig.CONFIG_DIR.glob("*.dat")
+ ]:
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["ren_err"],
+ Msg.STR["is_in_use"],
+ )
+
+ else:
+
+ try:
+ self.select_tunnel = self.l_box.curselection()
+ select_tl = self.l_box.get(self.select_tunnel[0])
+
+ # nmcli connection modify old connection.id iphone
+ process: CompletedProcess[str] = run(
+ [
+ "nmcli",
+ "connection",
+ "modify",
+ select_tl,
+ "connection.id",
+ self.lb_rename.get(),
+ ],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+ if process.stderr:
+ logging.error(
+ f"{process.stderr} Code: {process.returncode}", exc_info=True
+ )
+
+ source = Path(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat")
+ destination = AppConfig.CONFIG_DIR / f"{self.lb_rename.get()}.dat"
+ source.replace(destination)
+ self.tl[self.lb_rename.get()] = self.tl.pop(select_tl)
+ if select_tl == ConfigManager.get("autostart"):
+ ConfigManager.set("autostart", self.lb_rename.get())
+ self.autoconnect_var.set(value=self.lb_rename.get())
+ self.l_box.delete(self.select_tunnel[0])
+ self.l_box.insert("end", self.lb_rename.get())
+ self.l_box.update()
+ self.lb_rename.delete(0, tk.END)
+ self.update_connection_display()
+
+ except IndexError:
+
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["ren_err"],
+ Msg.STR["sel_list"],
+ )
+
+ except EOFError as e:
+ logging.error(e, exc_info=True)
+
+ def handle_tunnel_data(self, active=None, data=None) -> None:
+ """Processes tunnel data from an active connection and updates
+ UI elements like labels with information about address, DNS, and endpoint.
+ """
+ tunnel = active
+ values = data[tunnel]
# Address Label
self.add = tk.StringVar()
- self.add.set(_("Address: ") + data[0])
+ self.add.set(f"Address: {values['Address']}")
self.DNS = tk.StringVar()
- self.DNS.set(" DNS: " + data[1])
+ self.DNS.set(f" DNS: {values['DNS']}")
self.enp = tk.StringVar()
- self.enp.set(_("Endpoint: ") + data[2])
+ self.enp.set(f"Endpoint: {values['Endpoint']}")
- def label_empty(self):
+ def show_data(self) -> None:
"""
- empty labels
+ Displays network-related data (address, DNS, endpoint)
+ in the UI using ttk.Label widgets.
+ Creates three labels for address, DNS, and endpoint with
+ specific styling (color, font), positioning them in a
+ grid layout (`lb_frame` and `lb_frame2`).
+ Each label is linked to a corresponding text variable
+ (`self.add`, `self.DNS`, `self.enp`) for dynamic data updates.
"""
- self.add.set("")
- self.DNS.set("")
- self.enp.set("")
-
- def show_data(self):
- """
- shows data in the label
- """
-
# Address Label
self.address = ttk.Label(
self.lb_frame, textvariable=self.add, foreground="#0071ff"
@@ -1389,214 +1035,121 @@ class FrameWidgets(ttk.Frame):
self.endpoint.grid(column=0, row=8, sticky="w", padx=10, pady=20)
self.endpoint.config(font=("Ubuntu", 9))
- def stop(self):
+ def wg_switch(self, event=None) -> None:
"""
- Stop Button
+ Manages switching between active and inactiveVPN connections.
+ If no tunnel is selected (`self.a == ""`), it starts a new connection
+ with the selected tunnel from the listbox (`l_box`).
+ Otherwise, it stops the current connection and updates
+ tunnel data using `handle_tunnel_data`.
+ Handles errors like `IndexError` by displaying appropriate
+ messages if no items are selected or the listbox is empty.
"""
- self.btn_stst = ttk.Button(
- self.lb_frame_btn_lbox,
- image=self.wg_vpn_stop,
- command=self.wg_switch,
- padding=0,
- )
- self.btn_stst.grid(column=0, row=0, padx=5, pady=8)
-
- def stop_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("Click to stop selected Wireguard Tunnel")
- )
-
- def stop_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- self.btn_stst.bind("", stop_enter)
- self.btn_stst.bind("", stop_leave)
-
- def start(self):
- """
- Start Button
- """
- self.btn_stst = ttk.Button(
- self.lb_frame_btn_lbox,
- image=self.wg_vpn_start,
- command=self.wg_switch,
- padding=0,
- )
- self.btn_stst.grid(column=0, row=0, padx=5, pady=8)
-
- def empty_list_start_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root, event.y_root, _("No tunnels to start in the list")
- )
-
- def empty_list_start_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- def start_enter(event):
- """
- The mouse moves into the entry widget
- """
- window.my_tool_tip = MyToolTip(
- event.x_root,
- event.y_root,
- _("Click to start selected Wireguard Tunnel"),
- )
-
- def start_leave(_):
- """
- The mouse moves into the entry widget
- Remove Tool-Tip
- """
- window.my_tool_tip.destroy()
-
- tl = Tunnel.list()
- if len(tl) == 0:
- self.btn_stst.bind("", empty_list_start_enter)
- self.btn_stst.bind("", empty_list_start_leave)
- else:
- self.btn_stst.bind("", start_enter)
- self.btn_stst.bind("", start_leave)
-
- def color_label(self):
- """
- View activ Tunnel in color green or yellow
- """
-
- with open(wg_set, "r", encoding="utf-8") as read_file:
- lines = read_file.readlines()
- if "light\n" in lines:
- self.lb_tunnel = ttk.Label(
- self, textvariable=self.str_var, foreground="green"
- )
-
- else:
- self.lb_tunnel = ttk.Label(
- self, textvariable=self.str_var, foreground="yellow"
- )
-
- self.lb_tunnel.config(font=("Ubuntu", 11, "bold"))
- self.lb_tunnel.grid(column=2, padx=10, row=1)
-
- def wg_switch(self):
- """
- switch Tunnel method change from labels and buttons
- """
- self.a = Tunnel.active()
try:
if self.a == "":
-
- self.start()
self.select_tunnel = self.l_box.curselection()
select_tl = self.l_box.get(self.select_tunnel[0])
- check_call(["nmcli", "connection", "up", select_tl])
- wg_read = "/tmp/tlecdcwg/" + str(select_tl + ".conf")
- with open(wg_read, "r", encoding="utf-8") as file:
- data = Tunnel.con_to_dict(file)
+ self.handle_connection_state("start", select_tl)
- # Address Label
- self.init_and_report(data)
- self.show_data()
+ else:
- # Button Start/Stop
- self.stop()
- self.a = Tunnel.active()
- self.str_var = tk.StringVar()
- self.str_var.set(self.a)
- self.color_label()
-
- elif self.a != "":
-
- # Button Start/Stop
- self.stop()
- check_call(["nmcli", "connection", "down", self.a])
-
- # Button Start/Stop
- self.start()
- self.a = Tunnel.active()
- self.str_var.set("")
- self.color_label()
-
- # Address Label
- self.add.set("")
- self.DNS.set("")
- self.enp.set("")
- self.show_data()
+ self.handle_tunnel_data(self.a, self.tl)
+ self.handle_connection_state("stop")
except IndexError:
if self.l_box.size() != 0:
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Select tunnel")
- msg_t = _("Please select a tunnel from the list")
- msg_window(iw, ii, wt, msg_t)
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["sel_tl"],
+ Msg.STR["sel_list"],
+ )
else:
- # img_w, img_i, w_title, w_txt hand over
- iw = r"/usr/share/icons/lx-icons/64/info.png"
- ii = r"/usr/share/icons/lx-icons/48/wg_msg.png"
- wt = _("Select tunnel")
- msg_t = _("Please first import tunnel")
- msg_window(iw, ii, wt, msg_t)
+ LxTools.msg_window(
+ AppConfig.IMAGE_PATHS["icon_info"],
+ AppConfig.IMAGE_PATHS["icon_msg"],
+ Msg.STR["sel_tl"],
+ Msg.STR["tl_first"],
+ )
+ def handle_connection_state(self, action: str, tunnel_name: str = None) -> None:
+ """
+ central management for connection states
-class MyToolTip(tk.Toplevel):
- """
- Tooltip Class to view Tooltips
- """
+ Args:
+ action (str): "start", "stop" or "toggle"
+ tunnel_name (str, optional): name of a tunnel for a start-option. defaults to None.
+ """
+ if action == "stop":
+ if self.a:
+ process: CompletedProcess[str] = run(
+ ["nmcli", "connection", "down", self.a],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
- TIP_X_OFFSET = 20
- TIP_Y_OFFSET = 20
- if not WG_TIPS:
- AUTO_CLEAR_TIME = 0
- else:
- AUTO_CLEAR_TIME = 2000 # Millisecond. (1/200 sec.)
+ if process.stderr:
+ logging.error(
+ f"{process.stderr} Code: {process.returncode}", exc_info=True
+ )
- def __init__(self, x_pos, y_pos, message=None, auto_clear=True):
- self.x_pos = x_pos
- self.y_pos = y_pos
- self.message = message
- self.auto_clear = auto_clear
+ self.update_connection_display()
+ self.reset_fields()
+ self.start()
- tk.Toplevel.__init__(self)
- self.overrideredirect(True)
+ elif action == "start":
+ if tunnel_name or self.a:
+ target_tunnel = tunnel_name or self.a
+ process: CompletedProcess[str] = run(
+ ["nmcli", "connection", "up", target_tunnel],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
- self.message_label = ttk.Label(
- self, compound="left", text=self.message, padding=6
- )
- self.message_label.pack()
+ if process.stderr:
+ logging.error(
+ f"{process.stderr} Code: {process.returncode}", exc_info=True
+ )
- self.geometry(
- f"+{self.x_pos + self.TIP_X_OFFSET}+{self.y_pos + self.TIP_X_OFFSET}"
- )
+ self.update_connection_display()
+ self.handle_tunnel_data(self.a, self.tl)
+ self.show_data()
+ self.color_label()
+ self.stop()
- if self.auto_clear:
- self.after(self.AUTO_CLEAR_TIME, self.clear_tip)
+ elif action == "toggle":
+ if self.a:
+ self.handle_connection_state("stop")
+ else:
+ self.handle_connection_state("start")
- def clear_tip(self):
- """Remove Tool-Tip"""
- self.destroy()
+ def update_connection_display(self) -> None:
+ """
+ Updated the display after connection changes
+ """
+ self.a = Tunnel.get_active()
+ if not hasattr(self, "str_var"):
+ self.str_var = tk.StringVar()
+ self.str_var.set(self.a)
+ self.color_label()
+ self.show_data()
if __name__ == "__main__":
+ AppConfig.ensure_directories()
+ AppConfig.create_default_settings()
+ CryptoUtil.decrypt(getpass.getuser(), AppConfig.CONFIG_DIR)
+ _ = AppConfig.setup_translations()
+ LxTools.sigi(AppConfig.TEMP_DIR)
+
window = Wirepy()
+ LogConfig.logger(ConfigManager.get("logfile"))
"""
the hidden files are hidden in Filedialog
"""
@@ -1604,11 +1157,9 @@ if __name__ == "__main__":
window.tk.call("tk_getOpenFile", "-foobarbaz")
except TclError:
pass
-
window.tk.call("set", "::tk::dialog::file::showHiddenBtn", "0")
window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0")
window.mainloop()
-shutil.rmtree(dirname)
-Path.unlink("/tmp/.loguser")
+LxTools.clean_files(AppConfig.TEMP_DIR)
sys.exit(0)
diff --git a/wp_app_config.py b/wp_app_config.py
new file mode 100755
index 0000000..435f9cf
--- /dev/null
+++ b/wp_app_config.py
@@ -0,0 +1,247 @@
+#!/usr/bin/python3
+"""App configuration for Wire-Py"""
+import logging
+import gettext
+import locale
+from pathlib import Path
+from subprocess import CompletedProcess, run
+from typing import Dict, Any
+
+
+class AppConfig:
+ """Central configuration and system setup manager for the Wire-Py application.
+
+ This class serves as a singleton-like container for all global configuration data,
+ including paths, UI settings, localization, versioning, and system-specific resources.
+ It ensures that required directories, files, and services are created and configured
+ before the application starts. Additionally, it provides tools for managing translations,
+ default settings, and autostart functionality to maintain a consistent user experience.
+
+ Key Responsibilities:
+ - Centralizes all configuration values (paths, UI preferences, localization).
+ - Ensures required directories and files exist on startup.
+ - Handles translation setup via `gettext` for multilingual support.
+ - Manages default settings file generation.
+ - Configures autostart services using systemd for user-specific launch behavior.
+
+ This class is used globally across the application to access configuration data
+ consistently and perform system-level setup tasks.
+ """
+
+ # Logging
+ LOG_DIR = Path.home() / ".local/share/lxlogs"
+ Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
+ LOG_FILE_PATH = LOG_DIR / "wirepy.log"
+
+ # Localization
+ APP_NAME: str = "wirepy"
+ LOCALE_DIR: Path = Path("/usr/share/locale/")
+
+ # Base paths
+ BASE_DIR: Path = Path.home()
+ CONFIG_DIR: Path = BASE_DIR / ".config/wire_py"
+ TEMP_DIR: Path = Path("/tmp/tlecdcwg")
+ PUBLICKEY: Path = CONFIG_DIR / "pbwgk.pem"
+
+ # Configuration files
+ SETTINGS_FILE: Path = CONFIG_DIR / "settings"
+ SYSTEMD_USER_FOLDER: Path = Path.home() / ".config/systemd/user"
+ AUTOSTART_SERVICE: Path = Path.home() / ".config/systemd/user/wg_start.service"
+ DEFAULT_SETTINGS: Dict[str, str] = {
+ "# Configuration": "on",
+ "# Theme": "dark",
+ "# Tooltips": True,
+ "# Autostart": "off",
+ "# Logfile": LOG_FILE_PATH,
+ }
+
+ # Updates
+ # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year
+ VERSION: str = "v. 2.04.1725"
+ UPDATE_URL: str = "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases"
+ DOWNLOAD_URL: str = "https://git.ilunix.de/punix/Wire-Py/archive"
+
+ # UI configuration
+ UI_CONFIG: Dict[str, Any] = {
+ "window_title": "Wire-Py",
+ "window_title2": "LogViewer",
+ "window_size": (600, 383),
+ "font_family": "Ubuntu",
+ "font_size": 11,
+ "resizable_window": (False, False),
+ }
+
+ # System-dependent paths
+ SYSTEM_PATHS: Dict[str, Path] = {
+ "ssl_decrypt": "/usr/local/bin/ssl_decrypt.py",
+ "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py",
+ "tcl_path": "/usr/share/TK-Themes",
+ "pkey_path": "/usr/local/etc/ssl/pwgk.pem",
+ }
+
+ # Images and icons paths
+ IMAGE_PATHS: Dict[str, Path] = {
+ "icon_vpn": "/usr/share/icons/lx-icons/48/wg_vpn.png",
+ "icon_msg": "/usr/share/icons/lx-icons/48/wg_msg.png",
+ "icon_import": "/usr/share/icons/lx-icons/48/wg_import.png",
+ "icon_export": "/usr/share/icons/lx-icons/48/wg_export.png",
+ "icon_trash": "/usr/share/icons/lx-icons/48/wg_trash.png",
+ "icon_start": "/usr/share/icons/lx-icons/48/wg_vpn-start.png",
+ "icon_stop": "/usr/share/icons/lx-icons/48/wg_vpn-stop.png",
+ "icon_info": "/usr/share/icons/lx-icons/64/info.png",
+ "icon_error": "/usr/share/icons/lx-icons/64/error.png",
+ "icon_log": "/usr/share/icons/lx-icons/48/log.png",
+ }
+
+ @staticmethod
+ def setup_translations() -> gettext.gettext:
+ """
+ Initialize translations and set the translation function
+ Special method for translating strings in this file
+
+ Returns:
+ The gettext translation function
+ """
+ locale.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR)
+ gettext.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR)
+ gettext.textdomain(AppConfig.APP_NAME)
+ return gettext.gettext
+
+ @classmethod
+ def ensure_directories(cls) -> None:
+ """Ensures that all required directories exist"""
+ if not cls.CONFIG_DIR.exists():
+ cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
+ cls.TEMP_DIR.mkdir(parents=True, exist_ok=True)
+
+ @classmethod
+ def create_default_settings(cls) -> None:
+ """Creates default settings if they don't exist"""
+ if not cls.SETTINGS_FILE.exists():
+ content = "\n".join(
+ f"[{k.upper()}]\n{v}" for k, v in cls.DEFAULT_SETTINGS.items()
+ )
+ cls.SETTINGS_FILE.write_text(content)
+
+ @classmethod
+ def get_autostart_content(cls) -> None:
+ """Returns the content for an autostart service file"""
+ systemd_file: list[str] = [
+ "[Unit]",
+ "Description=Automatic Tunnel Start",
+ "After=network-online.target",
+ "",
+ "[Service]",
+ "Type=oneshot",
+ "ExecStartPre=/bin/sleep 5",
+ "ExecStart=/usr/local/bin/start_wg.py",
+ "",
+ "[Install]",
+ "WantedBy=default.target",
+ ]
+ if not cls.SYSTEMD_USER_FOLDER.exists():
+ cls.SYSTEMD_USER_FOLDER.mkdir(parents=True, exist_ok=True)
+
+ if not cls.AUTOSTART_SERVICE.is_file():
+
+ content = "\n".join([line for line in systemd_file])
+ cls.AUTOSTART_SERVICE.write_text(content)
+
+ process: CompletedProcess[str] = run(
+ ["systemctl", "--user", "enable", "wg_start.service"],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+ if process.stderr:
+ logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True)
+
+ @classmethod
+ def ensure_log(cls) -> None:
+ """Ensures that the log file exists"""
+ if not cls.LOG_FILE_PATH.exists():
+ cls.LOG_FILE_PATH.touch()
+
+
+# here is initializing the class for translation strings
+_ = AppConfig.setup_translations()
+
+
+class Msg:
+ """
+ A utility class that provides centralized access to translated message strings.
+
+ This class contains a dictionary of message strings used throughout the Wire-Py application.
+ All strings are prepared for translation using gettext. The short key names make the code
+ more concise while maintaining readability.
+
+ Attributes:
+ STR (dict): A dictionary mapping short keys to translated message strings.
+ Keys are abbreviated for brevity but remain descriptive.
+
+ Usage:
+ Import this class and access messages using the dictionary:
+ `Msg.STR["sel_tl"]` returns the translated "Select tunnel" message.
+
+ Note:
+ Ensure that gettext translation is properly initialized before
+ accessing these strings to ensure correct localization.
+ """
+
+ STR: Dict[str, str] = {
+ # Strings for messages
+ "sel_tl": _("Select tunnel"),
+ "ren_err": _("Renaming not possible"),
+ "exp_succ": _("Export successful"),
+ "exp_in_home": _("Your zip file is in home directory"),
+ "imp_err": _("Import Error"),
+ "exp_err": _("Export Error"),
+ "exp_try": _("Export failed! Please try again"),
+ "exp_zip": _(
+ "The error occurs because the zipfile module encountered an issue.\n"
+ "Please verify that you have the latest version of WirePy installed.\n"
+ "You can also contact the WirePy developer team to resolve this issue quickly.\n"
+ ),
+ "tl_first": _("Please first import tunnel"),
+ "sel_list": _("Please select a tunnel from the list"),
+ "sign_len": _("The new name may contain only 12 characters"),
+ "zero_signs": _("At least one character must be entered"),
+ "false_signs": _(
+ "No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n"
+ ),
+ "is_in_use": _("The tunnel is already in use"),
+ "no_valid_file": _(
+ "Oh... no valid Wireguard File!\nPlease select a valid Wireguard File"
+ ),
+ "tl_exist": _("Tunnel already available!\nPlease use another file for import"),
+ "invalid_base64": _(
+ "Invalid base64 format!\nPlease use a Config file with valid key."
+ ),
+ }
+ TTIP: Dict[str, str] = {
+ # Strings for Tooltips
+ "settings": _("Click for Settings"),
+ "import_tl": _("Click to import a Wireguard Tunnel"),
+ "start_tl": _("Click to start selected Wireguard Tunnel"),
+ "empty_list": _("No tunnels to start in the list"),
+ "stop_tl": _("Click to stop selected Wireguard Tunnel"),
+ "del_tl": _("Click to delete selected Wireguard Tunnel"),
+ "rename_tl": _(
+ "To rename a tunnel, you need to\nselect a tunnel from the list"
+ ),
+ "export_tl": _(" Click to export all\nWireguard Tunnel to Zipfile"),
+ "trash_tl": _("Click to delete a Wireguard Tunnel\nSelect from the list!"),
+ "autostart": _("To use the autostart, enable this Checkbox"),
+ "autostart_info": _(
+ "You must have at least one\ntunnel in the list,to use the autostart"
+ ),
+ "export_tl_info": _("No Tunnels in List for Export"),
+ "start_tl_info": _("Click to start selected Wireguard Tunnel"),
+ "rename_tl_info": _("To rename a tunnel, at least one must be in the list"),
+ "trash_tl_info": _("No tunnels to delete in the list"),
+ "list_auto_info": _(
+ "To use the autostart, a tunnel must be selected from the list"
+ ),
+ "download": _("Click to download new version"),
+ }