fix message window a path errors

This commit is contained in:
Désiré Werner Menrath 2025-05-07 20:25:09 +02:00
parent c56b42df3e
commit f4a51f0050
7 changed files with 724 additions and 299 deletions

View File

@ -1,7 +1,5 @@
""" Classes Method and Functions for lx Apps """
import gettext
import locale
import os
import shutil
import signal
@ -20,6 +18,7 @@ import requests
# Translate
_ = AppConfig.setup_translations()
class Create:
"""
This class is for the creation of the folders and files
@ -43,7 +42,9 @@ class Create:
else:
sett.touch()
sett.write_text("[UPDATES]\non\n[THEME]\nlight\n[TOOLTIP]\nTrue\n[AUTOSTART ON]\noff\n")
sett.write_text(
"[UPDATES]\non\n[THEME]\nlight\n[TOOLTIP]\nTrue\n[AUTOSTART ON]\noff\n"
)
if AppConfig.KEYS_FILE.exists():
pass
@ -66,9 +67,11 @@ class Create:
else:
wg_ser.touch()
wg_ser.write_text("[Unit]\nDescription=Automatic Tunnel Start\nAfter=network-online.target\n\n[Service]\n"
"Type=oneshot\nExecStartPre=/bin/sleep 5\nExecStart=/usr/local/bin/start_wg.py\n[Install]"
"\nWantedBy=default.target")
wg_ser.write_text(
"[Unit]\nDescription=Automatic Tunnel Start\nAfter=network-online.target\n\n[Service]\n"
"Type=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
@ -85,8 +88,12 @@ class Create:
"""
Starts SSL dencrypt
"""
process: CompletedProcess[str] = subprocess.run(["pkexec", "/usr/local/bin/ssl_decrypt.py"],
stdout=subprocess.PIPE, text=True, check=True)
process: CompletedProcess[str] = subprocess.run(
["pkexec", "/usr/local/bin/ssl_decrypt.py"],
stdout=subprocess.PIPE,
text=True,
check=True,
)
path: Path = Path.home() / ".config/wire_py/"
file_in_path: list[Path] = list(path.rglob("*.dat"))
if file_in_path:
@ -102,8 +109,12 @@ class Create:
"""
Starts SSL encryption
"""
process: CompletedProcess[str] = subprocess.run(["pkexec", "/usr/local/bin/ssl_encrypt.py"],
stdout=subprocess.PIPE, text=True, check=True)
process: CompletedProcess[str] = 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...")
@ -123,40 +134,44 @@ class LxTools(tk.Tk):
def center_window_cross_platform(window, width, height):
"""
Centers a window on the primary monitor in a way that works on both X11 and Wayland
Args:
window: The tkinter window to center
width: Window width
height: Window height
"""
# Calculate the position before showing the window
# First attempt: Try to use GDK if available (works on both X11 and Wayland)
try:
import gi
gi.require_version('Gdk', '3.0')
gi.require_version("Gdk", "3.0")
from gi.repository import Gdk
display = Gdk.Display.get_default()
monitor = display.get_primary_monitor() or display.get_monitor(0)
geometry = monitor.get_geometry()
scale_factor = monitor.get_scale_factor()
# Calculate center position on primary monitor
x = geometry.x + (geometry.width - width // scale_factor) // 2
y = geometry.y + (geometry.height - height // scale_factor) // 2
# Set window geometry
window.geometry(f"{width}x{height}+{x}+{y}")
return
except (ImportError, AttributeError):
pass
# Second attempt: Try xrandr for X11
try:
import subprocess
output = subprocess.check_output(["xrandr", "--query"], universal_newlines=True)
output = subprocess.check_output(
["xrandr", "--query"], universal_newlines=True
)
# Parse the output to find the primary monitor
primary_info = None
for line in output.splitlines():
@ -167,7 +182,7 @@ class LxTools(tk.Tk):
primary_info = part
break
break
if primary_info:
# Parse the geometry: WIDTHxHEIGHT+X+Y
geometry = primary_info.split("+")
@ -176,32 +191,33 @@ class LxTools(tk.Tk):
primary_height = int(dimensions[1])
primary_x = int(geometry[1])
primary_y = int(geometry[2])
# Calculate center position on primary monitor
x = primary_x + (primary_width - width) // 2
y = primary_y + (primary_height - height) // 2
# Set window geometry
window.geometry(f"{width}x{height}+{x}+{y}")
return
except (subprocess.SubprocessError, ImportError, IndexError, ValueError):
pass
# Final fallback: Use standard Tkinter method
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
# Try to make an educated guess for multi-monitor setups
# If screen width is much larger than height, assume multiple monitors side by side
if screen_width > screen_height * 1.8: # Heuristic for detecting multiple monitors
if (
screen_width > screen_height * 1.8
): # Heuristic for detecting multiple monitors
# Assume primary monitor is on the left half
screen_width = screen_width // 2
x = (screen_width - width) // 2
y = (screen_height - height) // 2
window.geometry(f"{width}x{height}+{x}+{y}")
@staticmethod
def get_file_name(path: Path, i: int = 5) -> List[str]:
"""
@ -253,8 +269,14 @@ class LxTools(tk.Tk):
Path.unlink(file)
@staticmethod
def msg_window(image_path: Path, image_path2: Path, w_title: str, w_txt: str, txt2: Optional[str] = None,
com: Optional[str] = None) -> None:
def msg_window(
image_path: Path,
image_path2: Path,
w_title: str,
w_txt: str,
txt2: Optional[str] = None,
com: Optional[str] = None,
) -> None:
"""
Creates message windows
@ -269,29 +291,44 @@ class LxTools(tk.Tk):
msg.resizable(width=False, height=False)
msg.title(w_title)
msg.configure(pady=15, padx=15)
msg.img = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_info"])
msg.i_window = tk.Label(msg, image=msg.img)
# Lade das erste Bild für das Fenster
try:
msg.img = tk.PhotoImage(file=image_path)
msg.i_window = tk.Label(msg, image=msg.img)
except Exception as e:
print(f"Fehler beim Laden des Fensterbildes: {e}")
msg.i_window = tk.Label(msg, text="Bild nicht gefunden")
label: tk.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 = ttk.Button(msg, text=f"{txt2}", command=com, padding=4)
button2: ttk.Button = ttk.Button(
msg, text=f"{txt2}", command=com, padding=4
)
button2.grid(column=0, row=1, sticky="e", columnspan=2)
button: ttk.Button = ttk.Button(msg, text="OK", command=msg.destroy, padding=4)
button: ttk.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 = ttk.Button(msg, text="OK", command=msg.destroy, padding=4)
button: ttk.Button = ttk.Button(
msg, text="OK", command=msg.destroy, padding=4
)
button.grid(column=0, columnspan=2, row=1)
AppConfig.IMAGE_PATHS["icon_vpn"]: tk.PhotoImage = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_vpn"])
msg.iconphoto(True, AppConfig.IMAGE_PATHS["icon_vpn"])
# Lade das Icon für das Fenster
try:
icon = tk.PhotoImage(file=image_path2)
msg.iconphoto(True, icon)
except Exception as e:
print(f"Fehler beim Laden des Fenstericons: {e}")
msg.columnconfigure(0, weight=1)
msg.rowconfigure(0, weight=1)
msg.winfo_toplevel()
@ -317,15 +354,22 @@ class LxTools(tk.Tk):
NoReturn since the function either exits the program or continues execution
"""
signals_to_names_dict: Dict[int, str] = dict((getattr(signal, n), n) for n in dir(signal)
if n.startswith("SIG") and "_" not in n)
signals_to_names_dict: Dict[int, str] = dict(
(getattr(signal, n), n)
for n in dir(signal)
if n.startswith("SIG") and "_" not in n
)
signal_name: str = signals_to_names_dict.get(signum, f"Unnamed signal: {signum}")
signal_name: str = 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: int = 1
print(f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.")
print(
f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}."
)
LxTools.clean_files(file_path, file)
print("Breakdown by user...")
sys.exit(exit_code)
@ -343,7 +387,8 @@ class LxTools(tk.Tk):
class Tunnel:
"""
Class of Methods for Wire-Py
"""
"""
@classmethod
def con_to_dict(cls, file: TextIO) -> Tuple[str, str, str, Optional[str]]:
"""
@ -394,7 +439,11 @@ class Tunnel:
"""
Shows the Active Tunnel
"""
active = (os.popen('nmcli con show --active | grep -iPo "(.*)(wireguard)"').read().split())
active = (
os.popen('nmcli con show --active | grep -iPo "(.*)(wireguard)"')
.read()
.split()
)
if not active:
active = ""
else:
@ -413,8 +462,14 @@ class Tunnel:
return wg_s
@staticmethod
def export(image_path: Path = None, image_path2: Path = None, image_path3: Path = None, image_path4: Path = None,
title: Dict = None, window_msg: Dict = None) -> None:
def export(
image_path: Path = None,
image_path2: Path = None,
image_path3: Path = None,
image_path4: Path = None,
title: Dict = None,
window_msg: Dict = None,
) -> None:
"""
This will export the tunnels.
A zipfile with the current date and time is created
@ -439,15 +494,30 @@ class Tunnel:
with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf:
if len(zf.namelist()) != 0:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_vpn"], Msg.STR["exp_succ"], Msg.STR["exp_in_home"])
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_vpn"],
Msg.STR["exp_succ"],
Msg.STR["exp_in_home"],
)
else:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["exp_err"], Msg.STR["exp_try"])
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["exp_err"],
Msg.STR["exp_try"],
)
else:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["sel_tl"], Msg.STR["tl_first"])
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["sel_tl"],
Msg.STR["tl_first"],
)
except TypeError:
pass
@ -459,15 +529,16 @@ class ConfigManager:
Universal class for managing configuration files with caching.
Can be reused in different projects.
"""
_config = None
_config_file = None
@classmethod
def init(cls, config_file):
"""Initial the Configmanager with the given config file"""
cls._config_file = config_file
cls._config = None # Reset the cache
@classmethod
def load(cls):
"""Load the config file and return the config as dict"""
@ -475,44 +546,45 @@ class ConfigManager:
try:
lines = Path(cls._config_file).read_text(encoding="utf-8").splitlines()
cls._config = {
'updates': lines[1].strip(),
'theme': lines[3].strip(),
'tooltips': lines[5].strip() == "True", # is converted here to boolean!!!
'autostart': lines[7].strip() if len(lines) > 7 else 'off'
"updates": lines[1].strip(),
"theme": lines[3].strip(),
"tooltips": lines[5].strip()
== "True", # is converted here to boolean!!!
"autostart": lines[7].strip() if len(lines) > 7 else "off",
}
except (IndexError, FileNotFoundError):
# DeDefault values in case of error
cls._config = {
'updates': 'on',
'theme': 'light',
'tooltips': "True", # Default Value as string !
'autostart': 'off'
"updates": "on",
"theme": "light",
"tooltips": "True", # Default Value as string !
"autostart": "off",
}
return cls._config
@classmethod
def save(cls):
"""Save the config to the config file"""
if cls._config:
lines = [
'# Configuration\n',
"# Configuration\n",
f"{cls._config['updates']}\n",
'# Theme\n',
"# Theme\n",
f"{cls._config['theme']}\n",
'# Tooltips\n',
"# Tooltips\n",
f"{str(cls._config['tooltips'])}\n",
'# Autostart\n',
f"{cls._config['autostart']}\n"
"# Autostart\n",
f"{cls._config['autostart']}\n",
]
Path(cls._config_file).write_text(''.join(lines), encoding="utf-8")
Path(cls._config_file).write_text("".join(lines), encoding="utf-8")
@classmethod
def set(cls, key, value):
"""Sets a configuration value and saves the change"""
cls.load()
cls._config[key] = value
cls.save()
@classmethod
def get(cls, key, default=None):
"""Returns a configuration value"""
@ -545,42 +617,48 @@ class GiteaUpdate:
update_api_url: Update API URL
version: Current version
update_setting: Update setting from ConfigManager (on/off)
Returns:
New version or status message
"""
# If updates are disabled, return immediately
if update_setting != "on":
return "False"
try:
response: requests.Response = requests.get(update_api_url, timeout=10)
response.raise_for_status() # Raise exception for HTTP errors
response_data = response.json()
if not response_data:
return "No Updates"
latest_version = response_data[0].get("tag_name")
if not latest_version:
return "Invalid API Response"
# Compare versions (strip 'v. ' prefix if present)
current_version = version[3:] if version.startswith("v. ") else version
if current_version != latest_version:
return latest_version
else:
return "No Updates"
except requests.exceptions.RequestException:
return "No Internet Connection!"
except (ValueError, KeyError, IndexError):
return "Invalid API Response"
@staticmethod
def download(urld: str, res: str, image_path: Path = None, image_path2: Path = None, image_path3: Path = None,
image_path4: Path = None) -> None:
def download(
urld: str,
res: str,
image_path: Path = None,
image_path2: Path = None,
image_path3: Path = None,
image_path4: Path = None,
) -> None:
"""
Downloads new version of wirepy
@ -600,35 +678,57 @@ class GiteaUpdate:
wt: str = _("Download Successful")
msg_t: str = _("Your zip file is in home directory")
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_vpn"], wt, msg_t)
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_vpn"],
wt,
msg_t,
)
else:
wt: str = _("Download error")
msg_t: str = _("Download failed! Please try again")
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"], wt, msg_t)
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"],
wt,
msg_t,
)
except subprocess.CalledProcessError:
wt: str = _("Download error")
msg_t: str = _("Download failed! No internet connection!")
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"], wt, msg_t)
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"],
wt,
msg_t,
)
class Tooltip():
class Tooltip:
"""Class for Tooltip
from common_tools.py import Tooltip
example: Tooltip(label, "Show tooltip on label")
example: Tooltip(button, "Show tooltip on button")
example: Tooltip(widget, "Text", state_var=tk.BooleanVar())
example: Tooltip(widget, "Text", state_var=tk.BooleanVar(), x_offset=20, y_offset=10)
info: label and button are parent widgets.
NOTE: When using with state_var, pass the tk.BooleanVar object directly,
NOT its value. For example: use state_var=my_bool_var, NOT state_var=my_bool_var.get()
"""
def __init__(self, widget: Any, text: str, state_var: Optional[tk.BooleanVar] = None,
x_offset: int = 65, y_offset: int = 40) -> None:
def __init__(
self,
widget: Any,
text: str,
state_var: Optional[tk.BooleanVar] = None,
x_offset: int = 65,
y_offset: int = 40,
) -> None:
"""Tooltip Class"""
self.widget: Any = widget
self.text: str = text
@ -636,55 +736,59 @@ class Tooltip():
self.state_var = state_var
self.x_offset = x_offset
self.y_offset = y_offset
# Initial binding based on current state
self.update_bindings()
# Add trace to the state_var if provided
if self.state_var is not None:
self.state_var.trace_add("write", self.update_bindings)
def update_bindings(self, *args) -> None:
"""Updates the bindings based on the current state"""
# Remove existing bindings first
self.widget.unbind("<Enter>")
self.widget.unbind("<Leave>")
# Add new bindings if tooltips are enabled
if self.state_var is None or self.state_var.get():
self.widget.bind("<Enter>", self.show_tooltip)
self.widget.bind("<Leave>", self.hide_tooltip)
def show_tooltip(self, event: Optional[Any] = None) -> None:
"""Shows the tooltip"""
if self.tooltip_window or not self.text:
return
x: int
y: int
cx: int
cy: int
x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() + self.x_offset
y += self.widget.winfo_rooty() + self.y_offset
self.tooltip_window = tw = tk.Toplevel(self.widget)
tw.wm_overrideredirect(True)
tw.wm_geometry(f"+{x}+{y}")
label: tk.Label = tk.Label(
tw, text=self.text, background="lightgreen", foreground="black",
relief="solid", borderwidth=1, padx=5, pady=5
tw,
text=self.text,
background="lightgreen",
foreground="black",
relief="solid",
borderwidth=1,
padx=5,
pady=5,
)
label.grid()
self.tooltip_window.after(2200, lambda: tw.destroy())
def hide_tooltip(self, event: Optional[Any] = None) -> None:
"""Hides the tooltip"""
if self.tooltip_window:
self.tooltip_window.destroy()
self.tooltip_window = None

