1178 lines
41 KiB
Python
Executable File
1178 lines
41 KiB
Python
Executable File
#!/usr/bin/python3
|
||
"""
|
||
this script is a simple GUI for managing Wireguard Tunnels
|
||
"""
|
||
|
||
import getpass
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
import tkinter as tk
|
||
import webbrowser
|
||
from pathlib import Path
|
||
from subprocess import CompletedProcess
|
||
from tkinter import TclError, filedialog, ttk
|
||
|
||
from common_tools import (
|
||
ConfigManager,
|
||
ThemeManager,
|
||
CryptoUtil,
|
||
GiteaUpdate,
|
||
Tunnel,
|
||
Tooltip,
|
||
LxTools,
|
||
)
|
||
from wp_app_config import AppConfig, Msg
|
||
|
||
AppConfig.USER_FILE.write_text(getpass.getuser())
|
||
AppConfig.ensure_directories()
|
||
AppConfig.create_default_settings()
|
||
CryptoUtil.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.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)
|
||
|
||
# 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):
|
||
"""
|
||
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
|
||
self.dns = None
|
||
self.address = None
|
||
self.auto_con = None
|
||
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"])
|
||
|
||
# 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")
|
||
|
||
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")
|
||
|
||
# App Menu
|
||
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)
|
||
|
||
Tooltip(self.options_btn, Msg.TTIP["settings"], self.tooltip_state)
|
||
|
||
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.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
|
||
)
|
||
|
||
# About BTN Menu / Label
|
||
self.about_btn = ttk.Button(
|
||
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)
|
||
|
||
self.a = Tunnel.active()
|
||
|
||
# Label Frame 1
|
||
self.lb_frame_btn_lbox = ttk.Frame(self)
|
||
self.lb_frame_btn_lbox.configure(relief="flat")
|
||
self.lb_frame_btn_lbox.grid(column=0, rowspan=3, row=1)
|
||
|
||
# Label Frame 2
|
||
self.lb_frame = ttk.Frame(self)
|
||
self.lb_frame.configure(relief="solid")
|
||
self.lb_frame.grid(column=2, row=2, sticky="snew", padx=20, pady=5)
|
||
|
||
# Label Frame 3
|
||
self.lb_frame2 = ttk.Frame(self)
|
||
self.lb_frame2.configure(relief="solid")
|
||
self.lb_frame2.grid(column=2, row=3, sticky="snew", padx=20, pady=5)
|
||
|
||
# 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
|
||
)
|
||
|
||
# Bottom Frame 5
|
||
self.lb_frame4 = ttk.Frame(self)
|
||
self.lb_frame4.configure(relief="flat")
|
||
self.lb_frame4.grid(column=2, row=5, columnspan=3, sticky="e", padx=15)
|
||
|
||
# Show active Label
|
||
self.select_tunnel = None
|
||
self.lb = ttk.Label(self, text=_("Active: "))
|
||
self.lb.config(font=("Ubuntu", 11, "bold"))
|
||
self.lb.grid(column=2, row=1, padx=15, pady=4, sticky="w")
|
||
|
||
# Label to Show active Tunnel
|
||
self.str_var = tk.StringVar(value=self.a)
|
||
self.color_label()
|
||
|
||
# Interface Label
|
||
self.interface = ttk.Label(self.lb_frame, text=_("Interface"))
|
||
self.interface.grid(column=0, row=3, sticky="we", padx=120)
|
||
self.interface.config(font=("Ubuntu", 9))
|
||
|
||
# Peer Label
|
||
self.peer = ttk.Label(self.lb_frame2, text=_("Peer"))
|
||
self.peer.config(font=("Ubuntu", 9))
|
||
self.peer.grid(column=0, row=4, sticky="we", padx=130)
|
||
|
||
# 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("<<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.grid(column=1, rowspan=4, row=0, sticky="nse")
|
||
self.l_box.configure(yscrollcommand=self.scrollbar.set)
|
||
|
||
# Tunnel List
|
||
self.tl = Tunnel.parse_files_to_dictionary(directory=AppConfig.TEMP_DIR)
|
||
LxTools.clean_files(AppConfig.TEMP_DIR, file=None)
|
||
AppConfig.ensure_directories()
|
||
# self.tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
|
||
for tunnels, values in self.tl.items():
|
||
self.l_box.insert("end", tunnels)
|
||
self.l_box.update()
|
||
|
||
# Button Vpn
|
||
if self.a != "":
|
||
self.stop()
|
||
self.handle_tunnel_data(self.a, self.tl)
|
||
self.show_data()
|
||
else:
|
||
self.start()
|
||
|
||
# Address Label
|
||
self.add = tk.StringVar()
|
||
self.DNS = tk.StringVar()
|
||
self.enp = tk.StringVar()
|
||
self.reset_fields()
|
||
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.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.grid(column=0, row=2, padx=15, pady=8)
|
||
|
||
if self.l_box.size() == 0:
|
||
Tooltip(self.btn_tr, Msg.TTIP["trash_tl_info"], self.tooltip_state)
|
||
else:
|
||
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.grid(column=0, row=3, padx=15, pady=8)
|
||
|
||
if self.l_box.size() == 0:
|
||
Tooltip(self.btn_exp, Msg.TTIP["export_tl_info"], self.tooltip_state)
|
||
else:
|
||
Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state)
|
||
|
||
# Label Entry
|
||
self.lb_rename = ttk.Entry(self.lb_frame4, width=20)
|
||
self.lb_rename.grid(column=2, row=0, padx=8, pady=10, sticky="ne")
|
||
self.lb_rename.insert(0, _("Max. 12 characters!"))
|
||
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,
|
||
)
|
||
else:
|
||
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.grid(column=3, row=0, padx=5, pady=10, sticky="ne")
|
||
|
||
# Check Buttons
|
||
self.selected_option = tk.IntVar()
|
||
self.autoconnect_var = tk.StringVar()
|
||
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.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.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,
|
||
)
|
||
|
||
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 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 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,
|
||
)
|
||
|
||
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
|
||
)
|
||
|
||
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,
|
||
AppConfig.IMAGE_PATHS["icon_info"],
|
||
AppConfig.IMAGE_PATHS["icon_vpn"],
|
||
AppConfig.IMAGE_PATHS["icon_error"],
|
||
AppConfig.IMAGE_PATHS["icon_msg"],
|
||
),
|
||
)
|
||
|
||
@staticmethod
|
||
def about() -> None:
|
||
"""
|
||
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"
|
||
)
|
||
|
||
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"
|
||
)
|
||
|
||
# Make sure UI is updated regardless of previous state
|
||
if hasattr(self, "update_btn"):
|
||
self.update_btn.grid_forget()
|
||
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:
|
||
print(f"Error checking for updates: {e}")
|
||
# 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 tooltips on/off and updates the menu label"""
|
||
# 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) -> str:
|
||
"""Update the theme label based on 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)
|
||
|
||
tl = LxTools.get_file_name(AppConfig.TEMP_DIR)
|
||
if len(self.tl) == 0:
|
||
Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state)
|
||
else:
|
||
Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tooltip_state)
|
||
|
||
def color_label(self) -> None:
|
||
"""
|
||
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"
|
||
)
|
||
|
||
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 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)
|
||
|
||
Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], self.tooltip_state)
|
||
|
||
def reset_fields(self) -> None:
|
||
"""
|
||
reset data from labels
|
||
"""
|
||
fields = [self.add, self.DNS, self.enp]
|
||
for field in fields:
|
||
field.set("")
|
||
|
||
def import_sl(self) -> None:
|
||
"""validity check of wireguard config files"""
|
||
|
||
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)
|
||
|
||
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"],
|
||
)
|
||
|
||
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:
|
||
print("Key is valid and does not exist – import allowed!")
|
||
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] = subprocess.run(
|
||
["nmcli", "connection", "down", self.a],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
|
||
if process.stderr:
|
||
print(process.stderr)
|
||
|
||
else:
|
||
print(f"Error process decrypt: Code {process.returncode}")
|
||
self.reset_fields()
|
||
|
||
process: CompletedProcess[str] = subprocess.run(
|
||
[
|
||
"nmcli",
|
||
"connection",
|
||
"import",
|
||
"type",
|
||
"wireguard",
|
||
"file",
|
||
import_file,
|
||
],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
|
||
if process.stderr:
|
||
print(process.stderr)
|
||
|
||
if process.returncode == 0:
|
||
print(f"Tunnel >> {import_file.stem} << import successfull")
|
||
else:
|
||
print(f"Error process decrypt: Code {process.returncode}")
|
||
|
||
CryptoUtil.encrypt()
|
||
LxTools.clean_files(AppConfig.TEMP_DIR, file=None)
|
||
AppConfig.ensure_directories()
|
||
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)
|
||
|
||
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] = subprocess.run(
|
||
["nmcli", "con", "mod", self.a, "connection.autoconnect", "no"],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
|
||
if process.stderr:
|
||
print(process.stderr)
|
||
|
||
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:
|
||
print("File import: abort by user...")
|
||
except EOFError as e:
|
||
print(e)
|
||
except TypeError:
|
||
print("File import: abort by user...")
|
||
except FileNotFoundError:
|
||
print("File import: abort by user...")
|
||
except subprocess.CalledProcessError:
|
||
print("Tunnel exist!")
|
||
|
||
def delete(self) -> None:
|
||
"""
|
||
delete Wireguard Tunnel
|
||
"""
|
||
try:
|
||
self.select_tunnel = self.l_box.curselection()
|
||
select_tl = self.l_box.get(self.select_tunnel[0])
|
||
|
||
process: CompletedProcess[str] = subprocess.run(
|
||
["nmcli", "connection", "delete", select_tl],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
|
||
if process.stderr:
|
||
print(process.stderr)
|
||
|
||
if process.returncode == 0:
|
||
print(f"Tunnel >> {select_tl} << successfully deleted...")
|
||
else:
|
||
print(f"Error process: Code {process.returncode}")
|
||
|
||
self.l_box.delete(self.select_tunnel[0])
|
||
Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat")
|
||
|
||
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:
|
||
|
||
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"],
|
||
)
|
||
|
||
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:
|
||
"""
|
||
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
|
||
"""
|
||
|
||
if ConfigManager.get("autostart") != "off":
|
||
print(
|
||
f"{ConfigManager.get("autostart")} starts automatically when the system starts."
|
||
)
|
||
self.selected_option.set(1)
|
||
self.autoconnect_var.set("")
|
||
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)
|
||
|
||
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)
|
||
|
||
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")
|
||
|
||
tl = [f"{file}" for file in AppConfig.CONFIG_DIR.glob("*.dat")]
|
||
|
||
if len(tl) == 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
|
||
"""
|
||
name_of_file = LxTools.get_file_name(AppConfig.TEMP_DIR)
|
||
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 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"],
|
||
)
|
||
|
||
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] = subprocess.run(
|
||
[
|
||
"nmcli",
|
||
"connection",
|
||
"modify",
|
||
select_tl,
|
||
"connection.id",
|
||
self.lb_rename.get(),
|
||
],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
if process.stderr:
|
||
print(process.stderr)
|
||
|
||
if process.returncode != 0:
|
||
print(f"Error process: Code {process.returncode}")
|
||
|
||
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 subprocess.CalledProcessError:
|
||
pass
|
||
|
||
except EOFError as e:
|
||
print(e)
|
||
|
||
def handle_tunnel_data(self, active=None, data=None) -> None:
|
||
|
||
tunnel = active
|
||
values = data[tunnel]
|
||
# Address Label
|
||
self.add = tk.StringVar()
|
||
self.add.set(f"Address: {values['Address']}")
|
||
self.DNS = tk.StringVar()
|
||
self.DNS.set(f" DNS: {values['DNS']}")
|
||
self.enp = tk.StringVar()
|
||
self.enp.set(f"Endpoint: {values['Endpoint']}")
|
||
|
||
def show_data(self) -> None:
|
||
"""
|
||
shows data in the label
|
||
"""
|
||
# Address Label
|
||
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))
|
||
|
||
# DNS Label
|
||
self.dns = ttk.Label(self.lb_frame, textvariable=self.DNS, foreground="#0071ff")
|
||
self.dns.grid(column=0, row=7, sticky="w", padx=10, pady=6)
|
||
self.dns.config(font=("Ubuntu", 9))
|
||
|
||
# Endpoint Label
|
||
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))
|
||
|
||
def wg_switch(self, event=None) -> None:
|
||
"""
|
||
Deals with switching the VPN connection
|
||
"""
|
||
try:
|
||
if self.a == "":
|
||
self.select_tunnel = self.l_box.curselection()
|
||
select_tl = self.l_box.get(self.select_tunnel[0])
|
||
self.handle_connection_state("start", select_tl)
|
||
|
||
else:
|
||
|
||
self.handle_tunnel_data(self.a, self.tl)
|
||
self.handle_connection_state("stop")
|
||
|
||
except IndexError:
|
||
|
||
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"],
|
||
)
|
||
|
||
else:
|
||
|
||
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
|
||
|
||
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] = subprocess.run(
|
||
["nmcli", "connection", "down", self.a],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
|
||
if process.stderr:
|
||
print(process.stderr)
|
||
|
||
if process.returncode != 0:
|
||
print(f"Error process: Code {process.returncode}")
|
||
|
||
self.update_connection_display()
|
||
self.reset_fields()
|
||
self.start()
|
||
|
||
elif action == "start":
|
||
if tunnel_name or self.a:
|
||
target_tunnel = tunnel_name or self.a
|
||
process: CompletedProcess[str] = subprocess.run(
|
||
["nmcli", "connection", "up", target_tunnel],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
|
||
if process.stderr:
|
||
print(process.stderr)
|
||
|
||
if process.returncode == 0:
|
||
print(f"Tunnel >> {target_tunnel} << started")
|
||
else:
|
||
print(f"Error process: Code {process.returncode}")
|
||
|
||
self.update_connection_display()
|
||
self.handle_tunnel_data(self.a, self.tl)
|
||
self.show_data()
|
||
self.color_label()
|
||
self.stop()
|
||
|
||
elif action == "toggle":
|
||
if self.a:
|
||
self.handle_connection_state("stop")
|
||
else:
|
||
self.handle_connection_state("start")
|
||
|
||
def update_connection_display(self) -> None:
|
||
"""
|
||
Updated the display after connection changes
|
||
"""
|
||
self.a = Tunnel.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.setup_translations()
|
||
LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE)
|
||
window = Wirepy()
|
||
"""
|
||
the hidden files are hidden in Filedialog
|
||
"""
|
||
try:
|
||
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()
|
||
|
||
LxTools.clean_files(AppConfig.TEMP_DIR, AppConfig.USER_FILE)
|
||
sys.exit(0)
|