diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index a8e736c..3e3e5a9 100644 Binary files a/__pycache__/cls_mth_fc.cpython-312.pyc and b/__pycache__/cls_mth_fc.cpython-312.pyc differ diff --git a/__pycache__/wp_app_config.cpython-312.pyc b/__pycache__/wp_app_config.cpython-312.pyc index e4df3cb..92aa7ca 100644 Binary files a/__pycache__/wp_app_config.cpython-312.pyc and b/__pycache__/wp_app_config.cpython-312.pyc differ diff --git a/cls_mth_fc.py b/cls_mth_fc.py index a184d87..6869907 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -14,16 +14,11 @@ from datetime import datetime from pathlib import Path from subprocess import check_call, CompletedProcess from tkinter import ttk, Toplevel -from wp_app_config import AppConfig +from wp_app_config import AppConfig, Msg import requests # Translate -AppConfig.APP_NAME -locale.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) -gettext.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) -gettext.textdomain(AppConfig.APP_NAME) -_ = gettext.gettext - +_ = AppConfig.setup_translations() class Create: """ @@ -80,7 +75,6 @@ class Create: def make_dir() -> None: """Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard""" - AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/") if AppConfig.TEMP_DIR.exists(): pass else: @@ -125,6 +119,31 @@ class LxTools(tk.Tk): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) + @staticmethod + def get_file_name(path: Path, i: int = 5) -> List[str]: + """ + Recursively searches the specified path for files and returns a list of filenames, + with the last 'i' characters of each filename removed. + + This method is useful for obtaining filenames without specific file extensions, + e.g., to remove '.conf' from Wireguard configuration files. + + Args: + path (Path): The directory path to search + i (int, optional): Number of characters to remove from the end of each filename. + Default is 5, which typically corresponds to the length of '.conf'. + + Returns: + List[str]: A list of filenames without the last 'i' characters + + Example: + If path contains files like 'tunnel1.conf', 'tunnel2.conf' and i=5, + the method returns ['tunnel1', 'tunnel2']. + """ + lists_file = list(path.rglob("*")) + lists_file = [conf_file.name[:-i] for conf_file in lists_file] + return lists_file + @staticmethod def uos() -> None: """ @@ -432,17 +451,15 @@ class Tunnel: with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: if len(zf.namelist()) != 0: - msg_t: str = _("Your zip file is in home directory") - LxTools.msg_window(img_w, img_i, _("Export Successful"), msg_t) + LxTools.msg_window(img_w, img_i, Msg.STR["exp_succ"], Msg.STR["exp_in_home"]) else: - msg_t: str = _("Export failed! Please try again") - LxTools.msg_window(img_w2, img_i2, _("Export error"), msg_t) + LxTools.msg_window(img_w2, img_i2, Msg.STR["exp_err"], Msg.STR["exp_try"]) else: - LxTools.msg_window(img_w, img_i2, sl, pfit) + LxTools.msg_window(img_w, img_i2, Msg.STR["sel_tl"], Msg.STR["tl_first"]) except TypeError: pass @@ -492,6 +509,7 @@ class Tooltip: label: tk.Label = tk.Label(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: """ diff --git a/wirepy.py b/wirepy.py index 46fff58..144ac7e 100755 --- a/wirepy.py +++ b/wirepy.py @@ -15,7 +15,7 @@ from subprocess import check_call from tkinter import TclError, filedialog, ttk from cls_mth_fc import (Create, GiteaUpdate, Tooltip, Tunnel, LxTools) -from wp_app_config import AppConfig +from wp_app_config import AppConfig, Msg LxTools.uos() Create.dir_and_files() @@ -24,28 +24,18 @@ Create.decrypt() tips = LxTools.if_tip(AppConfig.SETTINGS_FILE) - # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year VERSION: str = "v. 2.04.1725" res = GiteaUpdate.api_down("https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, AppConfig.SETTINGS_FILE) # Translate -AppConfig.APP_NAME -locale.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) -gettext.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) -gettext.textdomain(AppConfig.APP_NAME) -_ = gettext.gettext +_ = AppConfig.setup_translations() img_w: str = r"/usr/share/icons/lx-icons/64/info.png" img_i: str = r"/usr/share/icons/lx-icons/48/wg_vpn.png" img_w2: str = r"/usr/share/icons/lx-icons/64/error.png" img_i2: str = r"/usr/share/icons/lx-icons/48/wg_msg.png" -sl: str = _("Select tunnel") -rnp: str = _("Renaming not possible") -ie:str = _("Import Error") -pfit: str = _("Please first import tunnel") -pstl: str = _("Please select a tunnel from the list") LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE) @@ -71,7 +61,7 @@ class Wirepy(tk.Tk): LxTools.theme_change(self) # Load the image file from the disk - self.wg_icon = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn.png") + self.wg_icon = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_vpn"]) # Set it as the window icon self.iconphoto(True, self.wg_icon) @@ -92,12 +82,12 @@ class FrameWidgets(ttk.Frame): self.dns = None self.address = None self.auto_con = None - self.wg_vpn_start = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn-start.png") - self.wg_vpn_stop = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn-stop.png") - self.imp_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_import.png") - self.tr_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_trash.png") - self.exp_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_export.png") - self.warning_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/64/error.png") + self.wg_vpn_start = tk.PhotoImage(file=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"]) # Frame for Menu self.menu_frame = ttk.Frame(self) @@ -271,7 +261,8 @@ class FrameWidgets(ttk.Frame): # Button Export self.btn_exp = ttk.Button(self.lb_frame_btn_lbox, image=self.exp_pic, - command=lambda: Tunnel.export(img_w, img_i, img_w2, img_i2, sl, pfit), padding=0) + command=lambda: Tunnel.export(img_w, img_i, img_w2, img_i2, + 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: @@ -309,14 +300,14 @@ class FrameWidgets(ttk.Frame): 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, _("To use the autostart, enable this Checkbox"), tips) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], tips) if self.l_box.size() == 0: - Tooltip(self.wg_autostart, _("You must have at least one\ntunnel in the list,to use the autostart"), tips) + Tooltip(self.wg_autostart, Msg.TTIP["autostart_info"], tips) else: - Tooltip(self.wg_autostart, _("To use the autostart, a tunnel must be selected from the list"), tips) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], tips) self.on_off() @@ -459,30 +450,34 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() != 0: - LxTools.msg_window(img_w, img_i2, sl, pstl) + LxTools.msg_window(img_w, img_i2, Msg.STR["sel_tl"], Msg.STR["sel_list"]) else: - LxTools.msg_window(img_w, img_i2, sl, pfit) + LxTools.msg_window(img_w, img_i2, Msg.STR["sel_tl"], Msg.STR["tl_first"]) 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(img_w, img_i2, rnp, _("The new name may contain only 12 characters")) + LxTools.msg_window(img_w, img_i2, Msg.STR["ren_err"], Msg.STR["sign_len"]) elif len(self.lb_rename.get()) == 0: - LxTools.msg_window(img_w, img_i2, rnp, _("At least one character must be entered")) + LxTools.msg_window(img_w, img_i2, Msg.STR["ren_err"], Msg.STR["zero_signs"]) elif any(ch in special_characters for ch in self.lb_rename.get()): - msg_t = _("No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n") - LxTools.msg_window(img_w, img_i2, rnp, msg_t) + LxTools.msg_window(img_w, img_i2, Msg.STR["ren_err"], Msg.STR["false_signs"]) + + elif self.lb_rename.get() in name_of_file: + + LxTools.msg_window(img_w, img_i2, Msg.STR["ren_err"], Msg.STR["is_in_use"]) else: @@ -491,7 +486,7 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(self.select_tunnel[0]) # nmcli connection modify old connection.id iphone - check_call(["nmcli", "connection", "modify", select_tl, "connection.id", self.lb_rename.get()]) + 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) @@ -515,11 +510,14 @@ class FrameWidgets(ttk.Frame): except IndexError: - LxTools.msg_window(img_w, img_i2, rnp, pstl) + LxTools.msg_window(img_w, img_i2, Msg.STR["ren_err"], Msg.STR["sel_list"]) except subprocess.CalledProcessError: pass + except EOFError as e: + print(e) + def import_sl(self) -> None: """ validity check of wireguard config files @@ -543,18 +541,17 @@ class FrameWidgets(ttk.Frame): p_key = AppConfig.KEYS_FILE.read_text(encoding="utf-8") if pre_key in p_key or f"{pre_key}\n" in p_key: - msg_t = _("Tunnel already available!\nPlease use another file for import") - LxTools.msg_window(img_w2, img_i2, ie, msg_t) + LxTools.msg_window(img_w2, img_i2, 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, "/tmp/tlecdcwg/") + p1 = shutil.copy(filepath, AppConfig.TEMP_DIR) path_split = path_split1[len(path_split1) - 17:] - os.rename(p1, f"/tmp/tlecdcwg/{path_split}") - new_conf = f"/tmp/tlecdcwg/{path_split}" + 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() @@ -565,7 +562,7 @@ class FrameWidgets(ttk.Frame): Create.encrypt() else: - shutil.copy(filepath, "/tmp/tlecdcwg/") + shutil.copy(filepath, f"{AppConfig.TEMP_DIR}/") if self.a != "": check_call(["nmcli", "connection", "down", self.a]) self.reset_fields() @@ -583,16 +580,13 @@ class FrameWidgets(ttk.Frame): self.l_box.update() self.l_box.selection_set(0) - Tooltip(self.wg_autostart, _("To use the autostart, enable this Checkbox"), tips) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], tips) - Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!") - , tips,) + Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], tips) - Tooltip(self.btn_exp, _(" Click to export all\nWireguard Tunnel to Zipfile") - , tips) + Tooltip(self.btn_exp, Msg.TTIP["export_tl"], tips) - Tooltip(self.btn_rename, _("To rename a tunnel, you need to\nselect a tunnel from" - " the list"), tips) + Tooltip(self.btn_rename, Msg.TTIP["rename_tl"], tips) self.lb_rename.insert(0, "Max. 12 characters!") self.str_var = tk.StringVar() @@ -606,8 +600,7 @@ class FrameWidgets(ttk.Frame): pass else: - msg_t = _("Oh... no valid Wireguard File!\nPlease select a valid Wireguard File") - LxTools.msg_window(img_w2, img_i2, ie, msg_t) + LxTools.msg_window(img_w2, img_i2, Msg.STR["imp_err"], Msg.STR["no_valid_file"]) except EOFError as e: print(e) @@ -738,7 +731,7 @@ class FrameWidgets(ttk.Frame): command=lambda: self.wg_switch("stop"), padding=0) self.btn_stst.grid(column=0, row=0, padx=5, pady=8) - Tooltip(self.btn_stst, _("Click to stop selected Wireguard Tunnel"), tips) + Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], tips) def start(self) -> None: """ @@ -750,9 +743,9 @@ class FrameWidgets(ttk.Frame): tl = Tunnel.list() if len(tl) == 0: - Tooltip(self.btn_stst, _("No tunnels to start in the list"), tips) + Tooltip(self.btn_stst, Msg.TTIP["empty_list"], tips) else: - Tooltip(self.btn_stst, _("Click to start selected Wireguard Tunnel"), tips) + Tooltip(self.btn_stst, Msg.TTIP["start_tl"], tips) def color_label(self) -> None: """ @@ -789,11 +782,11 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() != 0: - LxTools.msg_window(img_w, img_i2, sl, pstl) + LxTools.msg_window(img_w, img_i2, Msg.STR["sel_tl"], Msg.STR["sel_list"]) else: - LxTools.msg_window(img_w, img_i2, sl, pfit) + LxTools.msg_window(img_w, img_i2, Msg.STR["sel_tl"], Msg.STR["tl_first"]) def handle_connection_state(self, action: str, tunnel_name: str = None) -> None: """ diff --git a/wp_app_config.py b/wp_app_config.py index 3485c59..fc6dbc5 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -1,26 +1,31 @@ +#!/usr/bin/python3 +"""App configuration for Wire-Py""" + +import gettext +import locale from pathlib import Path from typing import Dict, Any class AppConfig: - """Central configuration class for the application""" - - # Base paths - BASE_DIR = Path.home() - CONFIG_DIR = BASE_DIR / ".config/wire_py" - TEMP_DIR = Path("/tmp/tlecdcwg") - USER_FILE = Path("/tmp/.log_user") - - # Configuration files - SETTINGS_FILE = CONFIG_DIR / "settings" - KEYS_FILE = CONFIG_DIR / "keys" - AUTOSTART_SERVICE = Path.home() / ".config/systemd/user/wg_start.service" + """Central configuration class for Wire-Py application""" # Localization - APP_NAME = "wirepy" - LOCALE_DIR = "/usr/share/locale/" + APP_NAME: str = "wirepy" + LOCALE_DIR: Path = Path("/usr/share/locale/") + + # Base paths + BASE_DIR: Path = Path.home() + CONFIG_DIR: Path = BASE_DIR / ".config/wire_py" + TEMP_DIR: Path = Path("/tmp/tlecdcwg") + USER_FILE: Path = Path("/tmp/.log_user") + + # Configuration files + SETTINGS_FILE: Path = CONFIG_DIR / "settings" + KEYS_FILE: Path = CONFIG_DIR / "keys" + AUTOSTART_SERVICE: Path = Path.home() / ".config/systemd/user/wg_start.service" # Default settings - DEFAULT_SETTINGS = { + DEFAULT_SETTINGS: Dict[str, Any] = { "updates": "on", "theme": "light", "tooltip": True, @@ -28,7 +33,7 @@ class AppConfig: } # UI configuration - UI_CONFIG = { + UI_CONFIG: Dict[str, Any] = { "window_title": "Wire-Py", "window_size": (600, 383), "font_family": "Ubuntu", @@ -37,12 +42,41 @@ class AppConfig: } # System-dependent paths - SYSTEM_PATHS = { + 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" + "tcl_path": "/usr/share/TK-Themes", + } + # Images and icons paths + IMAGE_PATHS: Dict[str, str] = { + "icon_vpn": "/usr/share/icons/lx-icons/48/wg_vpn.png", + "icon_msg": "/usr/share/icons/lx-icons/48/wg_msg.png", + "icon_import": "/usr/share/icons/lx-icons/48/wg_import.png", + "icon_export": "/usr/share/icons/lx-icons/48/wg_export.png", + "icon_trash": "/usr/share/icons/lx-icons/48/wg_trash.png", + "icon_start": "/usr/share/icons/lx-icons/48/wg_vpn-start.png", + "icon_stop": "/usr/share/icons/lx-icons/48/wg_vpn-stop.png", + "icon_info": "/usr/share/icons/lx-icons/64/info.png", + "icon_error": "/usr/share/icons/lx-icons/64/error.png" + + } + + @staticmethod + def setup_translations() -> gettext.gettext: + """ + Initialize translations and set the translation function + Special method for translating strings in this file + + Returns: + The gettext translation function + """ + locale.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) + gettext.bindtextdomain(AppConfig.APP_NAME, AppConfig.LOCALE_DIR) + gettext.textdomain(AppConfig.APP_NAME) + return gettext.gettext + @classmethod def ensure_directories(cls) -> None: """Ensures that all required directories exist""" @@ -60,7 +94,7 @@ class AppConfig: def get_image_paths(cls) -> Dict[str, Path]: """Returns paths to UI images""" return { - "main_icon": cls.CONFIG_DIR / "images/main.png", + "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" @@ -78,4 +112,66 @@ Type=oneshot ExecStartPre=/bin/sleep 5 ExecStart=/usr/local/bin/start_wg.py [Install] -WantedBy=default.target""" \ No newline at end of file +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"), + "ren_err": _("Renaming not possible"), + "exp_succ": _("Export successful"), + "exp_in_home": _("Your zip file is in home directory"), + "imp_err": _("Import Error"), + "exp_err": _("Export Error"), + "exp_try": _("Export failed! Please try again"), + "tl_first": _("Please first import tunnel"), + "sel_list": _("Please select a tunnel from the list"), + "sign_len": _("The new name may contain only 12 characters"), + "zero_signs": _("At least one character must be entered"), + "false signs": _("No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n"), + "is_in_use": _("The tunnel is already in use"), + "no_valid_file": _("Oh... no valid Wireguard File!\nPlease select a valid Wireguard File"), + "tl_exist": _("Tunnel already available!\nPlease use another file for import") + + } + TTIP: Dict[str, str] = { + #Strings for Tooltips + "start_tl": _("Click to start selected Wireguard Tunnel"), + "empty_list": _("No tunnels to start in the list"), + "stop_tl": _("Click to stop selected Wireguard Tunnel"), + "del_tl": _("Click to delete selected Wireguard Tunnel"), + "rename_tl": _("To rename a tunnel, you need to\nselect a tunnel from the list"), + "export_tl": _(" Click to export all\nWireguard Tunnel to Zipfile"), + "trash_tl": _("Click to delete a Wireguard Tunnel\nSelect from the list!"), + "autostart": _("To use the autostart, enable this Checkbox"), + "autostart_info": _("You must have at least one\ntunnel in the list,to use the autostart"), + "export_tl_info": _("No Tunnels in List for Export"), + "start_tl_info": _("Click to start selected Wireguard Tunnel"), + "rename_tl_info": _("To rename a tunnel, at least one must be in the list"), + "trash_tl_info": _("No tunnels to delete in the list"), + "list_auto_info": _("To use the autostart, a tunnel must be selected from the list") + + } + \ No newline at end of file