View File

@ -1,15 +0,0 @@
#!/usr/bin/python3
from pathlib import Path
from subprocess import check_call
from tkinter import filedialog, ttk
from common_tools import Create, LxTools
from wp_app_config import AppConfig, Msg
import gettext
import locale
import os
import shutil
import subprocess
from typing import Optional, Dict, Any, NoReturn, TextIO, Tuple, List
# Translate
_ = AppConfig.setup_translations()

View File

@ -12,11 +12,24 @@ uname: Path = Path("/tmp/.log_user")
log_name = Path(uname).read_text(encoding="utf-8")
keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem")
#PKEYFILE: Path = "/usr/local/etc/ssl/pwgk.pem"
# PKEYFILE: Path = "/usr/local/etc/ssl/pwgk.pem"
if not keyfile.is_file():
check_call(["openssl", "rsa", "-in", AppConfig.SYSTEM_PATHS["pkey_path"], "-out", keyfile, "-outform", "PEM", "-pubout"])
check_call(
[
"openssl",
"rsa",
"-in",
AppConfig.SYSTEM_PATHS["pkey_path"],
"-out",
keyfile,
"-outform",
"PEM",
"-pubout",
]
)
shutil.chown(keyfile, 1000, 1000)
AppConfig.TEMP_DIR2 = f"/home/{log_name}/.config/wire_py/"
@ -29,6 +42,17 @@ if os.path.exists(f"{AppConfig.TEMP_DIR2}pbwgk.pem"):
for detunnels in detl:
tlname2 = f"{detunnels[:-4]}.conf"
extpath = f"{AppConfig.TEMP_DIR}/{tlname2}"
check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", AppConfig.SYSTEM_PATHS["pkey_path"], "-in", detunnels,
"-out", extpath])
check_call(
[
"openssl",
"pkeyutl",
"-decrypt",
"-inkey",
AppConfig.SYSTEM_PATHS["pkey_path"],
"-in",
detunnels,
"-out",
extpath,
]
)
shutil.chown(extpath, 1000, 1000)

