#!/usr/bin/python3 """ this script is a simple GUI for managing Wireguard Tunnels """ import os import shutil import subprocess import sys import tkinter as tk import webbrowser 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 wp_app_config import AppConfig, Msg LxTools.uos() 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.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("<>", "") self.l_box.bind("<>", 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 = LxTools.get_file_name(AppConfig.TEMP_DIR) for tunnels in self.tl: self.l_box.insert("end", tunnels) self.l_box.update() # Button Vpn if self.a != "": self.stop() data = self.handle_tunnel_data(self.a) 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 handle_tunnel_data(self, tunnel_name: str) -> tuple[str, str, str, str | None]: """_summary_ Args: tunnel_name (str): name of a tunnel Returns: tuple[str, str]: tuple with tunnel data """ wg_read = f"/tmp/tlecdcwg/{tunnel_name}.conf" with open(wg_read, "r", encoding="utf-8") as file: data = Tunnel.con_to_dict(file) self.init_and_report(data) self.show_data() return data 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""" Create.dir_and_files() try: filepath = filedialog.askopenfilename( initialdir=f"{Path.home()}", title=_("Select Wireguard config File"), 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 ): 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"], ) else: 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 :] 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, ) 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, ) 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) 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() data = self.handle_tunnel_data(self.a) 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"], ) 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]) 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(): lines6[7] = "off\n" with open(AppConfig.SETTINGS_FILE, "w", encoding="utf-8") as set_f7: set_f7.writelines(lines6) self.selected_option.set(0) self.autoconnect_var.set(_("no Autoconnect")) is_encrypt = Path.home() / f".config/wire_py/{select_tl}.dat" if is_encrypt.is_file(): 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: for line in readfile: if pre_key not in line.strip("\n"): writefile.write(line) file_one = Path(f"{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 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 """ 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") 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 """ 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.") self.selected_option.set(1) self.autoconnect_var.set("") self.auto_con = lines[7] else: self.selected_option.set(0) self.auto_con = _("no Autoconnect") print("Autostart disabled.") 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: 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) if len(tl) == 0: 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[7] = select_tl Path(AppConfig.SETTINGS_FILE).write_text( "".join(lines), encoding="utf-8" ) 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 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) Path.unlink(f"{Path.home()}/.config/wire_py/{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) 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: theme_set5.writelines(lines5) self.autoconnect_var.set(value=new_a_connect) self.update_connection_display() Create.encrypt() 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 init_and_report(self, data=None) -> None: """ Displays the value address, DNS and peer in the labels or empty it again """ # Address Label self.add = tk.StringVar() self.add.set(f"{_("Address: ")}{data[0]}") self.DNS = tk.StringVar() self.DNS.set(f" DNS: {data[1]}") self.enp = tk.StringVar() self.enp.set(f"{_("Endpoint: ")}{data[2]}") 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: data = self.handle_tunnel_data(self.a) if data: 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: check_call(["nmcli", "connection", "down", self.a]) 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 check_call(["nmcli", "connection", "up", target_tunnel]) self.update_connection_display() data = self.handle_tunnel_data(self.a) self.init_and_report(data) 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)