View File

@ -8,15 +8,26 @@ from subprocess import check_call
from common_tools import LxTools
from wp_app_config import AppConfig
#uname: Path = Path("/tmp/.log_user")
#log_name = AppConfig.USER_FILE.read_text(encoding="utf-8")
keyfile: Path = Path(f"/home/{AppConfig.USER_FILE.read_text(encoding="utf-8")}/.config/wire_py/pbwgk.pem")
keyfile: Path = Path(
f"/home/{AppConfig.USER_FILE.read_text(encoding="utf-8")}/.config/wire_py/pbwgk.pem"
)
if not keyfile.is_file():
check_call(["openssl", "rsa", "-in", AppConfig.SYSTEM_PATHS["pkey_path"], "-out", keyfile, "-outform", "PEM", "-pubout"])
check_call(
[
"openssl",
"rsa",
"-in",
AppConfig.SYSTEM_PATHS["pkey_path"],
"-out",
keyfile,
"-outform",
"PEM",
"-pubout",
]
)
shutil.chown(keyfile, 1000, 1000)
if AppConfig.TEMP_DIR.exists():
@ -28,8 +39,20 @@ if not keyfile.is_file():
for tunnels in tl:
sourcetl: str = f"{AppConfig.TEMP_DIR}/{tunnels}"
tlname: str = f"{CRYPTFILES}{tunnels[:-5]}.dat"
check_call(["openssl", "pkeyutl", "-encrypt", "-inkey", keyfile, "-pubin", "-in", sourcetl, "-out",
tlname,])
check_call(
[
"openssl",
"pkeyutl",
"-encrypt",
"-inkey",
keyfile,
"-pubin",
"-in",
sourcetl,
"-out",
tlname,
]
)
else:
@ -42,5 +65,17 @@ else:
for tunnels in tl:
sourcetl: str = f"{AppConfig.TEMP_DIR}/{tunnels}"
tlname: str = f"{CRYPTFILES}{tunnels[:-5]}.dat"
check_call(["openssl", "pkeyutl", "-encrypt", "-inkey", keyfile, "-pubin", "-in", sourcetl, "-out",
tlname])
check_call(
[
"openssl",
"pkeyutl",
"-encrypt",
"-inkey",
keyfile,
"-pubin",
"-in",
sourcetl,
"-out",
tlname,
]
)

View File

@ -1,7 +1,8 @@
#!/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

536
wirepy.py
View File

@ -2,8 +2,6 @@
"""
this script is a simple GUI for managing Wireguard Tunnels
"""
import gettext
import locale
import os
import shutil
import subprocess
@ -14,7 +12,15 @@ from pathlib import Path
from subprocess import check_call
from tkinter import TclError, filedialog, ttk
from common_tools import (ConfigManager, ThemeManager, Create, GiteaUpdate, Tunnel, Tooltip, LxTools)
from common_tools import (
ConfigManager,
ThemeManager,
Create,
GiteaUpdate,
Tunnel,
Tooltip,
LxTools,
)
from wp_app_config import AppConfig, Msg
LxTools.uos()
@ -22,26 +28,30 @@ Create.dir_and_files()
Create.make_dir()
Create.decrypt()
class Wirepy(tk.Tk):
"""
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 = 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.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")
@ -57,10 +67,10 @@ class Wirepy(tk.Tk):
# 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)
@ -69,9 +79,10 @@ class FrameWidgets(ttk.Frame):
"""
ttk frame class for better structure
"""
def __init__(self, container, **kwargs):
super().__init__(container, **kwargs)
self.lb_tunnel = None
self.btn_stst = None
self.endpoint = None
@ -100,13 +111,16 @@ class FrameWidgets(ttk.Frame):
else:
# If it's a string or something else
self.tooltip_state.set(str(state) == "True")
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")
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)
@ -118,7 +132,9 @@ class FrameWidgets(ttk.Frame):
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)
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)
@ -128,27 +144,37 @@ class FrameWidgets(ttk.Frame):
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=lambda: self.update_setting(self.set_update.get()), variable=self.set_update)
self.settings.add_checkbutton(
label=_("Disable Updates"),
command=lambda: self.update_setting(self.set_update.get()),
variable=self.set_update,
)
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"))
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.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)
self.update_theme_label()
self.settings.add_command(
label=self.theme_label.get(), command=self.on_theme_toggle
)
# About BTN Menu / Label
self.about_btn = ttk.Button(
self.menu_frame, text=_("About"), style="Toolbutton", command=self.about)
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)
@ -172,7 +198,9 @@ class FrameWidgets(ttk.Frame):
# Bottom Frame 4
self.lb_frame3 = ttk.Frame(self)
self.lb_frame3.configure(relief="flat")
self.lb_frame3.grid(column=0, row=5, columnspan=4, sticky="snew", padx=2, pady=2)
self.lb_frame3.grid(
column=0, row=5, columnspan=4, sticky="snew", padx=2, pady=2
)
# Bottom Frame 5
self.lb_frame4 = ttk.Frame(self)
@ -205,7 +233,9 @@ class FrameWidgets(ttk.Frame):
self.l_box.grid(column=1, rowspan=4, row=0, sticky="ns")
self.l_box.event_add("<<ClickEvent>>", "<Button-1>")
self.l_box.bind("<<ClickEvent>>", self.enable_check_box)
self.scrollbar = ttk.Scrollbar(self.lb_frame_btn_lbox, orient="vertical", command=self.l_box.yview)
self.scrollbar = ttk.Scrollbar(
self.lb_frame_btn_lbox, orient="vertical", command=self.l_box.yview
)
self.scrollbar.grid(column=1, rowspan=4, row=0, sticky="nse")
self.l_box.configure(yscrollcommand=self.scrollbar.set)
@ -230,14 +260,24 @@ class FrameWidgets(ttk.Frame):
self.show_data()
# Button Import
self.btn_i = ttk.Button(self.lb_frame_btn_lbox, image=self.imp_pic, command=self.import_sl, padding=0)
self.btn_i = ttk.Button(
self.lb_frame_btn_lbox,
image=self.imp_pic,
command=self.import_sl,
padding=0,
)
self.btn_i.grid(column=0, row=1, padx=15, pady=8)
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=self.delete, padding=0,
style="CButton.TButton")
self.btn_tr = ttk.Button(
self.lb_frame_btn_lbox,
image=self.tr_pic,
command=self.delete,
padding=0,
style="CButton.TButton",
)
self.btn_tr.grid(column=0, row=2, padx=15, pady=8)
if self.l_box.size() == 0:
@ -246,12 +286,20 @@ class FrameWidgets(ttk.Frame):
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=lambda: Tunnel.export(AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_vpn"], AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["sel_tl"], Msg.STR["tl_first"]), padding=0)
self.btn_exp = ttk.Button(
self.lb_frame_btn_lbox,
image=self.exp_pic,
command=lambda: Tunnel.export(
AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_vpn"],
AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["sel_tl"],
Msg.STR["tl_first"],
),
padding=0,
)
self.btn_exp.grid(column=0, row=3, padx=15, pady=8)
if self.l_box.size() == 0:
@ -266,13 +314,31 @@ class FrameWidgets(ttk.Frame):
self.lb_rename.config(state="disable")
if self.l_box.size() != 0:
Tooltip(self.lb_rename, Msg.TTIP["rename_tl"], self.tooltip_state, x_offset= -120, y_offset=-70)
Tooltip(
self.lb_rename,
Msg.TTIP["rename_tl"],
self.tooltip_state,
x_offset=-120,
y_offset=-70,
)
else:
Tooltip(self.lb_rename, Msg.TTIP["rename_tl_info"], self.tooltip_state, x_offset=-180, y_offset=-50)
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=self.tl_rename,
padding=4, style="RnButton.TButton")
self.btn_rename = ttk.Button(
self.lb_frame4,
text=_("Rename"),
state="disable",
command=self.tl_rename,
padding=4,
style="RnButton.TButton",
)
self.btn_rename.grid(column=3, row=0, padx=5, pady=10, sticky="ne")
# Check Buttons
@ -281,22 +347,46 @@ class FrameWidgets(ttk.Frame):
self.autoconnect_var.set(f"{self.auto_con}")
# Frame for Labels, Entry and Button
self.autoconnect = ttk.Label(self.lb_frame3, textvariable=self.autoconnect_var, width=15)
self.autoconnect = ttk.Label(
self.lb_frame3, textvariable=self.autoconnect_var, width=15
)
self.autoconnect.config(font=("Ubuntu", 11))
self.autoconnect.grid(column=1, row=0, sticky="e", pady=19)
self.wg_autostart = ttk.Checkbutton(self.lb_frame3, text=_("Autoconnect on:"), variable=self.selected_option,
command=self.box_set)
self.wg_autostart = ttk.Checkbutton(
self.lb_frame3,
text=_("Autoconnect on:"),
variable=self.selected_option,
command=self.box_set,
)
self.wg_autostart.grid(column=0, row=0, pady=15, padx=15, sticky="nw")
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)
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)
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)
Tooltip(
self.wg_autostart,
Msg.TTIP["autostart"],
self.tooltip_state,
x_offset=-10,
y_offset=-40,
)
self.on_off()
@ -316,10 +406,10 @@ class FrameWidgets(ttk.Frame):
def update_ui_for_update(self, res):
"""Update UI elements based on update check result"""
# First, remove the update button if it exists to avoid conflicts
if hasattr(self, 'update_btn'):
if hasattr(self, "update_btn"):
self.update_btn.grid_forget()
delattr(self, 'update_btn')
delattr(self, "update_btn")
if res == "False":
self.set_update.set(value=1)
self.update_label.set(_("Update search off"))
@ -328,44 +418,50 @@ class FrameWidgets(ttk.Frame):
self.update_foreground.set("")
# Set 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 tooltip for "No Server Connection"
Tooltip(self.updates_lb, _("Could not connect to update server"), self.tooltip_state)
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 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)
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,
res,
AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_vpn"],
AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"]
)
AppConfig.IMAGE_PATHS["icon_msg"],
),
)
@staticmethod
@ -373,15 +469,26 @@ class FrameWidgets(ttk.Frame):
"""
a tk.Toplevel window
"""
def link_btn() -> str | None:
webbrowser.open("https://git.ilunix.de/punix/Wire-Py")
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")
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,
)
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:
@ -398,14 +505,16 @@ class FrameWidgets(ttk.Frame):
# 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")
res = GiteaUpdate.api_down(
AppConfig.UPDATE_URL, AppConfig.VERSION, "on"
)
# Make sure UI is updated regardless of previous state
if hasattr(self, 'update_btn'):
if hasattr(self, "update_btn"):
self.update_btn.grid_forget()
if hasattr(self, 'updates_lb'):
if hasattr(self, "updates_lb"):
self.updates_lb.grid_forget()
# Now update the UI with the fresh result
self.update_ui_for_update(res)
except Exception as e:
@ -431,10 +540,10 @@ class FrameWidgets(ttk.Frame):
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())
@ -461,8 +570,12 @@ class FrameWidgets(ttk.Frame):
"""
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 = 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)
tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
@ -491,12 +604,16 @@ class FrameWidgets(ttk.Frame):
"""
View activ Tunnel in the color green or yellow
"""
if ConfigManager.get("theme") == "light":
self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green")
if ConfigManager.get("theme") == "light":
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 = 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)
@ -505,8 +622,12 @@ class FrameWidgets(ttk.Frame):
"""
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 = 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)
Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], self.tooltip_state)
@ -525,58 +646,91 @@ class FrameWidgets(ttk.Frame):
Create.dir_and_files()
try:
filepath = filedialog.askopenfilename(
initialdir=f"{Path.home()}",
initialdir=f"{Path.home()}",
title=_("Select Wireguard config File"),
filetypes=[(_("WG config files"), "*.conf")]
filetypes=[(_("WG config files"), "*.conf")],
)
# Überprüfe, ob der Benutzer den Dialog abgebrochen hat
if not filepath:
print("File import: abort by user...")
return
with open(filepath, "r", encoding="utf-8") as file:
read = file.read()
path_split = filepath.split("/")
path_split1 = path_split[-1]
if "PrivateKey = " in read and "PublicKey = " in read and "Endpoint =" in read:
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:
p_key = AppConfig.KEYS_FILE.read_text(encoding="utf-8")
if pre_key in p_key or f"{pre_key}\n" in p_key:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["imp_err"], Msg.STR["tl_exist"])
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["imp_err"],
Msg.STR["tl_exist"],
)
else:
with open(AppConfig.KEYS_FILE, "a", encoding="utf-8") as keyfile:
with open(
AppConfig.KEYS_FILE, "a", encoding="utf-8"
) as keyfile:
keyfile.write(f"{pre_key}\r")
if len(path_split1) > 17:
p1 = shutil.copy(filepath, AppConfig.TEMP_DIR)
path_split = path_split1[len(path_split1) - 17:]
path_split = path_split1[len(path_split1) - 17 :]
os.rename(p1, f"{AppConfig.TEMP_DIR}/{path_split}")
new_conf = f"{AppConfig.TEMP_DIR}/{path_split}"
if self.a != "":
check_call(["nmcli", "connection", "down", self.a])
self.reset_fields()
subprocess.check_output(["nmcli", "connection", "import", "type", "wireguard", "file", new_conf], text=True)
subprocess.check_output(
[
"nmcli",
"connection",
"import",
"type",
"wireguard",
"file",
new_conf,
],
text=True,
)
Create.encrypt()
else:
shutil.copy(filepath, f"{AppConfig.TEMP_DIR}/")
if self.a != "":
check_call(["nmcli", "connection", "down", self.a])
self.reset_fields()
subprocess.check_output(["nmcli", "connection", "import", "type", "wireguard", "file", filepath], text=True)
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)
@ -584,23 +738,45 @@ class FrameWidgets(ttk.Frame):
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.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)
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()
data = self.handle_tunnel_data(self.a)
check_call(["nmcli", "con", "mod", self.a, "connection.autoconnect", "no"])
check_call(
[
"nmcli",
"con",
"mod",
self.a,
"connection.autoconnect",
"no",
]
)
elif ("PrivateKey = " in read) and ("Endpoint = " in read):
pass
else:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["imp_err"], Msg.STR["no_valid_file"])
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_error"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["imp_err"],
Msg.STR["no_valid_file"],
)
except EOFError as e:
print(e)
@ -618,15 +794,16 @@ class FrameWidgets(ttk.Frame):
try:
self.select_tunnel = self.l_box.curselection()
select_tl = self.l_box.get(self.select_tunnel[0])
with open(f"/tmp/tlecdcwg/{select_tl}.conf", "r+", encoding="utf-8") as file2:
with open(
f"/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(AppConfig.SETTINGS_FILE, "r", encoding="utf-8") as set_f6:
lines6 = set_f6.readlines()
if (select_tl == lines6[7].strip()
and "off\n" not in lines6[7].strip()):
if select_tl == lines6[7].strip() and "off\n" not in lines6[7].strip():
lines6[7] = "off\n"
with open(AppConfig.SETTINGS_FILE, "w", encoding="utf-8") as set_f7:
set_f7.writelines(lines6)
@ -637,7 +814,9 @@ class FrameWidgets(ttk.Frame):
Path.unlink(f"{Path.home()}/.config/wire_py/{select_tl}.dat")
Path.unlink(f"/tmp/tlecdcwg/{select_tl}.conf")
with open(AppConfig.KEYS_FILE, "r", encoding="utf-8") as readfile:
with open(f"{Path.home()}/.config/wire_py/keys2", "w", encoding="utf-8") as writefile:
with open(
f"{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)
@ -650,8 +829,13 @@ class FrameWidgets(ttk.Frame):
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.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)
@ -668,11 +852,21 @@ class FrameWidgets(ttk.Frame):
if self.l_box.size() != 0:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["sel_tl"], Msg.STR["sel_list"])
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"])
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:
"""
@ -691,7 +885,11 @@ class FrameWidgets(ttk.Frame):
Set (on), the selected tunnel is displayed in the label.
At (off) the label is first emptied then filled with No Autoconnect
"""
lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True)
lines = (
Path(AppConfig.SETTINGS_FILE)
.read_text(encoding="utf-8")
.splitlines(keepends=True)
)
if lines[7] != "off\n":
print(f"{lines[7]} starts automatically when the system starts.")
@ -707,7 +905,12 @@ class FrameWidgets(ttk.Frame):
self.autoconnect_var = tk.StringVar()
self.autoconnect_var.set(self.auto_con)
self.autoconnect = ttk.Label(self.lb_frame3, textvariable=self.autoconnect_var, foreground="#0071ff", width=15)
self.autoconnect = ttk.Label(
self.lb_frame3,
textvariable=self.autoconnect_var,
foreground="#0071ff",
width=15,
)
self.autoconnect.config(font=("Ubuntu", 11))
self.autoconnect.grid(column=1, row=0, sticky="e", pady=19)
@ -727,9 +930,15 @@ class FrameWidgets(ttk.Frame):
select_tl = self.l_box.get(select_tunnel[0])
if self.selected_option.get() == 0:
lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True)
lines[7] = 'off\n'
Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8")
lines = (
Path(AppConfig.SETTINGS_FILE)
.read_text(encoding="utf-8")
.splitlines(keepends=True)
)
lines[7] = "off\n"
Path(AppConfig.SETTINGS_FILE).write_text(
"".join(lines), encoding="utf-8"
)
tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
@ -737,9 +946,15 @@ class FrameWidgets(ttk.Frame):
self.wg_autostart.configure(state="disabled")
if self.selected_option.get() >= 1:
lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True)
lines = (
Path(AppConfig.SETTINGS_FILE)
.read_text(encoding="utf-8")
.splitlines(keepends=True)
)
lines[7] = select_tl
Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8")
Path(AppConfig.SETTINGS_FILE).write_text(
"".join(lines), encoding="utf-8"
)
except IndexError:
self.selected_option.set(1)
@ -755,19 +970,39 @@ class FrameWidgets(ttk.Frame):
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"])
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"])
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"])
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 name_of_file:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["ren_err"], Msg.STR["is_in_use"])
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["ren_err"],
Msg.STR["is_in_use"],
)
else:
@ -776,7 +1011,17 @@ class FrameWidgets(ttk.Frame):
select_tl = self.l_box.get(self.select_tunnel[0])
# nmcli connection modify old connection.id iphone
subprocess.check_output(["nmcli", "connection", "modify", select_tl, "connection.id", self.lb_rename.get()], text=True)
subprocess.check_output(
[
"nmcli",
"connection",
"modify",
select_tl,
"connection.id",
self.lb_rename.get(),
],
text=True,
)
source = Path(f"/tmp/tlecdcwg/{select_tl}.conf")
destination = source.with_name(f"{self.lb_rename.get()}.conf")
source.replace(destination)
@ -787,12 +1032,14 @@ class FrameWidgets(ttk.Frame):
self.l_box.update()
new_a_connect = self.lb_rename.get()
self.lb_rename.delete(0, tk.END)
with open(AppConfig.SETTINGS_FILE, "r", encoding="utf-8") as set_f5:
lines5 = set_f5.readlines()
if select_tl == lines5[7].strip() and "off\n" not in lines5[7].strip():
lines5[7] = new_a_connect
with open(AppConfig.SETTINGS_FILE, "w", encoding="utf-8") as theme_set5:
with open(
AppConfig.SETTINGS_FILE, "w", encoding="utf-8"
) as theme_set5:
theme_set5.writelines(lines5)
self.autoconnect_var.set(value=new_a_connect)
self.update_connection_display()
@ -800,14 +1047,19 @@ class FrameWidgets(ttk.Frame):
except IndexError:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["ren_err"], Msg.STR["sel_list"])
LxTools.msg_window(
AppConfig.IMAGE_PATHS["icon_info"],
AppConfig.IMAGE_PATHS["icon_msg"],
Msg.STR["ren_err"],
Msg.STR["sel_list"],
)
except subprocess.CalledProcessError:
pass
except EOFError as e:
print(e)
def init_and_report(self, data=None) -> None:
"""
Displays the value address, DNS and peer in the labels
@ -826,7 +1078,9 @@ class FrameWidgets(ttk.Frame):
shows data in the label
"""
# Address Label
self.address = ttk.Label(self.lb_frame, textvariable=self.add, foreground="#0071ff")
self.address = ttk.Label(
self.lb_frame, textvariable=self.add, foreground="#0071ff"
)
self.address.grid(column=0, row=5, sticky="w", padx=10, pady=6)
self.address.config(font=("Ubuntu", 9))
@ -836,7 +1090,9 @@ class FrameWidgets(ttk.Frame):
self.dns.config(font=("Ubuntu", 9))
# Endpoint Label
self.endpoint = ttk.Label(self.lb_frame2, textvariable=self.enp, foreground="#0071ff")
self.endpoint = ttk.Label(
self.lb_frame2, textvariable=self.enp, foreground="#0071ff"
)
self.endpoint.grid(column=0, row=8, sticky="w", padx=10, pady=20)
self.endpoint.config(font=("Ubuntu", 9))
@ -851,7 +1107,7 @@ class FrameWidgets(ttk.Frame):
self.handle_connection_state("start", select_tl)
else:
data = self.handle_tunnel_data(self.a)
if data:
@ -861,11 +1117,21 @@ class FrameWidgets(ttk.Frame):
if self.l_box.size() != 0:
LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_msg"], Msg.STR["sel_tl"], Msg.STR["sel_list"])
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"])
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:
"""

View File

@ -6,9 +6,10 @@ import locale
from pathlib import Path
from typing import Dict, Any
class AppConfig:
"""Central configuration class for Wire-Py application"""
# Localization
APP_NAME: str = "wirepy"
LOCALE_DIR: Path = Path("/usr/share/locale/")
@ -23,32 +24,30 @@ class AppConfig:
SETTINGS_FILE: Path = CONFIG_DIR / "settings"
KEYS_FILE: Path = CONFIG_DIR / "keys"
AUTOSTART_SERVICE: Path = Path.home() / ".config/systemd/user/wg_start.service"
# 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_size": (600, 383),
"font_family": "Ubuntu",
"font_size": 11,
"resizable_window": (False, False)
"resizable_window": (False, False),
}
# System-dependent paths
SYSTEM_PATHS: Dict[str, str]= {
SYSTEM_PATHS: Dict[str, str] = {
"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"
"pkey_path": "/usr/local/etc/ssl/pwgk.pem",
}
# Images and icons paths
IMAGE_PATHS: Dict[str, str] = {
"icon_vpn": "/usr/share/icons/lx-icons/48/wg_vpn.png",
@ -59,16 +58,15 @@ class AppConfig:
"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_error": "/usr/share/icons/lx-icons/64/error.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
"""
@ -82,14 +80,16 @@ class AppConfig:
"""Ensures that all required directories exist"""
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())
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_image_paths(cls) -> Dict[str, Path]:
"""Returns paths to UI images"""
@ -97,14 +97,14 @@ class AppConfig:
"main_icon": cls.SYSTEM_PATHS["image_path"] / "48/wg_vpn.png",
"warning": cls.CONFIG_DIR / "images/warning.png",
"success": cls.CONFIG_DIR / "images/success.png",
"error": cls.CONFIG_DIR / "images/error.png"
"error": cls.CONFIG_DIR / "images/error.png",
}
@classmethod
def get_autostart_content(cls) -> str:
"""Returns the content for the autostart service file"""
return """[Unit]
Description=Automatic Tunnel Start
return """[Unit]Description=Automatic Tunnel Start
After=network-online.target
[Service]
@ -114,29 +114,32 @@ ExecStart=/usr/local/bin/start_wg.py
[Install]
WantedBy=default.target"""
# here is inizialize the class for translate strrings
_ = 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"),
@ -150,31 +153,38 @@ class Msg:
"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"),
"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")
}
"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"),
}
TTIP: Dict[str, str] = {
#Strings for Tooltips
# 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"),
"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"),
"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")
"list_auto_info": _(
"To use the autostart, a tunnel must be selected from the list"
),
"download": _("Click to download new version"),
}