From 47bdfbfb17a5daa6781b98740392315606c591f9 Mon Sep 17 00:00:00 2001 From: punix Date: Mon, 21 Apr 2025 22:25:10 +0200 Subject: [PATCH 01/61] new class foe tooltip --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 18172 -> 18196 bytes cls_mth_fc.py | 23 +- start_wg.py | 2 +- wirepy.py | 624 ++++--------------------- 4 files changed, 113 insertions(+), 536 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 18c17c46f804594ebb88a8cfff2db26712b80b78..696d866f41e078768eedd5a2e4e4064a39f40a83 100644 GIT binary patch delta 424 zcmey<%Q&Tvk@qw&FBbz42rXpGP};~VX6vs32vMje)ic@paihcuaF9JE?7E6d{KyWb& zkf|^^$6h6ADkB3!aR`vxz;HuMrh}!2`-X@Fkm9@{r_{l6g+ux#H~$SDfqw5!@9R7= z7kOl6M6O`Du3>pm!}1D`6%LuJJXRlA8F&PWB!LERer+$!%r+Nf&!x#c&YNU!@bFCt znI1VYazXhH#{-Th9KSF#337F?eqdwZWd{GsbwO delta 415 zcmbQz$M~n0k@qw&FBbz4xT&*cFm2=&vrSe5@}@JSGDI<^Fhnt>Fh((_Ftsp5v81r2 zu(U8nu>xsUAk9|EtjT_hJtRLrrzEqW>L`~25G3a$78fg|G2db-PR&Ux`VCZ4 zqyZ#~L5e3=*{eiPW(4wlfV>8V8)7mYEIr&eL?nO|=M6cf4wfq%(lZiIApGJm=#F^4fNaOAkEA+3uONJ$%@XKq-F>$P+6|IP;-O( z3Be12K^G#Ut_VbTu-@R{o&3Rh4kQ2MUKcIK6_ane6BKj8S&7soO<92_;7LPYgf;Y&ih3#B", self.show_tooltip) - self.widget.bind("", self.hide_tooltip) + if TIPS: + self.widget.bind("", self.show_tooltip) + self.widget.bind("", self.hide_tooltip) def show_tooltip(self, event=None): if self.tooltip_window or not self.text: return - + x, y, _, _ = self.widget.bbox("insert") x += self.widget.winfo_rootx() + 25 y += self.widget.winfo_rooty() + 20 @@ -418,12 +421,12 @@ class Tooltip: tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") - label = tk.Label(tw, text=self.text, relief="solid", borderwidth=1, padx=5, pady=5) + label = tk.Label( + tw, text=self.text, relief="solid", borderwidth=1, padx=5, pady=5 + ) label.grid() def hide_tooltip(self, event=None): if self.tooltip_window: self.tooltip_window.destroy() self.tooltip_window = None - - \ No newline at end of file diff --git a/start_wg.py b/start_wg.py index 8f2636c..52e8184 100755 --- a/start_wg.py +++ b/start_wg.py @@ -8,7 +8,7 @@ from subprocess import check_call path_to_file = Path(Path.home() / ".config/wire_py/settings") with open(path_to_file, "r", encoding="utf-8") as a_con: - + # This funtion is for the independent autostart of the previously selected tunnel lines = a_con.readlines() a_con = lines[7].strip() diff --git a/wirepy.py b/wirepy.py index c073737..bd212e3 100755 --- a/wirepy.py +++ b/wirepy.py @@ -23,7 +23,7 @@ Create.decrypt() tcl_path = Path("/usr/share/TK-Themes") wg_set = Path(Path.home() / ".config/wire_py/settings") -WG_TIPS = if_tip(wg_set) +TIPS = if_tip(wg_set) dirname = Path("/tmp/tlecdcwg/") # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year @@ -233,48 +233,17 @@ class FrameWidgets(ttk.Frame): self.menu_frame.configure(relief="flat") self.menu_frame.grid(column=0, row=0, columnspan=4, sticky="w") - def version_enter(event): - """ - The mouse moves into the Version widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, f"Version: {VERSION[2:]}" - ) - - def version_leave(_): - """ - The mouse moves from the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - # App Menu self.version_lb = ttk.Label(self.menu_frame, text=VERSION) self.version_lb.config(font=("Ubuntu", 11), foreground="#00c4ff") self.version_lb.grid(column=0, row=0, rowspan=4, padx=10) + Tooltip(self.version_lb, f"Version: {VERSION[2:]}", TIPS) + self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options")) self.options_btn.grid(column=1, columnspan=1, row=0) - def sets_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("Click for Settings") - ) - - def sets_leave(_): - """ - The mouse moves from the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.version_lb.bind("", version_enter) - self.version_lb.bind("", version_leave) - self.options_btn.bind("", sets_enter) - self.options_btn.bind("", sets_leave) + Tooltip(self.options_btn, _("Click for Settings"), TIPS) set_update = tk.IntVar() set_tip = tk.BooleanVar() @@ -301,7 +270,7 @@ class FrameWidgets(ttk.Frame): self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) # View Checkbox for enable or disable Tooltip - if WG_TIPS: + if TIPS: set_tip.set(value=False) else: set_tip.set(value=True) @@ -311,47 +280,15 @@ class FrameWidgets(ttk.Frame): set_update.set(value=1) self.updates_lb.configure(text=_("Update search off")) - def disable_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("Updates you have disabled") - ) + Tooltip(self.updates_lb, _("Updates you have disabled"), TIPS) - def disable_leave(_): - """ - The mouse moves from the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.updates_lb.bind("", disable_enter) - self.updates_lb.bind("", disable_leave) elif res == "No Internet Connection!": self.updates_lb.configure(text=_("No Server Connection!"), foreground="red") elif res == "No Updates": self.updates_lb.configure(text=_("No Updates")) - def congratulations_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("Congratulations! Wire-Py is up to date"), - ) + Tooltip(self.updates_lb, _("Congratulations! Wire-Py is up to date"), TIPS) - def congratulations_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.updates_lb.bind("", congratulations_enter) - self.updates_lb.bind("", congratulations_leave) else: set_update.set(value=0) text = f"Update {res} " + _("available!") @@ -360,23 +297,7 @@ class FrameWidgets(ttk.Frame): self.update_btn = ttk.Menubutton(self.menu_frame, text=text) self.update_btn.grid(column=4, columnspan=3, row=0, padx=0) - def download_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("Click to download new version") - ) - - def download_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.update_btn.bind("", download_enter) - self.update_btn.bind("", download_leave) + Tooltip(self.update_btn, _("Click to download new version"), TIPS) self.download = tk.Menu(self, relief="flat") @@ -470,45 +391,6 @@ class FrameWidgets(ttk.Frame): self.l_box.insert("end", tunnels[:-5]) self.l_box.update() - def list_empty_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("You must first import\na Wireguard tunnel"), - ) - - def list_empty_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - def list_not_empty_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("Select a Tunnel") - ) - - def list_not_empty_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - if self.l_box.size() == 0: - self.l_box.bind("", list_empty_enter) - self.l_box.bind("", list_empty_leave) - else: - self.l_box.bind("", list_not_empty_enter) - self.l_box.bind("", list_not_empty_leave) - # Button Vpn if self.a != "": self.stop() @@ -538,23 +420,7 @@ class FrameWidgets(ttk.Frame): ) self.btn_i.grid(column=0, row=1, padx=15, pady=8) - def imp_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("Click to import a Wireguard Tunnel") - ) - - def imp_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.btn_i.bind("", imp_enter) - self.btn_i.bind("", imp_leave) + Tooltip(self.btn_i, _("Click to import a Wireguard Tunnel"), TIPS) def delete(): """ @@ -604,36 +470,27 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") # for disable checkbox when Listbox empty - def empty_list_start_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("No tunnels to start in the list") - ) - - def empty_list_start_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - if self.l_box.size() == 0: self.wg_autostart.configure(state="disabled") self.lb_rename.configure(state="disabled") - self.l_box.bind("", list_empty_enter) - self.l_box.bind("", list_empty_leave) - self.wg_autostart.bind("", chk_enter) - self.wg_autostart.bind("", chk_leave) - self.btn_tr.bind("", empty_list_enter) - self.btn_tr.bind("", empty_list_leave) - self.btn_exp.bind("", empty_list_enter) - self.btn_exp.bind("", empty_list_leave) - self.btn_stst.bind("", empty_list_start_enter) - self.btn_stst.bind("", empty_list_start_leave) - self.lb_rename.bind("", rename_no_active_enter) - self.lb_rename.bind("", rename_no_active_leave) + Tooltip( + self.l_box, _("You must first import\na Wireguard tunnel"), TIPS + ) + Tooltip( + self.wg_autostart, + _( + "You must have at least one\ntunnel in the list,to use the autostart" + ), + TIPS, + ) + + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), TIPS) + Tooltip(self.btn_stst, _("No tunnels to start in the list"), TIPS) + Tooltip( + self.lb_rename, + _("To rename a tunnel, at least one must be in the list"), + TIPS, + ) self.lb_rename.insert(0, _("Max. 12 characters!")) if self.a != "" and self.a == select_tl: @@ -677,44 +534,14 @@ class FrameWidgets(ttk.Frame): ) self.btn_tr.grid(column=0, row=2, padx=15, pady=8) - def empty_list_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("No tunnels to delete in the list") - ) - - def empty_list_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - def del_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("Click to delete a Wireguard Tunnel\nSelect from the list!"), - ) - - def del_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - if self.l_box.size() == 0: - self.btn_tr.bind("", empty_list_enter) - self.btn_tr.bind("", empty_list_leave) + Tooltip(self.btn_tr, _("No tunnels to delete in the list"), TIPS) else: - self.btn_tr.bind("", del_enter) - self.btn_tr.bind("", del_leave) + Tooltip( + self.btn_tr, + _("Click to delete a Wireguard Tunnel\nSelect from the list!"), + TIPS, + ) # Button Export self.btn_exp = ttk.Button( @@ -722,39 +549,14 @@ class FrameWidgets(ttk.Frame): ) self.btn_exp.grid(column=0, row=3, padx=15, pady=8) - def empty_list_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("No Tunnels in List for Export") - ) - - empty_list_leave(_) - - def exp_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _(" Click to export all\nWireguard Tunnel to Zipfile"), - ) - - def exp_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - if self.l_box.size() == 0: - self.btn_exp.bind("", empty_list_enter) - self.btn_exp.bind("", empty_list_leave) + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), TIPS) else: - self.btn_exp.bind("", exp_enter) - self.btn_exp.bind("", exp_leave) + Tooltip( + self.btn_exp, + _(" Click to export all\nWireguard Tunnel to Zipfile"), + TIPS, + ) # Label Entry self.lb_rename = ttk.Entry(self.lb_frame4, width=20) @@ -762,46 +564,18 @@ class FrameWidgets(ttk.Frame): self.lb_rename.insert(0, _("Max. 12 characters!")) self.lb_rename.config(state="disable") - def rename_no_active_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - def rename_no_active_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("To rename a tunnel, at least one must be in the list"), - ) - - def rename_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("To rename a tunnel, you need to\nselect a tunnel from the list"), - ) - - def rename_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - if self.l_box.size() != 0: - self.lb_rename.bind("", rename_enter) - self.lb_rename.bind("", rename_leave) + Tooltip( + self.lb_rename, + _("To rename a tunnel, you need to\nselect a tunnel from the list"), + TIPS, + ) else: - self.lb_rename.bind("", rename_no_active_enter) - self.lb_rename.bind("", rename_no_active_leave) + Tooltip( + self.lb_rename, + _("To rename a tunnel, at least one must be in the list"), + TIPS, + ) def tl_rename(): @@ -921,72 +695,28 @@ class FrameWidgets(ttk.Frame): ) self.wg_autostart.grid(column=0, row=0, pady=15, padx=15, sticky="nw") - def chk_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _( - "You must have at least one\n" - "tunnel in the list,to use the autostart" - ), - ) - - def chk_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - if self.l_box.size() >= 1 and len(self.l_box.curselection()) >= 1: - def chk_a_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("To use the autostart, enable this Checkbox"), - ) - - def chk_a_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.wg_autostart.bind("", chk_a_enter) - self.wg_autostart.bind("", chk_a_leave) + Tooltip( + self.wg_autostart, _("To use the autostart, enable this Checkbox"), TIPS + ) if self.l_box.size() == 0: - self.wg_autostart.bind("", chk_enter) - self.wg_autostart.bind("", chk_leave) + Tooltip( + self.wg_autostart, + _( + "You must have at least one\ntunnel in the list,to use the autostart" + ), + TIPS, + ) + else: - def chk_a_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("To use the autostart, a tunnel must be selected from the list"), - ) - - def chk_a_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.wg_autostart.bind("", chk_a_enter) - self.wg_autostart.bind("", chk_a_leave) + Tooltip( + self.wg_autostart, + _("To use the autostart, a tunnel must be selected from the list"), + TIPS, + ) self.on_off() @@ -1125,109 +855,41 @@ class FrameWidgets(ttk.Frame): self.l_box.update() self.l_box.selection_set(0) - def chk_a_enter(event): - """The mouse moves into the entry widget""" - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _( - "To use the autostart, enable this " - "Checkbox" - ), - ) + Tooltip( + self.wg_autostart, + _("To use the autostart, enable this Checkbox"), + TIPS, + ) - def chk_a_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() + # Tooltip(self.l_box, _("List of available tunnels")) - def list_info_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("List of available tunnels"), - ) + Tooltip( + self.btn_tr, + _( + "Click to delete a Wireguard Tunnel\n" + "Select from the list!" + ), + TIPS, + ) - def list_info_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() + Tooltip( + self.btn_exp, + _( + " Click to export all\n" + "Wireguard Tunnel to Zipfile" + ), + TIPS, + ) - def del_enter(event): - """The mouse moves into the entry widget""" - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _( - "Click to delete a Wireguard " - "Tunnel\nSelect from the list!" - ), - ) + Tooltip( + self.btn_rename, + _( + "To rename a tunnel, you need to\n" + "select a tunnel from the list" + ), + TIPS, + ) - def del_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - def exp_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _( - " Click to export " - "all\nWireguard Tunnel to Zipfile" - ), - ) - - def exp_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - def rename_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _( - "To rename a tunnel, you need to\n" - "select a tunnel from the list" - ), - ) - - def rename_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.wg_autostart.bind("", chk_a_enter) - self.wg_autostart.bind("", chk_a_leave) - self.l_box.bind("", list_info_enter) - self.l_box.bind("", list_info_leave) - self.btn_tr.bind("", del_enter) - self.btn_tr.bind("", del_leave) - self.btn_exp.bind("", exp_enter) - self.btn_exp.bind("", exp_leave) - self.lb_rename.bind("", rename_enter) - self.lb_rename.bind("", rename_leave) self.lb_rename.insert(0, "Max. 12 characters!") self.str_var = tk.StringVar() self.str_var.set(self.a) @@ -1369,7 +1031,7 @@ class FrameWidgets(ttk.Frame): """ shows data in the label """ - + # Address Label self.address = ttk.Label( self.lb_frame, textvariable=self.add, foreground="#0071ff" @@ -1401,23 +1063,7 @@ class FrameWidgets(ttk.Frame): ) self.btn_stst.grid(column=0, row=0, padx=5, pady=8) - def stop_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("Click to stop selected Wireguard Tunnel") - ) - - def stop_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - self.btn_stst.bind("", stop_enter) - self.btn_stst.bind("", stop_leave) + Tooltip(self.btn_stst, _("Click to stop selected Wireguard Tunnel"), TIPS) def start(self): """ @@ -1431,45 +1077,11 @@ class FrameWidgets(ttk.Frame): ) self.btn_stst.grid(column=0, row=0, padx=5, pady=8) - def empty_list_start_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, event.y_root, _("No tunnels to start in the list") - ) - - def empty_list_start_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - - def start_enter(event): - """ - The mouse moves into the entry widget - """ - window.my_tool_tip = MyToolTip( - event.x_root, - event.y_root, - _("Click to start selected Wireguard Tunnel"), - ) - - def start_leave(_): - """ - The mouse moves into the entry widget - Remove Tool-Tip - """ - window.my_tool_tip.destroy() - tl = Tunnel.list() if len(tl) == 0: - self.btn_stst.bind("", empty_list_start_enter) - self.btn_stst.bind("", empty_list_start_leave) + Tooltip(self.btn_stst, _("No tunnels to start in the list"), TIPS) else: - self.btn_stst.bind("", start_enter) - self.btn_stst.bind("", start_leave) + Tooltip(self.btn_stst, _("Click to start selected Wireguard Tunnel"), TIPS) def color_label(self): """ @@ -1557,44 +1169,6 @@ class FrameWidgets(ttk.Frame): msg_window(iw, ii, wt, msg_t) -class MyToolTip(tk.Toplevel): - """ - Tooltip Class to view Tooltips - """ - - TIP_X_OFFSET = 20 - TIP_Y_OFFSET = 20 - if not WG_TIPS: - AUTO_CLEAR_TIME = 0 - else: - AUTO_CLEAR_TIME = 2000 # Millisecond. (1/200 sec.) - - def __init__(self, x_pos, y_pos, message=None, auto_clear=True): - self.x_pos = x_pos - self.y_pos = y_pos - self.message = message - self.auto_clear = auto_clear - - tk.Toplevel.__init__(self) - self.overrideredirect(True) - - self.message_label = ttk.Label( - self, compound="left", text=self.message, padding=6 - ) - self.message_label.pack() - - self.geometry( - f"+{self.x_pos + self.TIP_X_OFFSET}+{self.y_pos + self.TIP_X_OFFSET}" - ) - - if self.auto_clear: - self.after(self.AUTO_CLEAR_TIME, self.clear_tip) - - def clear_tip(self): - """Remove Tool-Tip""" - self.destroy() - - if __name__ == "__main__": window = Wirepy() """ From 5753a35d6c3228f3621851216188e50092251dce Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 22 Apr 2025 14:22:39 +0200 Subject: [PATCH 02/61] fix new tooltip signal now in modul cls_mth_fc.py --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 18196 -> 20307 bytes cls_mth_fc.py | 54 ++++++++++-- wirepy.py | 110 +++++++++---------------- 3 files changed, 90 insertions(+), 74 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 696d866f41e078768eedd5a2e4e4064a39f40a83..fddcbdbc2cb3697b8b7b79d0d6196dcadb6eb033 100644 GIT binary patch delta 3469 zcmai0du&tJ89(Rx`r5g+6UTWH0=WS}VjhM7h44rSA%u5;@{s0@2FLd%F*w)g+?#}W zrVcb3q#dsvT7nof-9xL2%34|LChd>1b^BwQwgnLeyMS70)z(VW#3RyHGcVZZ7M7pm@XKp>j1 ze>RTzbkhp8z-1xJqofJc=>J!}&wFq#!EiVRn zNlsohAOy>jb25LVuZeVKCM|1m#A1M0)eLYiIo)T#@KVNx`zMDBP zZ3~^*j!K6RRv=74C_~sZ2`9NVy+OimRN}w#6ldzH8YJ4v6SFo~ybN5+wZGReW1Rz* zp!WwI>RxKG-OHa52KqYP(aw!T0b>i1u}Q?nzK^ zXblk)bF-U?ncD3#t;`ZFNoU6ThC*p5{I3aDc`ZAi2rO& zk)XQy=$c|t%>$mRx@L};(IkpxJX1FJ& zYxblw#ncX&iXQ3F40|PvSeG7&D|&BFhsKPY)l+qZ8+b8%5BCFXCKM_uMfT1mCZ-W}1^xW=CFa|XvC7h@?n`;7d1kUSvbEcp|E;jIGysnl?4WZihdj8Wsg z>bFu8PLf}8cF)L;>%OYd!g24+`wlTZOa8*ZIy_ppm?r(Zb(~{W$&R@~VS^w_y5*hO z`DPL9&Q_Qf)!>LyLrO!VvZf=!%$VM#V7iKSC}t|rgsLfzXoxkSesMEy6gAPWD;)z$ zuK`JfLP?kHGaXlwr8$2J*tYVs_LEDO ztc3nE3!9F_C&jPeGJXs$U=b(Wqz+isfwP_Gdd~FxXwOexdh4aJk|ozXOKz1d5+$<)dpx`$ofK3wEc+_Oy@f-Z!={GUo4?a0tHq35ocMPHi3DI#M(4opHzO zKec&y^GLzCw-(ID0=47b;2m$lsSU#$PJiQux9U@G;pz1wtt0hgGnbDAR*ZXB-th*W zZ(eu9yU2bIv;{?6vov9_GYO*r_R4M8y&Z0EoK7a=gy1IGm)#?li1Ww1z#jD4YAwn>cMQ&kuT2c z*1%*i6_mf(QttmV^5+5n@yPtQEpyUA+6MW(09=O0AzOw}k1!2=VIHgk;h7gnG4c@J zgU=`hgCTNXa68=(izVmEhyFrm$;52QS(AOpqGC{JYYQs2Z~K79>&fB=*STwN;&Aq1 zW1mQH-4Nyg3(I95i0N>tF?(75&fi#9O<&}{SXZ|MZ;kAGYd2Cd!Ulv*2>488_L~U{ zpJCxath~InQeFl${D@)k#?~nnzT2emHj!_W;@c$uE}3?hl;0)QcS+f8exUVS`3ze3 TU=x*T(Z4F@(V7PYz)t9Yb(~47 delta 1601 zcmZvcYfKzf6vywG-5Hi$_5m#H@+?q!EDc*A8lXrkeL!K$YH{_$TH9`Srm`@*;N69m zEd+>Qs+KC=8XIHl^Gj3lkxBG}iI1e;OpLX$!D+E3CQYbcT5Hm@)#^R70kv_m`nR{j~oQ4y}!SRjVZYBK6dq*0mdT7Dng|`=KTc@&QkOkEew(z@2&5mb>vw?7J zR;_Vc*k))^72`SK979X06t{=%h89#u*db?yow6nD5{M!UR^bl+y%eY7IxA#iKzJ7X zJF9Uu`G3+LSoQw7D^6Apm9~7oj1B*Tb?k<@cuLMr+Xy)enZ$5`K)Q)+oh7nub?0!Q zoHI+p9>cM7$Fb&k)xH%_$uhe1+-yifw8nsja~C zW~kCeq)$!ouvTk50?@C$XDbz8kM@#of0BLq5FD7>LVyCpe{3|AD zGnaG=YxeAct?XsbyKq8l_D-0glYLZr9zM`!%67r}y_j?eu?bOx@F03tG0(1-cR-Kk zU-zU0JJ?)x04CY_>U?P$GiKPe>IXYA630h5bZc~29gQcFBawtIJQ|=KJm?eX941Vn zHqmL+#}PByQ#D?&4xvVTr%l$*i>@gg&m)3}7g%vaS#bd6a~vx<2s^@ou_J7E!_)SD zE`5c%DXJ#v4)%FNt!I!&bcpD1K!>OfGChXbUF>$lOCV@-{+|Wu35rU2|B^y%b|au z#@*>%!jiq~r3KWlvNwBcwwy%y28S<;68>81qB5o=QaT*gVVoLgEu;?7Gdv@i#Zi&D zG7Wu?FbH@Zr}(_&_uQN&OE#zG3$#OoOptv%lDWpoI5`9Oc#Kj`@hI209d+Sc{OD9v zWY+wP>$E#GJglZ-BmRMytn5o=28)Ckrwb6`=s16CE#NBxJhw=`;YN9 z#t<*!8iicUk{g0WwA^${V*5>(LoE2c!X(yaURF%>3BjPRjJ9&AXFa2&Qp(|!E~&%O zh^o+wJW2-;8w@`+loLy&6iST;dzGE*tAyR`YG2bvqaBU*3}Lzzf!~wNx4?KojTXaR z;RUilu+&=4YvezY{FXf%EOO^wBJNAXu}sRAiFcV)E)&l$?ET>Dc~;23Ra^tLzYz|j G^1lH}&2rlS diff --git a/cls_mth_fc.py b/cls_mth_fc.py index 5a647a7..7d199e9 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -4,14 +4,15 @@ import gettext import locale import os import shutil +import signal import subprocess +import sys import tkinter as tk import zipfile from datetime import datetime from pathlib import Path from subprocess import check_call from tkinter import ttk - import requests APP = "wirepy" @@ -378,6 +379,43 @@ class Tunnel: pass +def sigi(dirname): + """ + function for clean up after break + """ + + def signal_handler(signum, frame): + """ + Determine clear text names for signal numbers + """ + signals_to_names_dict = dict( + (getattr(signal, n), n) + for n in dir(signal) + if n.startswith("SIG") and "_" not in n + ) + signame = signals_to_names_dict.get(signum, f"Unnamed signal: {signum}") + + # End program for certain signals, report to others only reception + if signum in (signal.SIGINT, signal.SIGTERM): + exit_code = 1 + print( + f"\nSignal {signame} {(signum)} received. => Aborting with exit code {exit_code}." + ) + shutil.rmtree(dirname) + Path.unlink("/tmp/.loguser") + print("Breakdown by user...") + sys.exit(exit_code) + else: + print(f"Signal {signum} received and ignored.") + shutil.rmtree(dirname) + Path.unlink("/tmp/.loguser") + print("Process unexpectedly ended...") + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGHUP, signal_handler) + + def if_tip(path): """ method that writes in file whether tooltip is displayed or not @@ -402,21 +440,24 @@ class Tooltip: info: label and button is parrent. """ - def __init__(self, widget, text, TIPS=None): + def __init__(self, widget, text, tips=None): self.widget = widget self.text = text self.tooltip_window = None - if TIPS: + if tips: self.widget.bind("", self.show_tooltip) self.widget.bind("", self.hide_tooltip) def show_tooltip(self, event=None): + """ + shows the tooltip + """ if self.tooltip_window or not self.text: return x, y, _, _ = self.widget.bbox("insert") - x += self.widget.winfo_rootx() + 25 - y += self.widget.winfo_rooty() + 20 + x += self.widget.winfo_rootx() + 40 + y += self.widget.winfo_rooty() + 40 self.tooltip_window = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") @@ -427,6 +468,9 @@ class Tooltip: label.grid() def hide_tooltip(self, event=None): + """ + hide the tooltip + """ if self.tooltip_window: self.tooltip_window.destroy() self.tooltip_window = None diff --git a/wirepy.py b/wirepy.py index bd212e3..441d173 100755 --- a/wirepy.py +++ b/wirepy.py @@ -7,14 +7,22 @@ import locale import webbrowser import os import sys -import signal import subprocess import shutil import tkinter as tk from pathlib import Path from subprocess import check_call from tkinter import filedialog, ttk, TclError -from cls_mth_fc import Tooltip, Tunnel, Create, msg_window, if_tip, GiteaUpdate, uos +from cls_mth_fc import ( + Tooltip, + Tunnel, + Create, + msg_window, + sigi, + if_tip, + GiteaUpdate, + uos, +) uos() Create.dir_and_files() @@ -23,7 +31,7 @@ Create.decrypt() tcl_path = Path("/usr/share/TK-Themes") wg_set = Path(Path.home() / ".config/wire_py/settings") -TIPS = if_tip(wg_set) +tips = if_tip(wg_set) dirname = Path("/tmp/tlecdcwg/") # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year @@ -41,38 +49,7 @@ gettext.bindtextdomain(APP, LOCALE_DIR) gettext.textdomain(APP) _ = gettext.gettext - -def signal_handler(signum, frame): - """ - Determine clear text names for signal numbers - """ - signals_to_names_dict = dict( - (getattr(signal, n), n) - for n in dir(signal) - if n.startswith("SIG") and "_" not in n - ) - signame = signals_to_names_dict.get(signum, f"Unnamed signal: {signum}") - - # End program for certain signals, report to others only reception - if signum in (signal.SIGINT, signal.SIGTERM): - exit_code = 1 - print( - f"\nSignal {signame} {(signum)} received. => Aborting with exit code {exit_code}." - ) - shutil.rmtree(dirname) - Path.unlink("/tmp/.loguser") - print("Breakdown by user...") - sys.exit(exit_code) - else: - print(f"Signal {signum} received and ignored.") - shutil.rmtree(dirname) - Path.unlink("/tmp/.loguser") - print("Process unexpectedly ended...") - - -signal.signal(signal.SIGINT, signal_handler) -signal.signal(signal.SIGTERM, signal_handler) -signal.signal(signal.SIGHUP, signal_handler) +sigi(dirname) class Wirepy(tk.Tk): @@ -238,12 +215,12 @@ 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: {VERSION[2:]}", TIPS) + Tooltip(self.version_lb, f"Version: {VERSION[2:]}", tips) self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options")) self.options_btn.grid(column=1, columnspan=1, row=0) - Tooltip(self.options_btn, _("Click for Settings"), TIPS) + Tooltip(self.options_btn, _("Click for Settings"), tips) set_update = tk.IntVar() set_tip = tk.BooleanVar() @@ -270,7 +247,7 @@ class FrameWidgets(ttk.Frame): self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) # View Checkbox for enable or disable Tooltip - if TIPS: + if tips: set_tip.set(value=False) else: set_tip.set(value=True) @@ -280,14 +257,14 @@ class FrameWidgets(ttk.Frame): set_update.set(value=1) self.updates_lb.configure(text=_("Update search off")) - Tooltip(self.updates_lb, _("Updates you have disabled"), TIPS) + Tooltip(self.updates_lb, _("Updates you have disabled"), tips) elif res == "No Internet Connection!": self.updates_lb.configure(text=_("No Server Connection!"), foreground="red") elif res == "No Updates": self.updates_lb.configure(text=_("No Updates")) - Tooltip(self.updates_lb, _("Congratulations! Wire-Py is up to date"), TIPS) + Tooltip(self.updates_lb, _("Congratulations! Wire-Py is up to date"), tips) else: set_update.set(value=0) @@ -297,7 +274,7 @@ class FrameWidgets(ttk.Frame): self.update_btn = ttk.Menubutton(self.menu_frame, text=text) self.update_btn.grid(column=4, columnspan=3, row=0, padx=0) - Tooltip(self.update_btn, _("Click to download new version"), TIPS) + Tooltip(self.update_btn, _("Click to download new version"), tips) self.download = tk.Menu(self, relief="flat") @@ -420,7 +397,7 @@ class FrameWidgets(ttk.Frame): ) self.btn_i.grid(column=0, row=1, padx=15, pady=8) - Tooltip(self.btn_i, _("Click to import a Wireguard Tunnel"), TIPS) + Tooltip(self.btn_i, _("Click to import a Wireguard Tunnel"), tips) def delete(): """ @@ -473,23 +450,20 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() == 0: self.wg_autostart.configure(state="disabled") self.lb_rename.configure(state="disabled") - Tooltip( - self.l_box, _("You must first import\na Wireguard tunnel"), TIPS - ) Tooltip( self.wg_autostart, _( "You must have at least one\ntunnel in the list,to use the autostart" ), - TIPS, + tips, ) - Tooltip(self.btn_exp, _("No Tunnels in List for Export"), TIPS) - Tooltip(self.btn_stst, _("No tunnels to start in the list"), TIPS) + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) + Tooltip(self.btn_stst, _("No tunnels to start in the list"), tips) Tooltip( self.lb_rename, _("To rename a tunnel, at least one must be in the list"), - TIPS, + tips, ) self.lb_rename.insert(0, _("Max. 12 characters!")) @@ -535,12 +509,12 @@ class FrameWidgets(ttk.Frame): self.btn_tr.grid(column=0, row=2, padx=15, pady=8) if self.l_box.size() == 0: - Tooltip(self.btn_tr, _("No tunnels to delete in the list"), TIPS) + Tooltip(self.btn_tr, _("No tunnels to delete in the list"), tips) else: Tooltip( self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), - TIPS, + tips, ) # Button Export @@ -550,12 +524,12 @@ class FrameWidgets(ttk.Frame): self.btn_exp.grid(column=0, row=3, padx=15, pady=8) if self.l_box.size() == 0: - Tooltip(self.btn_exp, _("No Tunnels in List for Export"), TIPS) + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) else: Tooltip( self.btn_exp, _(" Click to export all\nWireguard Tunnel to Zipfile"), - TIPS, + tips, ) # Label Entry @@ -568,13 +542,13 @@ class FrameWidgets(ttk.Frame): Tooltip( self.lb_rename, _("To rename a tunnel, you need to\nselect a tunnel from the list"), - TIPS, + tips, ) else: Tooltip( self.lb_rename, _("To rename a tunnel, at least one must be in the list"), - TIPS, + tips, ) def tl_rename(): @@ -698,7 +672,7 @@ class FrameWidgets(ttk.Frame): 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 + self.wg_autostart, _("To use the autostart, enable this Checkbox"), tips ) if self.l_box.size() == 0: @@ -707,7 +681,7 @@ class FrameWidgets(ttk.Frame): _( "You must have at least one\ntunnel in the list,to use the autostart" ), - TIPS, + tips, ) else: @@ -715,7 +689,7 @@ class FrameWidgets(ttk.Frame): Tooltip( self.wg_autostart, _("To use the autostart, a tunnel must be selected from the list"), - TIPS, + tips, ) self.on_off() @@ -858,7 +832,7 @@ class FrameWidgets(ttk.Frame): Tooltip( self.wg_autostart, _("To use the autostart, enable this Checkbox"), - TIPS, + tips, ) # Tooltip(self.l_box, _("List of available tunnels")) @@ -869,7 +843,7 @@ class FrameWidgets(ttk.Frame): "Click to delete a Wireguard Tunnel\n" "Select from the list!" ), - TIPS, + tips, ) Tooltip( @@ -878,7 +852,7 @@ class FrameWidgets(ttk.Frame): " Click to export all\n" "Wireguard Tunnel to Zipfile" ), - TIPS, + tips, ) Tooltip( @@ -887,7 +861,7 @@ class FrameWidgets(ttk.Frame): "To rename a tunnel, you need to\n" "select a tunnel from the list" ), - TIPS, + tips, ) self.lb_rename.insert(0, "Max. 12 characters!") @@ -932,11 +906,10 @@ class FrameWidgets(ttk.Frame): except EOFError as e: print(e) except TypeError as e: - print(e) + print("File import: abort by user...") except FileNotFoundError as e: - print(e) + print("File import: abort by user...") except subprocess.CalledProcessError: - print("Tunnel exist!") def box_set(self): @@ -1063,7 +1036,7 @@ class FrameWidgets(ttk.Frame): ) 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, _("Click to stop selected Wireguard Tunnel"), tips) def start(self): """ @@ -1079,9 +1052,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, _("No tunnels to start in the list"), tips) else: - Tooltip(self.btn_stst, _("Click to start selected Wireguard Tunnel"), TIPS) + Tooltip(self.btn_stst, _("Click to start selected Wireguard Tunnel"), tips) def color_label(self): """ @@ -1178,7 +1151,6 @@ if __name__ == "__main__": window.tk.call("tk_getOpenFile", "-foobarbaz") except TclError: pass - window.tk.call("set", "::tk::dialog::file::showHiddenBtn", "0") window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0") window.mainloop() From 6c0662c62c61c930f955c8e8452ea32326a06ac9 Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 22 Apr 2025 14:26:04 +0200 Subject: [PATCH 03/61] import sorted --- cls_mth_fc.py | 1 + wirepy.py | 21 +++++++-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/cls_mth_fc.py b/cls_mth_fc.py index 7d199e9..c602ac9 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -13,6 +13,7 @@ from datetime import datetime from pathlib import Path from subprocess import check_call from tkinter import ttk + import requests APP = "wirepy" diff --git a/wirepy.py b/wirepy.py index 441d173..f0df081 100755 --- a/wirepy.py +++ b/wirepy.py @@ -4,25 +4,18 @@ this script is a simple GUI for managing Wireguard Tunnels """ import gettext import locale -import webbrowser import os -import sys -import subprocess import shutil +import subprocess +import sys import tkinter as tk +import webbrowser from pathlib import Path from subprocess import check_call -from tkinter import filedialog, ttk, TclError -from cls_mth_fc import ( - Tooltip, - Tunnel, - Create, - msg_window, - sigi, - if_tip, - GiteaUpdate, - uos, -) +from tkinter import TclError, filedialog, ttk + +from cls_mth_fc import (Create, GiteaUpdate, Tooltip, Tunnel, if_tip, + msg_window, sigi, uos) uos() Create.dir_and_files() From c2209517813f98ef3f5ec64feec8fc0c6c39a618 Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 22 Apr 2025 17:11:46 +0200 Subject: [PATCH 04/61] fix when list emty by start of wirepy --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 20307 -> 20595 bytes cls_mth_fc.py | 14 +++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index fddcbdbc2cb3697b8b7b79d0d6196dcadb6eb033..3009fde6c8ad0099a83ad012bbbe8cf728743ec7 100644 GIT binary patch delta 1870 zcmZvde@s(X6vumC+gI#wC{hrVVuac`TB$?|NCk&f3KY;zH}|8K_DL(HZSH%BRE#5& zOryq)tBZ3+nKRjv#Vqdahl$BF+x}RxECC5q@5r`fW|{ko%|DiHe{A=>4r7e(kI(y_ zbI&>NzIX1uZ|(wmFpbRLm`r*VyFPzgm#{QnHAi{0M$Yn|s*zuI>JK6mChr=yaovbi zZdgqsWw@&f)rT)jl%}d2~Z- zRVr+h3iG2XRlHy+5~;=vkNB43TrdT(;j zGn&LgBs=EGX`gb(qY^1MZ9AN^ZWp5QF~{Bvb|iG=kHyOkAMaEKa5+iXtvG)h(6KWTR4o8uuns zLL`}vfF-I~8Ha;Rn=1L2nG{cwe=Y6-Yw@YEV`Ik;&aCvzT0BQJ3-n zDm-)c^+%0&4fZ>hlG~Q5SxeP)|1C?+f_3>!$;NqW!#yTS8?QNUH+9c8bJKAksH=BekGG) zhWuddwG?Sx57a8YXpv)Tajonx)N|TyHa;o$*nU*=&p=i?@!DTR0dmTI37wO@rQ;gZ zK|ZW-YO$9Mle-n~p^xN=l^f8eMhI^LGy^sPS^zP?L4Y4n2CxG{OCV%%RSOEq?#i7! z3X;j{E;K=At8M%dh?pe5RoA!VCD`BMLQN^sXFvnKlMgvhY6Gu={RUtM;3UI+EvQydIzRzn83XPEyauKbU;>!IcbN3o zzGT|Pq^pFgV<`~_$@N;N9lj4mQHlmAicu8DuS0Yf`K$IgDv&4LKXLqS@O(*z>dJ~2 zn9P~NBk8n|az|rQa=)-qK3m5lM9D>OwGF!=!0>+nEY^U0$LrFdLBch-4GE~U9grjw z4b@~%Lpi?>%pqo8sJsdiDo}MwNaGGLAFh2oO#Hr99_EWPd0 zpR$7CG{?^a;J=6AJj)+6%I;Sd9dr{zTa@`=Kj{sXA|FYFn$geXawujN5b8$1tCZUx JePnsB|3874(=q@6 delta 1595 zcmZvcZERCj7{~kEwzs3NbTo8jOgdoeDzTGo$W~X_hT94o?XnGG6r8oaY=ui(&b?zV z4l?jvGE8{F2Q(m&m__3oxd|E}K_kWweo%ZP9wka*G=9+`ej&cZ^W4f8!u{~;|9Q^; zIrrS2=bRg-;mcFt{nX=eTEuhWkYg+pKkLoNu*xXO=d6%0BF@JFhVYClDP6E&N6Sj| zxm#>AmfbR5^Hswl{KpscEuw0Aat?FkcXzF|BDx3@o&sL-B(15a9c#V8RouxGeVi4t z#hk+JC6+JfuK{{^dru?<6d46mAwF#0dA|pNk_vP0xQjP6KFqAV$Xj*QN1Q61k~&`OyCm(qP~gGfdt_B0u2rd!1? zP~xz0AW{WRl{De9@p8kQy?2)4ql7f!d4XT;q}oY22o(g{WY$l3l0rAZL-0~=2DR8u z&ybMOrQDiTHP(YyW0BwQZx@EC_Iha8(DcP~~q9s#Q{-}s{>Z>P)$IM);kd@JO% zXL!6&P}Eo^tLOJCYmN7tWPmI9VRK!8+tj?KPrLqiP|#)EY>wI>i>q2=t{hF3A}F|{ zwL{)Ta#YAV{G@ei#ak3!72sA?DX=aIZ3G`7k0WgzGA)>?_-b3{5}o8YVLxF)0B0Kl z=!plR->8e9hv7SHK6ho+jHc~X^s!=&eMOaKJJ7e8FK5?D-PRN4(g^=ru%%s*NoE1V zy#d2$mu#>H&voeV46f;XXz{EVo33yUe*IJWKAg_;6o1NUh5bP@$Uzg`G7}VksW$TP+_WiFl!}e!~fp zFOki`s)dc)mHkRV=P=1(ikY1!=@aaA;n1j$!{w)Z2tyd|55Y%9y#Fakm@hd+GfC!5 z>~1lg`QhR6hqGlOF&!8#7F9i8ijC!S%7|VLzQMn@ZoK0yP(w=(5vcn19S=~B@D5I- zR&OR1l~So_*t|lxV^q;~Wm4y|TFhotg|QeUG?E6~@fJ<`?`a zRSSbCr`NWcy=?aOHgeM?&*+*f-$3R?VD=sDC!>8a*TdpiOjp5rd^UYQe216PS#JiQ PAFf->pFfhgZeYWIDmjad diff --git a/cls_mth_fc.py b/cls_mth_fc.py index c602ac9..f83f2cc 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -102,12 +102,16 @@ class Create: text=True, check=True, ) - # print(process.stdout) - if process.returncode == 0: - print("File successfully decrypted...") + path = Path.home() / ".config/wire_py/" + file_in_path = list(path.rglob("*.dat")) + if file_in_path: + if process.returncode == 0: + print("File successfully decrypted...") + else: + print(f"Error with the following code... {process.returncode}") else: - print(f"Error with the following code... {process.returncode}") - + print(_("Ready for import")) + @staticmethod def encrypt(): """ From aa66f4dc6810f60011f770404a979e20727b21e9 Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 22 Apr 2025 17:42:47 +0200 Subject: [PATCH 05/61] remove return select_tl --- wirepy.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/wirepy.py b/wirepy.py index f0df081..0bc2fb8 100755 --- a/wirepy.py +++ b/wirepy.py @@ -469,7 +469,6 @@ class FrameWidgets(ttk.Frame): self.add.set("") self.DNS.set("") self.enp.set("") - return select_tl except IndexError: @@ -621,8 +620,6 @@ class FrameWidgets(ttk.Frame): Create.encrypt() - return select_tl - except IndexError: # img_w, img_i, w_title, w_txt hand over @@ -898,9 +895,9 @@ class FrameWidgets(ttk.Frame): except EOFError as e: print(e) - except TypeError as e: + except TypeError: print("File import: abort by user...") - except FileNotFoundError as e: + except FileNotFoundError: print("File import: abort by user...") except subprocess.CalledProcessError: print("Tunnel exist!") From 3bab0710a44a9ebe912f2e9fb881434fb3bcf8ea Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 23 Apr 2025 14:18:06 +0200 Subject: [PATCH 06/61] replace "str(" with f"format{example}" --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 20595 -> 20555 bytes cls_mth_fc.py | 25 +++--- ssl_decrypt.py | 8 +- ssl_encrypt.py | 16 ++-- wirepy.py | 105 +++++++++---------------- 5 files changed, 64 insertions(+), 90 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 3009fde6c8ad0099a83ad012bbbe8cf728743ec7..913ab8ca6982a8cb6d75f9685030a59858d28970 100644 GIT binary patch delta 2104 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 diff --git a/cls_mth_fc.py b/cls_mth_fc.py index f83f2cc..f78278c 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -111,7 +111,7 @@ class Create: print(f"Error with the following code... {process.returncode}") else: print(_("Ready for import")) - + @staticmethod def encrypt(): """ @@ -138,7 +138,7 @@ def uos(): This method displays the user name of the logged-in user, even if you are rooted in a shell """ - logname = str(Path.home())[6:] + logname = f"{Path.home()}"[6:] file = Path.home() / "/tmp/.loguser" with open(file, "w", encoding="utf-8") as f: f.write(logname) @@ -181,10 +181,10 @@ class GiteaUpdate: this is for download new Version of wirepy """ try: - to_down = "wget -qP " + str(Path.home()) + " " + urld + to_down = f"wget -qP {Path.home()} {" "} {urld}" result = subprocess.call(to_down, shell=True) if result == 0: - shutil.chown(str(Path.home()) + f"/{res}.zip", 1000, 1000) + shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000) # img_w, img_i, w_title, w_txt hand over iw = r"/usr/share/icons/lx-icons/64/info.png" @@ -342,17 +342,17 @@ class Tunnel: in the user's home directory with correct right """ now_time = datetime.now() - now_datetime = now_time.strftime("wg-exp-" + "%m-%d-%Y" + "-" + "%H:%M") + now_datetime = now_time.strftime(f"wg-exp-%m-%d-%Y-%H:%M") tl = Tunnel.list() try: if len(tl) != 0: - wg_tar = str(Path.home()) + "/" + now_datetime + wg_tar = f"{Path.home()}/{now_datetime}" shutil.copytree("/tmp/tlecdcwg/", "/tmp/wire_py", dirs_exist_ok=True) source = Path("/tmp/wire_py") shutil.make_archive(wg_tar, "zip", source) shutil.rmtree(source) - with zipfile.ZipFile((wg_tar + ".zip"), "r") as zf: + with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: if len(zf.namelist()) != 0: # img_w, img_i, w_title, w_txt hand over @@ -461,14 +461,21 @@ class Tooltip: return x, y, _, _ = self.widget.bbox("insert") - x += self.widget.winfo_rootx() + 40 + x += self.widget.winfo_rootx() + 65 y += self.widget.winfo_rooty() + 40 self.tooltip_window = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") label = tk.Label( - tw, text=self.text, relief="solid", borderwidth=1, padx=5, pady=5 + tw, + text=self.text, + background="lightgreen", + foreground="black", + relief="solid", + borderwidth=1, + padx=5, + pady=5, ) label.grid() diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 7cb4630..cb3ef98 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -33,16 +33,16 @@ if not keyfile.is_file(): ) shutil.chown(keyfile, 1000, 1000) -dirname2 = "/home/" + logname + "/.config/wire_py/" +dirname2 = f"/home/{logname}/.config/wire_py/" detl = os.listdir(dirname2) os.chdir(dirname2) detl.remove("keys") detl.remove("settings") -if os.path.exists(dirname2 + "pbwgk.pem"): +if os.path.exists(f"{dirname2}pbwgk.pem"): detl.remove("pbwgk.pem") for detunnels in detl: - tlname2 = detunnels[:-4] + ".conf" - extpath = str(dirname) + "/" + tlname2 + tlname2 = f"{detunnels[:-4]}.conf" + extpath = f"{dirname}/{tlname2}" check_call( [ "openssl", diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 4f05032..4069f79 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -33,14 +33,14 @@ if not keyfile.is_file(): shutil.chown(keyfile, 1000, 1000) if dirname.exists(): - tl = os.listdir(str(dirname)) - CPTH = str(keyfile) + tl = os.listdir(f"{dirname}") + CPTH = f"{keyfile}" CRYPTFILES = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl = str(dirname) + "/" + tunnels - tlname = CRYPTFILES + tunnels[:-5] + ".dat" + sourcetl = f"{dirname}/{tunnels}" + tlname = f"{CRYPTFILES}{tunnels[:-5]}.dat" check_call( [ "openssl", @@ -59,14 +59,14 @@ if not keyfile.is_file(): else: if dirname.exists(): - tl = os.listdir(str(dirname)) - CPTH = str(keyfile) + tl = os.listdir(f"{dirname}") + CPTH = f"{keyfile}" CRYPTFILES = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl = str(dirname) + "/" + tunnels - tlname = CRYPTFILES + tunnels[:-5] + ".dat" + sourcetl = f"{dirname}/{tunnels}" + tlname = f"{CRYPTFILES}{tunnels[:-5]}.dat" check_call( [ "openssl", diff --git a/wirepy.py b/wirepy.py index 0bc2fb8..d1a7074 100755 --- a/wirepy.py +++ b/wirepy.py @@ -67,7 +67,7 @@ class Wirepy(tk.Tk): self.rowconfigure(0, weight=1) self.style = ttk.Style(self) - self.tk.call("source", str(tcl_path) + "/water.tcl") + self.tk.call("source", f"{tcl_path}/water.tcl") with open(wg_set, "r", encoding="utf-8") as read_file: lines = read_file.readlines() if "light\n" in lines: @@ -261,7 +261,7 @@ class FrameWidgets(ttk.Frame): else: set_update.set(value=0) - text = f"Update {res} " + _("available!") + text = f"Update {res} {_("available!")}" # Update BTN Menu self.update_btn = ttk.Menubutton(self.menu_frame, text=text) @@ -303,9 +303,8 @@ 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) @@ -364,7 +363,7 @@ class FrameWidgets(ttk.Frame): # Button Vpn if self.a != "": self.stop() - wg_read = "/tmp/tlecdcwg/" + str(self.a + ".conf") + wg_read = f"/tmp/tlecdcwg/{self.a}.conf" with open(wg_read, "r", encoding="utf-8") as file: data = Tunnel.con_to_dict(file) @@ -382,12 +381,8 @@ 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, _("Click to import a Wireguard Tunnel"), tips) @@ -399,8 +394,7 @@ class FrameWidgets(ttk.Frame): try: self.select_tunnel = self.l_box.curselection() select_tl = self.l_box.get(self.select_tunnel[0]) - with open( - "/tmp/tlecdcwg/" + select_tl + ".conf", "r+", encoding="utf-8" + with open(f"/tmp/tlecdcwg/{select_tl}.conf", "r+", encoding="utf-8" ) as file2: key = Tunnel.con_to_dict(file2) pre_key = key[3] @@ -417,24 +411,16 @@ class FrameWidgets(ttk.Frame): set_file7.writelines(lines6) self.selected_option.set(0) self.autoconnect_var.set(_("no Autoconnect")) - is_encrypt = Path.home() / ".config/wire_py" / str(select_tl + ".dat") + is_encrypt = Path.home() / f".config/wire_py/{select_tl}.dat" if is_encrypt.is_file(): - Path.unlink( - str(Path.home()) + "/.config/wire_py/" + str(select_tl + ".dat") - ) - Path.unlink(Path("/tmp/tlecdcwg") / str(select_tl + ".conf")) - with open( - str(Path.home()) + "/.config/wire_py/keys", "r", encoding="utf-8" - ) as readfile: - with open( - str(Path.home()) + "/.config/wire_py/keys2", - "w", - encoding="utf-8", - ) as writefile: + Path.unlink(f"{Path.home()}/.config/wire_py/{select_tl}.dat") + Path.unlink(f"/tmp/tlecdcwg/{select_tl}.conf") + with open(f"{Path.home()}/.config/wire_py/keys", "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(str(Path.home()) + "/.config/wire_py/keys2") + 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") @@ -443,21 +429,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, - _( - "You must have at least one\ntunnel in the list,to use the autostart" - ), - tips, - ) + Tooltip(self.wg_autostart, _("You must have at least one\ntunnel", + "in the list,to use the autostart"), tips) Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) Tooltip(self.btn_stst, _("No tunnels to start in the list"), tips) - Tooltip( - self.lb_rename, - _("To rename a tunnel, at least one must be in the list"), - tips, - ) + Tooltip(self.lb_rename, _("To rename a tunnel, at least one must", + "be in the list"), tips,) self.lb_rename.insert(0, _("Max. 12 characters!")) if self.a != "" and self.a == select_tl: @@ -503,9 +481,7 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() == 0: Tooltip(self.btn_tr, _("No tunnels to delete in the list"), tips) else: - Tooltip( - self.btn_tr, - _("Click to delete a Wireguard Tunnel\nSelect from the list!"), + Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), tips, ) @@ -520,7 +496,7 @@ class FrameWidgets(ttk.Frame): else: Tooltip( self.btn_exp, - _(" Click to export all\nWireguard Tunnel to Zipfile"), + _("Click to export all\nWireguard Tunnel to Zipfile"), tips, ) @@ -593,12 +569,11 @@ class FrameWidgets(ttk.Frame): self.lb_rename.get(), ] ) - source = Path("/tmp/tlecdcwg") / str(select_tl + ".conf") - destination = source.with_name(str(self.lb_rename.get() + ".conf")) + source = Path(f"/tmp/tlecdcwg/{select_tl}.conf") + destination = source.with_name(f"{self.lb_rename.get()}.conf") source.replace(destination) - Path.unlink( - str(Path.home()) + "/.config/wire_py/" + str(select_tl + ".dat") - ) + 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() @@ -703,7 +678,7 @@ class FrameWidgets(ttk.Frame): try: filepath = filedialog.askopenfilename( - initialdir=str(Path.home()), + initialdir=f"{Path.home()}", title=_("Select Wireguard config File"), filetypes=[(_("WG config files"), "*.conf")], ) @@ -723,13 +698,9 @@ class FrameWidgets(ttk.Frame): key = Tunnel.con_to_dict(file) pre_key = key[3] if len(pre_key) != 0: - with open( - str(Path.home()) + "/.config/wire_py/keys", - "r", - encoding="utf-8", - ) as readfile: + with open(f"{Path.home()}/.config/wire_py/keys", "r", encoding="utf-8") as readfile: p_key = readfile.readlines() - if pre_key in p_key or pre_key + "\n" in p_key: + if pre_key in p_key or f"{pre_key}\n" in p_key: # img_w, img_i, w_title, w_txt hand over iw = r"/usr/share/icons/lx-icons/64/error.png" @@ -742,21 +713,17 @@ class FrameWidgets(ttk.Frame): else: - with open( - str(Path.home()) + "/.config/wire_py/keys", - "a", - encoding="utf-8", - ) as keyfile: - keyfile.write(pre_key + "\r") + with open(f"{Path.home()}/.config/wire_py/keys", "a", encoding="utf-8") as keyfile: + keyfile.write(f"{pre_key}\r") if len(path_split1) > 17: p1 = shutil.copy(filepath, "/tmp/tlecdcwg/") path_split = path_split1[ len(path_split1) - 17 : ] os.rename( - p1, "/tmp/tlecdcwg/" + str(path_split) + p1, f"/tmp/tlecdcwg/{path_split}" ) - new_conf = "/tmp/tlecdcwg/" + path_split + new_conf = f"/tmp/tlecdcwg/{path_split}" if self.a != "": check_call( [ @@ -859,7 +826,7 @@ class FrameWidgets(ttk.Frame): self.str_var.set(self.a) self.color_label() self.stop() - wg_read = "/tmp/tlecdcwg/" + str(self.a + ".conf") + wg_read = f"/tmp/tlecdcwg/{self.a}.conf" with open( wg_read, "r", encoding="utf-8" ) as file_for_key: @@ -976,11 +943,11 @@ class FrameWidgets(ttk.Frame): # Address Label self.add = tk.StringVar() - self.add.set(_("Address: ") + data[0]) + self.add.set(f"{_("Address: ")}{data[0]}") self.DNS = tk.StringVar() - self.DNS.set(" DNS: " + data[1]) + self.DNS.set(f" DNS: {data[1]}") self.enp = tk.StringVar() - self.enp.set(_("Endpoint: ") + data[2]) + self.enp.set(f"{_("Endpoint: ")}{data[2]}") def label_empty(self): """ @@ -1078,7 +1045,7 @@ class FrameWidgets(ttk.Frame): self.select_tunnel = self.l_box.curselection() select_tl = self.l_box.get(self.select_tunnel[0]) check_call(["nmcli", "connection", "up", select_tl]) - wg_read = "/tmp/tlecdcwg/" + str(select_tl + ".conf") + wg_read = f"/tmp/tlecdcwg/{select_tl}.conf" with open(wg_read, "r", encoding="utf-8") as file: data = Tunnel.con_to_dict(file) From c43c12f9613642d2ea517d4f8b9d2814372bb234 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 23 Apr 2025 14:29:47 +0200 Subject: [PATCH 07/61] rows reformat --- .idea/misc.xml | 2 +- .idea/wire-py.iml | 2 +- .idea/workspace.xml | 44 ++++++++++++++++++++++---------------------- wirepy.py | 39 ++++++++++++++++++--------------------- 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index d5e9910..3663950 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/wire-py.iml b/.idea/wire-py.iml index 2c80e12..6cb8b9a 100644 --- a/.idea/wire-py.iml +++ b/.idea/wire-py.iml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 5c6d238..3a30f82 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,8 +5,7 @@ - - + - + - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "Python.INSTALL.executor": "Run", - "Python.install.executor": "Run", - "Python.main.executor": "Run", - "Python.messagebox.executor": "Run", - "Python.start_wg.executor": "Run", - "Python.testtheme.executor": "Run", - "Python.wg_func.executor": "Run", - "Python.wg_main.executor": "Run", - "RunOnceActivity.ShowReadmeOnStart": "true", - "Shell Script.install.executor": "Run", - "Shell Script.run_as.executor": "Run", - "git-widget-placeholder": "1.11.1024", - "last_opened_file_path": "/home/punix/Pyapps/wire-py", - "settings.editor.selected.configurable": "reference.settingsdialog.IDE.editor.colors" + +}]]> @@ -129,7 +129,7 @@ - diff --git a/wirepy.py b/wirepy.py index d1a7074..16f8b67 100755 --- a/wirepy.py +++ b/wirepy.py @@ -156,7 +156,6 @@ class FrameWidgets(ttk.Frame): Set light theme """ if self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "light") with open(wg_set, "r", encoding="utf-8") as theme_set2: lines3 = theme_set2.readlines() @@ -170,7 +169,6 @@ class FrameWidgets(ttk.Frame): Set dark theme """ if not self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "dark") with open(wg_set, "r", encoding="utf-8") as theme_set2: lines4 = theme_set2.readlines() @@ -303,8 +301,8 @@ 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) @@ -382,7 +380,7 @@ class FrameWidgets(ttk.Frame): # Button Import self.btn_i = ttk.Button(self.lb_frame_btn_lbox, image=self.imp_pic, - command=self.import_sl, padding=0) + command=self.import_sl, padding=0) self.btn_i.grid(column=0, row=1, padx=15, pady=8) Tooltip(self.btn_i, _("Click to import a Wireguard Tunnel"), tips) @@ -395,7 +393,7 @@ class FrameWidgets(ttk.Frame): 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: + ) as file2: key = Tunnel.con_to_dict(file2) pre_key = key[3] check_call(["nmcli", "connection", "delete", select_tl]) @@ -403,8 +401,8 @@ class FrameWidgets(ttk.Frame): with open(wg_set, "r", encoding="utf-8") as set_file6: lines6 = set_file6.readlines() if ( - select_tl == lines6[7].strip() - and "off\n" not in lines6[7].strip() + select_tl == lines6[7].strip() + and "off\n" not in lines6[7].strip() ): lines6[7] = "off\n" with open(wg_set, "w", encoding="utf-8") as set_file7: @@ -430,12 +428,12 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") self.lb_rename.configure(state="disabled") Tooltip(self.wg_autostart, _("You must have at least one\ntunnel", - "in the list,to use the autostart"), tips) + "in the list,to use the autostart"), tips) Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) Tooltip(self.btn_stst, _("No tunnels to start in the list"), tips) Tooltip(self.lb_rename, _("To rename a tunnel, at least one must", - "be in the list"), tips,) + "be in the list"), tips, ) self.lb_rename.insert(0, _("Max. 12 characters!")) if self.a != "" and self.a == select_tl: @@ -482,8 +480,8 @@ class FrameWidgets(ttk.Frame): Tooltip(self.btn_tr, _("No tunnels to delete in the list"), tips) else: Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), - tips, - ) + tips, + ) # Button Export self.btn_exp = ttk.Button( @@ -585,8 +583,8 @@ class FrameWidgets(ttk.Frame): with open(wg_set, "r", encoding="utf-8") as set_file5: lines5 = set_file5.readlines() if ( - select_tl == lines5[7].strip() - and "off\n" not in lines5[7].strip() + select_tl == lines5[7].strip() + and "off\n" not in lines5[7].strip() ): lines5[7] = new_a_connect with open(wg_set, "w", encoding="utf-8") as theme_set5: @@ -635,7 +633,6 @@ 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 ) @@ -690,9 +687,9 @@ class FrameWidgets(ttk.Frame): self.a = Tunnel.active() if ( - "PrivateKey = " in read - and "PublicKey = " in read - and "Endpoint =" in read + "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) @@ -718,8 +715,8 @@ class FrameWidgets(ttk.Frame): if len(path_split1) > 17: p1 = shutil.copy(filepath, "/tmp/tlecdcwg/") path_split = path_split1[ - len(path_split1) - 17 : - ] + len(path_split1) - 17: + ] os.rename( p1, f"/tmp/tlecdcwg/{path_split}" ) @@ -828,7 +825,7 @@ class FrameWidgets(ttk.Frame): self.stop() wg_read = f"/tmp/tlecdcwg/{self.a}.conf" with open( - wg_read, "r", encoding="utf-8" + wg_read, "r", encoding="utf-8" ) as file_for_key: data = Tunnel.con_to_dict(file_for_key) From 87943b24895ed7f8aa740cd105e21b474410fa4f Mon Sep 17 00:00:00 2001 From: punix Date: Thu, 24 Apr 2025 12:43:39 +0200 Subject: [PATCH 08/61] add new class LxTools and funktion to methode in new class --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 20555 -> 22727 bytes cls_mth_fc.py | 63 +++++++++++---- wirepy.py | 104 +++++++++---------------- 3 files changed, 86 insertions(+), 81 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 913ab8ca6982a8cb6d75f9685030a59858d28970..604aa1f922a23fb84a7819951e01c6d85be5503f 100644 GIT binary patch delta 3571 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 diff --git a/cls_mth_fc.py b/cls_mth_fc.py index f78278c..9179d64 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -129,19 +129,52 @@ class Create: else: print(f"Error with the following code... {process.returncode}") +class LxTools(tk.Tk): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) -def uos(): - """ - uos = LOGIN USERNAME + def theme_change_light(self): + """ + Set light theme + """ + if self.tk.call("ttk::style", "theme", "use") == "water-dark": + self.tk.call("set_theme", "light") + with open(wg_set, "r", encoding="utf-8") as theme_set2: + lines3 = theme_set2.readlines() + lines3[3] = "light\n" + with open(wg_set, "w", encoding="utf-8") as theme_set2: + theme_set2.writelines(lines3) + self.color_label() - This method displays the user name of the logged-in user, - even if you are rooted in a shell - """ - logname = f"{Path.home()}"[6:] - file = Path.home() / "/tmp/.loguser" - with open(file, "w", encoding="utf-8") as f: - f.write(logname) + + def theme_change_dark(self): + """ + Set dark theme + """ + if not self.tk.call("ttk::style", "theme", "use") == "water-dark": + self.tk.call("set_theme", "dark") + with open(wg_set, "r", encoding="utf-8") as theme_set2: + lines4 = theme_set2.readlines() + lines4[3] = "dark\n" + with open(wg_set, "w", encoding="utf-8") as theme_set2: + theme_set2.writelines(lines4) + self.color_label() + + @staticmethod + def uos(): + """ + + uos = LOGIN USERNAME + + This method displays the user name of the logged-in user, + even if you are rooted in a shell + """ + logname = f"{Path.home()}"[6:] + file = Path.home() / "/tmp/.loguser" + with open(file, "w", encoding="utf-8") as f: + f.write(logname) class GiteaUpdate: @@ -161,9 +194,9 @@ class GiteaUpdate: response = requests.get(update_api_url, timeout=10) response_dict = response.json() response_dict = response_dict[0] - with open(wg_set, "r", encoding="utf-8") as set_file: - set_file = set_file.read() - if "on\n" in set_file: + with open(wg_set, "r", encoding="utf-8") as set_f: + set_f = set_f.read() + if "on\n" in set_f: if version[3:] != response_dict["tag_name"]: req = response_dict["tag_name"] else: @@ -425,8 +458,8 @@ def if_tip(path): """ method that writes in file whether tooltip is displayed or not """ - with open(path, "r", encoding="utf-8") as set_file2: - lines2 = set_file2.readlines() + with open(path, "r", encoding="utf-8") as set_f2: + lines2 = set_f2.readlines() if "False\n" in lines2: tip = False else: diff --git a/wirepy.py b/wirepy.py index 16f8b67..0560043 100755 --- a/wirepy.py +++ b/wirepy.py @@ -15,9 +15,9 @@ from subprocess import check_call from tkinter import TclError, filedialog, ttk from cls_mth_fc import (Create, GiteaUpdate, Tooltip, Tunnel, if_tip, - msg_window, sigi, uos) + LxTools, msg_window, sigi) -uos() +LxTools.uos() Create.dir_and_files() Create.make_dir() Create.decrypt() @@ -120,62 +120,36 @@ class FrameWidgets(ttk.Frame): Set on or off in file """ if set_update.get() == 1: - with open(wg_set, "r", encoding="utf-8") as set_file2: - lines2 = set_file2.readlines() + with open(wg_set, "r", encoding="utf-8") as set_f2: + lines2 = set_f2.readlines() lines2[1] = "off\n" - with open(wg_set, "w", encoding="utf-8") as set_file2: - set_file2.writelines(lines2) + with open(wg_set, "w", encoding="utf-8") as set_f2: + set_f2.writelines(lines2) if set_update.get() == 0: - with open(wg_set, "r", encoding="utf-8") as set_file2: - lines2 = set_file2.readlines() + with open(wg_set, "r", encoding="utf-8") as set_f2: + lines2 = set_f2.readlines() lines2[1] = "on\n" - with open(wg_set, "w", encoding="utf-8") as set_file2: - set_file2.writelines(lines2) + with open(wg_set, "w", encoding="utf-8") as set_f2: + set_f2.writelines(lines2) def tooltip(): """ Set True or False in file """ if set_tip.get(): - with open(wg_set, "r", encoding="utf-8") as set_file2: - lines2 = set_file2.readlines() + with open(wg_set, "r", encoding="utf-8") as set_f2: + lines2 = set_f2.readlines() lines2[5] = "False\n" - with open(wg_set, "w", encoding="utf-8") as set_file2: - set_file2.writelines(lines2) + with open(wg_set, "w", encoding="utf-8") as set_f2: + set_f2.writelines(lines2) else: - with open(wg_set, "r", encoding="utf-8") as set_file2: - lines2 = set_file2.readlines() + with open(wg_set, "r", encoding="utf-8") as set_f2: + lines2 = set_f2.readlines() lines2[5] = "True\n" - with open(wg_set, "w", encoding="utf-8") as set_file2: - set_file2.writelines(lines2) - - def theme_change_light(): - """ - Set light theme - """ - if self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "light") - with open(wg_set, "r", encoding="utf-8") as theme_set2: - lines3 = theme_set2.readlines() - lines3[3] = "light\n" - with open(wg_set, "w", encoding="utf-8") as theme_set2: - theme_set2.writelines(lines3) - self.color_label() - - def theme_change_dark(): - """ - Set dark theme - """ - if not self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "dark") - with open(wg_set, "r", encoding="utf-8") as theme_set2: - lines4 = theme_set2.readlines() - lines4[3] = "dark\n" - with open(wg_set, "w", encoding="utf-8") as theme_set2: - theme_set2.writelines(lines4) - self.color_label() + with open(wg_set, "w", encoding="utf-8") as set_f2: + set_f2.writelines(lines2) def info(): def link_btn(): @@ -223,8 +197,8 @@ class FrameWidgets(ttk.Frame): self.settings.add_checkbutton( label=_("Disable Tooltips"), command=tooltip, variable=set_tip ) - self.settings.add_command(label=_("Light"), command=theme_change_light) - self.settings.add_command(label=_("Dark"), command=theme_change_dark) + self.settings.add_command(label=_("Light"), command=lambda: LxTools.theme_change_light(self)) + self.settings.add_command(label=_("Dark"), command=lambda: LxTools.theme_change_dark(self)) # About BTN Menu / Label self.about_btn = ttk.Button( @@ -398,15 +372,15 @@ class FrameWidgets(ttk.Frame): pre_key = key[3] check_call(["nmcli", "connection", "delete", select_tl]) self.l_box.delete(self.select_tunnel[0]) - with open(wg_set, "r", encoding="utf-8") as set_file6: - lines6 = set_file6.readlines() + with open(wg_set, "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(wg_set, "w", encoding="utf-8") as set_file7: - set_file7.writelines(lines6) + with open(wg_set, "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" @@ -427,13 +401,11 @@ 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, _("You must have at least one\ntunnel", - "in the list,to use the autostart"), tips) + Tooltip(self.wg_autostart, _("You must have at least one\ntunnel in the list,to use the autostart"), tips) Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) Tooltip(self.btn_stst, _("No tunnels to start in the list"), tips) - Tooltip(self.lb_rename, _("To rename a tunnel, at least one must", - "be in the list"), tips, ) + Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), tips, ) self.lb_rename.insert(0, _("Max. 12 characters!")) if self.a != "" and self.a == select_tl: @@ -580,8 +552,8 @@ class FrameWidgets(ttk.Frame): if self.a != "" and self.a == select_tl: self.a = Tunnel.active() self.str_var.set(value=self.a) - with open(wg_set, "r", encoding="utf-8") as set_file5: - lines5 = set_file5.readlines() + with open(wg_set, "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() @@ -877,11 +849,11 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(select_tunnel[0]) if self.selected_option.get() == 0: - with open(wg_set, "r", encoding="utf-8") as set_file3: - lines3 = set_file3.readlines() + with open(wg_set, "r", encoding="utf-8") as set_f3: + lines3 = set_f3.readlines() lines3[7] = "off\n" - with open(wg_set, "w", encoding="utf-8") as set_file3: - set_file3.writelines(lines3) + with open(wg_set, "w", encoding="utf-8") as set_f3: + set_f3.writelines(lines3) tl = Tunnel.list() @@ -889,11 +861,11 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") if self.selected_option.get() >= 1: - with open(wg_set, "r", encoding="utf-8") as set_file3: - lines3 = set_file3.readlines() + with open(wg_set, "r", encoding="utf-8") as set_f3: + lines3 = set_f3.readlines() lines3[7] = select_tl - with open(wg_set, "w", encoding="utf-8") as set_file3: - set_file3.writelines(lines3) + with open(wg_set, "w", encoding="utf-8") as set_f3: + set_f3.writelines(lines3) except IndexError: self.selected_option.set(1) @@ -906,8 +878,8 @@ 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 """ - with open(wg_set, "r", encoding="utf-8") as set_file4: - lines4 = set_file4.readlines() + with open(wg_set, "r", encoding="utf-8") as set_f4: + lines4 = set_f4.readlines() if lines4[7] != "off\n": print(lines4[7]) From d2a57b329b331b6864e7ae2ec3b2feb74ece9e78 Mon Sep 17 00:00:00 2001 From: punix Date: Thu, 24 Apr 2025 23:04:34 +0200 Subject: [PATCH 09/61] Class LxTools expands wg_set to set_file renamed --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 22727 -> 23194 bytes cls_mth_fc.py | 224 +++++++++++++------------ wirepy.py | 41 +++-- 3 files changed, 136 insertions(+), 129 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 604aa1f922a23fb84a7819951e01c6d85be5503f..f2b87c1cee3128a5553e7ec4b4586ef07ce98bcd 100644 GIT binary patch delta 5775 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 g4K(e!M)6DVY94~F1k+vNGbk*0AKs(g#Z8m delta 5317 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 Aborting with exit code {exit_code}." + ) + LxTools.clean_files(dirname) + print("Breakdown by user...") + sys.exit(exit_code) + else: + print(f"Signal {signum} received and ignored.") + LxTools.clean_files(dirname) + print("Process unexpectedly ended...") + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGHUP, signal_handler) + class GiteaUpdate: """ @@ -185,7 +285,7 @@ class GiteaUpdate: """ @staticmethod - def api_down(update_api_url, version): + def api_down(update_api_url, version, file=None): """ Calling api_down requests the URL and the version of the running script. Example: version = 'v. 1.1.1.1' GiteaUpdate.api_down(http://example.de, version) @@ -194,7 +294,7 @@ class GiteaUpdate: response = requests.get(update_api_url, timeout=10) response_dict = response.json() response_dict = response_dict[0] - with open(wg_set, "r", encoding="utf-8") as set_f: + with open(file, "r", encoding="utf-8") as set_f: set_f = set_f.read() if "on\n" in set_f: if version[3:] != response_dict["tag_name"]: @@ -224,7 +324,7 @@ class GiteaUpdate: ii = down_ok_image wt = _("Download Successful") msg_t = _("Your zip file is in home directory") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -233,7 +333,8 @@ class GiteaUpdate: ii = down_not_ok_image wt = _("Download error") msg_t = _("Download failed! Please try again") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) + except subprocess.CalledProcessError: # img_w, img_i, w_title, w_txt hand over @@ -241,52 +342,7 @@ class GiteaUpdate: ii = down_not_ok_image wt = _("Download error") msg_t = _("Download failed! No internet connection!") - msg_window(iw, ii, wt, msg_t) - - -def msg_window(img_w, img_i, w_title, w_txt, txt2=None, com=None): - """ - Function for different message windows for the user. with 4 arguments to be passed. - To create messages with your own images, icons, and titles. - As an alternative to Python Messagebox. - Paths to images must be specified: r'/usr/share/icons/lx-icons/64/info.png' - img_w = Image for Tk Window - img_i = Image for Icon - w_title = Windows Title - w_txt = Text for Tk Window - txt2 = Text for Button two - com = function for Button command - """ - msg = tk.Toplevel() - msg.resizable(width=False, height=False) - msg.title(w_title) - msg.configure(pady=15, padx=15) - msg.img = tk.PhotoImage(file=img_w) - msg.i_window = tk.Label(msg, image=msg.img) - - label = tk.Label(msg, text=w_txt) - - label.grid(column=1, row=0) - - if txt2 is not None and com is not None: - label.config(font=("Ubuntu", 11), padx=15, justify="left") - msg.i_window.grid(column=0, row=0, sticky="nw") - button2 = ttk.Button(msg, text=f"{txt2}", command=com, padding=4) - button2.grid(column=0, row=1, sticky="e", columnspan=2) - button = ttk.Button(msg, text="OK", command=msg.destroy, padding=4) - button.grid(column=0, row=1, sticky="w", columnspan=2) - - else: - label.config(font=("Ubuntu", 11), padx=15) - msg.i_window.grid(column=0, row=0) - button = ttk.Button(msg, text="OK", command=msg.destroy, padding=4) - button.grid(column=0, columnspan=2, row=1) - - img_i = tk.PhotoImage(file=img_i) - msg.iconphoto(True, img_i) - msg.columnconfigure(0, weight=1) - msg.rowconfigure(0, weight=1) - msg.winfo_toplevel() + LxTools.msg_window(iw, ii, wt, msg_t) class Tunnel: @@ -393,7 +449,7 @@ class Tunnel: ii = r"/usr/share/icons/lx-icons/48/wg_vpn.png" wt = _("Export Successful") msg_t = _("Your zip file is in home directory") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -402,7 +458,7 @@ class Tunnel: ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Export error") msg_t = _("Export failed! Please try again") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -411,60 +467,12 @@ class Tunnel: ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please first import tunnel") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) except TypeError: pass -def sigi(dirname): - """ - function for clean up after break - """ - - def signal_handler(signum, frame): - """ - Determine clear text names for signal numbers - """ - signals_to_names_dict = dict( - (getattr(signal, n), n) - for n in dir(signal) - if n.startswith("SIG") and "_" not in n - ) - signame = signals_to_names_dict.get(signum, f"Unnamed signal: {signum}") - - # End program for certain signals, report to others only reception - if signum in (signal.SIGINT, signal.SIGTERM): - exit_code = 1 - print( - f"\nSignal {signame} {(signum)} received. => Aborting with exit code {exit_code}." - ) - shutil.rmtree(dirname) - Path.unlink("/tmp/.loguser") - print("Breakdown by user...") - sys.exit(exit_code) - else: - print(f"Signal {signum} received and ignored.") - shutil.rmtree(dirname) - Path.unlink("/tmp/.loguser") - print("Process unexpectedly ended...") - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGHUP, signal_handler) - - -def if_tip(path): - """ - method that writes in file whether tooltip is displayed or not - """ - with open(path, "r", encoding="utf-8") as set_f2: - lines2 = set_f2.readlines() - if "False\n" in lines2: - tip = False - else: - tip = True - return tip class Tooltip: diff --git a/wirepy.py b/wirepy.py index 0560043..d3056c8 100755 --- a/wirepy.py +++ b/wirepy.py @@ -14,8 +14,7 @@ from pathlib import Path from subprocess import check_call from tkinter import TclError, filedialog, ttk -from cls_mth_fc import (Create, GiteaUpdate, Tooltip, Tunnel, if_tip, - LxTools, msg_window, sigi) +from cls_mth_fc import (Create, GiteaUpdate, Tooltip, Tunnel, LxTools) LxTools.uos() Create.dir_and_files() @@ -23,15 +22,16 @@ Create.make_dir() Create.decrypt() tcl_path = Path("/usr/share/TK-Themes") -wg_set = Path(Path.home() / ".config/wire_py/settings") -tips = if_tip(wg_set) +set_file = Path(Path.home() / ".config/wire_py/settings") +tips = LxTools.if_tip(set_file) dirname = Path("/tmp/tlecdcwg/") +userfile = Path("/tmp/.loguser") # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year VERSION = "v. 2.04.1725" res = GiteaUpdate.api_down( - "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION + "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, set_file ) # Translate @@ -42,7 +42,7 @@ gettext.bindtextdomain(APP, LOCALE_DIR) gettext.textdomain(APP) _ = gettext.gettext -sigi(dirname) +LxTools.sigi(dirname, userfile) class Wirepy(tk.Tk): @@ -168,7 +168,7 @@ class FrameWidgets(ttk.Frame): txt2 = _("Go to Wire-Py git") com = link_btn - msg_window(iw, ii, wt, msg_t, txt2, com) + LxTools.msg_window(iw, ii, wt, msg_t, txt2, com) # Frame for Menu self.menu_frame = ttk.Frame(self) @@ -197,8 +197,8 @@ class FrameWidgets(ttk.Frame): self.settings.add_checkbutton( label=_("Disable Tooltips"), command=tooltip, variable=set_tip ) - self.settings.add_command(label=_("Light"), command=lambda: LxTools.theme_change_light(self)) - self.settings.add_command(label=_("Dark"), command=lambda: LxTools.theme_change_dark(self)) + self.settings.add_command(label=_("Light"), command=lambda: LxTools.theme_change_light(self, set_file)) + self.settings.add_command(label=_("Dark"), command=lambda: LxTools.theme_change_dark(self, set_file)) # About BTN Menu / Label self.about_btn = ttk.Button( @@ -427,7 +427,7 @@ class FrameWidgets(ttk.Frame): ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please select a tunnel from the list") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -436,7 +436,7 @@ class FrameWidgets(ttk.Frame): ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please first import tunnel") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) # Button Trash self.btn_tr = ttk.Button( @@ -500,7 +500,7 @@ class FrameWidgets(ttk.Frame): ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") msg_t = _("The new name may contain only 12 characters") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) elif len(self.lb_rename.get()) == 0: @@ -509,7 +509,7 @@ class FrameWidgets(ttk.Frame): ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") msg_t = _("At least one character must be entered") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) elif any(ch in special_characters for ch in self.lb_rename.get()): @@ -520,7 +520,7 @@ class FrameWidgets(ttk.Frame): msg_t = _( "No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n" ) - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -572,7 +572,7 @@ class FrameWidgets(ttk.Frame): ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") msg_t = _("Please select a tunnel from the list") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) # Button Rename self.btn_rename = ttk.Button( @@ -678,7 +678,7 @@ class FrameWidgets(ttk.Frame): msg_t = _( "Tunnel already available!\nPlease use another file for import" ) - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -827,7 +827,7 @@ class FrameWidgets(ttk.Frame): msg_t = _( "Oh... no valid Wireguard File!\nPlease select a valid Wireguard File" ) - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) except EOFError as e: print(e) @@ -1056,7 +1056,7 @@ class FrameWidgets(ttk.Frame): ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please select a tunnel from the list") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -1065,7 +1065,7 @@ class FrameWidgets(ttk.Frame): ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please first import tunnel") - msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(iw, ii, wt, msg_t) if __name__ == "__main__": @@ -1081,6 +1081,5 @@ if __name__ == "__main__": window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0") window.mainloop() -shutil.rmtree(dirname) -Path.unlink("/tmp/.loguser") +LxTools.clean_files(dirname, userfile) sys.exit(0) From af702f297bf11690ad7ecd0d8b57bde7011b4947 Mon Sep 17 00:00:00 2001 From: punix Date: Fri, 25 Apr 2025 13:40:19 +0200 Subject: [PATCH 10/61] rename wg_set to file_set fix LxTools.clean_files --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23194 -> 23208 bytes cls_mth_fc.py | 12 ++++---- wirepy.py | 38 ++++++++++++------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index f2b87c1cee3128a5553e7ec4b4586ef07ce98bcd..17c51db277dab0f0908ad38eec3132c17cb74c6d 100644 GIT binary patch delta 238 zcmbQWm2t&ZM&8rByj%=Gu(y~y!(=1xLK()q$?IjD7{w;vHY{@nEzCe7Az|`a17((<4Gdo=e=&H? VE%%w3flcTG6AP={=2wOrGyt`;L{@nm0F zGe-T%EwbutCNtEo@+fUyFDuK#^8%=|onbmd2SXa;<~y3YjBKkI85pKAPUhEM%ji3K zueRUhOY9<(SMcym*44=Zu{W>Pnasqe zDF#MhdbWXqfH6=_(Lo?l%nT$F6eiy`P-gkj!0=@ Aborting with exit code {exit_code}." ) - LxTools.clean_files(dirname) + LxTools.clean_files(dirname, file) print("Breakdown by user...") sys.exit(exit_code) else: print(f"Signal {signum} received and ignored.") - LxTools.clean_files(dirname) + LxTools.clean_files(dirname, file) print("Process unexpectedly ended...") signal.signal(signal.SIGINT, signal_handler) diff --git a/wirepy.py b/wirepy.py index d3056c8..da37fcd 100755 --- a/wirepy.py +++ b/wirepy.py @@ -68,7 +68,7 @@ class Wirepy(tk.Tk): self.style = ttk.Style(self) self.tk.call("source", f"{tcl_path}/water.tcl") - with open(wg_set, "r", encoding="utf-8") as read_file: + with open(set_file, "r", encoding="utf-8") as read_file: lines = read_file.readlines() if "light\n" in lines: self.tk.call("set_theme", "light") @@ -120,17 +120,17 @@ class FrameWidgets(ttk.Frame): Set on or off in file """ if set_update.get() == 1: - with open(wg_set, "r", encoding="utf-8") as set_f2: + with open(set_file, "r", encoding="utf-8") as set_f2: lines2 = set_f2.readlines() lines2[1] = "off\n" - with open(wg_set, "w", encoding="utf-8") as set_f2: + with open(set_file, "w", encoding="utf-8") as set_f2: set_f2.writelines(lines2) if set_update.get() == 0: - with open(wg_set, "r", encoding="utf-8") as set_f2: + with open(set_file, "r", encoding="utf-8") as set_f2: lines2 = set_f2.readlines() lines2[1] = "on\n" - with open(wg_set, "w", encoding="utf-8") as set_f2: + with open(set_file, "w", encoding="utf-8") as set_f2: set_f2.writelines(lines2) def tooltip(): @@ -138,17 +138,17 @@ class FrameWidgets(ttk.Frame): Set True or False in file """ if set_tip.get(): - with open(wg_set, "r", encoding="utf-8") as set_f2: + with open(set_file, "r", encoding="utf-8") as set_f2: lines2 = set_f2.readlines() lines2[5] = "False\n" - with open(wg_set, "w", encoding="utf-8") as set_f2: + with open(set_file, "w", encoding="utf-8") as set_f2: set_f2.writelines(lines2) else: - with open(wg_set, "r", encoding="utf-8") as set_f2: + with open(set_file, "r", encoding="utf-8") as set_f2: lines2 = set_f2.readlines() lines2[5] = "True\n" - with open(wg_set, "w", encoding="utf-8") as set_f2: + with open(set_file, "w", encoding="utf-8") as set_f2: set_f2.writelines(lines2) def info(): @@ -372,14 +372,14 @@ class FrameWidgets(ttk.Frame): pre_key = key[3] check_call(["nmcli", "connection", "delete", select_tl]) self.l_box.delete(self.select_tunnel[0]) - with open(wg_set, "r", encoding="utf-8") as set_f6: + with open(set_filele, "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(wg_set, "w", encoding="utf-8") as set_f7: + with open(set_file, "w", encoding="utf-8") as set_f7: set_f7.writelines(lines6) self.selected_option.set(0) self.autoconnect_var.set(_("no Autoconnect")) @@ -552,14 +552,14 @@ class FrameWidgets(ttk.Frame): if self.a != "" and self.a == select_tl: self.a = Tunnel.active() self.str_var.set(value=self.a) - with open(wg_set, "r", encoding="utf-8") as set_f5: + with open(set_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(wg_set, "w", encoding="utf-8") as theme_set5: + with open(set_file, "w", encoding="utf-8") as theme_set5: theme_set5.writelines(lines5) self.autoconnect_var.set(value=new_a_connect) @@ -849,10 +849,10 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(select_tunnel[0]) if self.selected_option.get() == 0: - with open(wg_set, "r", encoding="utf-8") as set_f3: + with open(set_file, "r", encoding="utf-8") as set_f3: lines3 = set_f3.readlines() lines3[7] = "off\n" - with open(wg_set, "w", encoding="utf-8") as set_f3: + with open(set_file, "w", encoding="utf-8") as set_f3: set_f3.writelines(lines3) tl = Tunnel.list() @@ -861,10 +861,10 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") if self.selected_option.get() >= 1: - with open(wg_set, "r", encoding="utf-8") as set_f3: + with open(set_file, "r", encoding="utf-8") as set_f3: lines3 = set_f3.readlines() lines3[7] = select_tl - with open(wg_set, "w", encoding="utf-8") as set_f3: + with open(set_file, "w", encoding="utf-8") as set_f3: set_f3.writelines(lines3) except IndexError: @@ -878,7 +878,7 @@ 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 """ - with open(wg_set, "r", encoding="utf-8") as set_f4: + with open(set_file, "r", encoding="utf-8") as set_f4: lines4 = set_f4.readlines() if lines4[7] != "off\n": @@ -987,7 +987,7 @@ class FrameWidgets(ttk.Frame): View activ Tunnel in color green or yellow """ - with open(wg_set, "r", encoding="utf-8") as read_file: + with open(set_file, "r", encoding="utf-8") as read_file: lines = read_file.readlines() if "light\n" in lines: self.lb_tunnel = ttk.Label( From 67ff24f0b627e9470583ec205821761e29695a0e Mon Sep 17 00:00:00 2001 From: punix Date: Fri, 25 Apr 2025 14:42:37 +0200 Subject: [PATCH 11/61] fix set_file and remove wg_set variable --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23208 -> 23547 bytes cls_mth_fc.py | 26 ++++++++++++++----------- wirepy.py | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 17c51db277dab0f0908ad38eec3132c17cb74c6d..67aa271abd0b127f8673e9cbf341b79999c38dc9 100644 GIT binary patch delta 944 zcmZ{g-%Aux6vywG*%{Z}ab4GbSpK>iVun~Ig_(w8{gN@T(Yfm`tTVbZmYJl| zMk^5pLn-|MQ3R1(tcPBD=^@ILeo%x+^csPtrkF%`R#72359geF&bi;u;XD|J-_OAF zIw{G8nC%&G2hUWGc}hjdr8VMH9?I#T#47-ewAY=$i|5&lam!lv7SVA}1qWT$ymtoD zt#>IS{V~?NfkqHPGN?%ARFL@?3V_N*ow7r9seBY*TY6{jSDlY4l9O%>q1)h zS{Zi`GpMTuHiL?Z+Y}8eVME8Q1a})m#Q{Bnbu&mJSn2FE;tJNP?;gbHNa`yNZc=53 zUv#xH=m_dVgWIj!CD@wDkYLc7%P8!J%TT~60w5IsYA>+H+r%-FqN)s~1Wbm}2un>J zt0YWJ)?yokRU5Cd2^2nPy09FWL~UinJSL8!sEB_J<;x(n0XvmNk`@ zLacY1Azf3#1!cIUQ!#@!YrR>7kCTAX9X8o9bX>1%WK-Ohi^G`|+u?sgc591_?;o9d zD=$+$IO|1H>XbWcTJjD1hy0`YAEeyR?&K+V`n2R7t{ti!b$paEXL%%LMAF!8+ra9| z-bN=gD*;|mZ}xX5OwiT*RFQK4O(}W~90K}we?Gv9_3OYnpC?kvenpPhuFQ5vExEWk z^BPNyKIWFAXpZ9q=YkW2ee1tghF8pva-AxH3u={ zTui#^QWAtXXpnRrQ5VqQsLIYI zMeiYHDMkiNGNcq;g^8rykWGgvC1EInWx;ApGnFJQTLVQHHc>lGHHrUR)U_Gm+`(8wqyC`ved8H}4w>X};nc);v>UWR_;hr0Z=& z@qKJ}Ai%==UCvptby@PU?r^XM%P6l$pxffadql;i0%r49y?Vh+cWrunS@&c2;kH+nT`m$-3k9nvr2wZ|KP zBI{}ybGzg1;{JHpf|G2iCF-}nGjU76PLhfW)TV-6SA&HEtUeT|RMbF0CapRri7e;h z!=V)gLH?&Ls-lfw?3e@me4{g^VuDZfRm;fmoBf@@Q_cs@Yk0<#k%MY;_s7~^-ZWAI zF7ZR7KP7B)f8@Q4Jl`IB2k2~Vd=^_gWZcJDXABrZp2}1_Cyz=vk-CzJ&Buyl<)ulb d2WZKYg{1-M9#EAh3rl@U2n|0-K-@3h^A9z6sOta# diff --git a/cls_mth_fc.py b/cls_mth_fc.py index 31ab390..9c0997d 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -23,8 +23,6 @@ gettext.bindtextdomain(APP, LOCALE_DIR) gettext.textdomain(APP) _ = gettext.gettext -#wg_set = Path(Path.home() / ".config/wire_py/settings") - class Create: """ @@ -129,12 +127,15 @@ class Create: else: print(f"Error with the following code... {process.returncode}") + class LxTools(tk.Tk): - + """ + Class LinuxTools methods that can also be used for other apps + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def theme_change_light(self, file=None): """ Set light theme @@ -148,7 +149,6 @@ class LxTools(tk.Tk): theme_set2.writelines(lines3) self.color_label() - def theme_change_dark(self, file=None): """ Set dark theme @@ -178,9 +178,15 @@ class LxTools(tk.Tk): @staticmethod def clean_files(dirname=None, file=None): - if dirname != None: - shutil.rmtree(dirname) - if file != None: + """ + method that can be added after need to delete a folder and a file when quitting. + Args: + dirname (_folder_, optional): _path to folder_. Defaults to None. + file (_file_, optional): _path to file_. Defaults to None. + """ + if dirname is not None: + shutil.rmtree(dirname) + if file is not None: Path.unlink(file) @staticmethod @@ -431,7 +437,7 @@ class Tunnel: in the user's home directory with correct right """ now_time = datetime.now() - now_datetime = now_time.strftime(f"wg-exp-%m-%d-%Y-%H:%M") + now_datetime = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") tl = Tunnel.list() try: @@ -473,8 +479,6 @@ class Tunnel: pass - - class Tooltip: """ class for Tooltip diff --git a/wirepy.py b/wirepy.py index da37fcd..7cd4cae 100755 --- a/wirepy.py +++ b/wirepy.py @@ -372,7 +372,7 @@ class FrameWidgets(ttk.Frame): pre_key = key[3] check_call(["nmcli", "connection", "delete", select_tl]) self.l_box.delete(self.select_tunnel[0]) - with open(set_filele, "r", encoding="utf-8") as set_f6: + with open(set_file, "r", encoding="utf-8") as set_f6: lines6 = set_f6.readlines() if ( select_tl == lines6[7].strip() From f9ecd54e0a18a642eba008e82f06c9d8f7fc52a8 Mon Sep 17 00:00:00 2001 From: punix Date: Fri, 25 Apr 2025 16:19:36 +0200 Subject: [PATCH 12/61] methods back in wirepy as functions "theme dark and light" --- .idea/dictionaries/project.xml | 3 +++ .idea/workspace.xml | 5 +++-- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23547 -> 21850 bytes cls_mth_fc.py | 27 ---------------------- wirepy.py | 30 +++++++++++++++++++++++-- 5 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 .idea/dictionaries/project.xml diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..4787784 --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3a30f82..71a1f4b 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,7 +5,8 @@ - + + diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 67aa271abd0b127f8673e9cbf341b79999c38dc9..a683d9cc65388184f3199ac0095d84e42b10a998 100644 GIT binary patch delta 1400 zcmZvbUrZcT6vp?=?948^Kq{orB3&1DD-5PBubFj6csm4D|sxih+W7C-UWYQ`R#wxW=jrPHqU}~a1kCO6z5#;(HtKsnaAEi z_ZP5$dg6+};#9SOsp&yoGt?ZG1s{d8RxX|eQRo|MQp0&&l89>D6_C|RdTYD33 zxQtTQR{s^MxU;KG{+!|^E;r-3u9=D(6mH?LMO`)S{w=H*o8y&m5R>sbSirgX zuF_>ny+-(kaGe9!DeLiz9Z<2PF;2{ssCKPvn!zks}n>Cj3*i@i8^N|#`i?`$DRhLV@L#N*Rz`W{oZzgd2fc9H*`9cKf{88!?b<0Q2rL$g{+0j6bcB1u7$<0(|HdVc+7O~ zdvsuerD`=rO`*MNFh;`{4O(MsUShi&KUg<$H@j+rWw*`St{VNIb=L$#EruqWz2{A# z*mkptAH9?L&AI2EbMATP{?5B&zJYF>L#cmtyPXUj{-dt`{dI4r{>F=rlL2qexa^d< zW2lR#zc%_SD0Z1Y#+(LQI=UQm?vjOLXjPL!HTE?YPRqiv#=>Q3Qr2k1)8+3{w|iem*4H~Pq-9Vt6F**V6^u4;%T!smau zyMN@)Jj#+Yw)EU_?lem7f$nf0V+LI0q%CV4F{9$B=x0h84aHf+bTWv3o~;|Fd{X^?qeYS=BE)b$xK>PE8+G6_dBtOm0|HmYwmCuHZsB zga^mX6iv~4l8d|*#YA|SAJ%&dt6oQ@Ly3lCawOW95UfS#Rj6)@iQ=ydCaVvcws1&Q zO5|Y(ORMAXl97l8|DZ9ckHHLfC&_P9&YfVA^^kG%2S(4jK2*6iQ9H z0rf1?Y;+x-3#Wsj*GjD?+#ibeDLqyWiL9I4D7tg)8S10GMd2v3;9x}0WaUKVWc5V# zv^di@JABbqaX$MK*MpboLU1BD*)%cUH2sU2zIn0e%KEe^dAe?P)BO6<1%`ERJUejN z=fC7Dobwf4^lh6GE@ua(gsI=1Oj+XT>NSD!rmt~8?jq{o$-3kT%y|Mcz4M;JC5|dT z7d@B6>^U*}Z{ocR>lm>fE!n8MwZrm~fNI=D3kB%La2oAt^|dpX&NWV&v78A=bp*7xRamxxA+hpK;7TU-J6wwz3chxS?zLE}FT1`uh*8D1aw z2!##_nL=EFx2ojDzor^vI?s>LN5;GNZxC1;B6FKN(HPkx&7d>nLn$A{$ahjJO|G_} zk!27$QSe#*8Hjcs;P(_vp}irvXMZc*D-U!QuP%_2Tk_E|d1p&=1Kvf|65G-urQ#~E z*a5H;;Fkb(06hRQ0NhG=03ZgS1Iz;ak<@H0%Ne8M5677ASeAEOb0EH8QE>71EoM)71eQ>MvTr5hPfJBOe;yLPU+6J^(6dxe(7T2NfQze}!V04un=jh3OS)PG% z@WZf~3YL7hKNgST5>WgKV3uTVe+AVT=e9qB{FgxUFMt|=OaOSd;}2HgUQ$_AgFY~Z ztNzXfA#waP05l|iio#X6t8p(#b^r%J3h4d}a1^E@fE&Q#;s`laU6%*kF?sEf8qrMw zD?_ov3WjHs(oHU`YPg$RudWg%s0K$!dCfDZ&v>uqbGGbb(EJAsDGfqlJ#tvl;4V*I zN;rec33tbEG^(gY$>rybiw_70r4e&iC3?;9*0$M@N#yz>*FPYZRq89`<@yJNf5QAz zDh7xnIGXh>ObaQPoT^0EbdyS63TC4raz0pqX33YqU7oMN^lN~t0N;@EJw@oO5#IA} zRO+QEB`E>PSpjh&NVnD8+793#d91M)eQSK(*u|n!QrBEYC-LTaR zQ3{fX%OKh&P?VCH)_eh)0-qp%Y27G12jUCldTUYDY~wV&_h>a^bd-H)4&$Eimj$B0O=1DOuIf9iPC>VR*z#pxzV17J|sCEyImGL lt#TC;wIc^TOrGv|!Fzy36~_-wDig{z2IQr~q;&u0{{p5-;DP`E diff --git a/cls_mth_fc.py b/cls_mth_fc.py index 9c0997d..8debe1f 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -136,32 +136,6 @@ class LxTools(tk.Tk): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def theme_change_light(self, file=None): - """ - Set light theme - """ - if self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "light") - with open(file, "r", encoding="utf-8") as theme_set2: - lines3 = theme_set2.readlines() - lines3[3] = "light\n" - with open(file, "w", encoding="utf-8") as theme_set2: - theme_set2.writelines(lines3) - self.color_label() - - def theme_change_dark(self, file=None): - """ - Set dark theme - """ - if not self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "dark") - with open(file, "r", encoding="utf-8") as theme_set2: - lines4 = theme_set2.readlines() - lines4[3] = "dark\n" - with open(file, "w", encoding="utf-8") as theme_set2: - theme_set2.writelines(lines4) - self.color_label() - @staticmethod def uos(): """ @@ -282,7 +256,6 @@ class LxTools(tk.Tk): signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGHUP, signal_handler) - class GiteaUpdate: """ Calling download requests the download URL of the running script, diff --git a/wirepy.py b/wirepy.py index 7cd4cae..eca364f 100755 --- a/wirepy.py +++ b/wirepy.py @@ -133,6 +133,32 @@ class FrameWidgets(ttk.Frame): with open(set_file, "w", encoding="utf-8") as set_f2: set_f2.writelines(lines2) + def theme_change_light(): + """ + Set light theme + """ + if self.tk.call("ttk::style", "theme", "use") == "water-dark": + self.tk.call("set_theme", "light") + with open(set_file, "r", encoding="utf-8") as theme_set2: + lines3 = theme_set2.readlines() + lines3[3] = "light\n" + with open(set_file, "w", encoding="utf-8") as theme_set2: + theme_set2.writelines(lines3) + self.color_label() + + def theme_change_dark(): + """ + Set dark theme + """ + if not self.tk.call("ttk::style", "theme", "use") == "water-dark": + self.tk.call("set_theme", "dark") + with open(set_file, "r", encoding="utf-8") as theme_set2: + lines4 = theme_set2.readlines() + lines4[3] = "dark\n" + with open(set_file, "w", encoding="utf-8") as theme_set2: + theme_set2.writelines(lines4) + self.color_label() + def tooltip(): """ Set True or False in file @@ -197,8 +223,8 @@ class FrameWidgets(ttk.Frame): self.settings.add_checkbutton( label=_("Disable Tooltips"), command=tooltip, variable=set_tip ) - self.settings.add_command(label=_("Light"), command=lambda: LxTools.theme_change_light(self, set_file)) - self.settings.add_command(label=_("Dark"), command=lambda: LxTools.theme_change_dark(self, set_file)) + self.settings.add_command(label=_("Light"), command=theme_change_light) + self.settings.add_command(label=_("Dark"), command=theme_change_dark) # About BTN Menu / Label self.about_btn = ttk.Button( From f6204c907120235518690e6e69cc798315026430 Mon Sep 17 00:00:00 2001 From: punix Date: Fri, 25 Apr 2025 17:37:04 +0200 Subject: [PATCH 13/61] line formatted for better reading --- .idea/workspace.xml | 31 ++- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 21850 -> 21831 bytes cls_mth_fc.py | 50 +--- ssl_decrypt.py | 28 +-- ssl_encrypt.py | 44 +--- start_wg.py | 2 +- wirepy.py | 326 ++++++------------------- 7 files changed, 117 insertions(+), 364 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 71a1f4b..5c5afba 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -7,6 +7,10 @@ + + + + - + + + + + diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index a683d9cc65388184f3199ac0095d84e42b10a998..032154260be20b32f5c412ab881cd2653f6d0afd 100644 GIT binary patch delta 726 zcmb7>-%FEG7{||ZUfWx`;&=n$Sg@!ay(VDyf?@9QcC>iLUHs%U2}mqVrDwfzHMEe0cbN&v|&B?M3*! z252I@ZDjX-)rTOKoX?UphykW%JHg&X1X_ z-OqYM?9SL`VNtY~FV^aeh&y~c&W>NOtt!57{s%pm!`pj^lGq>`_Od0hcF$rF6|IcE z^#$SbH2^OpQR>phaTvf8>3cfxctArsAB2TQfKy4N>p>Hq0I(*Jt_S0S3(9{ZE~%OQ E1EVwEI{*Lx delta 749 zcmb7B-%Auh9G~y3v*+FIuI}#L?VV(qL75&xXP!aIT|sooTK*_S3yZo7iF(NLp{Nvu zy5kU$EwmmY2to`38GFj0H>rpgBUmJ`2zs!W-P(|zYWBK+pkesTXFl`&d}qG%U0Hy& z1(4o~VgRwRIu{%nPAy6uJlr;PenhmY2V4$|l3j@eiN569Ili66UAbud@egSj%R0DR zg($fJBx zNm+8(QX;OOsDZrC6;O4EE=v1x^0Md6{-4(`8QbMo!y3_ka)?)K%$S{E%)u%-3T4rw zc4JBPku7DBFqu+qjV`XER(z}i2Y?>ibCN(k zeVQBx%mIz>yF);yOC1WAxDG7YA(o8wK|o^Fbf%Co!}wJM>m0svFIeyK%XWdBm zEHzky#5y}0n~7Q44oll=sWnzKX2}YhuI!fsd18kUYgnZBJGch!y8$c4cwdA>N!Z;> zAM|D6NDe>;2bC7Lo4Wkj+mQeO diff --git a/cls_mth_fc.py b/cls_mth_fc.py index 8debe1f..ec49e7b 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -47,9 +47,7 @@ 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 ks.exists(): pass @@ -72,11 +70,9 @@ class Create: else: wg_ser.touch() - wg_ser.write_text( - "[Unit]\nDescription=Automatic Tunnel Start\nAfter=network-online.target" - "\n\n[Service]\nType=oneshot\nExecStartPre=/bin/sleep 5\nExecStart=/usr/" - "local/bin/start_wg.py\n[Install]\nWantedBy=default.target" - ) + 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 @@ -94,12 +90,8 @@ class Create: """ This start ssl_decrypt file """ - process = subprocess.run( - ["pkexec", "/usr/local/bin/ssl_decrypt.py"], - stdout=subprocess.PIPE, - text=True, - check=True, - ) + process = subprocess.run(["pkexec", "/usr/local/bin/ssl_decrypt.py"], stdout=subprocess.PIPE, text=True, + check=True) path = Path.home() / ".config/wire_py/" file_in_path = list(path.rglob("*.dat")) if file_in_path: @@ -115,12 +107,8 @@ class Create: """ this start ssl_encrypt file """ - process = subprocess.run( - ["pkexec", "/usr/local/bin/ssl_encrypt.py"], - stdout=subprocess.PIPE, - text=True, - check=True, - ) + process = subprocess.run(["pkexec", "/usr/local/bin/ssl_encrypt.py"], stdout=subprocess.PIPE, text=True, + check=True) print(process.stdout) if process.returncode == 0: print("All Files successfully encrypted...") @@ -241,9 +229,7 @@ class LxTools(tk.Tk): # End program for certain signals, report to others only reception if signum in (signal.SIGINT, signal.SIGTERM): exit_code = 1 - print( - f"\nSignal {signame} {(signum)} received. => Aborting with exit code {exit_code}." - ) + print(f"\nSignal {signame} {(signum)} received. => Aborting with exit code {exit_code}.") LxTools.clean_files(dirname, file) print("Breakdown by user...") sys.exit(exit_code) @@ -380,11 +366,7 @@ 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: @@ -485,16 +467,8 @@ class Tooltip: tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") - label = tk.Label( - tw, - text=self.text, - background="lightgreen", - foreground="black", - relief="solid", - borderwidth=1, - padx=5, - pady=5, - ) + label = tk.Label(tw, text=self.text, background="lightgreen", foreground="black", relief="solid", borderwidth=1, + padx=5, pady=5) label.grid() def hide_tooltip(self, event=None): diff --git a/ssl_decrypt.py b/ssl_decrypt.py index cb3ef98..c2352c9 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -18,19 +18,7 @@ PKEYFILE = "/usr/local/etc/ssl/pwgk.pem" if not keyfile.is_file(): - check_call( - [ - "openssl", - "rsa", - "-in", - PKEYFILE, - "-out", - keyfile, - "-outform", - "PEM", - "-pubout", - ] - ) + check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) dirname2 = f"/home/{logname}/.config/wire_py/" @@ -43,17 +31,5 @@ if os.path.exists(f"{dirname2}pbwgk.pem"): for detunnels in detl: tlname2 = f"{detunnels[:-4]}.conf" extpath = f"{dirname}/{tlname2}" - check_call( - [ - "openssl", - "pkeyutl", - "-decrypt", - "-inkey", - PKEYFILE, - "-in", - detunnels, - "-out", - extpath, - ] - ) + check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", PKEYFILE, "-in", detunnels, "-out", extpath]) shutil.chown(extpath, 1000, 1000) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 4069f79..3433a26 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -17,19 +17,7 @@ PKEYFILE = "/usr/local/etc/ssl/pwgk.pem" if not keyfile.is_file(): - check_call( - [ - "openssl", - "rsa", - "-in", - PKEYFILE, - "-out", - keyfile, - "-outform", - "PEM", - "-pubout", - ] - ) + check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) if dirname.exists(): @@ -41,20 +29,8 @@ if not keyfile.is_file(): for tunnels in tl: sourcetl = f"{dirname}/{tunnels}" tlname = 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: @@ -68,16 +44,4 @@ else: sourcetl = f"{dirname}/{tunnels}" tlname = f"{CRYPTFILES}{tunnels[:-5]}.dat" check_call( - [ - "openssl", - "pkeyutl", - "-encrypt", - "-inkey", - keyfile, - "-pubin", - "-in", - sourcetl, - "-out", - tlname, - ] - ) + ["openssl", "pkeyutl", "-encrypt", "-inkey", keyfile, "-pubin", "-in", sourcetl, "-out", tlname]) diff --git a/start_wg.py b/start_wg.py index 52e8184..84999a1 100755 --- a/start_wg.py +++ b/start_wg.py @@ -9,7 +9,7 @@ path_to_file = Path(Path.home() / ".config/wire_py/settings") with open(path_to_file, "r", encoding="utf-8") as a_con: - # This funtion is for the independent autostart of the previously selected tunnel + # This function is for the independent autostarted of the previously selected tunnel lines = a_con.readlines() a_con = lines[7].strip() if a_con != "off": diff --git a/wirepy.py b/wirepy.py index eca364f..4fcf804 100755 --- a/wirepy.py +++ b/wirepy.py @@ -30,9 +30,7 @@ userfile = Path("/tmp/.loguser") # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year VERSION = "v. 2.04.1725" -res = GiteaUpdate.api_down( - "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, set_file -) +res = GiteaUpdate.api_down("https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, set_file) # Translate APP = "wirepy" @@ -60,9 +58,7 @@ class Wirepy(tk.Tk): self.monitor_center_y = int(self.winfo_screenheight() / 2 - (self.y_height / 2)) self.resizable(width=False, height=False) self.title("Wire-Py") - self.geometry( - f"{self.x_width}x{self.y_height}+{self.monitor_center_x}+{self.monitor_center_y}" - ) + self.geometry(f"{self.x_width}x{self.y_height}+{self.monitor_center_x}+{self.monitor_center_y}") self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) @@ -98,22 +94,14 @@ 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.wg_vpn_start = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn-start.png") + self.wg_vpn_stop = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn-stop.png") self.imp_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_import.png") self.tr_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_trash.png") self.exp_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_export.png") self.warning_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/64/error.png") - self.wg_vpn_start = tk.PhotoImage( - file=r"/usr/share/icons/lx-icons/48/wg_vpn-start.png" - ) - self.wg_vpn_stop = tk.PhotoImage( - file=r"/usr/share/icons/lx-icons/48/wg_vpn-stop.png" - ) + self.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") def update(): """ @@ -185,12 +173,10 @@ class FrameWidgets(ttk.Frame): iw = r"/usr/share/icons/lx-icons/48/wg_vpn.png" ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Info") - msg_t = _( - "Wire-Py a simple Wireguard Gui for Linux systems.\n\n" - "Wire-Py is open source software written in Python.\n\n" - "Email: polunga40@unity-mail.de also likes for donation.\n\n" - "Use without warranty!\n" - ) + msg_t = _("Wire-Py a simple Wireguard Gui for Linux systems.\n\n" + "Wire-Py is open source software written in Python.\n\n" + "Email: polunga40@unity-mail.de also likes for donation.\n\n" + "Use without warranty!\n") txt2 = _("Go to Wire-Py git") com = link_btn @@ -217,12 +203,8 @@ class FrameWidgets(ttk.Frame): set_tip = tk.BooleanVar() self.settings = tk.Menu(self, relief="flat") self.options_btn.configure(menu=self.settings, style="Toolbutton") - self.settings.add_checkbutton( - label=_("Disable Updates"), command=update, variable=set_update - ) - self.settings.add_checkbutton( - label=_("Disable Tooltips"), command=tooltip, variable=set_tip - ) + self.settings.add_checkbutton(label=_("Disable Updates"), command=update, variable=set_update) + self.settings.add_checkbutton(label=_("Disable Tooltips"), command=tooltip, variable=set_tip) self.settings.add_command(label=_("Light"), command=theme_change_light) self.settings.add_command(label=_("Dark"), command=theme_change_dark) @@ -272,13 +254,9 @@ class FrameWidgets(ttk.Frame): self.update_btn.configure(menu=self.download, style="Toolbutton") self.download.add_command( label=_("Download"), - command=lambda: GiteaUpdate.download( - f"https://git.ilunix.de/punix/Wire-Py/archive/{res}.zip", - r"/usr/share/icons/lx-icons/48/wg_vpn.png", - r"/usr/share/icons/lx-icons/48/wg_msg.png", - res, - ), - ) + command=lambda: GiteaUpdate.download(f"https://git.ilunix.de/punix/Wire-Py/archive/{res}.zip", + r"/usr/share/icons/lx-icons/48/wg_vpn.png", + r"/usr/share/icons/lx-icons/48/wg_msg.png", res)) # Show active Tunnel self.a = Tunnel.active() @@ -301,8 +279,7 @@ 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) @@ -346,9 +323,7 @@ class FrameWidgets(ttk.Frame): self.l_box.grid(column=1, rowspan=4, row=0, sticky="ns") self.l_box.event_add("<>", "") self.l_box.bind("<>", enable_check_box) - self.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) @@ -379,8 +354,7 @@ 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, _("Click to import a Wireguard Tunnel"), tips) @@ -392,8 +366,7 @@ 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]) @@ -427,7 +400,8 @@ 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, _("You must have at least one\ntunnel in the list,to use the autostart"), tips) + Tooltip(self.wg_autostart, _("You must have at least one\ntunnel in the list,to use the autostart") + , tips) Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) Tooltip(self.btn_stst, _("No tunnels to start in the list"), tips) @@ -465,36 +439,23 @@ class FrameWidgets(ttk.Frame): LxTools.msg_window(iw, ii, wt, msg_t) # Button Trash - self.btn_tr = ttk.Button( - self.lb_frame_btn_lbox, - image=self.tr_pic, - command=delete, - padding=0, - style="CButton.TButton", - ) + self.btn_tr = ttk.Button(self.lb_frame_btn_lbox, image=self.tr_pic, command=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, _("No tunnels to delete in the list"), tips) else: - Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), - tips, - ) + Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), tips) # Button Export - self.btn_exp = ttk.Button( - self.lb_frame_btn_lbox, image=self.exp_pic, command=Tunnel.export, padding=0 - ) + self.btn_exp = ttk.Button(self.lb_frame_btn_lbox, image=self.exp_pic, command=Tunnel.export, padding=0) self.btn_exp.grid(column=0, row=3, padx=15, pady=8) if self.l_box.size() == 0: Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) else: - Tooltip( - self.btn_exp, - _("Click to export all\nWireguard Tunnel to Zipfile"), - tips, - ) + Tooltip(self.btn_exp, _("Click to export all\nWireguard Tunnel to Zipfile"), tips) # Label Entry self.lb_rename = ttk.Entry(self.lb_frame4, width=20) @@ -503,17 +464,9 @@ class FrameWidgets(ttk.Frame): self.lb_rename.config(state="disable") if self.l_box.size() != 0: - Tooltip( - self.lb_rename, - _("To rename a tunnel, you need to\nselect a tunnel from the list"), - tips, - ) + Tooltip(self.lb_rename, _("To rename a tunnel, you need to\nselect a tunnel from the list"), tips) else: - Tooltip( - self.lb_rename, - _("To rename a tunnel, at least one must be in the list"), - tips, - ) + Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), tips) def tl_rename(): @@ -543,9 +496,7 @@ class FrameWidgets(ttk.Frame): iw = r"/usr/share/icons/lx-icons/64/info.png" ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") - msg_t = _( - "No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n" - ) + msg_t = _("No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n") LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -555,16 +506,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(), - ] - ) + check_call(["nmcli", "connection", "modify", select_tl, "connection.id", self.lb_rename.get()]) source = Path(f"/tmp/tlecdcwg/{select_tl}.conf") destination = source.with_name(f"{self.lb_rename.get()}.conf") source.replace(destination) @@ -580,10 +522,7 @@ class FrameWidgets(ttk.Frame): self.str_var.set(value=self.a) with open(set_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() - ): + if select_tl == lines5[7].strip() and "off\n" not in lines5[7].strip(): lines5[7] = new_a_connect with open(set_file, "w", encoding="utf-8") as theme_set5: theme_set5.writelines(lines5) @@ -601,14 +540,8 @@ class FrameWidgets(ttk.Frame): LxTools.msg_window(iw, ii, wt, msg_t) # Button Rename - self.btn_rename = ttk.Button( - self.lb_frame4, - text=_("Rename"), - state="disable", - command=tl_rename, - padding=4, - style="RnButton.TButton", - ) + self.btn_rename = ttk.Button(self.lb_frame4, text=_("Rename"), state="disable", command=tl_rename, padding=4, + style="RnButton.TButton") self.btn_rename.grid(column=3, row=0, padx=5, pady=10, sticky="ne") # Check Buttons @@ -617,40 +550,22 @@ class FrameWidgets(ttk.Frame): self.autoconnect_var.set(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, _("To use the autostart, enable this Checkbox"), tips - ) + Tooltip(self.wg_autostart, _("To use the autostart, enable this Checkbox"), 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, _("You must have at least one\ntunnel in the list,to use the autostart"), tips) else: - Tooltip( - self.wg_autostart, - _("To use the autostart, a tunnel must be selected from the list"), - tips, - ) + Tooltip(self.wg_autostart, _("To use the autostart, a tunnel must be selected from the list"), tips) self.on_off() @@ -672,11 +587,8 @@ class FrameWidgets(ttk.Frame): Create.dir_and_files() try: - filepath = filedialog.askopenfilename( - initialdir=f"{Path.home()}", - title=_("Select Wireguard config File"), - filetypes=[(_("WG config files"), "*.conf")], - ) + filepath = filedialog.askopenfilename(initialdir=f"{Path.home()}", title=_("Select Wireguard config File"), + filetypes=[(_("WG config files"), "*.conf")]) with open(filepath, "r", encoding="utf-8") as file: read = file.read() @@ -684,11 +596,7 @@ class FrameWidgets(ttk.Frame): path_split1 = path_split[-1] self.a = Tunnel.active() - 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] @@ -701,9 +609,7 @@ class FrameWidgets(ttk.Frame): iw = r"/usr/share/icons/lx-icons/64/error.png" ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Import Error") - msg_t = _( - "Tunnel already available!\nPlease use another file for import" - ) + msg_t = _("Tunnel already available!\nPlease use another file for import") LxTools.msg_window(iw, ii, wt, msg_t) else: @@ -712,64 +618,26 @@ class FrameWidgets(ttk.Frame): keyfile.write(f"{pre_key}\r") if len(path_split1) > 17: p1 = shutil.copy(filepath, "/tmp/tlecdcwg/") - path_split = path_split1[ - len(path_split1) - 17: - ] - os.rename( - p1, f"/tmp/tlecdcwg/{path_split}" - ) + path_split = path_split1[len(path_split1) - 17:] + os.rename(p1, f"/tmp/tlecdcwg/{path_split}") new_conf = f"/tmp/tlecdcwg/{path_split}" if self.a != "": - check_call( - [ - "nmcli", - "connection", - "down", - Tunnel.active(), - ] - ) + check_call(["nmcli", "connection", "down", Tunnel.active()]) self.label_empty() - 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, "/tmp/tlecdcwg/") if self.a != "": - check_call( - [ - "nmcli", - "connection", - "down", - Tunnel.active(), - ] - ) + check_call(["nmcli", "connection", "down", Tunnel.active()]) self.label_empty() - 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() @@ -781,40 +649,18 @@ 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, _("To use the autostart, enable this Checkbox"), tips) # Tooltip(self.l_box, _("List of available tunnels")) - Tooltip( - self.btn_tr, - _( - "Click to delete a Wireguard Tunnel\n" - "Select from the list!" - ), - tips, - ) + Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!") + , tips,) - Tooltip( - self.btn_exp, - _( - " Click to export all\n" - "Wireguard Tunnel to Zipfile" - ), - tips, - ) + Tooltip(self.btn_exp, _(" Click to export all\nWireguard Tunnel to Zipfile") + , tips) - Tooltip( - self.btn_rename, - _( - "To rename a tunnel, you need to\n" - "select a tunnel from the list" - ), - tips, - ) + Tooltip(self.btn_rename, _("To rename a tunnel, you need to\nselect a tunnel from" + " the list"), tips) self.lb_rename.insert(0, "Max. 12 characters!") self.str_var = tk.StringVar() @@ -822,24 +668,13 @@ class FrameWidgets(ttk.Frame): self.color_label() self.stop() wg_read = f"/tmp/tlecdcwg/{self.a}.conf" - with open( - wg_read, "r", encoding="utf-8" - ) as file_for_key: + with open(wg_read, "r", encoding="utf-8") as file_for_key: data = Tunnel.con_to_dict(file_for_key) # Address Label self.init_and_report(data) self.show_data() - check_call( - [ - "nmcli", - "con", - "mod", - self.a, - "connection.autoconnect", - "no", - ] - ) + check_call(["nmcli", "con", "mod", self.a, "connection.autoconnect", "no"]) Path.chmod(wg_read, 0o600) if ("PrivateKey = " in read) and ("Endpoint = " in read): @@ -850,9 +685,7 @@ class FrameWidgets(ttk.Frame): iw = r"/usr/share/icons/lx-icons/64/error.png" ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Import Error") - msg_t = _( - "Oh... no valid Wireguard File!\nPlease select a valid Wireguard File" - ) + msg_t = _("Oh... no valid Wireguard File!\nPlease select a valid Wireguard File") LxTools.msg_window(iw, ii, wt, msg_t) except EOFError as e: @@ -921,12 +754,7 @@ 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) @@ -958,9 +786,7 @@ class FrameWidgets(ttk.Frame): """ # 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)) @@ -970,9 +796,7 @@ 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)) @@ -980,12 +804,7 @@ class FrameWidgets(ttk.Frame): """ Stop Button """ - self.btn_stst = ttk.Button( - self.lb_frame_btn_lbox, - image=self.wg_vpn_stop, - command=self.wg_switch, - padding=0, - ) + self.btn_stst = ttk.Button(self.lb_frame_btn_lbox, image=self.wg_vpn_stop, command=self.wg_switch, padding=0) self.btn_stst.grid(column=0, row=0, padx=5, pady=8) Tooltip(self.btn_stst, _("Click to stop selected Wireguard Tunnel"), tips) @@ -994,12 +813,7 @@ class FrameWidgets(ttk.Frame): """ Start Button """ - self.btn_stst = ttk.Button( - self.lb_frame_btn_lbox, - image=self.wg_vpn_start, - command=self.wg_switch, - padding=0, - ) + self.btn_stst = ttk.Button(self.lb_frame_btn_lbox, image=self.wg_vpn_start, command=self.wg_switch, padding=0) self.btn_stst.grid(column=0, row=0, padx=5, pady=8) tl = Tunnel.list() @@ -1016,14 +830,10 @@ class FrameWidgets(ttk.Frame): with open(set_file, "r", encoding="utf-8") as read_file: lines = read_file.readlines() if "light\n" in lines: - self.lb_tunnel = ttk.Label( - self, textvariable=self.str_var, foreground="green" - ) + 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) From 1a853d4ff1fde008a678fe14ee638a137bbe558b Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 26 Apr 2025 00:28:34 +0200 Subject: [PATCH 14/61] korreckturen throughout code --- .idea/workspace.xml | 51 ++-- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 21831 -> 23341 bytes cls_mth_fc.py | 352 ++++++++++++++----------- ssl_decrypt.py | 24 +- ssl_encrypt.py | 36 +-- wirepy.py | 14 +- 6 files changed, 255 insertions(+), 222 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 5c5afba..f44537c 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -9,7 +9,6 @@ - - - + { "associatedIndex": 3 @@ -48,28 +46,28 @@ - { + "keyToString": { + "ASKED_ADD_EXTERNAL_FILES": "true", + "Python.INSTALL.executor": "Run", + "Python.cls_mth_fc.executor": "Run", + "Python.install.executor": "Run", + "Python.main.executor": "Run", + "Python.messagebox.executor": "Run", + "Python.start_wg.executor": "Run", + "Python.testtheme.executor": "Run", + "Python.wg_func.executor": "Run", + "Python.wg_main.executor": "Run", + "Python.wirepy.executor": "Run", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "Shell Script.install.executor": "Run", + "Shell Script.run_as.executor": "Run", + "git-widget-placeholder": "21-04-2025-new-tooltip", + "last_opened_file_path": "/home/punix/Pyapps/wire-py", + "settings.editor.selected.configurable": "ml.llm.LLMConfigurable" } -}]]> +} @@ -630,6 +628,11 @@ 1128 diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 032154260be20b32f5c412ab881cd2653f6d0afd..4879fc8b297e0df0e931a2fec5726ac2ad843336 100644 GIT binary patch delta 8653 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 delta 7129 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 diff --git a/cls_mth_fc.py b/cls_mth_fc.py index ec49e7b..1e51c2e 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -8,11 +8,12 @@ import signal import subprocess import sys import tkinter as tk +from typing import Optional, Dict, Any, NoReturn, TextIO, Tuple, List import zipfile from datetime import datetime from pathlib import Path -from subprocess import check_call -from tkinter import ttk +from subprocess import check_call, CompletedProcess +from tkinter import ttk, Toplevel import requests @@ -32,15 +33,15 @@ class Create: """ @staticmethod - def dir_and_files(): + def dir_and_files() -> None: """ check and create folders and files if not present """ - pth = Path.home() / ".config/wire_py" + pth: Path = Path.home() / ".config/wire_py" pth.mkdir(parents=True, exist_ok=True) - sett = Path.home() / ".config/wire_py/settings" - ks = Path.home() / ".config/wire_py/keys" + sett: Path = Path.home() / ".config/wire_py/settings" + ks: Path = Path.home() / ".config/wire_py/keys" if sett.exists(): pass @@ -56,14 +57,14 @@ class Create: ks.touch() @staticmethod - def files_for_autostart(): + def files_for_autostart() -> None: """ - check and create file for auto start if not present and enable the service + check and create a file for auto start if not present and enable the service """ - pth2 = Path.home() / ".config/systemd/user" + pth2: Path = Path.home() / ".config/systemd/user" pth2.mkdir(parents=True, exist_ok=True) - wg_ser = Path.home() / ".config/systemd/user/wg_start.service" + wg_ser: Path = Path.home() / ".config/systemd/user/wg_start.service" if wg_ser.exists(): pass @@ -76,24 +77,24 @@ class Create: check_call(["systemctl", "--user", "enable", "wg_start.service"]) @staticmethod - def make_dir(): - """Dirname "tlecdewg" = Tunnel Encrypt Decrypt Wireguard""" + def make_dir() -> None: + """Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard""" - dirname = Path("/tmp/tlecdcwg/") - if dirname.exists(): + folder_path: Path = Path("/tmp/tlecdcwg/") + if folder_path.exists(): pass else: - dirname.mkdir() + folder_path.mkdir() @staticmethod - def decrypt(): + def decrypt() -> None: """ - This start ssl_decrypt file + Starts SSL dencrypt """ - process = subprocess.run(["pkexec", "/usr/local/bin/ssl_decrypt.py"], stdout=subprocess.PIPE, text=True, - check=True) - path = Path.home() / ".config/wire_py/" - file_in_path = list(path.rglob("*.dat")) + 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: if process.returncode == 0: print("File successfully decrypted...") @@ -103,12 +104,12 @@ class Create: print(_("Ready for import")) @staticmethod - def encrypt(): + def encrypt() -> None: """ - this start ssl_encrypt file + Starts SSL encryption """ - process = 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...") @@ -121,38 +122,37 @@ class LxTools(tk.Tk): Class LinuxTools methods that can also be used for other apps """ - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @staticmethod - def uos(): + def uos() -> None: """ - uos = LOGIN USERNAME - This method displays the user name of the logged-in user, + This method displays the username of the logged-in user, even if you are rooted in a shell """ - logname = f"{Path.home()}"[6:] - file = Path.home() / "/tmp/.loguser" + log_name: str = f"{Path.home()}"[6:] + file: Path = Path.home() / "/tmp/.log_user" with open(file, "w", encoding="utf-8") as f: - f.write(logname) + f.write(log_name) @staticmethod - def clean_files(dirname=None, file=None): + def clean_files(folder_path: Path = None, file: Path = None) -> None: """ method that can be added after need to delete a folder and a file when quitting. Args: - dirname (_folder_, optional): _path to folder_. Defaults to None. - file (_file_, optional): _path to file_. Defaults to None. + :param file: default None + :param folder_path: default None """ - if dirname is not None: - shutil.rmtree(dirname) + if folder_path is not None: + shutil.rmtree(folder_path) if file is not None: Path.unlink(file) @staticmethod - def if_tip(path): + def if_tip(path: Path) -> bool: """ method that writes in file whether tooltip is displayed or not """ @@ -165,83 +165,93 @@ class LxTools(tk.Tk): return tip @staticmethod - def msg_window(img_w, img_i, w_title, w_txt, txt2=None, com=None): + def msg_window(img_w: str, img_i: str, w_title: str, w_txt: str, txt2: Optional[str] = None, + com: Optional[str] = None) -> None: """ - Method for different message windows for the user. with 4 arguments to be passed. - To create messages with your own images, icons, and titles. - As an alternative to Python Messagebox. - Paths to images must be specified: r'/usr/share/icons/lx-icons/64/info.png' - img_w = Image for Tk Window - img_i = Image for Icon - w_title = Windows Title - w_txt = Text for Tk Window - txt2 = Text for Button two - com = function for Button command + Creates message windows + + :argument img_w = Image for Tk Window + :argument img_i = Image for Icon + :argument w_title = Windows Title + :argument w_txt = Text for Tk Window + :argument txt2 = Text for Button two + :argument com = function for Button command """ - msg = tk.Toplevel() + msg: tk.Toplevel = tk.Toplevel() msg.resizable(width=False, height=False) msg.title(w_title) msg.configure(pady=15, padx=15) msg.img = tk.PhotoImage(file=img_w) msg.i_window = tk.Label(msg, image=msg.img) - label = tk.Label(msg, text=w_txt) + label: 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(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(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(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) - img_i = tk.PhotoImage(file=img_i) + img_i: tk.PhotoImage = tk.PhotoImage(file=img_i) msg.iconphoto(True, img_i) msg.columnconfigure(0, weight=1) msg.rowconfigure(0, weight=1) msg.winfo_toplevel() @staticmethod - def sigi(dirname=None, file=None): + def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None: """ - function for clean up after break + Function for cleanup after a program interruption + + :param file: Optional - File to be deleted + :param file_path: Optional - Directory to be deleted """ - def signal_handler(signum, frame): + def signal_handler(signum: int, frame: Any) -> NoReturn: """ - Determine clear text names for signal numbers + Determines clear text names for signal numbers and handles signals + + Args: + signum: The signal number + frame: The current stack frame + + Returns: + NoReturn since the function either exits the program or continues execution """ - signals_to_names_dict = dict( - (getattr(signal, n), n) - for n in dir(signal) - if n.startswith("SIG") and "_" not in n - ) - signame = signals_to_names_dict.get(signum, f"Unnamed signal: {signum}") + + 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}") # End program for certain signals, report to others only reception if signum in (signal.SIGINT, signal.SIGTERM): - exit_code = 1 - print(f"\nSignal {signame} {(signum)} received. => Aborting with exit code {exit_code}.") - LxTools.clean_files(dirname, file) + exit_code: int = 1 + 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) else: print(f"Signal {signum} received and ignored.") - LxTools.clean_files(dirname, file) + LxTools.clean_files(file_path, file) print("Process unexpectedly ended...") + # Register signal handlers for various signals signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGHUP, signal_handler) + class GiteaUpdate: """ Calling download requests the download URL of the running script, @@ -250,63 +260,76 @@ class GiteaUpdate: """ @staticmethod - def api_down(update_api_url, version, file=None): + def api_down(update_api_url: str, version: str, file: Optional[Path] = None) -> str: """ - Calling api_down requests the URL and the version of the running script. - Example: version = 'v. 1.1.1.1' GiteaUpdate.api_down(http://example.de, version) + Checks for updates via API + + Args: + update_api_url: Update API URL + version: Current version + file: Optional - Configuration file + + Returns: + New version or status message """ try: - response = requests.get(update_api_url, timeout=10) - response_dict = response.json() - response_dict = response_dict[0] + response: requests.Response = requests.get(update_api_url, timeout=10) + response_dict: Any = response.json() + response_dict: Dict[str, Any] = response_dict[0] with open(file, "r", encoding="utf-8") as set_f: set_f = set_f.read() if "on\n" in set_f: if version[3:] != response_dict["tag_name"]: - req = response_dict["tag_name"] + req: str = response_dict["tag_name"] else: - req = "No Updates" + req: str = "No Updates" else: - req = "False" + req: str = "False" return req except requests.exceptions.RequestException: - req = "No Internet Connection!" + req: str = "No Internet Connection!" return req @staticmethod - def download(urld, down_ok_image, down_not_ok_image, res): + def download(urld: str, down_ok_image: str, down_not_ok_image: str, res: str) -> None: """ - this is for download new Version of wirepy + Downloads new version of wirepy + + Args: + urld: Download URL + down_ok_image: Path to success image + down_not_ok_image: Path to error image + res: Result filename """ try: - to_down = f"wget -qP {Path.home()} {" "} {urld}" - result = subprocess.call(to_down, shell=True) + to_down: str = f"wget -qP {Path.home()} {" "} {urld}" + result: int = subprocess.call(to_down, shell=True) if result == 0: shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000) - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = down_ok_image - wt = _("Download Successful") - msg_t = _("Your zip file is in home directory") + # Message window variables + iw: str = r"/usr/share/icons/lx-icons/64/info.png" + ii: str = down_ok_image + wt: str = _("Download Successful") + msg_t: str = _("Your zip file is in home directory") LxTools.msg_window(iw, ii, wt, msg_t) else: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/error.png" - ii = down_not_ok_image - wt = _("Download error") - msg_t = _("Download failed! Please try again") + # Error message window variables + iw: str = r"/usr/share/icons/lx-icons/64/error.png" + ii: str = down_not_ok_image + wt: str = _("Download error") + msg_t: str = _("Download failed! Please try again") LxTools.msg_window(iw, ii, wt, msg_t) except subprocess.CalledProcessError: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/error.png" - ii = down_not_ok_image - wt = _("Download error") - msg_t = _("Download failed! No internet connection!") + # Exception message window variables + iw: str = r"/usr/share/icons/lx-icons/64/error.png" + ii: str = down_not_ok_image + wt: str = _("Download error") + msg_t: str = _("Download failed! No internet connection!") LxTools.msg_window(iw, ii, wt, msg_t) @@ -316,15 +339,14 @@ class Tunnel: """ @classmethod - def con_to_dict(cls, file): + def con_to_dict(cls, file: TextIO) -> Tuple[str, str, str, Optional[str]]: """ - The config file is packed into a dictionary, - to display the values Address , DNS and Peer in the labels + Returns tuple of (address, dns, endpoint, pre_key) """ - dictlist = [] + dictlist: List[str] = [] for lines in file.readlines(): - line_plit = lines.split() + line_plit: List[str] = lines.split() dictlist = dictlist + line_plit dictlist.remove("[Interface]") dictlist.remove("[Peer]") @@ -336,33 +358,33 @@ class Tunnel: # Here is the beginning (Loop) of convert List to Dictionary for _ in dictlist: - a = [dictlist[0], dictlist[1]] - b = [dictlist[2], dictlist[3]] - c = [dictlist[4], dictlist[5]] - d = [dictlist[6], dictlist[7]] - e = [dictlist[8], dictlist[9]] - f = [dictlist[10], dictlist[11]] - g = [dictlist[12], dictlist[13]] - h = [dictlist[14], dictlist[15]] - new_list = [a, b, c, d, e, f, g, h] - final_dict = {} + a: List[str] = [dictlist[0], dictlist[1]] + b: List[str] = [dictlist[2], dictlist[3]] + c: List[str] = [dictlist[4], dictlist[5]] + d: List[str] = [dictlist[6], dictlist[7]] + e: List[str] = [dictlist[8], dictlist[9]] + f: List[str] = [dictlist[10], dictlist[11]] + g: List[str] = [dictlist[12], dictlist[13]] + h: List[str] = [dictlist[14], dictlist[15]] + new_list: List[List[str]] = [a, b, c, d, e, f, g, h] + final_dict: Dict[str, str] = {} for elements in new_list: final_dict[elements[0]] = elements[1] # end... result a Dictionary - address = final_dict["Address"] - dns = final_dict["DNS"] + address: str = final_dict["Address"] + dns: str = final_dict["DNS"] if "," in dns: dns = dns[:-1] - endpoint = final_dict["Endpoint"] - pre_key = final_dict.get("PresharedKey") + endpoint: str = final_dict["Endpoint"] + pre_key: Optional[str] = final_dict.get("PresharedKey") if pre_key is None: - pre_key = final_dict.get("PreSharedKey") + pre_key: Optional[str] = final_dict.get("PreSharedKey") return address, dns, endpoint, pre_key @staticmethod - def active(): + def active() -> str: """ Shows the Active Tunnel """ @@ -375,59 +397,59 @@ class Tunnel: return active @staticmethod - def list(): + def list() -> List[str]: """ - Shows all existing Wireguard tunnels a login user + Returns a list of Wireguard tunnel names """ - dirname = Path("/tmp/tlecdcwg/") - wg_s = os.listdir(dirname) + folder_path: Path = Path("/tmp/tlecdcwg/") + wg_s: List[str] = os.listdir(folder_path) return wg_s @staticmethod - def export(): + def export() -> None: """ This will export the tunnels. - A zipfile with current date and time is created - in the user's home directory with correct right + A zipfile with the current date and time is created + in the user's home directory with the correct right """ - now_time = datetime.now() - now_datetime = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") - tl = Tunnel.list() + now_time: datetime = datetime.now() + now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") + tl: List[str] = Tunnel.list() try: if len(tl) != 0: - wg_tar = f"{Path.home()}/{now_datetime}" + wg_tar: str = f"{Path.home()}/{now_datetime}" shutil.copytree("/tmp/tlecdcwg/", "/tmp/wire_py", dirs_exist_ok=True) - source = Path("/tmp/wire_py") + source: Path = Path("/tmp/wire_py") shutil.make_archive(wg_tar, "zip", source) shutil.rmtree(source) with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: if len(zf.namelist()) != 0: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_vpn.png" - wt = _("Export Successful") - msg_t = _("Your zip file is in home directory") + # Message window variables + iw: str = r"/usr/share/icons/lx-icons/64/info.png" + ii: str = r"/usr/share/icons/lx-icons/48/wg_vpn.png" + wt: str = _("Export Successful") + msg_t: str = _("Your zip file is in home directory") LxTools.msg_window(iw, ii, wt, msg_t) else: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/error.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" - wt = _("Export error") - msg_t = _("Export failed! Please try again") + # Error message window variables + iw: str = r"/usr/share/icons/lx-icons/64/error.png" + ii: str = r"/usr/share/icons/lx-icons/48/wg_msg.png" + wt: str = _("Export error") + msg_t: str = _("Export failed! Please try again") LxTools.msg_window(iw, ii, wt, msg_t) else: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" - wt = _("Select tunnel") - msg_t = _("Please first import tunnel") + # Message window variables + iw: str = r"/usr/share/icons/lx-icons/64/info.png" + ii: str = r"/usr/share/icons/lx-icons/48/wg_msg.png" + wt: str = _("Select tunnel") + msg_t: str = _("Please first import tunnel") LxTools.msg_window(iw, ii, wt, msg_t) except TypeError: @@ -438,43 +460,51 @@ class Tooltip: """ class for Tooltip - imoprt Tooltip + import Tooltip example: Tooltip(label, "Show tooltip on label") - examble: Tooltip(button, "Show tooltip on button") - info: label and button is parrent. + example: Tooltip(button, "Show tooltip on button") + info: label and button are parent. """ - def __init__(self, widget, text, tips=None): - self.widget = widget - self.text = text - self.tooltip_window = None + def __init__(self, widget: Any, text: str, tips: Optional[bool] = None) -> None: + """ + Tooltip Class + """ + + self.widget: Any = widget + self.text: str = text + self.tooltip_window: Optional[Toplevel] = None if tips: self.widget.bind("", self.show_tooltip) self.widget.bind("", self.hide_tooltip) - def show_tooltip(self, event=None): + def show_tooltip(self, event: Optional[Any] = None) -> None: """ - shows the tooltip + Shows the tooltip """ if self.tooltip_window or not self.text: return - x, y, _, _ = self.widget.bbox("insert") + x: int + y: int + cx: int + cy: int + x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() + 65 y += self.widget.winfo_rooty() + 40 self.tooltip_window = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") - label = tk.Label(tw, text=self.text, background="lightgreen", foreground="black", relief="solid", borderwidth=1, - padx=5, pady=5) + label: tk.Label = tk.Label(tw, text=self.text, background="lightgreen", foreground="black", relief="solid", + borderwidth=1, padx=5, pady=5) label.grid() - def hide_tooltip(self, event=None): + def hide_tooltip(self, event: Optional[Any] = None) -> None: """ - hide the tooltip + Hides the tooltip """ if self.tooltip_window: self.tooltip_window.destroy() - self.tooltip_window = None + self.tooltip_window = None \ No newline at end of file diff --git a/ssl_decrypt.py b/ssl_decrypt.py index c2352c9..38e6935 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -1,35 +1,35 @@ #!/usr/bin/python3 -""" This Script decrypt Wireguardfiles for Wirepy users """ +""" This Script decrypt Wireguard files for Wirepy users """ import os import shutil from pathlib import Path from subprocess import check_call -uname = Path("/tmp/.loguser") +uname: Path = Path("/tmp/.log_user") with open(uname, "r", encoding="utf-8") as f: - logname = f.readline() + log_name = f.readline() -# Dirname "tlecdewg" = Tunnel Encrypt Decrypt Wireguard -dirname = Path("/tmp/tlecdcwg/") -keyfile = Path(f"/home/{logname}/.config/wire_py/pbwgk.pem") -PKEYFILE = "/usr/local/etc/ssl/pwgk.pem" +# Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard +folder_path: Path = Path("/tmp/tlecdcwg/") +keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") +PKEYFILE: Path = "/usr/local/etc/ssl/pwgk.pem" if not keyfile.is_file(): check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) -dirname2 = f"/home/{logname}/.config/wire_py/" -detl = os.listdir(dirname2) -os.chdir(dirname2) +folder_path2 = f"/home/{log_name}/.config/wire_py/" +detl: list[str] = os.listdir(folder_path2) +os.chdir(folder_path2) detl.remove("keys") detl.remove("settings") -if os.path.exists(f"{dirname2}pbwgk.pem"): +if os.path.exists(f"{folder_path2}pbwgk.pem"): detl.remove("pbwgk.pem") for detunnels in detl: tlname2 = f"{detunnels[:-4]}.conf" - extpath = f"{dirname}/{tlname2}" + extpath = f"{folder_path}/{tlname2}" check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", PKEYFILE, "-in", detunnels, "-out", extpath]) shutil.chown(extpath, 1000, 1000) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 3433a26..d5a73c9 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -6,13 +6,13 @@ import shutil from pathlib import Path from subprocess import check_call -uname = Path("/tmp/.loguser") +uname: Path = Path("/tmp/.log_user") with open(uname, "r", encoding="utf-8") as f: - logname = f.readline() + log_name: str = f.readline() -keyfile = Path(f"/home/{logname}/.config/wire_py/pbwgk.pem") -dirname = Path("/tmp/tlecdcwg/") +keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") +folder_path: Path = Path("/tmp/tlecdcwg/") PKEYFILE = "/usr/local/etc/ssl/pwgk.pem" if not keyfile.is_file(): @@ -20,28 +20,28 @@ if not keyfile.is_file(): check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) - if dirname.exists(): - tl = os.listdir(f"{dirname}") - CPTH = f"{keyfile}" - CRYPTFILES = CPTH[:-9] + if folder_path.exists(): + tl = os.listdir(f"{folder_path}") + CPTH: str = f"{keyfile}" + CRYPTFILES: str = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl = f"{dirname}/{tunnels}" - tlname = f"{CRYPTFILES}{tunnels[:-5]}.dat" + sourcetl: str = f"{folder_path}/{tunnels}" + tlname: str = f"{CRYPTFILES}{tunnels[:-5]}.dat" check_call(["openssl", "pkeyutl", "-encrypt", "-inkey", keyfile, "-pubin", "-in", sourcetl, "-out", tlname,]) else: - if dirname.exists(): - tl = os.listdir(f"{dirname}") - CPTH = f"{keyfile}" - CRYPTFILES = CPTH[:-9] + if folder_path.exists(): + tl: list[str] = os.listdir(f"{folder_path}") + CPTH: str = f"{keyfile}" + CRYPTFILES: str = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl = f"{dirname}/{tunnels}" - tlname = f"{CRYPTFILES}{tunnels[:-5]}.dat" - check_call( - ["openssl", "pkeyutl", "-encrypt", "-inkey", keyfile, "-pubin", "-in", sourcetl, "-out", tlname]) + sourcetl: str = f"{folder_path}/{tunnels}" + tlname: str = f"{CRYPTFILES}{tunnels[:-5]}.dat" + check_call(["openssl", "pkeyutl", "-encrypt", "-inkey", keyfile, "-pubin", "-in", sourcetl, "-out", + tlname]) diff --git a/wirepy.py b/wirepy.py index 4fcf804..25c9755 100755 --- a/wirepy.py +++ b/wirepy.py @@ -21,14 +21,14 @@ Create.dir_and_files() Create.make_dir() Create.decrypt() -tcl_path = Path("/usr/share/TK-Themes") -set_file = Path(Path.home() / ".config/wire_py/settings") +tcl_path: Path = Path("/usr/share/TK-Themes") +set_file: Path = Path(Path.home() / ".config/wire_py/settings") tips = LxTools.if_tip(set_file) -dirname = Path("/tmp/tlecdcwg/") -userfile = Path("/tmp/.loguser") +folder_path: Path = Path("/tmp/tlecdcwg/") +userfile = Path("/tmp/.log_user") # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year -VERSION = "v. 2.04.1725" +VERSION: str = "v. 2.04.1725" res = GiteaUpdate.api_down("https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, set_file) @@ -40,7 +40,7 @@ gettext.bindtextdomain(APP, LOCALE_DIR) gettext.textdomain(APP) _ = gettext.gettext -LxTools.sigi(dirname, userfile) +LxTools.sigi(folder_path, userfile) class Wirepy(tk.Tk): @@ -917,5 +917,5 @@ if __name__ == "__main__": window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0") window.mainloop() -LxTools.clean_files(dirname, userfile) +LxTools.clean_files(folder_path, userfile) sys.exit(0) From 950e04a246b93aa211c0bed377f1f925912412d9 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 26 Apr 2025 00:41:17 +0200 Subject: [PATCH 15/61] korreckturen throughout code2 --- cls_mth_fc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cls_mth_fc.py b/cls_mth_fc.py index 1e51c2e..64c305c 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -507,4 +507,5 @@ class Tooltip: """ if self.tooltip_window: self.tooltip_window.destroy() - self.tooltip_window = None \ No newline at end of file + self.tooltip_window = None + \ No newline at end of file From cd625d173d90da57fc42128a2804b4c732866d11 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 26 Apr 2025 00:49:34 +0200 Subject: [PATCH 16/61] empty row add --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23341 -> 23341 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 4879fc8b297e0df0e931a2fec5726ac2ad843336..8a48e2958525b25efac23b135c4f1aeb7aed6202 100644 GIT binary patch delta 24 ecmZ3xjdATZM&8rByj%=GAkEK{;kl7lAsPTy;sxaZ delta 24 ecmZ3xjdATZM&8rByj%=GpwG&aVY87}AsPTz1qI>& From 97ea07d34bfcea20f4c938f0e6bc792968d1f453 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 26 Apr 2025 21:32:35 +0200 Subject: [PATCH 17/61] Message window optimized --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23341 -> 23628 bytes cls_mth_fc.py | 49 +++++++---------- wirepy.py | 72 +++++++------------------ 3 files changed, 39 insertions(+), 82 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 8a48e2958525b25efac23b135c4f1aeb7aed6202..b917af1ef416dfd34187a85f5d3ec1dd7b7f781c 100644 GIT binary patch delta 2569 zcmc&$YfMx}6rQ>JyzCQ%@(|byxZr|34Im3xMR_TLB3SW9m2wx^@L0TiaYYxg)u#Sw zN_*Ob#+vf4STtQr(~y`prdFFq)1nkfThgRTZA{Y?kcL+Kqi1$`Eb6bOX=gLvnK}2I zZ)WGt`F1aSOmB=(<8^}}gkZanV`w?G<)TrxP$!(UY+Juw@Hjg}mq+llIGb7or&|!6 z?#{NxZigtyE1Cf1U13EOk1{D z)H+lmk39st4!a(^0lQk{nvJ6Ilxm1=1lvBoo3n)u=VcvN9?yg~^OLOEE7p8EgMl%ud4iz~*aO{8yGu#FX%@QSVpMo2OmV(0rhX*BJ z?rnX+G>^`Q?r3CwspOe0TJl+R2I3X%X1Z0c_*}Y}1l-1ZEz9@^{g_@!SdI}B| zZ53xYL1piAPZKl)tNdngx8Gb=_|0hDa{=4Z8nB*Yvn=?5f~dp%*-`@1OP3LvurD?J z!stOi+{_GzxZ(pg1D2!)OHzX+slk%ehze2D93tw2O3~P@u^ET^iU*andbptECyvg8 zr8GL+jCbs)p{}c=!_k(}*d#j-JB~qB>0i_cm1X(#G`v`rwQjj^rEy$dx{c7ZP%-}P zER}4Q0)(xJFp#Z>O&jC69u-8ErzEI(BI_3RO}y&Gq7u2*d_wxDq=fzDX&TiTa#qo! zf!E7RsTL;6Eh>$u^=jc!xee#m3R{8(3!Fd8i6M(ZkV|^_AZ8w16=~_2vSo2Cak>0L zg1q|09rWnn>k7-W#7n&)Ufr??bYuiY87pW-rr&A&GlJF!ba-Hqqd*~C9N5#9!p%`&rMpBM^h@std;$$$tcT2RO-#BRMe%s zk(`mPZ=y=)BN^*7VK^6$%GF;p7PGF8n~aMewG@uU74@km&5;x4#GB^Cf!x7lU!u=3 zZZ4WnT26a3mrTsjvpWgpL%-FVrxj=)y0O;Cr4n-)Pp;@T+e?cTS5;b^Ts3AeELN4N zHP<)=j<2cx$n#|pifiE%QJTVBn+b%XaQL)3u}FtUA(f*~V)`NRY?Q|!nN2I$D1iYh zMCsllRN_PT2M}9xyl{p~RfYC7O%pVOsE=qyP#@8Z?y3ve1L7>3F9;%z0+jcLeKjw0 zG!^n{OLVDhxh%$?1}AIR((A+LYwvLMKD<`HRhPc>Fcp5NZ=~6ytHKjbaUw{g-gMuR`z#LfO?2tr`Nq2{c{Z^7m8s$~a+tjS=o#)Y%2-)pfFZnY$dTp~^l zrhJE8<`Q#q(iEgANt1BQ0R^WzDw?8H#5(1pj6)M1%s(WqRde29PnWm9%YCrB*EOpb zh90kf)y48IUymq~DWH@fOWLCn2BEi+9B~-F(mn@Pku2^c;uwyh7(^%0DM?5KU&LDy zMI8IFXo`eW3yFhbPTZe~5WgSZEom^bBwl(-#K}0>6eT8x5ho8xf;c1vU8zwm^Q$9% zgg7NUEp=2%g3_QYD4$y17)GNyPDL`9kTfP%J%uBAp;!hDWr+V51{?B=KvSQRpN{xq zvV~_Q)KbFKb%Y&cQ^RP4?LfoWp&z>oJIi|7*WCAJYfHDk&+7MDeSw}Hm(OPvUW39K zW9#0?YLbiRy?i7EJ}AqAFUxc6OBs0qAD`#z>*if~?jEnlmp3qy6X}!`<+(ivyt#v( zUU*Pmp1HK~e`5uDAvk06S-7=1MdBWwW!!F9+FY$nBsY5)&6~_}Cp2t%p>AG+lDFe= z=4O(8oS92Xo=z#4Npg~|`MGJ!_8D{axOh$%e?ymcRhKs5oZu&&lYwix;&}y1t`^cZ z3289B#cW@lesWivcte|gRhzwlq;e-&B>s7Ptm>x8eynN^q!pGzULY&Jt}2+gYk+~?1ys|)qCa{yVyDIn@8j^ zStvX%L>}L^3Zv<^js-gTh((gPhcuc`B}DuM0zITfO{i8fdUOPE{s!<=zG#qhvY67- zj7lAf@M7hp2)BhNs_rqlOC;QZXC>V|e)o{eM?Y9AZz0GVNPkSdC*bk8267|QM<87D zC)UH6+EV-`{9c=%ux9yaY1Xswl+9v72zDz?@cCASDun$KLMj-f%2Luoj2!uuE3P23 zQYswNUy9H;=2b9Tw@Hdg8M7f4RP{Bi$ghXnZDujc$$}yns&_zMrV>7?x7cV2%TF zbZfZu(JWca3UN`dz}3Evi!;#Q%KnL<7=(SU<=zlwiRCs%y#jaBI_^<}4HY0C9%eA= zAmT>ZBf3B&Cw=7>i0ZI(e$)_E4b#|Z2<lIj3%PcM)-fQf|_d(J$h!yZgQ-R?Z%66B+ zJqo`PfZJ(Cm}@er-yxr_?c~?sOs-`R!uLS<_xC~l$B^4XdMP(IJD`zjMWwl0h$>bz> zSuU=PlU~-av>zv+QC^$MUbbR_{5E)h*A}CO9Fec1$EU^0%W1C!TK2{3`1`a^p@rVX z>tJd*WLjWy#rkCtA-#x*+ztYMbOorgPm}^JrUmtpKd>nD}|7^NWO#NcT<`ox;p*mwJxhlA#sdt*yHsM z_}zoKeQwUx<&O-Wz{?#To0Bd=gZO%ypGrZHqipJ$F?K#g6c_h1BN*!+B8rPcY$3KUAOga&{{X_O BNFD$H diff --git a/cls_mth_fc.py b/cls_mth_fc.py index 64c305c..ef85a7b 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -170,8 +170,8 @@ class LxTools(tk.Tk): """ Creates message windows - :argument img_w = Image for Tk Window - :argument img_i = Image for Icon + :argument img_w = Image for TK window which is displayed to the left of the text + :argument img_i = Image for Task Icon :argument w_title = Windows Title :argument w_txt = Text for Tk Window :argument txt2 = Text for Button two @@ -291,15 +291,17 @@ class GiteaUpdate: return req @staticmethod - def download(urld: str, down_ok_image: str, down_not_ok_image: str, res: str) -> None: + def download(urld: str, res: str, img_w: str = None, img_i: str = None, img_w2: str = None, img_i2: str = None) -> None: """ Downloads new version of wirepy Args: urld: Download URL - down_ok_image: Path to success image - down_not_ok_image: Path to error image res: Result filename + img_w: Image for TK window which is displayed to the left of the text + img_i: Image for Task Icon + img_w2: Image for TK window which is displayed to the left of the text + img_i2: Image for Task Icon """ try: to_down: str = f"wget -qP {Path.home()} {" "} {urld}" @@ -307,30 +309,21 @@ class GiteaUpdate: if result == 0: shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000) - # Message window variables - iw: str = r"/usr/share/icons/lx-icons/64/info.png" - ii: str = down_ok_image wt: str = _("Download Successful") msg_t: str = _("Your zip file is in home directory") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i, wt, msg_t) else: - # Error message window variables - iw: str = r"/usr/share/icons/lx-icons/64/error.png" - ii: str = down_not_ok_image wt: str = _("Download error") msg_t: str = _("Download failed! Please try again") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w2, img_i2, wt, msg_t) except subprocess.CalledProcessError: - # Exception message window variables - iw: str = r"/usr/share/icons/lx-icons/64/error.png" - ii: str = down_not_ok_image wt: str = _("Download error") msg_t: str = _("Download failed! No internet connection!") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w2, img_i2, wt, msg_t) class Tunnel: @@ -407,11 +400,16 @@ class Tunnel: return wg_s @staticmethod - def export() -> None: + def export(img_w: str = None, img_i: str = None, img_w2: str = None, img_i2: str = None) -> None: """ This will export the tunnels. A zipfile with the current date and time is created in the user's home directory with the correct right + Args: + img_w: Image for TK window which is displayed to the left of the text + img_i: Image for Task Icon + img_w2: Image for TK window which is displayed to the left of the text + img_i2: Image for Task Icon """ now_time: datetime = datetime.now() now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") @@ -427,30 +425,21 @@ class Tunnel: with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: if len(zf.namelist()) != 0: - # Message window variables - iw: str = r"/usr/share/icons/lx-icons/64/info.png" - ii: str = r"/usr/share/icons/lx-icons/48/wg_vpn.png" wt: str = _("Export Successful") msg_t: str = _("Your zip file is in home directory") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i, wt, msg_t) else: - # Error message window variables - iw: str = r"/usr/share/icons/lx-icons/64/error.png" - ii: str = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt: str = _("Export error") msg_t: str = _("Export failed! Please try again") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w2, img_i2, wt, msg_t) else: - # Message window variables - iw: str = r"/usr/share/icons/lx-icons/64/info.png" - ii: str = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt: str = _("Select tunnel") msg_t: str = _("Please first import tunnel") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) except TypeError: pass diff --git a/wirepy.py b/wirepy.py index 25c9755..9d599bc 100755 --- a/wirepy.py +++ b/wirepy.py @@ -32,6 +32,11 @@ VERSION: str = "v. 2.04.1725" res = GiteaUpdate.api_down("https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, set_file) +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" + # Translate APP = "wirepy" LOCALE_DIR = "/usr/share/locale/" @@ -100,8 +105,6 @@ class FrameWidgets(ttk.Frame): self.tr_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_trash.png") self.exp_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_export.png") self.warning_pic = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/64/error.png") - self.wg_vpn_start = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn-start.png") - self.wg_vpn_stop = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn-stop.png") def update(): """ @@ -169,9 +172,6 @@ class FrameWidgets(ttk.Frame): def link_btn(): webbrowser.open("https://git.ilunix.de/punix/Wire-Py") - # img_w, img_i, w_title, w_txt , txt2, com hand over - iw = r"/usr/share/icons/lx-icons/48/wg_vpn.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Info") msg_t = _("Wire-Py a simple Wireguard Gui for Linux systems.\n\n" "Wire-Py is open source software written in Python.\n\n" @@ -180,7 +180,7 @@ class FrameWidgets(ttk.Frame): txt2 = _("Go to Wire-Py git") com = link_btn - LxTools.msg_window(iw, ii, wt, msg_t, txt2, com) + LxTools.msg_window(img_i, img_i, wt, msg_t, txt2, com) # Frame for Menu self.menu_frame = ttk.Frame(self) @@ -254,9 +254,7 @@ class FrameWidgets(ttk.Frame): self.update_btn.configure(menu=self.download, style="Toolbutton") self.download.add_command( label=_("Download"), - command=lambda: GiteaUpdate.download(f"https://git.ilunix.de/punix/Wire-Py/archive/{res}.zip", - r"/usr/share/icons/lx-icons/48/wg_vpn.png", - r"/usr/share/icons/lx-icons/48/wg_msg.png", res)) + command=lambda: GiteaUpdate.download(f"https://git.ilunix.de/punix/Wire-Py/archive/{res}.zip", res, img_w, img_i, img_w2, img_i2)) # Show active Tunnel self.a = Tunnel.active() @@ -422,21 +420,15 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() != 0: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please select a tunnel from the list") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) else: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please first import tunnel") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) # Button Trash self.btn_tr = ttk.Button(self.lb_frame_btn_lbox, image=self.tr_pic, command=delete, padding=0, @@ -449,7 +441,7 @@ class FrameWidgets(ttk.Frame): Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), tips) # Button Export - self.btn_exp = ttk.Button(self.lb_frame_btn_lbox, image=self.exp_pic, command=Tunnel.export, padding=0) + 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), padding=0) self.btn_exp.grid(column=0, row=3, padx=15, pady=8) if self.l_box.size() == 0: @@ -474,30 +466,21 @@ class FrameWidgets(ttk.Frame): if len(self.lb_rename.get()) > 12: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") msg_t = _("The new name may contain only 12 characters") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) elif len(self.lb_rename.get()) == 0: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") msg_t = _("At least one character must be entered") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) elif any(ch in special_characters for ch in self.lb_rename.get()): - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") msg_t = _("No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) else: @@ -532,12 +515,9 @@ class FrameWidgets(ttk.Frame): except IndexError: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Renaming not possible") msg_t = _("Please select a tunnel from the list") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) # Button Rename self.btn_rename = ttk.Button(self.lb_frame4, text=_("Rename"), state="disable", command=tl_rename, padding=4, @@ -605,12 +585,9 @@ class FrameWidgets(ttk.Frame): p_key = readfile.readlines() if pre_key in p_key or f"{pre_key}\n" in p_key: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/error.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Import Error") msg_t = _("Tunnel already available!\nPlease use another file for import") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w2, img_i2, wt, msg_t) else: @@ -681,12 +658,9 @@ class FrameWidgets(ttk.Frame): pass else: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/error.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Import Error") msg_t = _("Oh... no valid Wireguard File!\nPlease select a valid Wireguard File") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w2, img_i2, wt, msg_t) except EOFError as e: print(e) @@ -741,7 +715,7 @@ class FrameWidgets(ttk.Frame): lines4 = set_f4.readlines() if lines4[7] != "off\n": - print(lines4[7]) + print(f"{lines4[7]} starts automatically when the system starts.") self.selected_option.set(1) self.autoconnect_var.set("") self.auto_con = lines4[7] @@ -749,7 +723,7 @@ class FrameWidgets(ttk.Frame): 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) @@ -887,21 +861,15 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() != 0: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please select a tunnel from the list") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) else: - # img_w, img_i, w_title, w_txt hand over - iw = r"/usr/share/icons/lx-icons/64/info.png" - ii = r"/usr/share/icons/lx-icons/48/wg_msg.png" wt = _("Select tunnel") msg_t = _("Please first import tunnel") - LxTools.msg_window(iw, ii, wt, msg_t) + LxTools.msg_window(img_w, img_i2, wt, msg_t) if __name__ == "__main__": From 4eb9d6acd48fc8e8e914330dfe793d24db82ae9d Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 26 Apr 2025 22:33:59 +0200 Subject: [PATCH 18/61] Message window optimized2 --- .idea/workspace.xml | 3 - __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23628 -> 23550 bytes cls_mth_fc.py | 14 ++-- wirepy.py | 94 +++++++++++-------------- 4 files changed, 45 insertions(+), 66 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index f44537c..c381f24 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,10 +5,7 @@ - - - - + - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "Python.INSTALL.executor": "Run", - "Python.cls_mth_fc.executor": "Run", - "Python.install.executor": "Run", - "Python.main.executor": "Run", - "Python.messagebox.executor": "Run", - "Python.start_wg.executor": "Run", - "Python.testtheme.executor": "Run", - "Python.wg_func.executor": "Run", - "Python.wg_main.executor": "Run", - "Python.wirepy.executor": "Run", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "Shell Script.install.executor": "Run", - "Shell Script.run_as.executor": "Run", - "git-widget-placeholder": "21-04-2025-new-tooltip", - "last_opened_file_path": "/home/punix/Pyapps/wire-py", - "settings.editor.selected.configurable": "ml.llm.LLMConfigurable" + +}]]> diff --git a/wirepy.py b/wirepy.py index ca0631b..25fe1d7 100755 --- a/wirepy.py +++ b/wirepy.py @@ -131,8 +131,10 @@ class FrameWidgets(ttk.Frame): set_tip = tk.BooleanVar() 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(set_update.get()), variable=set_update) - self.settings.add_checkbutton(label=_("Disable Tooltips"), command=lambda: self.tooltip(set_tip.get()), variable=set_tip) + self.settings.add_checkbutton(label=_("Disable Updates"), + command=lambda: self.update_setting(set_update.get()), variable=set_update) + self.settings.add_checkbutton(label=_("Disable Tooltips"), + command=lambda: self.tooltip(set_tip.get()), variable=set_tip) self.settings.add_command(label=_("Light"), command=self.theme_change_light) self.settings.add_command(label=_("Dark"), command=self.theme_change_dark) @@ -300,14 +302,14 @@ class FrameWidgets(ttk.Frame): Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), tips) # 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 self.selected_option = tk.IntVar() self.autoconnect_var = tk.StringVar() - self.autoconnect_var.set(self.auto_con) + self.autoconnect_var.set(f"{self.auto_con}") # Frame for Labels, Entry and Button self.autoconnect = ttk.Label(self.lb_frame3, textvariable=self.autoconnect_var, width=15) @@ -329,8 +331,9 @@ class FrameWidgets(ttk.Frame): self.on_off() - def info(self): - def link_btn(): + @staticmethod + def info(): + 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" @@ -366,7 +369,8 @@ class FrameWidgets(ttk.Frame): theme_set2.writelines(lines4) self.color_label() - def update(self, update_res): + @staticmethod + def update_setting(update_res): """ write off or on in file Args: @@ -385,8 +389,9 @@ class FrameWidgets(ttk.Frame): lines2[1] = "on\n" with open(set_file, "w", encoding="utf-8") as set_f2: set_f2.writelines(lines2) - - def tooltip(self, tip) -> None: + + @staticmethod + def tooltip(tip) -> None: """ write True or False in a file Args: @@ -651,14 +656,14 @@ class FrameWidgets(ttk.Frame): except subprocess.CalledProcessError: print("Tunnel exist!") - def handle_tunnel_data(self, tunnel_name: str) -> dict[str, str]: + def handle_tunnel_data(self, tunnel_name: str) -> tuple[str, str, str, str | None]: """_summary_ Args: - tunnel_name (str): name of tunnel + tunnel_name (str): name of a tunnel Returns: - Dict[str, str]: dictionary with tunneldata + tuple[str, str]: tuple with tunnel data """ wg_read = f"/tmp/tlecdcwg/{tunnel_name}.conf" @@ -670,7 +675,7 @@ class FrameWidgets(ttk.Frame): def box_set(self): """ - This Method will display the autostart label which + This Method will display the autostarted label which Tunnel is automatically started regardless of the active tunnel. The selected tunnel is written into a file to read it after the start of the system. """ @@ -776,7 +781,8 @@ 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, _("Click to stop selected Wireguard Tunnel"), tips) @@ -785,7 +791,8 @@ 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 = Tunnel.list() @@ -839,11 +846,11 @@ class FrameWidgets(ttk.Frame): def handle_connection_state(self, action: str, tunnel_name: str = None) -> None: """ - Central management for connection states + central management for connection states Args: action (str): "start", "stop" or "toggle" - tunnel_name (str, optional): name of tunnel for start-option. defaults to None. + tunnel_name (str, optional): name of a tunnel for a start-option. defaults to None. """ if action == "stop": if self.a: From 6f02724daa35e7d4dcc07f52e26dae06d07d9a50 Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 29 Apr 2025 09:02:06 +0200 Subject: [PATCH 22/61] with opening reduced --- wirepy.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/wirepy.py b/wirepy.py index 25fe1d7..b62cb6e 100755 --- a/wirepy.py +++ b/wirepy.py @@ -73,12 +73,12 @@ class Wirepy(tk.Tk): self.style = ttk.Style(self) self.tk.call("source", f"{tcl_path}/water.tcl") - with open(set_file, "r", encoding="utf-8") as read_file: - lines = read_file.readlines() - if "light\n" in lines: - self.tk.call("set_theme", "light") - else: - self.tk.call("set_theme", "dark") + + lines = set_file.read_text() + if "light\n" in lines: + self.tk.call("set_theme", "light") + else: + self.tk.call("set_theme", "dark") # Load the image file from the disk self.wg_icon = tk.PhotoImage(file=r"/usr/share/icons/lx-icons/48/wg_vpn.png") @@ -436,10 +436,8 @@ class FrameWidgets(ttk.Frame): self.l_box.delete(self.select_tunnel[0]) with open(set_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(set_file, "w", encoding="utf-8") as set_f7: set_f7.writelines(lines6) @@ -805,14 +803,12 @@ class FrameWidgets(ttk.Frame): """ View activ Tunnel in the color green or yellow """ + lines = set_file.read_text() + if "light\n" in lines: + self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green") - with open(set_file, "r", encoding="utf-8") as read_file: - lines = read_file.readlines() - if "light\n" in lines: - self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green") - - else: - self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="yellow") + 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) From 213f772f40719050c1423785d069587d1dac4c03 Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 29 Apr 2025 11:39:05 +0200 Subject: [PATCH 23/61] methods optimized in wirepy and cls_mth_fc --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23550 -> 23366 bytes cls_mth_fc.py | 16 +++--- wirepy.py | 70 ++++++++++--------------- 3 files changed, 36 insertions(+), 50 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index cad7fecc4342ea16b8e912544eb5d3f8f4c5111f..8c9b363d4c3d0e5e58ff9c192aa90c4cda2e92e9 100644 GIT binary patch delta 1844 zcmZXUeM}Tr5Wx5CeR9W@hbB)^Crq#rzNt@_tt7J`6W1_}1saI>#)_%U(Luu^(@!Oes zGjHb2?AyOjkuOgY+t)Umg<}snXzzXNLmP2|Xi6i8;lRowDA1ds&bh=v&;g%eGMWx8nYVbSxf&XO>h;HPwMgoJg&WPqdkuzrLe z=3BY2A@%kchg#rnjuZaLt6okwV74}fA!9>Olm$zJ-@|L?QPZdp40kHR4uzd?DRWT` zVnh;TRi{K^vW^*G&y0fAuQEZdX4uKyw{V%xF+W(Y+T97e`&iFKyZf?z;izHKntjNS zu;x!Xaz^WtM$Vo!YDwz2ET&CnI7Thn$>YYEnMQWx+`=_8VwldfwO>{u-GeGGZD%FO z%tjR$DXgqciKst^j+RhV?C+E0SWr^=b!$|zotpfHEv%fwXw%%|@I|?sB;aQG!IE~gYG0C; zM!zK*6=@hFb+SS!Hk@L!=}9=^?aD|qRQ;mkjE*GFq@)WnD!ln=hO9Avl4Gw!;d}Uc z8OwN3Jroq#I_{j*{m1vD)jr@hjZ? zJ{+je%eJvQJ!JFuOOhaZgCQj{APmBp`ag*kPB$zfdm!21vp?RgxUVs(Cs$y0v)6hR z_d1K1gpTITi*BH}kU`ang7lw1lr}AMC>k47s3556eZg%)XOM<^SxUMY?lzZ{X`@T? z55|Pl#BBeY6Kk{Nfd{@hEo_N62T5I*7|2&K=0e@>TIUgTs{hl{!)4d5@ZHsg8qSGjYC!Ll( z^7g=qHaFP~-?o)H_M*pr^f1%)D9?k;jjr66nJD8oP%!mS?W6L-GaK_rA|BYdpDfch z9K%q4DwJi={^_(=(7L- delta 1954 zcmZXVdrVVz6vyxH-acrdqDae25D_13p;8{crqTK+Dh((W1`H>)yH7}d3#V8(qB%lds zPw0*diE#zj&-d~0>#B69%19{+@OU2Q6G6ho__>^ulSq^&Tq8%=8RL6|MlMRMdN^(V zP*6w|e3tYw4NYcqXa<9#iFAr#MH{6-S+ojDQYg~4J?!fc6-{d}Br2p;!o~C9uqBT) zz!^*NCew_;!b8H5wlR}qf+L2!lXJ#XOb06WRt|0$OUa!Sm}?UKcMuOGTAOxnjx0yB>UlbrU$Dre>aQ2$;pIW)T56JkN|%|c&ZYl zkAO3IR$f0J(Dd=$92ElEej&^Ubp1k~;O7H+JmHix*Pwq?g(RrYs`V&@(t4JaKF`pr zbNfP)Xmoq4tvqdH9>qXKUmz3=i;`7CSD;5HiSqW=QbnhFOBF#5c1ju+$%j9(@>||w zmb8gsH+SF2O-p;KJUT!gTnYs z%cO}ZCpm_@hzC*R`{AKTF0F1F<(XjW_mpNb#(MX#hlNxccB?VbRfcr z2%-~`3cTc4d|Sa4J-G!XwieO@2W>~l2$b3jGyP1X2vST^G-6Lsw(99oob@h*?PWS_ zHyws!_D$qxxNE;tID(E-h>sW)-PVnj8|pT*+ZjfmD*p`h#f9W56crz|y@+0Q&#KF4 zc0?i}+Kw{?Nv0HcD18TU25uI&q#Ct(_XJLtHo-G%A`|Kzj$ETQW6(RnvCE;rUA#O| zWK3knk2=geX^x*R0X~I_m?^PCz+1K@^&~E&X_q2lb%VesWebweq2@fI8E%#}5*@h8 z|I#Ij7hhFzMIa|2ZP|BKEjU-}-x-O9>2j3gK2h&gNn7g+g~Y&S8u5#gw4TxkwHoPF zjE^CvI`KUYe}JOpTgklm?&U60`UQH_$5yLy1&6;QHX!C8W+O%)<6pS<+z=TB$D%A4 zUU7{*Yjh|?l{f-oVbp-+a<)oT{hR%R<&sWTLm6&)2j zBHbdz8d)uh;15Y^`;??W&@YFAlB{Uew+2?Y3JrTO%QgmRa}AK{_=M{^Uv>$df8uMXKr8su@NS#FOwVbi#2Av+C{EK~xN(B8hH6 zyOhZk(xVXe+3@$UAG^v?`ZjddTXITRus7YyUMu!#b4YBvZkH2-tMQ}t`^j8&kvA|M zKb20xzn=U@eR9HGnKHR4{;VAHtbxDmUTXLvpI^s zO_XJ^hi$V^#P17<)P&j=OgDlkL&O;ry}T1X1 diff --git a/cls_mth_fc.py b/cls_mth_fc.py index a6ab225..cffc669 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -135,8 +135,7 @@ class LxTools(tk.Tk): """ log_name: str = f"{Path.home()}"[6:] file: Path = Path.home() / "/tmp/.log_user" - with open(file, "w", encoding="utf-8") as f: - f.write(log_name) + Path(file).write_text(log_name, encoding="utf-8") @staticmethod def clean_files(folder_path: Path = None, file: Path = None) -> None: @@ -156,13 +155,12 @@ class LxTools(tk.Tk): """ method that writes in file whether tooltip is displayed or not """ - with open(path, "r", encoding="utf-8") as set_f2: - lines2 = set_f2.readlines() - if "False\n" in lines2: - tip = False - else: - tip = True - return tip + lines = Path(path).read_text(encoding="utf-8") + if "False\n" in lines: + tip = False + else: + tip = True + return tip @staticmethod def msg_window(img_w: str, img_i: str, w_title: str, w_txt: str, txt2: Optional[str] = None, diff --git a/wirepy.py b/wirepy.py index b62cb6e..e442916 100755 --- a/wirepy.py +++ b/wirepy.py @@ -349,11 +349,9 @@ class FrameWidgets(ttk.Frame): """ if self.tk.call("ttk::style", "theme", "use") == "water-dark": self.tk.call("set_theme", "light") - with open(set_file, "r", encoding="utf-8") as theme_set2: - lines3 = theme_set2.readlines() - lines3[3] = "light\n" - with open(set_file, "w", encoding="utf-8") as theme_set2: - theme_set2.writelines(lines3) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed + lines[3] = 'light\n' + Path(set_file).write_text(''.join(lines), encoding="utf-8") self.color_label() def theme_change_dark(self): @@ -362,33 +360,27 @@ class FrameWidgets(ttk.Frame): """ if not self.tk.call("ttk::style", "theme", "use") == "water-dark": self.tk.call("set_theme", "dark") - with open(set_file, "r", encoding="utf-8") as theme_set2: - lines4 = theme_set2.readlines() - lines4[3] = "dark\n" - with open(set_file, "w", encoding="utf-8") as theme_set2: - theme_set2.writelines(lines4) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines[3] = 'dark\n' + Path(set_file).write_text(''.join(lines), encoding="utf-8") self.color_label() @staticmethod - def update_setting(update_res): + def update_setting(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: - with open(set_file, "r", encoding="utf-8") as set_f2: - lines2 = set_f2.readlines() - lines2[1] = "off\n" - with open(set_file, "w", encoding="utf-8") as set_f2: - set_f2.writelines(lines2) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines[1] = 'off\n' + Path(set_file).write_text(''.join(lines), encoding="utf-8") else: - with open(set_file, "r", encoding="utf-8") as set_f2: - lines2 = set_f2.readlines() - lines2[1] = "on\n" - with open(set_file, "w", encoding="utf-8") as set_f2: - set_f2.writelines(lines2) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines[1] = 'on\n' + Path(set_file).write_text(''.join(lines), encoding="utf-8") @staticmethod def tooltip(tip) -> None: @@ -398,20 +390,16 @@ class FrameWidgets(ttk.Frame): tip (bool): argument that is passed contains True or False """ if tip: - with open(set_file, "r", encoding="utf-8") as set_f2: - lines2 = set_f2.readlines() - lines2[5] = "False\n" - with open(set_file, "w", encoding="utf-8") as set_f2: - set_f2.writelines(lines2) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines[5] = 'False\n' + Path(set_file).write_text(''.join(lines), encoding="utf-8") else: - with open(set_file, "r", encoding="utf-8") as set_f2: - lines2 = set_f2.readlines() - lines2[5] = "True\n" - with open(set_file, "w", encoding="utf-8") as set_f2: - set_f2.writelines(lines2) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines[5] = 'True\n' + Path(set_file).write_text(''.join(lines), encoding="utf-8") - def enable_check_box(self, _): + def enable_check_box(self, _) -> None: """ checkbox for enable autostart Tunnel """ @@ -422,7 +410,7 @@ class FrameWidgets(ttk.Frame): self.lb_rename.delete(0, tk.END) self.btn_rename.config(state="normal") - def delete(self): + def delete(self) -> None: """ delete Wireguard Tunnel """ @@ -541,7 +529,7 @@ class FrameWidgets(ttk.Frame): except subprocess.CalledProcessError: pass - def import_sl(self): + def import_sl(self) -> None: """ Import Methode for Wireguard config Files. Before importing, it is checked whether PrivateKey and PublicKey are in the file. @@ -671,7 +659,7 @@ class FrameWidgets(ttk.Frame): self.show_data() return data - def box_set(self): + def box_set(self) -> None: """ This Method will display the autostarted label which Tunnel is automatically started regardless of the active tunnel. @@ -705,7 +693,7 @@ class FrameWidgets(ttk.Frame): self.on_off() - def on_off(self): + 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. @@ -732,7 +720,7 @@ class FrameWidgets(ttk.Frame): self.autoconnect.config(font=("Ubuntu", 11)) self.autoconnect.grid(column=1, row=0, sticky="e", pady=19) - def init_and_report(self, data=None): + def init_and_report(self, data=None) -> None: """ Displays the value address, DNS and peer in the labels or empty it again @@ -755,7 +743,7 @@ class FrameWidgets(ttk.Frame): for field in fields: field.set("") - def show_data(self): + def show_data(self) -> None: """ shows data in the label """ @@ -775,7 +763,7 @@ class FrameWidgets(ttk.Frame): self.endpoint.grid(column=0, row=8, sticky="w", padx=10, pady=20) self.endpoint.config(font=("Ubuntu", 9)) - def stop(self): + def stop(self) -> None: """ Stop Button """ @@ -785,7 +773,7 @@ class FrameWidgets(ttk.Frame): Tooltip(self.btn_stst, _("Click to stop selected Wireguard Tunnel"), tips) - def start(self): + def start(self) -> None: """ Start Button """ @@ -799,7 +787,7 @@ class FrameWidgets(ttk.Frame): else: Tooltip(self.btn_stst, _("Click to start selected Wireguard Tunnel"), tips) - def color_label(self): + def color_label(self) -> None: """ View activ Tunnel in the color green or yellow """ From 19d413ea97fd8d817a65ce528b76a57c00fb6bef Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 29 Apr 2025 12:32:23 +0200 Subject: [PATCH 24/61] optimized remove with open --- ssl_decrypt.py | 6 +++--- ssl_encrypt.py | 3 +-- start_wg.py | 15 ++++++--------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 38e6935..e9fda31 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -8,8 +8,7 @@ from subprocess import check_call uname: Path = Path("/tmp/.log_user") -with open(uname, "r", encoding="utf-8") as f: - log_name = f.readline() +log_name = Path(uname).read_text(encoding="utf-8") # Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard folder_path: Path = Path("/tmp/tlecdcwg/") @@ -31,5 +30,6 @@ if os.path.exists(f"{folder_path2}pbwgk.pem"): for detunnels in detl: tlname2 = f"{detunnels[:-4]}.conf" extpath = f"{folder_path}/{tlname2}" - check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", PKEYFILE, "-in", detunnels, "-out", extpath]) + check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", PKEYFILE, "-in", detunnels, + "-out", extpath]) shutil.chown(extpath, 1000, 1000) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index d5a73c9..7ab4fa7 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -8,8 +8,7 @@ from subprocess import check_call uname: Path = Path("/tmp/.log_user") -with open(uname, "r", encoding="utf-8") as f: - log_name: str = f.readline() +log_name = Path(uname).read_text(encoding="utf-8") keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") folder_path: Path = Path("/tmp/tlecdcwg/") diff --git a/start_wg.py b/start_wg.py index 84999a1..decf670 100755 --- a/start_wg.py +++ b/start_wg.py @@ -7,12 +7,9 @@ from subprocess import check_call path_to_file = Path(Path.home() / ".config/wire_py/settings") -with open(path_to_file, "r", encoding="utf-8") as a_con: - - # This function is for the independent autostarted of the previously selected tunnel - lines = a_con.readlines() - a_con = lines[7].strip() - if a_con != "off": - check_call(["nmcli", "connection", "up", a_con]) - else: - pass +a_con = Path(path_to_file).read_text(encoding="utf-8").splitlines(keepends=True) +a_con = a_con[7].strip() +if a_con != "off": + check_call(["nmcli", "connection", "up", a_con]) +else: + pass From 5fb4e68867cbc6890aa294102af1c6252851fe86 Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 29 Apr 2025 13:25:03 +0200 Subject: [PATCH 25/61] methods optimized-13:24 --- wirepy.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/wirepy.py b/wirepy.py index e442916..01f18d5 100755 --- a/wirepy.py +++ b/wirepy.py @@ -554,7 +554,6 @@ class FrameWidgets(ttk.Frame): read = file.read() path_split = filepath.split("/") path_split1 = path_split[-1] - self.a = Tunnel.active() if "PrivateKey = " in read and "PublicKey = " in read and "Endpoint =" in read: with open(filepath, "r", encoding="utf-8") as file: @@ -578,7 +577,7 @@ class FrameWidgets(ttk.Frame): os.rename(p1, f"/tmp/tlecdcwg/{path_split}") new_conf = f"/tmp/tlecdcwg/{path_split}" if self.a != "": - check_call(["nmcli", "connection", "down", Tunnel.active()]) + check_call(["nmcli", "connection", "down", self.a]) self.reset_fields() subprocess.check_output(["nmcli", "connection", "import", "type", @@ -589,7 +588,7 @@ class FrameWidgets(ttk.Frame): else: shutil.copy(filepath, "/tmp/tlecdcwg/") if self.a != "": - check_call(["nmcli", "connection", "down", Tunnel.active()]) + check_call(["nmcli", "connection", "down", self.a]) self.reset_fields() subprocess.check_output(["nmcli", "connection", "import", "type", @@ -670,11 +669,9 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(select_tunnel[0]) if self.selected_option.get() == 0: - with open(set_file, "r", encoding="utf-8") as set_f3: - lines3 = set_f3.readlines() - lines3[7] = "off\n" - with open(set_file, "w", encoding="utf-8") as set_f3: - set_f3.writelines(lines3) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines[7] = 'off\n' + Path(set_file).write_text(''.join(lines), encoding="utf-8") tl = Tunnel.list() @@ -682,11 +679,9 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") if self.selected_option.get() >= 1: - with open(set_file, "r", encoding="utf-8") as set_f3: - lines3 = set_f3.readlines() - lines3[7] = select_tl - with open(set_file, "w", encoding="utf-8") as set_f3: - set_f3.writelines(lines3) + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines[7] = select_tl + Path(set_file).write_text(''.join(lines), encoding="utf-8") except IndexError: self.selected_option.set(1) @@ -699,14 +694,13 @@ 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 """ - with open(set_file, "r", encoding="utf-8") as set_f4: - lines4 = set_f4.readlines() + lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) - if lines4[7] != "off\n": - print(f"{lines4[7]} starts automatically when the system starts.") + 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 = lines4[7] + self.auto_con = lines[7] else: self.selected_option.set(0) From 5dcfc9162109645a7116364d7aba1383fc11579d Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 29 Apr 2025 15:09:56 +0200 Subject: [PATCH 26/61] replace more "with open" --- wirepy.py | 134 +++++++++++++++++++++++++----------------------------- 1 file changed, 63 insertions(+), 71 deletions(-) diff --git a/wirepy.py b/wirepy.py index 01f18d5..b3eb207 100755 --- a/wirepy.py +++ b/wirepy.py @@ -23,6 +23,8 @@ Create.decrypt() tcl_path: Path = Path("/usr/share/TK-Themes") set_file: Path = Path(Path.home() / ".config/wire_py/settings") +keys: Path = Path(Path.home() / ".config/wire_py/keys") + tips = LxTools.if_tip(set_file) folder_path: Path = Path("/tmp/tlecdcwg/") user_file = Path("/tmp/.log_user") @@ -140,7 +142,7 @@ class FrameWidgets(ttk.Frame): # About BTN Menu / Label self.about_btn = ttk.Button( - self.menu_frame, text=_("About"), style="Toolbutton", command=self.info) + self.menu_frame, text=_("About"), style="Toolbutton", command=self.about) self.about_btn.grid(column=2, columnspan=2, row=0) self.readme = tk.Menu(self) @@ -332,7 +334,10 @@ class FrameWidgets(ttk.Frame): self.on_off() @staticmethod - def info(): + def about() -> None: + """ + a tk.Toplevel window + """ def link_btn() -> str | None: webbrowser.open("https://git.ilunix.de/punix/Wire-Py") @@ -343,7 +348,7 @@ class FrameWidgets(ttk.Frame): LxTools.msg_window(img_i, img_i, _("Info"), msg_t, _("Go to Wire-Py git"), link_btn) - def theme_change_light(self): + def theme_change_light(self) -> None: """ Set a light theme """ @@ -354,7 +359,7 @@ class FrameWidgets(ttk.Frame): Path(set_file).write_text(''.join(lines), encoding="utf-8") self.color_label() - def theme_change_dark(self): + def theme_change_dark(self) -> None: """ Set a dark theme """ @@ -435,7 +440,7 @@ class FrameWidgets(ttk.Frame): 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(f"{Path.home()}/.config/wire_py/keys", "r", encoding="utf-8") as readfile: + with open(keys, "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"): @@ -475,7 +480,9 @@ class FrameWidgets(ttk.Frame): LxTools.msg_window(img_w, img_i2, sl, pfit) def tl_rename(self) -> None: - + """ + method to rename a tunnel + """ special_characters = ["\\", "/", "{", "}", " "] if len(self.lb_rename.get()) > 12: @@ -531,19 +538,8 @@ class FrameWidgets(ttk.Frame): def import_sl(self) -> None: """ - Import Methode for Wireguard config Files. - Before importing, it is checked whether PrivateKey and PublicKey are in the file. - If True, then it is checked whether the PreSharedKey is already in the key file - to avoid an import error so that no double wgconf are imported. - Thus, tunnels can be renamed without the problems arising. - If False, then the key is written into the file. - Furthermore, it is checked whether the name is longer than 12 characters. - If True, then the name is automatically shortened to 12 characters - and then imported. - If in each case false comes out, a corresponding window comes to - inform the user that something is wrong. + validity check of wireguard config files """ - Create.dir_and_files() try: @@ -560,70 +556,70 @@ class FrameWidgets(ttk.Frame): key = Tunnel.con_to_dict(file) pre_key = key[3] if len(pre_key) != 0: - with open(f"{Path.home()}/.config/wire_py/keys", "r", encoding="utf-8") as readfile: - p_key = readfile.readlines() - if pre_key in p_key or f"{pre_key}\n" in p_key: + + p_key = keys.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) + msg_t = _("Tunnel already available!\nPlease use another file for import") + LxTools.msg_window(img_w2, img_i2, ie, msg_t) - else: + else: - with open(f"{Path.home()}/.config/wire_py/keys", "a", encoding="utf-8") as keyfile: - keyfile.write(f"{pre_key}\r") - if len(path_split1) > 17: - p1 = shutil.copy(filepath, "/tmp/tlecdcwg/") - path_split = path_split1[len(path_split1) - 17:] - os.rename(p1, f"/tmp/tlecdcwg/{path_split}") - new_conf = f"/tmp/tlecdcwg/{path_split}" - if self.a != "": - check_call(["nmcli", "connection", "down", self.a]) - self.reset_fields() + with open(keys, "a", encoding="utf-8") as keyfile: + keyfile.write(f"{pre_key}\r") + if len(path_split1) > 17: + p1 = shutil.copy(filepath, "/tmp/tlecdcwg/") + path_split = path_split1[len(path_split1) - 17:] + os.rename(p1, f"/tmp/tlecdcwg/{path_split}") + new_conf = f"/tmp/tlecdcwg/{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() + Create.encrypt() - else: - shutil.copy(filepath, "/tmp/tlecdcwg/") - if self.a != "": - check_call(["nmcli", "connection", "down", self.a]) - self.reset_fields() + else: + shutil.copy(filepath, "/tmp/tlecdcwg/") + 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() + 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) + 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, _("To use the autostart, enable this Checkbox"), tips) + Tooltip(self.wg_autostart, _("To use the autostart, enable this Checkbox"), tips) - # Tooltip(self.l_box, _("List of available tunnels")) + # Tooltip(self.l_box, _("List of available tunnels")) - Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!") - , tips,) + Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!") + , tips,) - Tooltip(self.btn_exp, _(" Click to export all\nWireguard Tunnel to Zipfile") - , tips) + Tooltip(self.btn_exp, _(" Click to export all\nWireguard Tunnel to Zipfile") + , tips) - Tooltip(self.btn_rename, _("To rename a tunnel, you need to\nselect a tunnel from" - " the list"), tips) + Tooltip(self.btn_rename, _("To rename a tunnel, you need to\nselect a tunnel from" + " the list"), tips) - 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"]) + 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"]) if ("PrivateKey = " in read) and ("Endpoint = " in read): pass @@ -650,7 +646,6 @@ class FrameWidgets(ttk.Frame): 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) @@ -719,7 +714,6 @@ class FrameWidgets(ttk.Frame): 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]}") @@ -731,7 +725,6 @@ class FrameWidgets(ttk.Frame): def reset_fields(self) -> None: """ reset data from labels - """ fields = [self.add, self.DNS, self.enp] for field in fields: @@ -741,7 +734,6 @@ 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.grid(column=0, row=5, sticky="w", padx=10, pady=6) From 18ed97bf2082d8865d20f451598cd274299dd9cd Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 29 Apr 2025 16:20:07 +0200 Subject: [PATCH 27/61] delete empty row --- wirepy.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/wirepy.py b/wirepy.py index b3eb207..a33df56 100755 --- a/wirepy.py +++ b/wirepy.py @@ -58,7 +58,6 @@ class Wirepy(tk.Tk): """ Class Wirepy this is the Main Window of wirepy """ - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -95,7 +94,6 @@ class FrameWidgets(ttk.Frame): """ ttk frame class for better structure """ - def __init__(self, container, **kwargs): super().__init__(container, **kwargs) @@ -466,7 +464,6 @@ class FrameWidgets(ttk.Frame): self.str_var.set(value="") self.start() self.l_box.update() - self.reset_fields() except IndexError: @@ -556,7 +553,6 @@ class FrameWidgets(ttk.Frame): key = Tunnel.con_to_dict(file) pre_key = key[3] if len(pre_key) != 0: - p_key = keys.read_text(encoding="utf-8") if pre_key in p_key or f"{pre_key}\n" in p_key: @@ -602,8 +598,6 @@ class FrameWidgets(ttk.Frame): Tooltip(self.wg_autostart, _("To use the autostart, enable this Checkbox"), tips) - # Tooltip(self.l_box, _("List of available tunnels")) - Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!") , tips,) From 2e94a324a6a7f70c4ef31cba7c1daa6e937791bc Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 30 Apr 2025 09:48:40 +0200 Subject: [PATCH 28/61] add wp_app_config.py for central configuration --- app_config.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 app_config.py diff --git a/app_config.py b/app_config.py new file mode 100644 index 0000000..54f97a7 --- /dev/null +++ b/app_config.py @@ -0,0 +1,78 @@ +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") + + # Configuration files + SETTINGS_FILE = CONFIG_DIR / "settings" + KEYS_FILE = CONFIG_DIR / "keys" + AUTOSTART_SERVICE = Path.home() / ".config/systemd/user/wg_start.service" + + # Localization + APP_NAME = "wirepy" + LOCALE_DIR = "/usr/share/locale/" + + # Default settings + DEFAULT_SETTINGS = { + "updates": "on", + "theme": "light", + "tooltip": True, + "autostart": "off" + } + + # UI configuration + UI_CONFIG = { + "window_title": "Wire-Py", + "window_size": (800, 600), + "font_family": "Ubuntu", + "font_size": 11 + } + + # System-dependent paths + SYSTEM_PATHS = { + "ssl_decrypt": "/usr/local/bin/ssl_decrypt.py", + "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py" + } + + @classmethod + def ensure_directories(cls) -> None: + """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()) + cls.SETTINGS_FILE.write_text(content) + + @classmethod + def get_image_paths(cls) -> Dict[str, Path]: + """Returns paths to UI images""" + return { + "main_icon": cls.CONFIG_DIR / "images/main.png", + "warning": cls.CONFIG_DIR / "images/warning.png", + "success": cls.CONFIG_DIR / "images/success.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 +After=network-online.target + +[Service] +Type=oneshot +ExecStartPre=/bin/sleep 5 +ExecStart=/usr/local/bin/start_wg.py +[Install] +WantedBy=default.target""" \ No newline at end of file From 08bef8fe6e9efbe0878a8d749fb107803199e53b Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 30 Apr 2025 09:49:57 +0200 Subject: [PATCH 29/61] add_wp_app_config.py for central configuration --- Changelog | 2 +- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 23366 -> 24258 bytes __pycache__/wp_app_config.cpython-312.pyc | Bin 0 -> 3612 bytes cls_mth_fc.py | 45 ++++++----- install | 10 +-- ssl_decrypt.py | 13 ++-- ssl_encrypt.py | 15 ++-- wirepy.py | 87 ++++++++++------------ app_config.py => wp_app_config.py | 11 ++- 9 files changed, 92 insertions(+), 91 deletions(-) create mode 100644 __pycache__/wp_app_config.cpython-312.pyc rename app_config.py => wp_app_config.py (90%) diff --git a/Changelog b/Changelog index 0384163..f5c3d3e 100644 --- a/Changelog +++ b/Changelog @@ -41,7 +41,7 @@ My standard System: Linux Mint 22 Cinnamon - Fix ipv6 in Config File on import - Wirepy run now as user - - settings, keys and Config Files now in ~/.config/wire_py + - settings, AppConfig.KEYS_FILE and Config Files now in ~/.config/wire_py - For new users, the required files are created and autostart service is started. - Tunnels are now read from the directory to view them in the list. To display only own tunnels, and read errors are minimized. diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 8c9b363d4c3d0e5e58ff9c192aa90c4cda2e92e9..a8e736cfd3a37abd062f0a5165ae39e1a573da33 100644 GIT binary patch delta 3377 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 delta 2802 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 None: @@ -80,11 +80,11 @@ class Create: def make_dir() -> None: """Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard""" - folder_path: Path = Path("/tmp/tlecdcwg/") - if folder_path.exists(): + AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/") + if AppConfig.TEMP_DIR.exists(): pass else: - folder_path.mkdir() + AppConfig.TEMP_DIR.mkdir() @staticmethod def decrypt() -> None: @@ -138,15 +138,15 @@ class LxTools(tk.Tk): Path(file).write_text(log_name, encoding="utf-8") @staticmethod - def clean_files(folder_path: Path = None, file: Path = None) -> None: + def clean_files(TEMP_DIR: Path = None, file: Path = None) -> None: """ method that can be added after need to delete a folder and a file when quitting. Args: :param file: default None - :param folder_path: default None + :param AppConfig.TEMP_DIR: default None """ - if folder_path is not None: - shutil.rmtree(folder_path) + if AppConfig.TEMP_DIR is not None: + shutil.rmtree(AppConfig.TEMP_DIR) if file is not None: Path.unlink(file) @@ -162,6 +162,15 @@ class LxTools(tk.Tk): tip = True return tip + + def theme_change(self) -> None: + + lines = AppConfig.SETTINGS_FILE.read_text() + if "light\n" in lines: + self.tk.call("set_theme", "light") + else: + self.tk.call("set_theme", "dark") + @staticmethod def msg_window(img_w: str, img_i: str, w_title: str, w_txt: str, txt2: Optional[str] = None, com: Optional[str] = None) -> None: @@ -392,8 +401,8 @@ class Tunnel: """ Returns a list of Wireguard tunnel names """ - folder_path: Path = Path("/tmp/tlecdcwg/") - wg_s: List[str] = os.listdir(folder_path) + AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/") + wg_s: List[str] = os.listdir(AppConfig.TEMP_DIR) return wg_s diff --git a/install b/install index 1b88d2b..8ca2607 100755 --- a/install +++ b/install @@ -17,7 +17,7 @@ install_file_with(){ exit 0 else sudo apt install python3-tk && \ - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -43,7 +43,7 @@ install_arch_d(){ exit 0 else sudo pacman -S --noconfirm tk python3 python-requests && \ - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -120,7 +120,7 @@ install(){ exit 0 else sudo dnf install python3-tkinter -y - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -145,7 +145,7 @@ install(){ rm -r ~/.config/wire_py && rm -r ~/.config/systemd exit 0 else - sudo cp -fv wirepy.py start_wg.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -181,7 +181,7 @@ install(){ remove(){ sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \ - /usr/local/bin/cls_mth_fc.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py + /usr/local/bin/wp_app_config.py cls_mth_fc.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py if [ $? -ne 0 ] then exit 0 diff --git a/ssl_decrypt.py b/ssl_decrypt.py index e9fda31..42015fd 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -5,13 +5,12 @@ import os import shutil from pathlib import Path from subprocess import check_call +from wp_app_config import AppConfig uname: Path = Path("/tmp/.log_user") log_name = Path(uname).read_text(encoding="utf-8") -# Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard -folder_path: Path = Path("/tmp/tlecdcwg/") keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") PKEYFILE: Path = "/usr/local/etc/ssl/pwgk.pem" @@ -20,16 +19,16 @@ if not keyfile.is_file(): check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) -folder_path2 = f"/home/{log_name}/.config/wire_py/" -detl: list[str] = os.listdir(folder_path2) -os.chdir(folder_path2) +AppConfig.TEMP_DIR2 = f"/home/{log_name}/.config/wire_py/" +detl: list[str] = os.listdir(AppConfig.TEMP_DIR2) +os.chdir(AppConfig.TEMP_DIR2) detl.remove("keys") detl.remove("settings") -if os.path.exists(f"{folder_path2}pbwgk.pem"): +if os.path.exists(f"{AppConfig.TEMP_DIR2}pbwgk.pem"): detl.remove("pbwgk.pem") for detunnels in detl: tlname2 = f"{detunnels[:-4]}.conf" - extpath = f"{folder_path}/{tlname2}" + extpath = f"{AppConfig.TEMP_DIR}/{tlname2}" check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", PKEYFILE, "-in", detunnels, "-out", extpath]) shutil.chown(extpath, 1000, 1000) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 7ab4fa7..2505a59 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -6,12 +6,13 @@ import shutil from pathlib import Path from subprocess import check_call +from wp_app_config import AppConfig + 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") -folder_path: Path = Path("/tmp/tlecdcwg/") PKEYFILE = "/usr/local/etc/ssl/pwgk.pem" if not keyfile.is_file(): @@ -19,28 +20,28 @@ if not keyfile.is_file(): check_call(["openssl", "rsa", "-in", PKEYFILE, "-out", keyfile, "-outform", "PEM", "-pubout"]) shutil.chown(keyfile, 1000, 1000) - if folder_path.exists(): - tl = os.listdir(f"{folder_path}") + if AppConfig.TEMP_DIR.exists(): + tl = os.listdir(f"{AppConfig.TEMP_DIR}") CPTH: str = f"{keyfile}" CRYPTFILES: str = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl: str = f"{folder_path}/{tunnels}" + 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,]) else: - if folder_path.exists(): - tl: list[str] = os.listdir(f"{folder_path}") + if AppConfig.TEMP_DIR.exists(): + tl: list[str] = os.listdir(f"{AppConfig.TEMP_DIR}") CPTH: str = f"{keyfile}" CRYPTFILES: str = CPTH[:-9] if keyfile.exists() and len(tl) != 0: for tunnels in tl: - sourcetl: str = f"{folder_path}/{tunnels}" + 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]) diff --git a/wirepy.py b/wirepy.py index a33df56..3ece856 100755 --- a/wirepy.py +++ b/wirepy.py @@ -15,31 +15,26 @@ 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 LxTools.uos() Create.dir_and_files() Create.make_dir() Create.decrypt() - -tcl_path: Path = Path("/usr/share/TK-Themes") -set_file: Path = Path(Path.home() / ".config/wire_py/settings") -keys: Path = Path(Path.home() / ".config/wire_py/keys") -tips = LxTools.if_tip(set_file) -folder_path: Path = Path("/tmp/tlecdcwg/") -user_file = Path("/tmp/.log_user") +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, set_file) +res = GiteaUpdate.api_down("https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases", VERSION, AppConfig.SETTINGS_FILE) # Translate -APP = "wirepy" -LOCALE_DIR = "/usr/share/locale/" -locale.bindtextdomain(APP, LOCALE_DIR) -gettext.bindtextdomain(APP, LOCALE_DIR) -gettext.textdomain(APP) +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 img_w: str = r"/usr/share/icons/lx-icons/64/info.png" @@ -52,7 +47,7 @@ ie:str = _("Import Error") pfit: str = _("Please first import tunnel") pstl: str = _("Please select a tunnel from the list") -LxTools.sigi(folder_path, user_file) +LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE) class Wirepy(tk.Tk): """ @@ -62,24 +57,18 @@ class Wirepy(tk.Tk): super().__init__(*args, **kwargs) self.my_tool_tip = None - self.x_width = 600 - self.y_height = 383 + self.x_width = AppConfig.UI_CONFIG["window_size"][0] + self.y_height = AppConfig.UI_CONFIG["window_size"][1] self.monitor_center_x = int(self.winfo_screenwidth() / 2 - (self.x_width / 2)) self.monitor_center_y = int(self.winfo_screenheight() / 2 - (self.y_height / 2)) - self.resizable(width=False, height=False) - self.title("Wire-Py") + self.resizable(AppConfig.UI_CONFIG["resizable_window"][0], AppConfig.UI_CONFIG["resizable_window"][1]) + self.title(AppConfig.UI_CONFIG["window_title"]) self.geometry(f"{self.x_width}x{self.y_height}+{self.monitor_center_x}+{self.monitor_center_y}") self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.style = ttk.Style(self) - self.tk.call("source", f"{tcl_path}/water.tcl") - - lines = set_file.read_text() - if "light\n" in lines: - self.tk.call("set_theme", "light") - else: - self.tk.call("set_theme", "dark") + self.tk.call("source", f"{AppConfig.SYSTEM_PATHS["tcl_path"]}/water.tcl") + 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") @@ -352,9 +341,9 @@ class FrameWidgets(ttk.Frame): """ if self.tk.call("ttk::style", "theme", "use") == "water-dark": self.tk.call("set_theme", "light") - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed lines[3] = 'light\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") self.color_label() def theme_change_dark(self) -> None: @@ -363,9 +352,9 @@ class FrameWidgets(ttk.Frame): """ if not self.tk.call("ttk::style", "theme", "use") == "water-dark": self.tk.call("set_theme", "dark") - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[3] = 'dark\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") self.color_label() @staticmethod @@ -376,14 +365,14 @@ class FrameWidgets(ttk.Frame): update_res (int): argument that is passed contains 0 or 1 """ if update_res == 1: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[1] = 'off\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") else: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[1] = 'on\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") @staticmethod def tooltip(tip) -> None: @@ -393,14 +382,14 @@ class FrameWidgets(ttk.Frame): tip (bool): argument that is passed contains True or False """ if tip: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[5] = 'False\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") else: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[5] = 'True\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") def enable_check_box(self, _) -> None: """ @@ -425,12 +414,12 @@ class FrameWidgets(ttk.Frame): pre_key = key[3] check_call(["nmcli", "connection", "delete", select_tl]) self.l_box.delete(self.select_tunnel[0]) - with open(set_file, "r", encoding="utf-8") as set_f6: + 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(set_file, "w", encoding="utf-8") as set_f7: + 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")) @@ -516,11 +505,11 @@ class FrameWidgets(ttk.Frame): if self.a != "" and self.a == select_tl: self.a = Tunnel.active() self.str_var.set(value=self.a) - with open(set_file, "r", encoding="utf-8") as set_f5: + 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(set_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) @@ -658,9 +647,9 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(select_tunnel[0]) if self.selected_option.get() == 0: - lines = Path(set_file).read_text(encoding="utf-8").splitlines(keepends=True) + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) lines[7] = 'off\n' - Path(set_file).write_text(''.join(lines), encoding="utf-8") + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") tl = Tunnel.list() @@ -668,9 +657,9 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") if self.selected_option.get() >= 1: - lines = Path(set_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(set_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) @@ -683,7 +672,7 @@ 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(set_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.") @@ -771,7 +760,7 @@ class FrameWidgets(ttk.Frame): """ View activ Tunnel in the color green or yellow """ - lines = set_file.read_text() + lines = AppConfig.SETTINGS_FILE.read_text() if "light\n" in lines: self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green") @@ -865,5 +854,5 @@ if __name__ == "__main__": window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0") window.mainloop() -LxTools.clean_files(folder_path, user_file) +LxTools.clean_files(AppConfig.TEMP_DIR, AppConfig.USER_FILE) sys.exit(0) diff --git a/app_config.py b/wp_app_config.py similarity index 90% rename from app_config.py rename to wp_app_config.py index 54f97a7..3485c59 100644 --- a/app_config.py +++ b/wp_app_config.py @@ -8,7 +8,8 @@ class AppConfig: 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" @@ -29,15 +30,17 @@ class AppConfig: # UI configuration UI_CONFIG = { "window_title": "Wire-Py", - "window_size": (800, 600), + "window_size": (600, 383), "font_family": "Ubuntu", - "font_size": 11 + "font_size": 11, + "resizable_window": (False, False) } # System-dependent paths SYSTEM_PATHS = { "ssl_decrypt": "/usr/local/bin/ssl_decrypt.py", - "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py" + "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py", + "tcl_path": "/usr/share/TK-Themes" } @classmethod From c10667ec21f7d2a022ed9621e9c9e72107f9e5fa Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 30 Apr 2025 10:43:51 +0200 Subject: [PATCH 30/61] bug fix in rename --- wirepy.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/wirepy.py b/wirepy.py index 3ece856..46fff58 100755 --- a/wirepy.py +++ b/wirepy.py @@ -427,7 +427,7 @@ class FrameWidgets(ttk.Frame): 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(keys, "r", encoding="utf-8") as readfile: + 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"): @@ -502,17 +502,15 @@ class FrameWidgets(ttk.Frame): self.l_box.update() new_a_connect = self.lb_rename.get() self.lb_rename.delete(0, tk.END) - if self.a != "" and self.a == select_tl: - self.a = Tunnel.active() - self.str_var.set(value=self.a) - with open(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) - + + 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: @@ -542,7 +540,7 @@ class FrameWidgets(ttk.Frame): key = Tunnel.con_to_dict(file) pre_key = key[3] if len(pre_key) != 0: - p_key = keys.read_text(encoding="utf-8") + 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") @@ -550,7 +548,7 @@ class FrameWidgets(ttk.Frame): else: - with open(keys, "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, "/tmp/tlecdcwg/") From 23116617350910c6cac38e73b271d66835a50e2d Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 30 Apr 2025 23:24:00 +0200 Subject: [PATCH 31/61] part one more optimization with app_config file --- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 24258 -> 25421 bytes __pycache__/wp_app_config.cpython-312.pyc | Bin 3612 -> 8883 bytes cls_mth_fc.py | 44 ++++--- wirepy.py | 95 +++++++-------- wp_app_config.py | 136 ++++++++++++++++++---- 5 files changed, 191 insertions(+), 84 deletions(-) diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index a8e736cfd3a37abd062f0a5165ae39e1a573da33..3e3e5a962e62f1b0c737f31e1a2cf8fc8c9c78af 100644 GIT binary patch delta 4351 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 delta 3196 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 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 From 2cdc40f414dded7ce8c8e780ff43bb2ffe99c491 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 3 May 2025 17:57:57 +0200 Subject: [PATCH 32/61] part two more optimization with app_config file --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0c64554 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "workbench.settings.openDefaultSettings": true +} \ No newline at end of file From 0cdad100b6bef7c8627e41c8e1be53c72cc1f197 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 3 May 2025 17:58:19 +0200 Subject: [PATCH 33/61] part two more optimization with app_config file --- .idea/workspace.xml | 48 +- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 25421 -> 26419 bytes __pycache__/manage_tunnel.cpython-312.pyc | Bin 0 -> 12893 bytes __pycache__/wp_app_config.cpython-312.pyc | Bin 8883 -> 8928 bytes cls_mth_fc.py | 190 +++---- manage_tunnel.py | 15 + ssl_decrypt.py | 6 +- ssl_encrypt.py | 13 +- wirepy.py | 652 ++++++++++++---------- wp_app_config.py | 1 + 10 files changed, 493 insertions(+), 432 deletions(-) create mode 100644 __pycache__/manage_tunnel.cpython-312.pyc create mode 100644 manage_tunnel.py diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3af9204..e22a54e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,8 +5,12 @@ - + + + + + - { + "keyToString": { + "ASKED_ADD_EXTERNAL_FILES": "true", + "Python.INSTALL.executor": "Run", + "Python.cls_mth_fc.executor": "Run", + "Python.install.executor": "Run", + "Python.main.executor": "Run", + "Python.messagebox.executor": "Run", + "Python.start_wg.executor": "Run", + "Python.testtheme.executor": "Run", + "Python.wg_func.executor": "Run", + "Python.wg_main.executor": "Run", + "Python.wirepy.executor": "Run", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "Shell Script.install.executor": "Run", + "Shell Script.run_as.executor": "Run", + "git-widget-placeholder": "28-04-2025-more-methods-and-optimize-methods", + "last_opened_file_path": "/home/punix/Pyapps/wire-py", + "settings.editor.selected.configurable": "ml.llm.LLMConfigurable" } -}]]> +} diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index 3e3e5a962e62f1b0c737f31e1a2cf8fc8c9c78af..b250738e7093043a4d3def650f62d29f7bbcd3d5 100644 GIT binary patch delta 4720 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 delta 4052 zcmb7HYfxLq6~23=s|ONDNIWC~M&>P>;9#Bx6KuykY*WBSjspoHGWP<)vLN>g3#ce@ z;yh?Owlh0PNzx{?(>A1LI=D<{^6E5B;?&J+pxCi{$9Z+!rtZ`ou$kE7bTU2X$^zm1 z=w#u3XZM`5XU{&)+11QPyykNX-jBfT&eQRMeF?rwv*7U_a~6o=pRxt z>5Z6F!wBI?h#X?Xv=E(Jmq@>SAR(S-dDRJ4@t+~g5VI^91&KOZ;^G>IH9l{5PasCt zOG2O{&?6iaWKLnpYThFraB-p;Ov$k=HE#Eg#=4e{+8qrwVh{4zzyVp^>GAnothgH( zq8C8Ik)2M_2}Cw320J=?JpOKBcr>A5(B0SPlM15QuVSZd01q>l%=Yu<+&OdZd2`{M zxp3ZG^dxt|kTA~88PYBo&ByA->W(#xHH@cBbe}U8e7=g)b2W%;~6ei-Vp?512#D6nBC^S12J(hlW12$4c8e60$WR zO|LP`kH#rYWD!AI7tywALaHfkL^7fcX?v~ol*yW+cc+`afhD+c_PXa zjl7DFor*i53XcIAnqD(K+-N0A|1>?X=S@@Q6?q^z;!UrO9$lLfA3tR|p$_v#bRk`M z+;ly!(MIxW8~*Ny=T`81$`&=VReq;{ z-_i5_u48NUd;+hVveTr@JWGhIY|#h-r_&Ut9U-!6_;lpPfzwkd%Y1q))5a+*`az}> zHr~J)T&W%}tt7Vzd+KdK?*#0=Qj7)&vDF zM4!tdJ_H^8DJ~+Fw4(TA`8At{D_Afy8q0R6Qv~aD0Z< z&k>gQL;M1pf1_*fZ829v9+#G&{V=oONYGc%$L7WBZpDdAQ9$hM@rbHO*Tgi5#>Ky|h{iHRo>|OLgIrhqYKE>vL5Y{7PAUF};i+~;dJVH0OZQr@bI=PDD z(gFiy!m5zT7KLCO0287hOnVN5Y4H13k$+7Ev8414kMJJ-t;J3z=rbi!0dImh(L$VP zAx^XqCt8RTEyRfyZW}E;O5&m(8gVm~rRA!+!|2>GggJyCA)H3I58yH$?8`a?J@P(6 zcnq6(Lte(ScR3DuBlNXR?c`NztJp@4()%iolAf7=ReZ)4i|0{HVB(nhJe>jWAwhx` z6Mig+4yX^ux(EDz!B-F#KRxqJRW)I4gjQGINq#~5t1HrO-*zw{vyx9%_jh>%^wa7j z_P=b@6@P#*w5dahznR}{NhG6p1k$04Qg}n;+8;3noVS=vZ)T*nLPJc5MD;U9`h|=u zvFp!|(uj>{+-@1~f>i~RKvc)LNXKZ?u$%C=e5El%rvK}%3gdpoxgk>~Q^-gpSj-Zp z72bvLGBkx#0Hl$uh)0k~R45-ZEhh-G=Vf?xZh!qdWrzx$_;;`=rV=_@n>#B00~@ml zUjhur*9`XeiGirJFr0pi)#dT}1b(fv*(Z1;!5I*ToStrv*FS8$r67o6pV;WKikRtT zmA@}2>!g6#g$Z9=z#+;Mflu&9k_#=b+gcWtNKmeK5A_Q*Xv-D*tyF?m(&0FYPD1}! zx6vS80gi-c+)2Nx%Zr@?nN$z(FtcQ%>+4-4neMHZM)mP~$t(LWCOgJAzF7HeY?v+Y~bF*&)j zx%KneJEHYUr%T@`eC2`p%7!z-S)2R16^B`15SJWGyzRU>W6qobNp3V}I%ArLEO$L+ zMdjt{{LeO`}p=CUHBiQw>5bLLEK2GiA?8Don%`tVaokwy2~H99S&E=53i# z**Szx5K3=DwNde3*&@3I$`^ z*jn}~>fB0|Pz$HZ1!hKWJHnDO+R?tvP=@{#qG#oFsy&Z|gxO{F=EoYVBigXJ2G zTJZ<8)YC{_ppSWKS(c?AdQuG)%RS5Jk|&EiMD6?6*FTHe&jZMurz;?cE?xNd(OsY| zlR=rlW3qD)z9I3KsM-L02~#UxxKOr)FN@9e$NTN%%FM6#|B7TPBfS7Rksn)VOv2aA ztsh%r74`FWR?X5ezBTGX`V)@OjIbGxapT57AS?2I8WN!gls7L(96>8I)^J+2K;^ z>q?99fD!m-q-QAY;wyNTlsvirm@p2vB)4^ceDM3#4j?4*Sl23B=;%< Iplsy-04Rm}jsO4v diff --git a/__pycache__/manage_tunnel.cpython-312.pyc b/__pycache__/manage_tunnel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50273d04311b3c76229d191a87570b7caf8abbaf GIT binary patch literal 12893 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 literal 0 HcmV?d00001 diff --git a/__pycache__/wp_app_config.cpython-312.pyc b/__pycache__/wp_app_config.cpython-312.pyc index 92aa7ca57ab037499b2ba5f54941cd566eeb32e8..a42c96c77cf60cda1551f895732afd40d3504a79 100644 GIT binary patch delta 800 zcmdn&`oNX%G%qg~0}%LT31?_YPvm>WsIc*8IAeW^${dy`(Nv*S)>PIsCP|=*RJIgV z8z7ycmZF}bk)oNRm7<-ZlcJlVm!dz1GfE6itpP}Fs(6ZFszjCnkckG;m{N>dSXM(t zqa;(MQY=zT;3EDEQPN-`Q@9XAlnhYB5Nr)Q#7RIy%#h?|!SX=orkEp%%2gU`T5gtM zc4LySlGZORF4E7*PfpCyPc2E-FD}l}FDOsX)+c znpaZH1mr0w6j@K6$7jzNKKUMBG*FKfzd56r22iGm2ShjmiDE{OLZG^K0e!}}$=d}2 zf$BH~B^U)QF_ahySxaVs^sobo28Iu8oC4BCRzS8UQ<3rHHlb;WV6%;YBDZ*oOA<>m zlXFu`GV)W3%z;8hpxC&@ot%?cTnrU<1_=j&h)56-4I;qSf($LP1`!~uzyv3dQG9fA zt?+m08=^)ZIoXui8hj?GOemh=JfUKu-e(3jR<;i!lY2zA13lm(s?Y5i1JW7`BH}7++Kh;;?{-1Q3x3BAP)2n$2k-F%VI?d5?kj3Kv-vF0&{#Fn(vT NU}XEm03^Tx3ji-8vW5Tv delta 769 zcmZXQO-vI(6o7ZOyDe>(rBtCU)n-#bTvS^ATS|;pF(J`JVxndNvFQu~0UCGL1Wz<# zJeQ0y9=IA0ULbogUcGWM9!z?Y=tWOO4~-@!zPDh~)H!@R^UeF4{XFGNsr#+10Rh&l zN_eT>wXJ?9iM?NU$mWj6=wM+cGkH;r!9DZRo=N!9UV4W1(X&*i{q!6?PcP7c9lz1p z6q~|W7NKbtT}i{XX)TI$@Tv6X*cvg`MaSuIgSioR)kuRn1t#sb@g0o>MMfL$PrLUp zE*)#IiN`~F#z}atiK6b|TXL`C$omq>^UXk!WcioCA2PuYf}`Xnk0?2kb#5uU1eBy+ zB?g~Xm*baV%BrteTDRG{ZHaKwG;;!FgxAyv|EhMA+q|W17LsY(IO*(zJ5xbi0$2n9 z34AYjKWwb`{Pl@?^w1}Fd&}1= zJr(QamCDBUz)wMzy|plZck(Sv|F9z!oSwzJImA5T9-vAVOdAgpVgYd<@dVM-mZ3vz zIN!89iMRvdSy$XzM=u)T`J}GnyUeYB2~CxLw@j{;=%ZD3(R{|ACzJgvgv1X8WX None: - - lines = AppConfig.SETTINGS_FILE.read_text() - if "light\n" in lines: - self.tk.call("set_theme", "light") - else: - self.tk.call("set_theme", "dark") @staticmethod - def msg_window(img_w: str, img_i: str, w_title: str, w_txt: str, txt2: Optional[str] = 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 - :argument img_w = Image for TK window which is displayed to the left of the text - :argument img_i = Image for Task Icon + :argument AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text + :argument AppConfig.IMAGE_PATHS["icon_vpn"] = Image for Task Icon :argument w_title = Windows Title :argument w_txt = Text for Tk Window :argument txt2 = Text for Button two @@ -207,7 +198,7 @@ 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=img_w) + msg.img = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_info"]) msg.i_window = tk.Label(msg, image=msg.img) label: tk.Label = tk.Label(msg, text=w_txt) @@ -228,8 +219,8 @@ class LxTools(tk.Tk): button: ttk.Button = ttk.Button(msg, text="OK", command=msg.destroy, padding=4) button.grid(column=0, columnspan=2, row=1) - img_i: tk.PhotoImage = tk.PhotoImage(file=img_i) - msg.iconphoto(True, img_i) + AppConfig.IMAGE_PATHS["icon_vpn"]: tk.PhotoImage = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_vpn"]) + msg.iconphoto(True, AppConfig.IMAGE_PATHS["icon_vpn"]) msg.columnconfigure(0, weight=1) msg.rowconfigure(0, weight=1) msg.winfo_toplevel() @@ -278,85 +269,10 @@ class LxTools(tk.Tk): signal.signal(signal.SIGHUP, signal_handler) -class GiteaUpdate: - """ - Calling download requests the download URL of the running script, - the taskbar image for the “Download OK” window, the taskbar image for the - “Download error” window and the variable res - """ - - @staticmethod - def api_down(update_api_url: str, version: str, file: Optional[Path] = None) -> str: - """ - Checks for updates via API - - Args: - update_api_url: Update API URL - version: Current version - file: Optional - Configuration file - - Returns: - New version or status message - """ - try: - response: requests.Response = requests.get(update_api_url, timeout=10) - response_dict: Any = response.json() - response_dict: Dict[str, Any] = response_dict[0] - with open(file, "r", encoding="utf-8") as set_f: - set_f = set_f.read() - if "on\n" in set_f: - if version[3:] != response_dict["tag_name"]: - req: str = response_dict["tag_name"] - else: - req: str = "No Updates" - else: - req: str = "False" - return req - except requests.exceptions.RequestException: - req: str = "No Internet Connection!" - return req - - @staticmethod - def download(urld: str, res: str, img_w: str = None, img_i: str = None, img_w2: str = None, img_i2: str = None) -> None: - """ - Downloads new version of wirepy - - Args: - urld: Download URL - res: Result filename - img_w: Image for TK window which is displayed to the left of the text - img_i: Image for Task Icon - img_w2: Image for TK window which is displayed to the left of the text - img_i2: Image for Task Icon - """ - try: - to_down: str = f"wget -qP {Path.home()} {" "} {urld}" - result: int = subprocess.call(to_down, shell=True) - if result == 0: - shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000) - - wt: str = _("Download Successful") - msg_t: str = _("Your zip file is in home directory") - LxTools.msg_window(img_w, img_i, wt, msg_t) - - else: - - wt: str = _("Download error") - msg_t: str = _("Download failed! Please try again") - LxTools.msg_window(img_w2, img_i2, wt, msg_t) - - except subprocess.CalledProcessError: - - wt: str = _("Download error") - msg_t: str = _("Download failed! No internet connection!") - LxTools.msg_window(img_w2, img_i2, wt, msg_t) - - class Tunnel: """ Class of Methods for Wire-Py - """ - + """ @classmethod def con_to_dict(cls, file: TextIO) -> Tuple[str, str, str, Optional[str]]: """ @@ -426,16 +342,17 @@ class Tunnel: return wg_s @staticmethod - def export(img_w: str = None, img_i: str = None, img_w2: str = None, img_i2: str = None, sl: str = None, pfit:str = 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 in the user's home directory with the correct right Args: - img_w: Image for TK window which is displayed to the left of the text - img_i: Image for Task Icon - img_w2: Image for TK window which is displayed to the left of the text - img_i2: Image for Task Icon + AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text + AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon + AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text + AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon """ now_time: datetime = datetime.now() now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") @@ -451,20 +368,95 @@ class Tunnel: with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: if len(zf.namelist()) != 0: - LxTools.msg_window(img_w, img_i, 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(img_w2, img_i2, 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(img_w, img_i2, 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 +class GiteaUpdate: + """ + Calling download requests the download URL of the running script, + the taskbar image for the “Download OK” window, the taskbar image for the + “Download error” window and the variable res + """ + + @staticmethod + def api_down(update_api_url: str, version: str, file: Optional[Path] = None) -> str: + """ + Checks for updates via API + + Args: + update_api_url: Update API URL + version: Current version + file: Optional - Configuration file + + Returns: + New version or status message + """ + try: + response: requests.Response = requests.get(update_api_url, timeout=10) + response_dict: Any = response.json() + response_dict: Dict[str, Any] = response_dict[0] + with open(file, "r", encoding="utf-8") as set_f: + set_f = set_f.read() + if "on\n" in set_f: + if version[3:] != response_dict["tag_name"]: + req: str = response_dict["tag_name"] + else: + req: str = "No Updates" + else: + req: str = "False" + return req + except requests.exceptions.RequestException: + req: str = "No Internet Connection!" + return req + + @staticmethod + 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 + + Args: + urld: Download URL + res: Result filename + AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text + AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon + AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text + AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon + """ + try: + to_down: str = f"wget -qP {Path.home()} {" "} {urld}" + result: int = subprocess.call(to_down, shell=True) + if result == 0: + shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000) + + 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) + + 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) + + 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) + + class Tooltip: """ class for Tooltip diff --git a/manage_tunnel.py b/manage_tunnel.py new file mode 100644 index 0000000..04cdcb2 --- /dev/null +++ b/manage_tunnel.py @@ -0,0 +1,15 @@ +#!/usr/bin/python3 +from pathlib import Path +from subprocess import check_call +from tkinter import filedialog, ttk +from cls_mth_fc 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() + diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 42015fd..66c5902 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -12,11 +12,11 @@ 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", PKEYFILE, "-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 +29,6 @@ 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", PKEYFILE, "-in", detunnels, + check_call(["openssl", "pkeyutl", "-decrypt", "-inkey", AppConfig.SYSTEM_PATHS["pkey_path"], "-in", detunnels, "-out", extpath]) shutil.chown(extpath, 1000, 1000) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 2505a59..df62157 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -5,23 +5,22 @@ import os import shutil from pathlib import Path from subprocess import check_call - +from cls_mth_fc import LxTools from wp_app_config import AppConfig -uname: Path = Path("/tmp/.log_user") +#uname: Path = Path("/tmp/.log_user") -log_name = Path(uname).read_text(encoding="utf-8") +#log_name = AppConfig.USER_FILE.read_text(encoding="utf-8") -keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") -PKEYFILE = "/usr/local/etc/ssl/pwgk.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", PKEYFILE, "-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(): - tl = os.listdir(f"{AppConfig.TEMP_DIR}") + tl = LxTools.get_file_name(AppConfig.TEMP_DIR) CPTH: str = f"{keyfile}" CRYPTFILES: str = CPTH[:-9] diff --git a/wirepy.py b/wirepy.py index 144ac7e..101e3f8 100755 --- a/wirepy.py +++ b/wirepy.py @@ -14,31 +14,18 @@ from pathlib import Path from subprocess import check_call from tkinter import TclError, filedialog, ttk -from cls_mth_fc import (Create, GiteaUpdate, Tooltip, Tunnel, LxTools) +from cls_mth_fc import (Create, GiteaUpdate, Tunnel, Tooltip, LxTools) from wp_app_config import AppConfig, Msg LxTools.uos() Create.dir_and_files() Create.make_dir() 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.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" - -LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE) - class Wirepy(tk.Tk): """ Class Wirepy this is the Main Window of wirepy @@ -58,7 +45,11 @@ class Wirepy(tk.Tk): self.rowconfigure(0, weight=1) self.style = ttk.Style(self) self.tk.call("source", f"{AppConfig.SYSTEM_PATHS["tcl_path"]}/water.tcl") - LxTools.theme_change(self) + lines = AppConfig.SETTINGS_FILE.read_text() + if "light\n" in lines: + self.tk.call("set_theme", "light") + else: + self.tk.call("set_theme", "dark") # Load the image file from the disk self.wg_icon = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_vpn"]) @@ -66,16 +57,18 @@ class Wirepy(tk.Tk): # Set it as the window icon self.iconphoto(True, self.wg_icon) - FrameWidgets(self).grid() + tips = LxTools.if_tip(AppConfig.SETTINGS_FILE) + FrameWidgets(self, tips_enabled=tips).grid() class FrameWidgets(ttk.Frame): """ ttk frame class for better structure """ - def __init__(self, container, **kwargs): + def __init__(self, container, tips_enabled=None, **kwargs): super().__init__(container, **kwargs) + self.tunnel = Tunnel() self.lb_tunnel = None self.btn_stst = None self.endpoint = None @@ -88,7 +81,7 @@ class FrameWidgets(ttk.Frame): 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"]) - + self.tips_enabled = tips_enabled if tips_enabled is not None else LxTools.if_tip(AppConfig.SETTINGS_FILE) # Frame for Menu self.menu_frame = ttk.Frame(self) self.menu_frame.configure(relief="flat") @@ -99,12 +92,12 @@ 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: {VERSION[2:]}", tips) + Tooltip(self.version_lb, f"Version: {VERSION[2:]}", self.tips_enabled) self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options")) self.options_btn.grid(column=1, columnspan=1, row=0) - Tooltip(self.options_btn, _("Click for Settings"), tips) + Tooltip(self.options_btn, _("Click for Settings"), self.tips_enabled) set_update = tk.IntVar() set_tip = tk.BooleanVar() @@ -138,14 +131,14 @@ class FrameWidgets(ttk.Frame): set_update.set(value=1) self.updates_lb.configure(text=_("Update search off")) - Tooltip(self.updates_lb, _("Updates you have disabled"), tips) + Tooltip(self.updates_lb, _("Updates you have disabled"), self.tips_enabled) elif res == "No Internet Connection!": self.updates_lb.configure(text=_("No Server Connection!"), foreground="red") elif res == "No Updates": self.updates_lb.configure(text=_("No Updates")) - Tooltip(self.updates_lb, _("Congratulations! Wire-Py is up to date"), tips) + Tooltip(self.updates_lb, _("Congratulations! Wire-Py is up to date"), self.tips_enabled) else: set_update.set(value=0) @@ -155,7 +148,7 @@ class FrameWidgets(ttk.Frame): self.update_btn = ttk.Menubutton(self.menu_frame, text=text) self.update_btn.grid(column=4, columnspan=3, row=0, padx=0) - Tooltip(self.update_btn, _("Click to download new version"), tips) + Tooltip(self.update_btn, _("Click to download new version"), self.tips_enabled) self.download = tk.Menu(self, relief="flat") @@ -163,7 +156,7 @@ class FrameWidgets(ttk.Frame): self.download.add_command( label=_("Download"), command=lambda: GiteaUpdate.download(f"https://git.ilunix.de/punix/Wire-Py/archive/{res}.zip", - res, img_w, img_i, img_w2, img_i2)) + res, AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_vpn"], AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"])) # Show active Tunnel self.a = Tunnel.active() @@ -224,9 +217,9 @@ class FrameWidgets(ttk.Frame): self.l_box.configure(yscrollcommand=self.scrollbar.set) # Tunnel List - self.tl = Tunnel.list() + self.tl = LxTools.get_file_name(AppConfig.TEMP_DIR) for tunnels in self.tl: - self.l_box.insert("end", tunnels[:-5]) + self.l_box.insert("end", tunnels) self.l_box.update() # Button Vpn @@ -247,7 +240,7 @@ class FrameWidgets(ttk.Frame): 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, _("Click to import a Wireguard Tunnel"), tips) + Tooltip(self.btn_i, _("Click to import a Wireguard Tunnel"), self.tips_enabled) # Button Trash self.btn_tr = ttk.Button(self.lb_frame_btn_lbox, image=self.tr_pic, command=self.delete, padding=0, @@ -255,20 +248,20 @@ class FrameWidgets(ttk.Frame): self.btn_tr.grid(column=0, row=2, padx=15, pady=8) if self.l_box.size() == 0: - Tooltip(self.btn_tr, _("No tunnels to delete in the list"), tips) + Tooltip(self.btn_tr, _("No tunnels to delete in the list"), self.tips_enabled) else: - Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), tips) + Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), self.tips_enabled) # 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, + 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, _("No Tunnels in List for Export"), tips) + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tips_enabled) else: - Tooltip(self.btn_exp, _("Click to export all\nWireguard Tunnel to Zipfile"), tips) + Tooltip(self.btn_exp, _("Click to export all\nWireguard Tunnel to Zipfile"), self.tips_enabled) # Label Entry self.lb_rename = ttk.Entry(self.lb_frame4, width=20) @@ -277,9 +270,9 @@ class FrameWidgets(ttk.Frame): self.lb_rename.config(state="disable") if self.l_box.size() != 0: - Tooltip(self.lb_rename, _("To rename a tunnel, you need to\nselect a tunnel from the list"), tips) + Tooltip(self.lb_rename, _("To rename a tunnel, you need to\nselect a tunnel from the list"), self.tips_enabled) else: - Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), tips) + Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), self.tips_enabled) # Button Rename self.btn_rename = ttk.Button(self.lb_frame4, text=_("Rename"), state="disable", command=self.tl_rename, @@ -300,53 +293,17 @@ 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, Msg.TTIP["autostart"], tips) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], tself.tips_enabled) if self.l_box.size() == 0: - Tooltip(self.wg_autostart, Msg.TTIP["autostart_info"], tips) + Tooltip(self.wg_autostart, Msg.TTIP["autostart_info"], self.tips_enabled) else: - Tooltip(self.wg_autostart, Msg.TTIP["autostart"], tips) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tips_enabled) self.on_off() - @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(img_i, img_i, _("Info"), msg_t, _("Go to Wire-Py git"), link_btn) - - def theme_change_light(self) -> None: - """ - Set a light theme - """ - if self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "light") - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed - lines[3] = 'light\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - self.color_label() - - def theme_change_dark(self) -> None: - """ - Set a dark theme - """ - if not self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "dark") - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) - lines[3] = 'dark\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - self.color_label() @staticmethod def update_setting(update_res) -> None: @@ -382,17 +339,197 @@ class FrameWidgets(ttk.Frame): lines[5] = 'True\n' Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - def enable_check_box(self, _) -> None: + @staticmethod + def about() -> None: """ - checkbox for enable autostart Tunnel + a tk.Toplevel window """ - 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 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 theme_change_light(self) -> None: + """ + Set a light theme + """ + if self.tk.call("ttk::style", "theme", "use") == "water-dark": + self.tk.call("set_theme", "light") + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed + lines[3] = 'light\n' + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") + self.color_label() + + def theme_change_dark(self) -> None: + """ + Set a dark theme + """ + if not self.tk.call("ttk::style", "theme", "use") == "water-dark": + self.tk.call("set_theme", "dark") + lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) + lines[3] = 'dark\n' + Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") + self.color_label() + + 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.tips_enabled) + else: + Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tips_enabled) + + 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 + """ + lines = AppConfig.SETTINGS_FILE.read_text() + if "light\n" in lines: + self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green") + + else: + self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="yellow") + + self.lb_tunnel.config(font=("Ubuntu", 11, "bold")) + self.lb_tunnel.grid(column=2, padx=10, row=1) + + def 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.tips_enabled) + + 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.tips_enabled) + Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tips_enabled) + Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tips_enabled) + Tooltip(self.btn_rename, Msg.TTIP["rename_tl"], self.tips_enabled) + + 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 @@ -433,10 +570,10 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") self.lb_rename.configure(state="disabled") Tooltip(self.wg_autostart, _("You must have at least one\ntunnel in the list,to use the autostart") - , tips) + , self.tips_enabled) - Tooltip(self.btn_exp, _("No Tunnels in List for Export"), tips) - Tooltip(self.btn_stst, _("No tunnels to start in the list"), tips) + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tips_enabled) + Tooltip(self.btn_stst, _("No tunnels to start in the list"), self.tips_enabled) Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), tips, ) self.lb_rename.insert(0, _("Max. 12 characters!")) @@ -450,11 +587,83 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() != 0: - LxTools.msg_window(img_w, img_i2, 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(img_w, img_i2, 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: + """ + 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: """ @@ -465,19 +674,19 @@ class FrameWidgets(ttk.Frame): if len(self.lb_rename.get()) > 12: - LxTools.msg_window(img_w, img_i2, 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(img_w, img_i2, 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(img_w, img_i2, 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(img_w, img_i2, 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: @@ -510,7 +719,7 @@ class FrameWidgets(ttk.Frame): except IndexError: - LxTools.msg_window(img_w, img_i2, 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 @@ -518,170 +727,53 @@ class FrameWidgets(ttk.Frame): except EOFError as e: print(e) - def import_sl(self) -> None: - """ - validity check of wireguard config files - """ - Create.dir_and_files() - + def activate_tunnel(self, tunnel_name): + """Activates a tunnel after a delay""" try: - filepath = filedialog.askopenfilename(initialdir=f"{Path.home()}", title=_("Select Wireguard config File"), - filetypes=[(_("WG config files"), "*.conf")]) - - 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(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, 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"], tips) - - Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], tips) - - Tooltip(self.btn_exp, Msg.TTIP["export_tl"], tips) - - Tooltip(self.btn_rename, Msg.TTIP["rename_tl"], tips) - - 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"]) - - if ("PrivateKey = " in read) and ("Endpoint = " in read): - pass + # First check if the tunnel exists in NetworkManager + nm_connections = subprocess.run( + ["nmcli", "-t", "-f", "NAME", "connection", "show"], + check=True, + stdout=subprocess.PIPE, + text=True + ).stdout.strip().split('\n') + + # Find the actual connection name (it might have been modified) + actual_name = None + for conn in nm_connections: + if tunnel_name in conn: + actual_name = conn + break + + if actual_name: + # Use the actual connection name + subprocess.run(["nmcli", "connection", "up", actual_name], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) else: - - LxTools.msg_window(img_w2, img_i2, 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 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 box_set(self) -> None: - """ - This Method will display the autostarted label which - Tunnel is automatically started regardless of the active tunnel. - The selected tunnel is written into a file to read it after the start of the system. - """ - 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 = Tunnel.list() - - 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 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) + # Use the original name as fallback + subprocess.run(["nmcli", "connection", "up", tunnel_name], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # After successful activation, update the display + self.a = Tunnel.active() + self.str_var.set(self.a) + self.color_label() + + # Try to load the tunnel data + try: + data = self.handle_tunnel_data(self.a) + self.init_and_report(data) + self.show_data() + self.stop() + except Exception as e: + print(f"Error loading tunnel data: {e}") + + except subprocess.CalledProcessError as e: + print(f"Error activating tunnel: {e}", "hier simma") def init_and_report(self, data=None) -> None: """ @@ -696,14 +788,6 @@ class FrameWidgets(ttk.Frame): self.enp = tk.StringVar() self.enp.set(f"{_("Endpoint: ")}{data[2]}") - def reset_fields(self) -> None: - """ - reset data from labels - """ - fields = [self.add, self.DNS, self.enp] - for field in fields: - field.set("") - def show_data(self) -> None: """ shows data in the label @@ -723,44 +807,6 @@ class FrameWidgets(ttk.Frame): self.endpoint.grid(column=0, row=8, sticky="w", padx=10, pady=20) self.endpoint.config(font=("Ubuntu", 9)) - def stop(self) -> 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"], tips) - - 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 = Tunnel.list() - if len(tl) == 0: - Tooltip(self.btn_stst, Msg.TTIP["empty_list"], tips) - else: - Tooltip(self.btn_stst, Msg.TTIP["start_tl"], tips) - - def color_label(self) -> None: - """ - View activ Tunnel in the color green or yellow - """ - lines = AppConfig.SETTINGS_FILE.read_text() - if "light\n" in lines: - self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green") - - else: - self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="yellow") - - self.lb_tunnel.config(font=("Ubuntu", 11, "bold")) - self.lb_tunnel.grid(column=2, padx=10, row=1) - def wg_switch(self, event=None) -> None: """ Deals with switching the VPN connection @@ -782,11 +828,11 @@ class FrameWidgets(ttk.Frame): if self.l_box.size() != 0: - LxTools.msg_window(img_w, img_i2, 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(img_w, img_i2, 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: """ @@ -833,6 +879,10 @@ class FrameWidgets(ttk.Frame): if __name__ == "__main__": + + _ = AppConfig.setup_translations() + tips = LxTools.if_tip(AppConfig.SETTINGS_FILE) + LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE) window = Wirepy() """ the hidden files are hidden in Filedialog diff --git a/wp_app_config.py b/wp_app_config.py index fc6dbc5..26cc313 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -46,6 +46,7 @@ class AppConfig: "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" } From d0aed9e2537f31841dee281b1e11b71cab8915e8 Mon Sep 17 00:00:00 2001 From: punix Date: Mon, 5 May 2025 20:42:19 +0200 Subject: [PATCH 34/61] optimize performance 03-05-2025 --- .vscode/settings.json | 3 + __pycache__/cls_mth_fc.cpython-312.pyc | Bin 26419 -> 29714 bytes __pycache__/wp_app_config.cpython-312.pyc | Bin 8928 -> 9170 bytes cls_mth_fc.py | 133 +++++-- wirepy.py | 443 ++++++++++++++-------- wp_app_config.py | 6 + 6 files changed, 390 insertions(+), 195 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0c64554..34dfa2f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { "workbench.settings.openDefaultSettings": true + "workbench.startupEditor": "none" + "update.showReleaseNotes": false + "terminal.integrated.fontSize": 22 } \ No newline at end of file diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index b250738e7093043a4d3def650f62d29f7bbcd3d5..d1ce12316f634408ba823cf598851ce560a9e523 100644 GIT binary patch delta 5460 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 WkvDSWojkbBe#n!|@=ZdB@B9a5{IjC~ diff --git a/cls_mth_fc.py b/cls_mth_fc.py index cb89a1d..ba9cb42 100755 --- a/cls_mth_fc.py +++ b/cls_mth_fc.py @@ -169,18 +169,6 @@ class LxTools(tk.Tk): if file is not None: Path.unlink(file) - @staticmethod - def if_tip(path: Path) -> bool: - """ - method that writes in file whether tooltip is displayed or not - """ - lines = Path(path).read_text(encoding="utf-8") - if "False\n" in lines: - tip = False - else: - tip = True - return tip - @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: @@ -382,6 +370,82 @@ class Tunnel: pass +# ConfigManager with caching +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""" + if not cls._config: + 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', + '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, + 'autostart': 'off' + } + return cls._config + + @classmethod + def save(cls): + """Save the config to the config file""" + if cls._config: + lines = [ + '# Configuration\n', + f"{cls._config['updates']}\n", + '# Theme\n', + f"{cls._config['theme']}\n", + '# Tooltips\n', + f"{str(cls._config['tooltips'])}\n", + '# Autostart\n', + f"{cls._config['autostart']}\n" + ] + 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""" + config = cls.load() + return config.get(key, default) + + +class ThemeManager: + @staticmethod + def change_theme(root, theme_in_use, theme_name=None): + """Change application theme centrally""" + root.tk.call("set_theme", theme_in_use) + if theme_in_use == theme_name: + ConfigManager.set("theme", theme_in_use) + + class GiteaUpdate: """ Calling download requests the download URL of the running script, @@ -390,35 +454,46 @@ class GiteaUpdate: """ @staticmethod - def api_down(update_api_url: str, version: str, file: Optional[Path] = None) -> str: + def api_down(update_api_url: str, version: str, update_setting: str = None) -> str: """ Checks for updates via API - + Args: update_api_url: Update API URL version: Current version - file: Optional - Configuration file + 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_dict: Any = response.json() - response_dict: Dict[str, Any] = response_dict[0] - with open(file, "r", encoding="utf-8") as set_f: - set_f = set_f.read() - if "on\n" in set_f: - if version[3:] != response_dict["tag_name"]: - req: str = response_dict["tag_name"] - else: - req: str = "No Updates" - else: - req: str = "False" - return req + 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: - req: str = "No Internet Connection!" - return req + 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, diff --git a/wirepy.py b/wirepy.py index 101e3f8..181d298 100755 --- a/wirepy.py +++ b/wirepy.py @@ -14,17 +14,13 @@ from pathlib import Path from subprocess import check_call from tkinter import TclError, filedialog, ttk -from cls_mth_fc import (Create, GiteaUpdate, Tunnel, Tooltip, LxTools) +from cls_mth_fc 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() -# 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) class Wirepy(tk.Tk): """ @@ -43,22 +39,17 @@ class Wirepy(tk.Tk): self.geometry(f"{self.x_width}x{self.y_height}+{self.monitor_center_x}+{self.monitor_center_y}") self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.style = ttk.Style(self) self.tk.call("source", f"{AppConfig.SYSTEM_PATHS["tcl_path"]}/water.tcl") - lines = AppConfig.SETTINGS_FILE.read_text() - if "light\n" in lines: - self.tk.call("set_theme", "light") - else: - self.tk.call("set_theme", "dark") - + 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) - tips = LxTools.if_tip(AppConfig.SETTINGS_FILE) - FrameWidgets(self, tips_enabled=tips).grid() + FrameWidgets(self).grid() class FrameWidgets(ttk.Frame): @@ -68,47 +59,59 @@ class FrameWidgets(ttk.Frame): def __init__(self, container, tips_enabled=None, **kwargs): super().__init__(container, **kwargs) - self.tunnel = Tunnel() self.lb_tunnel = None self.btn_stst = None self.endpoint = None self.dns = None self.address = None self.auto_con = None + self.tips_enabled = tips_enabled + 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"]) - self.tips_enabled = tips_enabled if tips_enabled is not None else LxTools.if_tip(AppConfig.SETTINGS_FILE) + self.tips_enabled = tips_enabled if tips_enabled is not None else ConfigManager.get("tooltip") + # StringVar-Variables initialization + self.update_label = tk.StringVar() + self.update_tooltip = tk.StringVar() + 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=VERSION) + self.version_lb = ttk.Label(self.menu_frame, text=AppConfig.VERSION) self.version_lb.config(font=("Ubuntu", 11), foreground="#00c4ff") self.version_lb.grid(column=0, row=0, rowspan=4, padx=10) - Tooltip(self.version_lb, f"Version: {VERSION[2:]}", self.tips_enabled) + Tooltip(self.version_lb, f"Version: {AppConfig.VERSION[2:]}", self.tips_enabled) self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options")) self.options_btn.grid(column=1, columnspan=1, row=0) Tooltip(self.options_btn, _("Click for Settings"), self.tips_enabled) - set_update = tk.IntVar() - set_tip = tk.BooleanVar() + self.set_update = tk.IntVar() + self.set_tip = tk.BooleanVar() 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(set_update.get()), variable=set_update) - self.settings.add_checkbutton(label=_("Disable Tooltips"), - command=lambda: self.tooltip(set_tip.get()), variable=set_tip) - self.settings.add_command(label=_("Light"), command=self.theme_change_light) - self.settings.add_command(label=_("Dark"), command=self.theme_change_dark) + command=lambda: self.update_setting(self.set_update.get()), variable=self.set_update) + self.settings.add_command(label=_("Disable Tooltips"), + command=lambda: self.tooltip(self.set_tip.get()), variable=self.set_tip) + + self.updates_lb = ttk.Label(self.menu_frame) + res = GiteaUpdate.api_down(AppConfig.UPDATE_URL, AppConfig.VERSION, ConfigManager.get("updates")) + self.update_ui_for_update(res) + # 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( @@ -116,49 +119,10 @@ class FrameWidgets(ttk.Frame): self.about_btn.grid(column=2, columnspan=2, row=0) self.readme = tk.Menu(self) - # Update and Tooltip Label - self.updates_lb = ttk.Label(self.menu_frame) - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) # View Checkbox to enable or disable Tooltip - if tips: - set_tip.set(value=False) - else: - set_tip.set(value=True) + self.set_tip.set(value=not self.tips_enabled) - # View Checkbox for enable or disable Updates - if res == "False": - set_update.set(value=1) - self.updates_lb.configure(text=_("Update search off")) - - Tooltip(self.updates_lb, _("Updates you have disabled"), self.tips_enabled) - - elif res == "No Internet Connection!": - self.updates_lb.configure(text=_("No Server Connection!"), foreground="red") - elif res == "No Updates": - self.updates_lb.configure(text=_("No Updates")) - - Tooltip(self.updates_lb, _("Congratulations! Wire-Py is up to date"), self.tips_enabled) - - else: - set_update.set(value=0) - text = f"Update {res} {_("available!")}" - - # Update BTN Menu - self.update_btn = ttk.Menubutton(self.menu_frame, text=text) - self.update_btn.grid(column=4, columnspan=3, row=0, padx=0) - - Tooltip(self.update_btn, _("Click to download new version"), self.tips_enabled) - - self.download = tk.Menu(self, relief="flat") - - self.update_btn.configure(menu=self.download, style="Toolbutton") - self.download.add_command( - label=_("Download"), - command=lambda: GiteaUpdate.download(f"https://git.ilunix.de/punix/Wire-Py/archive/{res}.zip", - res, AppConfig.IMAGE_PATHS["icon_info"], AppConfig.IMAGE_PATHS["icon_vpn"], AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"])) - - # Show active Tunnel self.a = Tunnel.active() # Label Frame 1 @@ -254,8 +218,11 @@ class FrameWidgets(ttk.Frame): # 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"], + 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: @@ -304,40 +271,97 @@ class FrameWidgets(ttk.Frame): self.on_off() - - @staticmethod - def update_setting(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: - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) - lines[1] = 'off\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - + # Update the labels based on the result + def update_ui_for_update(self, res): + """Update UI elements based on update check result""" + if res == "False": + self.set_update.set(value=1) + self.update_label.set(_("Update search off")) + self.update_tooltip.set(_("Updates you have disabled")) + self.update_foreground.set("red") + + # Remove update button if it exists + if hasattr(self, 'update_btn'): + self.update_btn.grid_forget() + + # Display the label + self.updates_lb.configure( + textvariable=self.update_label, + foreground=self.update_foreground.get() + ) + self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + + elif res == "No Internet Connection!": + self.update_label.set(_("No Server Connection!")) + self.update_foreground.set("red") + + # Remove update button if it exists + if hasattr(self, 'update_btn'): + self.update_btn.grid_forget() + + # Display the label + self.updates_lb.configure( + textvariable=self.update_label, + foreground=self.update_foreground.get() + ) + self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) + + 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("black") + + # Remove update button if it exists + if hasattr(self, 'update_btn'): + self.update_btn.grid_forget() + + # Display the label + self.updates_lb.configure( + textvariable=self.update_label, + foreground=self.update_foreground.get() + ) + self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + else: - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) - lines[1] = 'on\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - - @staticmethod - def tooltip(tip) -> None: + self.set_update.set(value=0) + update_text = f"Update {res} {_('available!')}" + + # Remove the label if displayed + self.updates_lb.grid_forget() + + # Create or update the update button + if not hasattr(self, 'update_btn'): + # Create the update button if it doesn't exist yet + 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.tips_enabled) + + 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"] + ) + ) +} + def tooltip(self, tip) -> None: """ - write True or False in a file + Aktualisiert die Tooltip-Einstellung im ConfigManager Args: - tip (bool): argument that is passed contains True or False + tip (bool): True zum Deaktivieren, False zum Aktivieren von Tooltips """ - if tip: - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) - lines[5] = 'False\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - - else: - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) - lines[5] = 'True\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") + # Beachten Sie die umgekehrte Logik: tip=True bedeutet Tooltips deaktivieren + ConfigManager.set("tooltip", not tip) + # Aktualisieren Sie die lokale Variable für sofortige Wirkung + self.tips_enabled = not tip @staticmethod def about() -> None: @@ -354,27 +378,163 @@ class FrameWidgets(ttk.Frame): LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_vpn"], AppConfig.IMAGE_PATHS["icon_vpn"], _("Info"), msg_t, _("Go to Wire-Py git"), link_btn) - def theme_change_light(self) -> None: - """ - Set a light theme - """ - if self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "light") - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) # (keepends=True) = not changed - lines[3] = 'light\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - self.color_label() + def update_ui_for_update_status(self, res): + """Update UI elements based on update check result""" + print(f"Updating UI for result: {res}") # Debug output + + # First, clean up any existing UI elements + if hasattr(self, 'update_btn') and self.update_btn.winfo_exists(): + self.update_btn.grid_forget() + + # Reset all variables to ensure fresh state + self.update_label.set("") + self.update_tooltip.set("") + self.update_foreground.set("black") + + if res == "False": + self.set_update.set(value=1) + self.update_label.set(_("Update search off")) + self.update_tooltip.set(_("Updates you have disabled")) + self.update_foreground.set("red") + + # Display the label + self.updates_lb.configure( + textvariable=self.update_label, + foreground=self.update_foreground.get() + ) + self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + + elif res == "No Internet Connection!": + self.update_label.set(_("No Server Connection!")) + self.update_foreground.set("red") + + # Display the label + self.updates_lb.configure( + textvariable=self.update_label, + foreground=self.update_foreground.get() + ) + self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) + + 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("black") + + # Display the label + self.updates_lb.configure( + textvariable=self.update_label, + foreground=self.update_foreground.get() + ) + self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + + else: + # We have an update available + self.set_update.set(value=0) + update_text = f"Update {res} {_('available!')}" + + # Hide the label if it's visible + if self.updates_lb.winfo_ismapped(): + self.updates_lb.grid_forget() + + # Create or update the update button + if not hasattr(self, 'update_btn') or not self.update_btn.winfo_exists(): + # Create the update button if it doesn't exist yet + 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.tips_enabled) + + 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"] + ) + ) + else: + # Update the existing update button + self.update_btn.configure(text=update_text) + # Make sure it's visible + self.update_btn.grid(column=4, columnspan=3, row=0, padx=0) + + # Update the download command + if hasattr(self, 'download'): + self.download.entryconfigure( + 0, # First entry in the menu + 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"] + ) + ) - def theme_change_dark(self) -> None: + 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 """ - Set a dark theme - """ - if not self.tk.call("ttk::style", "theme", "use") == "water-dark": - self.tk.call("set_theme", "dark") - lines = Path(AppConfig.SETTINGS_FILE).read_text(encoding="utf-8").splitlines(keepends=True) - lines[3] = 'dark\n' - Path(AppConfig.SETTINGS_FILE).write_text(''.join(lines), encoding="utf-8") - self.color_label() + 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_status("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") + print(f"API returned: {res}") # Debug output + + # 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_status(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_status("No Internet Connection!") + + def update_tooletip_label(self) -> str: + """Update the theme label based on current theme""" + current_value = ConfigManager.get("tooletip") + if current_value == "True": + self.set_tip.set(_("Enable Tooltips")) + else: + self.set_tip.set(_("Disable Tooltips")) + + 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: """ @@ -410,8 +570,8 @@ class FrameWidgets(ttk.Frame): """ View activ Tunnel in the color green or yellow """ - lines = AppConfig.SETTINGS_FILE.read_text() - if "light\n" in lines: + if ConfigManager.get("theme") == "light": + self.lb_tunnel = ttk.Label(self, textvariable=self.str_var, foreground="green") else: @@ -727,54 +887,6 @@ class FrameWidgets(ttk.Frame): except EOFError as e: print(e) - def activate_tunnel(self, tunnel_name): - """Activates a tunnel after a delay""" - try: - # First check if the tunnel exists in NetworkManager - nm_connections = subprocess.run( - ["nmcli", "-t", "-f", "NAME", "connection", "show"], - check=True, - stdout=subprocess.PIPE, - text=True - ).stdout.strip().split('\n') - - # Find the actual connection name (it might have been modified) - actual_name = None - for conn in nm_connections: - if tunnel_name in conn: - actual_name = conn - break - - if actual_name: - # Use the actual connection name - subprocess.run(["nmcli", "connection", "up", actual_name], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - else: - # Use the original name as fallback - subprocess.run(["nmcli", "connection", "up", tunnel_name], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - # After successful activation, update the display - self.a = Tunnel.active() - self.str_var.set(self.a) - self.color_label() - - # Try to load the tunnel data - try: - data = self.handle_tunnel_data(self.a) - self.init_and_report(data) - self.show_data() - self.stop() - except Exception as e: - print(f"Error loading tunnel data: {e}") - - except subprocess.CalledProcessError as e: - print(f"Error activating tunnel: {e}", "hier simma") - def init_and_report(self, data=None) -> None: """ Displays the value address, DNS and peer in the labels @@ -881,7 +993,6 @@ class FrameWidgets(ttk.Frame): if __name__ == "__main__": _ = AppConfig.setup_translations() - tips = LxTools.if_tip(AppConfig.SETTINGS_FILE) LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE) window = Wirepy() """ diff --git a/wp_app_config.py b/wp_app_config.py index 26cc313..aa68942 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -24,6 +24,12 @@ class AppConfig: 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" + # Default settings DEFAULT_SETTINGS: Dict[str, Any] = { "updates": "on", From dba6138aa78297fae174032abe9c0ab971a053bc Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 6 May 2025 19:47:14 +0200 Subject: [PATCH 35/61] optimize performance 06-05-2025 --- .idea/workspace.xml | 4 +- .vscode/settings.json | 2 +- Changelog | 2 +- __pycache__/cls_mth_fc.cpython-312.pyc | Bin 29714 -> 30387 bytes __pycache__/common_tools.cpython-312.pyc | Bin 0 -> 30660 bytes __pycache__/wp_app_config.cpython-312.pyc | Bin 9170 -> 9040 bytes cls_mth_fc.py => common_tools.py | 71 ++++++----- install | 10 +- manage_tunnel.py | 2 +- ssl_encrypt.py | 2 +- wirepy.py | 138 ++++++++++++++-------- wp_app_config.py | 7 -- 12 files changed, 140 insertions(+), 98 deletions(-) create mode 100644 __pycache__/common_tools.cpython-312.pyc rename cls_mth_fc.py => common_tools.py (92%) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e22a54e..dba3213 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,7 +6,7 @@ - + @@ -51,7 +51,7 @@ "keyToString": { "ASKED_ADD_EXTERNAL_FILES": "true", "Python.INSTALL.executor": "Run", - "Python.cls_mth_fc.executor": "Run", + "Python.common_tools.executor": "Run", "Python.install.executor": "Run", "Python.main.executor": "Run", "Python.messagebox.executor": "Run", diff --git a/.vscode/settings.json b/.vscode/settings.json index 34dfa2f..18313e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,5 @@ "workbench.settings.openDefaultSettings": true "workbench.startupEditor": "none" "update.showReleaseNotes": false - "terminal.integrated.fontSize": 22 + "terminal.integrated.fontSize": 18 } \ No newline at end of file diff --git a/Changelog b/Changelog index f5c3d3e..8c67e92 100644 --- a/Changelog +++ b/Changelog @@ -3,7 +3,7 @@ My standard System: Linux Mint 22 Cinnamon ## [Unreleased] - - os import in cls_mth_fc.py replaced by other methods + - os import in common_tools.py replaced by other methods - If Wire-Py already runs, prevent further start - for loops with lists replaced by List Comprehensions diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc index d1ce12316f634408ba823cf598851ce560a9e523..f8af799729cc47d1be98d439fc0730bd9991e94f 100644 GIT binary patch delta 1293 zcmZuwO>7%Q6rNe{+H3FH{mY*nH*AtRi4jeqgogCTf)pA=6PG|RZ6q3PJey`s{F7Oy zZK6m{qzDpPDWxOSNWG+Rs1cV&1`(tl0v8TkY7}J4Tzufb1>rP3zyXQ3>$FNMo;35` ze)D$U_r5oK^ADW+6MKGkyIlyZzSlSYYGln*9>8|?*T5@SW`Xc>?%Qx5#%;NO&wW$h zG7D+9kcNgaLBkxu!!{xlo5mAf6NsSMH3xhBxtfd@(ukdKV3?tuaCw>wdP{r)KU)Cse~w#*$Rofm{lh9#17{dN;%d$G>zr zjkKE9MlP$A{WIQ(e`K}#OM7S0h|O%(v{@8$vbB`o4}rZ3?cV3xO`L7=ZH($ItgH(S zwi)+MVdb<*C0r7&P(7_NU(DZ3qZpymo~aZu%Z};^q9-mHC1;wdQEfyegq@F_$TV>X z8RwzS94a?9FY?!=qW2&;XYuHr3-Y17d}#H9EGcyM<~w_Tl}}e}NN%Go@NtvM6XPLrz}k*r#NO@qvP`6og=#&Pn^L|UUE2>Lbk zvK9&l9dy)`(LR+hKVWG}?*VysEY_Og=pcmjENlkoh!lk?CU6N}nW^6yP3JQcc)?WM zyti}65_MTxk_w_PFZxz`HpM_ul%GL%iQT+9bob(&i-kaUKG40%1$x-`v2L#Ichv) z)`ib5WKVAN6%Y8b_#R%zj|5wzYf-3($mv;jExE4CW%hPzrDxw;I?e z!=V#qi8#AI*_??1xPZ1eK@hiOS$G4l7!_ptY-`o8cWfN}>C_LW^2Z0Oz=IFfZ7XBj z?-WjK)insA)sF1TcMew&gzH_~rrYY`h4#lVgzH_85IF01pemFPcF;a3o$IF#jBm0b OW6d4H?GF)bR?>eQop6Q# delta 839 zcmZvYO-vI}5Xax!?zYSB77OxKp|}(jn;(sX%Awv&yqKs-LrkoVCLFyOT8oJX56%{-7be*^|M}0`nO|P)C!YO* z3qRQH0)psHR0JOOLDl3Wc#8eZSAG?D z&}*Yd7|9WPI;t^swfW&afzY@v8v#`|Hl8Eji4~2B7GF!nBm6DKdBNKl2$)QMYIIc^IMs zA9(@@pf8$anh6YCNR~HH1N5ctbaa}i4PbAQ(iOdl@rX1!SV~<+VR4G>hZiB;=7=G4 zW`KH)nyY@OCsBssIX+V&aBXvX8bMmD&84NwaV2^^C*K=cvyrJN<=P|r77xwuPa}}= nmaLgFjaIIH%LW;5Sx44b%GibRWBU7c)ZT*Ye<3)C8y5ZpIo0T| diff --git a/__pycache__/common_tools.cpython-312.pyc b/__pycache__/common_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb299c76a01a906ddd52909e90bb88d195b8629c GIT binary patch literal 30660 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 s>t;)m1?!Xi z`nc(F-#%+-qRXDM(I39~;hXafI}`j)cDPRuq$MQQfgn-CpW&zj~;H%#|!mesDf4H9K%A(X9FrKw+7}#==cOxUfc`qKhG$x6~vh;wb@T$5pWCHu?0d|@)P?M>Jx#Xvx z;>fF{M$!-x`RTOrcgj4ry{2hUko25w(4IG1Zf^e5T|e2C?0iX!NSyTFV=3;{IBh%c zR8-lXn`xQd@?QHghv1E^_vmqFtJT(QL?d`(>wOLnt&>!FYYQWqnF${DBS3a$Oh=Oc zj(SL&j%I*_v1ERiy6K3XW*i(%GHxf+^AJN4oM;NqY-mVhDobOU4nWyS9j0TN>>@@_ zg@OYbZBF=-Oq&TaFR1MV1>>U-eU9pytD1S`VoGuVg{UQvV~vgafMJIfh*s#A0W|Iq z?TMEqe$%%2z$Ix4R%;a*63QzirqNJPw~|^rTMB^|w@YHZP!r;v z%+2&j^x#F)s|WPrfs2oW$$IkQ!4rDWXky~`Eof@z@SFMm^Zy<*@8;jkIS%c1iw+j& z?JDf~_8ebG@AID%WQTUw3}TzqE*T_GUf&50$w>XmlNKIfkN_eR6o&6K9AlvqNVZYWBF|#1n!$*@nO`DiZ)C zFvUGUR@lD#8u-4uJBS;9?SG5Eb2Ehz+8rS(x(>(B>+ zYAY*QWi7WUDTNZ1he49#s9SRz*>d?roxB5e>X1ibi_YNq X!0~T6{yi70k{{eCF@Dnlv|s%Lp6Is| delta 973 zcmZ9J&rcIU6vubk-IhXWDfAb#v`f*-f{2Php|nH;#Tr9mC^1$PYv0z^v|Zd?5Pn&T++!!a0u<8T5_!XTW2p*2&o{lHt=YdK8Jd4`)R)!6b%+ zh#7mGwgCi6%yt2HTD7^6ja>ErmN%_of067lVk}ctgQ>+MMsUpOMDiLlTtM-(Jt+lo zENX3raHN{?dF5QXz{^8^#L)EamyA_(-9<1HHEp^6XNC c)vTUkEZa2ufo8Ypz%u$thmherg`qvkU%v?9^#A|> diff --git a/cls_mth_fc.py b/common_tools.py similarity index 92% rename from cls_mth_fc.py rename to common_tools.py index ba9cb42..1be0ade 100755 --- a/cls_mth_fc.py +++ b/common_tools.py @@ -394,7 +394,7 @@ class ConfigManager: cls._config = { 'updates': lines[1].strip(), 'theme': lines[3].strip(), - 'tooltips': lines[5].strip() == 'True', + 'tooltips': lines[5].strip() == "True", # is converted here to boolean!!! 'autostart': lines[7].strip() if len(lines) > 7 else 'off' } except (IndexError, FileNotFoundError): @@ -402,7 +402,7 @@ class ConfigManager: cls._config = { 'updates': 'on', 'theme': 'light', - 'tooltips': True, + 'tooltips': "True", # Default Value as string ! 'autostart': 'off' } return cls._config @@ -532,57 +532,72 @@ class GiteaUpdate: LxTools.msg_window(AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"], wt, msg_t) -class Tooltip: - """ - class for Tooltip - - import 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") - info: label and button are parent. + example: Tooltip(widget, "Text", state_var=tk.BooleanVar()) + + 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, tips: Optional[bool] = None) -> None: - """ - Tooltip Class - """ - + def __init__(self, widget: Any, text: str, state_var: Optional[tk.BooleanVar] = None) -> None: + """Tooltip Class""" self.widget: Any = widget self.text: str = text self.tooltip_window: Optional[Toplevel] = None - if tips: + self.state_var = state_var + + # 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("") + self.widget.unbind("") + + # Add new bindings if tooltips are enabled + if self.state_var is None or self.state_var.get(): self.widget.bind("", self.show_tooltip) self.widget.bind("", self.hide_tooltip) - + def show_tooltip(self, event: Optional[Any] = None) -> None: - """ - Shows the tooltip - """ + """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() + 65 y += self.widget.winfo_rooty() + 40 + 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) + + 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: - """ - Hides the tooltip - """ + """Hides the tooltip""" if self.tooltip_window: self.tooltip_window.destroy() self.tooltip_window = None + \ No newline at end of file diff --git a/install b/install index 8ca2607..0495dde 100755 --- a/install +++ b/install @@ -17,7 +17,7 @@ install_file_with(){ exit 0 else sudo apt install python3-tk && \ - sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -43,7 +43,7 @@ install_arch_d(){ exit 0 else sudo pacman -S --noconfirm tk python3 python-requests && \ - sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -120,7 +120,7 @@ install(){ exit 0 else sudo dnf install python3-tkinter -y - sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -145,7 +145,7 @@ install(){ rm -r ~/.config/wire_py && rm -r ~/.config/systemd exit 0 else - sudo cp -fv wirepy.py start_wg.py wp_app_config.py cls_mth_fc.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -181,7 +181,7 @@ install(){ remove(){ sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \ - /usr/local/bin/wp_app_config.py cls_mth_fc.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py + /usr/local/bin/wp_app_config.py common_tools.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py if [ $? -ne 0 ] then exit 0 diff --git a/manage_tunnel.py b/manage_tunnel.py index 04cdcb2..7199260 100644 --- a/manage_tunnel.py +++ b/manage_tunnel.py @@ -2,7 +2,7 @@ from pathlib import Path from subprocess import check_call from tkinter import filedialog, ttk -from cls_mth_fc import Create, LxTools +from common_tools import Create, LxTools from wp_app_config import AppConfig, Msg import gettext import locale diff --git a/ssl_encrypt.py b/ssl_encrypt.py index df62157..9710ece 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -5,7 +5,7 @@ import os import shutil from pathlib import Path from subprocess import check_call -from cls_mth_fc import LxTools +from common_tools import LxTools from wp_app_config import AppConfig #uname: Path = Path("/tmp/.log_user") diff --git a/wirepy.py b/wirepy.py index 181d298..fe4549c 100755 --- a/wirepy.py +++ b/wirepy.py @@ -14,7 +14,7 @@ from pathlib import Path from subprocess import check_call from tkinter import TclError, filedialog, ttk -from cls_mth_fc 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() @@ -43,6 +43,7 @@ class Wirepy(tk.Tk): 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"]) @@ -56,16 +57,15 @@ class FrameWidgets(ttk.Frame): """ ttk frame class for better structure """ - def __init__(self, container, tips_enabled=None, **kwargs): + 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.tips_enabled = tips_enabled 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"]) @@ -73,10 +73,26 @@ class FrameWidgets(ttk.Frame): 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"]) - self.tips_enabled = tips_enabled if tips_enabled is not None else ConfigManager.get("tooltip") + # StringVar-Variables initialization - self.update_label = tk.StringVar() - self.update_tooltip = tk.StringVar() + 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 @@ -89,25 +105,25 @@ 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.tips_enabled) + 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, _("Click for Settings"), self.tips_enabled) + Tooltip(self.options_btn, _("Click for Settings"), self.tooltip_state) self.set_update = tk.IntVar() - self.set_tip = tk.BooleanVar() 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_command(label=_("Disable Tooltips"), - command=lambda: self.tooltip(self.set_tip.get()), variable=self.set_tip) self.updates_lb = ttk.Label(self.menu_frame) 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() @@ -119,10 +135,6 @@ class FrameWidgets(ttk.Frame): self.about_btn.grid(column=2, columnspan=2, row=0) self.readme = tk.Menu(self) - - # View Checkbox to enable or disable Tooltip - self.set_tip.set(value=not self.tips_enabled) - self.a = Tunnel.active() # Label Frame 1 @@ -204,7 +216,7 @@ class FrameWidgets(ttk.Frame): 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, _("Click to import a Wireguard Tunnel"), self.tips_enabled) + Tooltip(self.btn_i, _("Click to import a Wireguard Tunnel"), self.tooltip_state) # Button Trash self.btn_tr = ttk.Button(self.lb_frame_btn_lbox, image=self.tr_pic, command=self.delete, padding=0, @@ -212,9 +224,9 @@ class FrameWidgets(ttk.Frame): self.btn_tr.grid(column=0, row=2, padx=15, pady=8) if self.l_box.size() == 0: - Tooltip(self.btn_tr, _("No tunnels to delete in the list"), self.tips_enabled) + Tooltip(self.btn_tr, _("No tunnels to delete in the list"), self.tooltip_state) else: - Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), self.tips_enabled) + Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), self.tooltip_state) # Button Export self.btn_exp = ttk.Button(self.lb_frame_btn_lbox, image=self.exp_pic, @@ -226,9 +238,9 @@ class FrameWidgets(ttk.Frame): self.btn_exp.grid(column=0, row=3, padx=15, pady=8) if self.l_box.size() == 0: - Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tips_enabled) + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tooltip_state) else: - Tooltip(self.btn_exp, _("Click to export all\nWireguard Tunnel to Zipfile"), self.tips_enabled) + Tooltip(self.btn_exp, _("Click to export all\nWireguard Tunnel to Zipfile"), self.tooltip_state) # Label Entry self.lb_rename = ttk.Entry(self.lb_frame4, width=20) @@ -237,9 +249,9 @@ class FrameWidgets(ttk.Frame): self.lb_rename.config(state="disable") if self.l_box.size() != 0: - Tooltip(self.lb_rename, _("To rename a tunnel, you need to\nselect a tunnel from the list"), self.tips_enabled) + Tooltip(self.lb_rename, _("To rename a tunnel, you need to\nselect a tunnel from the list"), self.tooltip_state) else: - Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), self.tips_enabled) + Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), self.tooltip_state) # Button Rename self.btn_rename = ttk.Button(self.lb_frame4, text=_("Rename"), state="disable", command=self.tl_rename, @@ -260,14 +272,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, Msg.TTIP["autostart"], tself.tips_enabled) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tooltip_state) if self.l_box.size() == 0: - Tooltip(self.wg_autostart, Msg.TTIP["autostart_info"], self.tips_enabled) + Tooltip(self.wg_autostart, Msg.TTIP["autostart_info"], self.tooltip_state) else: - Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tips_enabled) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tooltip_state) self.on_off() @@ -290,7 +302,7 @@ class FrameWidgets(ttk.Frame): foreground=self.update_foreground.get() ) self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) elif res == "No Internet Connection!": self.update_label.set(_("No Server Connection!")) @@ -322,7 +334,7 @@ class FrameWidgets(ttk.Frame): foreground=self.update_foreground.get() ) self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) else: self.set_update.set(value=0) @@ -336,7 +348,7 @@ class FrameWidgets(ttk.Frame): # Create the update button if it doesn't exist yet 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.tips_enabled) + 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") @@ -350,8 +362,8 @@ class FrameWidgets(ttk.Frame): AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"] ) - ) -} + ) + def tooltip(self, tip) -> None: """ Aktualisiert die Tooltip-Einstellung im ConfigManager @@ -361,7 +373,7 @@ class FrameWidgets(ttk.Frame): # Beachten Sie die umgekehrte Logik: tip=True bedeutet Tooltips deaktivieren ConfigManager.set("tooltip", not tip) # Aktualisieren Sie die lokale Variable für sofortige Wirkung - self.tips_enabled = not tip + self.tooltip_state = not tip @staticmethod def about() -> None: @@ -403,7 +415,7 @@ class FrameWidgets(ttk.Frame): foreground=self.update_foreground.get() ) self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) elif res == "No Internet Connection!": self.update_label.set(_("No Server Connection!")) @@ -427,7 +439,7 @@ class FrameWidgets(ttk.Frame): foreground=self.update_foreground.get() ) self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - Tooltip(self.updates_lb, self.update_tooltip.get(), self.tips_enabled) + Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) else: # We have an update available @@ -443,7 +455,7 @@ class FrameWidgets(ttk.Frame): # Create the update button if it doesn't exist yet 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.tips_enabled) + 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") @@ -510,13 +522,35 @@ class FrameWidgets(ttk.Frame): # Fallback to a default message if there's an error self.update_ui_for_update_status("No Internet Connection!") - def update_tooletip_label(self) -> str: - """Update the theme label based on current theme""" - current_value = ConfigManager.get("tooletip") - if current_value == "True": - self.set_tip.set(_("Enable Tooltips")) + 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: - self.set_tip.set(_("Disable Tooltips")) + # 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)) + print(f"Tooltips are now: {new_bool_state} in ConfigManager") + # 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""" @@ -546,9 +580,9 @@ class FrameWidgets(ttk.Frame): tl = LxTools.get_file_name(AppConfig.TEMP_DIR) if len(self.tl) == 0: - Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tips_enabled) + Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state) else: - Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tips_enabled) + 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_ @@ -588,7 +622,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, Msg.TTIP["stop_tl"], self.tips_enabled) + Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], self.tooltip_state) def reset_fields(self) -> None: """ @@ -664,10 +698,10 @@ class FrameWidgets(ttk.Frame): self.l_box.update() self.l_box.selection_set(0) - Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tips_enabled) - Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tips_enabled) - Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tips_enabled) - Tooltip(self.btn_rename, Msg.TTIP["rename_tl"], self.tips_enabled) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tooltip_state) + 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() @@ -730,11 +764,11 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") self.lb_rename.configure(state="disabled") Tooltip(self.wg_autostart, _("You must have at least one\ntunnel in the list,to use the autostart") - , self.tips_enabled) + , self.tooltip_state) - Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tips_enabled) - Tooltip(self.btn_stst, _("No tunnels to start in the list"), self.tips_enabled) - Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), tips, ) + Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tooltip_state) + Tooltip(self.btn_stst, _("No tunnels to start in the list"), self.tooltip_state) + Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), self.tooltip_state) self.lb_rename.insert(0, _("Max. 12 characters!")) if self.a != "" and self.a == select_tl: diff --git a/wp_app_config.py b/wp_app_config.py index aa68942..f9f3684 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -30,13 +30,6 @@ class AppConfig: 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" - # Default settings - DEFAULT_SETTINGS: Dict[str, Any] = { - "updates": "on", - "theme": "light", - "tooltip": True, - "autostart": "off" - } # UI configuration UI_CONFIG: Dict[str, Any] = { From 9a4d8b35068b568ed264cf7225390d354ab83154 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 08:14:46 +0200 Subject: [PATCH 36/61] optimize performance 06-05-2025-23:00 --- __pycache__/common_tools.cpython-312.pyc | Bin 30660 -> 33666 bytes common_tools.py | 83 ++++++++++++++ new_window_for_add_trace_test.py | 45 ++++++++ wirepy.py | 140 +++-------------------- 4 files changed, 146 insertions(+), 122 deletions(-) create mode 100644 new_window_for_add_trace_test.py diff --git a/__pycache__/common_tools.cpython-312.pyc b/__pycache__/common_tools.cpython-312.pyc index bb299c76a01a906ddd52909e90bb88d195b8629c..70ccf6b5d5e3d5b11a7ea809e42816c53901815f 100644 GIT binary patch delta 5113 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 ZUIS{J={+YrA$-CB{vYiYa&~O*e*>?~m2v<8 delta 2569 zcmZWqYitzP6`ni$^mupu1Y_fX!KP$u>KY8i!q~9J5E~N*zd$h|>+#ODcg%QZb7$7O z>jndcC~;CHEhka)P)d^@5pAOi7*$DBh@1wgQQKdQtS0j(iCRU_s7=$*CL${7J+rJ! zoss6#neU$0oO92;bB~HwKP8dhhr^P=Qgp`m3)JV&NHO&*s}$*scZmscrdlcHb|L_fJgwUHV%?e``E_U2pNU?=w@=x z9**AfmpHzRs#rsF3(Z&Bj-)G2v_MhV-@zpoQ`y7_xG3K`+6OciHhv|xQzY^F3up4 zfx*p-H4`W%5mbapgkuOAbZ!|Uui8J}@^wJ`n*h)5h?6vYx#OqgJUrLduxgwuIUX}- zI$oMpt!RL~gk~?pVq1%U6vcB8*|}f*NPv-@A8vI#s%|kK@_vN#2p179ad7;DJ@<4E zALI$1Lv9q|b)9gr|CAkw|f^6KM~*uJ^Cv~ zqu}Y+$O5GLSCItF_tz2yUg_TubAP=QHJ(H$U)3$&ZjK`z<*{s>`5G1y9-U1ZH`vt5MC#6C9#gQ+8-oZz2OGVIKF+dW>WTBXh`lQZ^LBr z+b1w1rW#8*jE_AzdCR-{Ri4BU zp66zg@+#mF&AvdmDZptXRv6sh8QTN5kp5bLU~1R5+AwT_6OJ#DG%`AC!^QmwcT6)( zrD|3Wqv|1qZ=?V(rv}7-68qn&r%02#>xNLz-Fk79mQ_E79Ku@!Z1y6F+5P4tp4DSW z<2~d=to)vjXH;D=GAsd~O>Zn-TIb*OnY+n)`#@%mSb?t;%WUJ=57Eh4%-JW8Ta-nEtRE>IlOkD%@C!7NA(^p*nArrkXP5^=Ps>{-BZp80+fQYQ3>Qw- z)_;O_xO?(<>Qbs-}*3Na29#G~QTNY5soo06iV+Fs<0^SVn loHCTOMmyM9_|4o@#eE{#_So1{be`T2Q2s*kLg(q-{{>N+s5Jlp diff --git a/common_tools.py b/common_tools.py index 1be0ade..e321658 100755 --- a/common_tools.py +++ b/common_tools.py @@ -119,6 +119,89 @@ class LxTools(tk.Tk): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) + @staticmethod + 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') + 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) + + # Parse the output to find the primary monitor + primary_info = None + for line in output.splitlines(): + if "primary" in line: + parts = line.split() + for part in parts: + if "x" in part and "+" in part: + primary_info = part + break + break + + if primary_info: + # Parse the geometry: WIDTHxHEIGHT+X+Y + geometry = primary_info.split("+") + dimensions = geometry[0].split("x") + primary_width = int(dimensions[0]) + 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 + # 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]: """ diff --git a/new_window_for_add_trace_test.py b/new_window_for_add_trace_test.py new file mode 100644 index 0000000..fd87687 --- /dev/null +++ b/new_window_for_add_trace_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/python3 + +import tkinter as tk + +class MainWindow(tk.Tk): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.title("Trace-Test-Window") + self.geometry("400x300") + self.text_label = tk.StringVar() + self.text_forground = tk.StringVar(value="red") + self.text_label.set("This is the main window") + self.label = tk.Label(self, textvariable=self.text_label) + self.label.grid(row=0, column=0, padx=10, pady=10) + self.label.grid_remove() + + self.button_text = tk.StringVar() + self.button_text.set("Drück für andere Text anzeige") + self.button = tk.Button(self, textvariable=self.button_text, command=self.toggle_lable) + self.button.grid(row=1, column=0, padx=10, pady=10) + + self.text_label.trace_add("write", self.update_label) + self.text_forground.trace_add("write", self.update_label) + + def update_label(self, *args): + self.label.configure(foreground=self.text_forground.get()) + + if self.text_label.get(): + self.label.grid() + else: + self.label.grid_remove() + + def toggle_lable(self): + + if 'main window' in self.text_label.get(): + self.text_label.set("gewechseltes label") + self.button_text.set("Drück für main window") + else: + self.text_label.set("This is the main window") + self.button_text.set("Drück für andere Text anzeige") + +if __name__ == "__main__": + window = MainWindow() + window.mainloop() diff --git a/wirepy.py b/wirepy.py index fe4549c..0ecc69e 100755 --- a/wirepy.py +++ b/wirepy.py @@ -29,17 +29,22 @@ class Wirepy(tk.Tk): 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] - self.monitor_center_x = int(self.winfo_screenwidth() / 2 - (self.x_width / 2)) - self.monitor_center_y = int(self.winfo_screenheight() / 2 - (self.y_height / 2)) - self.resizable(AppConfig.UI_CONFIG["resizable_window"][0], AppConfig.UI_CONFIG["resizable_window"][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.geometry(f"{self.x_width}x{self.y_height}+{self.monitor_center_x}+{self.monitor_center_y}") + self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.tk.call("source", f"{AppConfig.SYSTEM_PATHS["tcl_path"]}/water.tcl") + 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) @@ -50,7 +55,14 @@ class Wirepy(tk.Tk): # 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): @@ -362,18 +374,7 @@ class FrameWidgets(ttk.Frame): AppConfig.IMAGE_PATHS["icon_error"], AppConfig.IMAGE_PATHS["icon_msg"] ) - ) - - def tooltip(self, tip) -> None: - """ - Aktualisiert die Tooltip-Einstellung im ConfigManager - Args: - tip (bool): True zum Deaktivieren, False zum Aktivieren von Tooltips - """ - # Beachten Sie die umgekehrte Logik: tip=True bedeutet Tooltips deaktivieren - ConfigManager.set("tooltip", not tip) - # Aktualisieren Sie die lokale Variable für sofortige Wirkung - self.tooltip_state = not tip + ) @staticmethod def about() -> None: @@ -389,107 +390,6 @@ class FrameWidgets(ttk.Frame): "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_ui_for_update_status(self, res): - """Update UI elements based on update check result""" - print(f"Updating UI for result: {res}") # Debug output - - # First, clean up any existing UI elements - if hasattr(self, 'update_btn') and self.update_btn.winfo_exists(): - self.update_btn.grid_forget() - - # Reset all variables to ensure fresh state - self.update_label.set("") - self.update_tooltip.set("") - self.update_foreground.set("black") - - if res == "False": - self.set_update.set(value=1) - self.update_label.set(_("Update search off")) - self.update_tooltip.set(_("Updates you have disabled")) - self.update_foreground.set("red") - - # Display the label - self.updates_lb.configure( - textvariable=self.update_label, - foreground=self.update_foreground.get() - ) - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - 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") - - # Display the label - self.updates_lb.configure( - textvariable=self.update_label, - foreground=self.update_foreground.get() - ) - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - - 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("black") - - # Display the label - self.updates_lb.configure( - textvariable=self.update_label, - foreground=self.update_foreground.get() - ) - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) - - else: - # We have an update available - self.set_update.set(value=0) - update_text = f"Update {res} {_('available!')}" - - # Hide the label if it's visible - if self.updates_lb.winfo_ismapped(): - self.updates_lb.grid_forget() - - # Create or update the update button - if not hasattr(self, 'update_btn') or not self.update_btn.winfo_exists(): - # Create the update button if it doesn't exist yet - 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"] - ) - ) - else: - # Update the existing update button - self.update_btn.configure(text=update_text) - # Make sure it's visible - self.update_btn.grid(column=4, columnspan=3, row=0, padx=0) - - # Update the download command - if hasattr(self, 'download'): - self.download.entryconfigure( - 0, # First entry in the menu - 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"] - ) - ) - def update_setting(self, update_res) -> None: """write off or on in file Args: @@ -532,15 +432,12 @@ class FrameWidgets(ttk.Frame): # 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)) - print(f"Tooltips are now: {new_bool_state} in ConfigManager") # Update the tooltip_state variable for immediate effect self.tooltip_state.set(new_bool_state) @@ -551,7 +448,6 @@ class FrameWidgets(ttk.Frame): # 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") From 42870e2942e0aab8ef1336acd5d3ac5afc843406 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 10:47:49 +0200 Subject: [PATCH 37/61] trace_add() for menu labels works --- new_window_for_add_trace_test.py | 45 ------------- wirepy.py | 111 +++++++++++++------------------ 2 files changed, 48 insertions(+), 108 deletions(-) delete mode 100644 new_window_for_add_trace_test.py diff --git a/new_window_for_add_trace_test.py b/new_window_for_add_trace_test.py deleted file mode 100644 index fd87687..0000000 --- a/new_window_for_add_trace_test.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/python3 - -import tkinter as tk - -class MainWindow(tk.Tk): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.title("Trace-Test-Window") - self.geometry("400x300") - self.text_label = tk.StringVar() - self.text_forground = tk.StringVar(value="red") - self.text_label.set("This is the main window") - self.label = tk.Label(self, textvariable=self.text_label) - self.label.grid(row=0, column=0, padx=10, pady=10) - self.label.grid_remove() - - self.button_text = tk.StringVar() - self.button_text.set("Drück für andere Text anzeige") - self.button = tk.Button(self, textvariable=self.button_text, command=self.toggle_lable) - self.button.grid(row=1, column=0, padx=10, pady=10) - - self.text_label.trace_add("write", self.update_label) - self.text_forground.trace_add("write", self.update_label) - - def update_label(self, *args): - self.label.configure(foreground=self.text_forground.get()) - - if self.text_label.get(): - self.label.grid() - else: - self.label.grid_remove() - - def toggle_lable(self): - - if 'main window' in self.text_label.get(): - self.text_label.set("gewechseltes label") - self.button_text.set("Drück für main window") - else: - self.text_label.set("This is the main window") - self.button_text.set("Drück für andere Text anzeige") - -if __name__ == "__main__": - window = MainWindow() - window.mainloop() diff --git a/wirepy.py b/wirepy.py index 0ecc69e..814e52e 100755 --- a/wirepy.py +++ b/wirepy.py @@ -107,6 +107,7 @@ class FrameWidgets(ttk.Frame): 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") @@ -130,7 +131,11 @@ class FrameWidgets(ttk.Frame): 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) + 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) @@ -295,86 +300,67 @@ class FrameWidgets(ttk.Frame): 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")) - self.update_foreground.set("red") - - # Remove update button if it exists - if hasattr(self, 'update_btn'): - self.update_btn.grid_forget() - - # Display the label - self.updates_lb.configure( - textvariable=self.update_label, - foreground=self.update_foreground.get() - ) - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) + # Clear the foreground color as requested + self.update_foreground.set("") elif res == "No Internet Connection!": self.update_label.set(_("No Server Connection!")) self.update_foreground.set("red") - # Remove update button if it exists - if hasattr(self, 'update_btn'): - self.update_btn.grid_forget() - - # Display the label - self.updates_lb.configure( - textvariable=self.update_label, - foreground=self.update_foreground.get() - ) - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - 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("black") - - # Remove update button if it exists - if hasattr(self, 'update_btn'): - self.update_btn.grid_forget() - - # Display the label - self.updates_lb.configure( - textvariable=self.update_label, - foreground=self.update_foreground.get() - ) - self.updates_lb.grid(column=4, columnspan=3, row=0, padx=10) - Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) + self.update_foreground.set("") 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("") - # Remove the label if displayed - self.updates_lb.grid_forget() + # 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) - # Create or update the update button - if not hasattr(self, 'update_btn'): - # Create the update button if it doesn't exist yet - 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"] - ) + 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: @@ -399,7 +385,7 @@ class FrameWidgets(ttk.Frame): # Disable updates ConfigManager.set("updates", "off") # When updates are disabled, we know the result should be "False" - self.update_ui_for_update_status("False") + self.update_ui_for_update("False") else: # Enable updates ConfigManager.set("updates", "on") @@ -407,7 +393,6 @@ class FrameWidgets(ttk.Frame): try: # Force a fresh check by passing "on" as the update setting res = GiteaUpdate.api_down(AppConfig.UPDATE_URL, AppConfig.VERSION, "on") - print(f"API returned: {res}") # Debug output # Make sure UI is updated regardless of previous state if hasattr(self, 'update_btn'): @@ -416,11 +401,11 @@ class FrameWidgets(ttk.Frame): self.updates_lb.grid_forget() # Now update the UI with the fresh result - self.update_ui_for_update_status(res) + 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_status("No Internet Connection!") + 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""" From 742c6d0cc5f9b3acb32bfb353ebdf04bc4a3ae90 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 11:41:33 +0200 Subject: [PATCH 38/61] tooltip message complete with AppConfig Manager --- __pycache__/wp_app_config.cpython-312.pyc | Bin 9040 -> 9435 bytes wirepy.py | 26 +++++++++++----------- wp_app_config.py | 5 ++++- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/__pycache__/wp_app_config.cpython-312.pyc b/__pycache__/wp_app_config.cpython-312.pyc index 6cd2071e7c985438ab3156d21200dc6300e44b6b..ed1738b3fe4f990281232bec949dd032e15152c1 100644 GIT binary patch delta 1076 zcmYk)PfXKL90%}r!EPOFz}D_RYdijqe}Xa+Vf-;5!ILsfFlbz=EwDIN(iMSSW;8K) zb9ooJc=Y1Qcr)>C;-$&+R6OX>czIE7CdR(EGTK9%-{<#zzxVP|bLI4A@Jk@zr|jqJ zP;zG~^*;EE_pExrU;bacL@8kprM4;DI~pe_3eXBqB;t}Nk(f*3L=rAZ5=ptFhe+Bb z0uiw#by|@KWm-fly+rz4(obZ-C4)qUT#_X+?2-{8?n93f8N0~bR>lckz940S$fQfI z5V?9Go2<&HUe&whiQ6}(`jn}>{XNQMr$3xVX{Mle+GQ=YR4zSwEN*GKxUL#Tsj^+Q z*wXHvrW-}0T+1}uh9)*ml*Nsbu5Rzkx+31$uT<2s<*OPpNrP`a!yT4i-Q6>eise$( z08Cn9EpOG(_UvI`FrD>UjG~r{2Dp_T^;@j2R^(mPE^Fz7np4vGymCQW`Gh8Iu(OORr_8R z2Dt*_u|%BA66BXr6ibX^<0q1tl40T@iepJQG7A%Vl*E#v*w{(8nY;nxE2sxc+Q}4P zVjT%wYK1|f6PknZc_iUb#tGeniTkJ*OP_O~9OR}^zb#LfkNb|Rua=GvUyh$q9C)2s brJsWznGF2`wDm3oW@+X(Wxw>C{pbD%l6o&` delta 735 zcmccZdBKhEG%qg~0}!x?OJz)y-pKb_l<5xRX{f)SyF^oGlFCp7{ELc6rLyw zPYi`8j>3~b;Yp(Kq)>R$a9%RVdKowu(=oCra&jm&ikkJBe787?OA?Dp;!ARFai!)KlvKv&0QKEsFD}V1012|Cq~?HV&Z5-3#N1Rc zGZkzgh{;h>lvtbr7ECNH$p;&Cix19>&&*59zr_bv58?B|Y>YRC$it0>%EJtWY6m$e z9%Lq%Kl!1eDPzNAaV14Y%gL5X%j!RBF^Gy?5OcUJ;&@X`{DOq#WihLp!Xg($?Jf)3 z-xLzQAYy-6$l)eXjd=KF(Fiaj=(1?=O%c%xVu6=Mg1{oqmqlG}N=aXkNxUqT1ZG%Y zma@4iDRn{G^|GWJSS0AOR4`b?_OhfMSS0$glxWOHc|jSr2A2+%j^YW<9TlB=Ul;@@ gE30@gHcrk_QDSVF+^=HKYr)9&i2+CynF1pi04-S8<^TWy diff --git a/wirepy.py b/wirepy.py index 814e52e..6e6c784 100755 --- a/wirepy.py +++ b/wirepy.py @@ -123,7 +123,7 @@ class FrameWidgets(ttk.Frame): self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options")) self.options_btn.grid(column=1, columnspan=1, row=0) - Tooltip(self.options_btn, _("Click for Settings"), self.tooltip_state) + Tooltip(self.options_btn, Msg.TTIP["settings"], self.tooltip_state) self.set_update = tk.IntVar() self.settings = tk.Menu(self, relief="flat") @@ -233,7 +233,7 @@ class FrameWidgets(ttk.Frame): 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, _("Click to import a Wireguard Tunnel"), self.tooltip_state) + 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, @@ -241,9 +241,9 @@ class FrameWidgets(ttk.Frame): self.btn_tr.grid(column=0, row=2, padx=15, pady=8) if self.l_box.size() == 0: - Tooltip(self.btn_tr, _("No tunnels to delete in the list"), self.tooltip_state) + Tooltip(self.btn_tr, Msg.TTIP["trash_tl_info"], self.tooltip_state) else: - Tooltip(self.btn_tr, _("Click to delete a Wireguard Tunnel\nSelect from the list!"), self.tooltip_state) + 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, @@ -255,9 +255,9 @@ class FrameWidgets(ttk.Frame): self.btn_exp.grid(column=0, row=3, padx=15, pady=8) if self.l_box.size() == 0: - Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tooltip_state) + Tooltip(self.btn_exp, Msg.TTIP["export_tl_info"], self.tooltip_state) else: - Tooltip(self.btn_exp, _("Click to export all\nWireguard Tunnel to Zipfile"), self.tooltip_state) + Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state) # Label Entry self.lb_rename = ttk.Entry(self.lb_frame4, width=20) @@ -266,9 +266,9 @@ class FrameWidgets(ttk.Frame): self.lb_rename.config(state="disable") if self.l_box.size() != 0: - Tooltip(self.lb_rename, _("To rename a tunnel, you need to\nselect a tunnel from the list"), self.tooltip_state) + Tooltip(self.lb_rename, Msg.TTIP["rename_tl"], self.tooltip_state) else: - Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), self.tooltip_state) + Tooltip(self.lb_rename, Msg.TTIP["rename_tl_info"], self.tooltip_state) # Button Rename self.btn_rename = ttk.Button(self.lb_frame4, text=_("Rename"), state="disable", command=self.tl_rename, @@ -346,7 +346,7 @@ class FrameWidgets(ttk.Frame): # 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, Msg.TTIP["download"], self.tooltip_state) self.download = tk.Menu(self, relief="flat") self.update_btn.configure(menu=self.download, style="Toolbutton") @@ -644,12 +644,12 @@ 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, _("You must have at least one\ntunnel in the list,to use the autostart") + Tooltip(self.wg_autostart, Msg.TTIP["autostart_info"] , self.tooltip_state) - Tooltip(self.btn_exp, _("No Tunnels in List for Export"), self.tooltip_state) - Tooltip(self.btn_stst, _("No tunnels to start in the list"), self.tooltip_state) - Tooltip(self.lb_rename, _("To rename a tunnel, at least one must be in the list"), self.tooltip_state) + 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: diff --git a/wp_app_config.py b/wp_app_config.py index f9f3684..f0b24d4 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -158,6 +158,8 @@ class Msg: } TTIP: Dict[str, str] = { #Strings for Tooltips + "settings": _("Click for Settings"), + "import_tl": _("Click to import a Wireguard Tunnel"), "start_tl": _("Click to start selected Wireguard Tunnel"), "empty_list": _("No tunnels to start in the list"), "stop_tl": _("Click to stop selected Wireguard Tunnel"), @@ -171,7 +173,8 @@ class Msg: "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") + "list_auto_info": _("To use the autostart, a tunnel must be selected from the list"), + "download": _("Click to download new version") } \ No newline at end of file From a8aba7163899e1e5f81cba041b371cf52e8801c7 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 12:46:52 +0200 Subject: [PATCH 39/61] tooltips add x_offset: int = 65, y_offset: int = 40 as atribute for edit --- __pycache__/common_tools.cpython-312.pyc | Bin 33666 -> 33948 bytes __pycache__/wp_app_config.cpython-312.pyc | Bin 9435 -> 9435 bytes common_tools.py | 10 +++++++--- wirepy.py | 22 ++++++++++++++-------- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/__pycache__/common_tools.cpython-312.pyc b/__pycache__/common_tools.cpython-312.pyc index 70ccf6b5d5e3d5b11a7ea809e42816c53901815f..83b6bc0a5ec05ef93b94ea3e2a83b6102b98e0a4 100644 GIT binary patch delta 1034 zcmZuvTWC{35Z%4Gxw(1V#L~1HW9qiP8Z2s4T1Bk*rYcsY3K~C(cyCBW8oBqHmoY{u zn&JyPA6C>q5w(hzB8vFo<3q%+pkmC2=%?sMYY}`PIGeOSz=55ab7ppDXJ?Nd7cTY- z?&pf)ASjz!{LzRu?Cvt^6^>EH^Qg`djfAO6!h%Y}7CW&M)eTvrF|65D8!lNDA}-b5 zXAL`4kLu_X!%lUI>eM7n^x^h$TvS~ccm0P~kXKSdu&QL+7K`mOd-m^U0(><-!DVyl z^d^?&jlCFZ%Zo2%f6>QTcX1j+qfD3) zBhewy0eFy_L65_m)D;?qT>2&qCu?Eh!C5fJ>W9rrts{*`r*Te)Ea2Q&2OT@=;LgEn zS_8XX)rw^ZZ^MO>ejAK6)zpg_;Q~>`jO7BkB>st-Rh2wKV1KO_S~5PrRh3j54$ZFG zZ#ahJO8k(Nk@ot5rmErg1{-XNTR}>CX@FO2apr9iiUXF1uxBomq-an{S9L{W34@tY zjhU6vc9x``UPIGO_?Y=V^E!Vb{?)$b$Zg@SW7sj`$`QQ*nV?5M3u2Z{EkAC_nds?_ z#JcrZ%uMO0u{UVWuBGQ8kqxnP2-Dt)zq7!=lHC*42Kf2k;8}M4eDAHmwLn2>E68nm zT9obiZD)4%?|QAwE-15KD08p=6(4Cm%xL>rJXL?fZz5D?ejW*f_s5vd(CCBz)Q$G2 z+GO1uFpyj93tBoqti;3*jXZl5SDmPWZg`bzt?A`>3s(cY5^q8ugy~1>cOFBskBkWp zt2|muw$q)o*t&Sk;<2uwgT^S~_Uf#Pz1R3K^tAm^dtvF;3Q_dejg~Wu3qupmvK8ZH bX1rk}d1O7M_u=u;Vb3~B=Y1wzm!JC=HF+CH delta 769 zcmZuuUr1A76u;lS*ZXJp-Wg4^>o&UTLt93nP(rjYD@dWp>LC^((l_x>w7Q$l&CTf? zq$Fyc)WCY^MHCYfUwpKpdI+jPkVr&Nh0sH5^wLY6ujN|@zTY{&^PThKeCK;G#%FHv z&ew+F;LyB%m7NK?;=B;?s*Ix*k_fY0hzk0@WcC@tvo9heG9SNl>K*RkC8x zL3OHzq>yN03zhI*qz=5-{*%`*ug7`6b5p(;4quCQ-DrPFK#YEamfoKp`vfXn6%n?L z#R8tfGYIz`6S!^_ZpK~kq(z1=eRc3vbP1GCLQX7)w{Z>5#Jog;p4c9^7xPw%b}P2p zw+TWWi^egEFiH3jvwXcgvP;N@xX2X1$qKjj4w+q^J zmnkLrIb2avnByLbOE?WP!ePsLg9j>7rYTc}s?sy)`1lww6nN=lI5Uj~+dY!j_!4G>ixYn-ET%k;t>ehId`C}EXc?PYtm}G5 zxXliSqj4xtpCYqxIbBbtVIplx6KpSeSWY*~J99>DPN@YlRK6XrRt;GsFv)iM9M%1N zamll6wQLN_vMqG>3?>p~|33LZXBQ2%MTaH6i#7cr$!x$2rEQnC>)# z8Q7hv3Up&K%jJ2sM9G)wB`3-K$ajwUg_@#Y^R3sv3Oo(uj 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 self.tooltip_window: Optional[Toplevel] = None self.state_var = state_var + self.x_offset = x_offset + self.y_offset = y_offset # Initial binding based on current state self.update_bindings() @@ -662,8 +666,8 @@ class Tooltip(): cy: int x, y, cx, cy = self.widget.bbox("insert") - x += self.widget.winfo_rootx() + 65 - y += self.widget.winfo_rooty() + 40 + 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) diff --git a/wirepy.py b/wirepy.py index 6e6c784..9e7114d 100755 --- a/wirepy.py +++ b/wirepy.py @@ -266,9 +266,9 @@ 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) + 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) + 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, @@ -289,14 +289,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, Msg.TTIP["autostart"], self.tooltip_state) + 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) + 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) + Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tooltip_state, x_offset=-10, y_offset=-40) self.on_off() @@ -326,15 +326,21 @@ class FrameWidgets(ttk.Frame): 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) @@ -346,7 +352,7 @@ class FrameWidgets(ttk.Frame): # 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, Msg.TTIP["download"], 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") @@ -579,7 +585,7 @@ class FrameWidgets(ttk.Frame): self.l_box.update() self.l_box.selection_set(0) - Tooltip(self.wg_autostart, Msg.TTIP["autostart"], self.tooltip_state) + 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) @@ -645,7 +651,7 @@ class FrameWidgets(ttk.Frame): self.wg_autostart.configure(state="disabled") self.lb_rename.configure(state="disabled") Tooltip(self.wg_autostart, Msg.TTIP["autostart_info"] - , self.tooltip_state) + , 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) From 786b909adc56af408fe5e8de7ab24960075b23de Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 19:24:34 +0200 Subject: [PATCH 40/61] remove files and folders that are not for repo --- .idea/dictionaries/project.xml | 3 - .../inspectionProfiles/profiles_settings.xml | 6 - .idea/misc.xml | 7 - .idea/modules.xml | 8 - .../shelved.patch | 76 --- .../shelved.patch | 0 ..._Checkout_at_19_08_24__06_49__Changes_.xml | 4 - .idea/vcs.xml | 6 - .idea/wire-py.iml | 10 - .idea/workspace.xml | 640 ------------------ .vscode/settings.json | 6 - __pycache__/cls_mth_fc.cpython-312.pyc | Bin 30387 -> 0 bytes __pycache__/common_tools.cpython-312.pyc | Bin 33948 -> 0 bytes __pycache__/manage_tunnel.cpython-312.pyc | Bin 12893 -> 0 bytes __pycache__/message.cpython-312.pyc | Bin 2957 -> 0 bytes __pycache__/start_wg.cpython-312.pyc | Bin 630 -> 0 bytes __pycache__/wg_main.cpython-312.pyc | Bin 68832 -> 0 bytes __pycache__/wp_app_config.cpython-312.pyc | Bin 9435 -> 0 bytes 18 files changed, 766 deletions(-) delete mode 100644 .idea/dictionaries/project.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]/shelved.patch delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]1/shelved.patch delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24__06_49__Changes_.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/wire-py.iml delete mode 100644 .idea/workspace.xml delete mode 100644 .vscode/settings.json delete mode 100644 __pycache__/cls_mth_fc.cpython-312.pyc delete mode 100644 __pycache__/common_tools.cpython-312.pyc delete mode 100644 __pycache__/manage_tunnel.cpython-312.pyc delete mode 100644 __pycache__/message.cpython-312.pyc delete mode 100644 __pycache__/start_wg.cpython-312.pyc delete mode 100644 __pycache__/wg_main.cpython-312.pyc delete mode 100644 __pycache__/wp_app_config.cpython-312.pyc diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml deleted file mode 100644 index 4787784..0000000 --- a/.idea/dictionaries/project.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 3663950..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index cca3bd8..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]/shelved.patch deleted file mode 100644 index 99be84b..0000000 --- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]/shelved.patch +++ /dev/null @@ -1,76 +0,0 @@ -Index: .idea/workspace.xml -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+>\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {\n "associatedIndex": 3\n}\n \n \n \n \n \n \n {\n "keyToString": {\n "ASKED_ADD_EXTERNAL_FILES": "true",\n "Python.main.executor": "Run",\n "RunOnceActivity.ShowReadmeOnStart": "true",\n "git-widget-placeholder": "wire-py-reformat-14-08-2024",\n "last_opened_file_path": "/home/punix/Downloads/tkinter-bitcoin_price_converter_objectoriented(1).py"\n }\n}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 1723279982210\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/.idea/workspace.xml b/.idea/workspace.xml ---- a/.idea/workspace.xml -+++ b/.idea/workspace.xml -@@ -4,10 +4,8 @@ - - -- -+ - -- -- - - - -+ - - -\ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]1/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24,_06_49_[Changes]1/shelved.patch deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24__06_49__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24__06_49__Changes_.xml deleted file mode 100644 index 85897b2..0000000 --- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_19_08_24__06_49__Changes_.xml +++ /dev/null @@ -1,4 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/wire-py.iml b/.idea/wire-py.iml deleted file mode 100644 index 6cb8b9a..0000000 --- a/.idea/wire-py.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index dba3213..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,640 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - { - "associatedIndex": 3 -} - - - - - - - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "Python.INSTALL.executor": "Run", - "Python.common_tools.executor": "Run", - "Python.install.executor": "Run", - "Python.main.executor": "Run", - "Python.messagebox.executor": "Run", - "Python.start_wg.executor": "Run", - "Python.testtheme.executor": "Run", - "Python.wg_func.executor": "Run", - "Python.wg_main.executor": "Run", - "Python.wirepy.executor": "Run", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "Shell Script.install.executor": "Run", - "Shell Script.run_as.executor": "Run", - "git-widget-placeholder": "28-04-2025-more-methods-and-optimize-methods", - "last_opened_file_path": "/home/punix/Pyapps/wire-py", - "settings.editor.selected.configurable": "ml.llm.LLMConfigurable" - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1723279982210 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - file://$PROJECT_DIR$/wg_main.py - 1128 - - - file://$PROJECT_DIR$/ssl_decrypt.py - 3 - - - - - \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 18313e7..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "workbench.settings.openDefaultSettings": true - "workbench.startupEditor": "none" - "update.showReleaseNotes": false - "terminal.integrated.fontSize": 18 -} \ No newline at end of file diff --git a/__pycache__/cls_mth_fc.cpython-312.pyc b/__pycache__/cls_mth_fc.cpython-312.pyc deleted file mode 100644 index f8af799729cc47d1be98d439fc0730bd9991e94f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30387 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 diff --git a/__pycache__/common_tools.cpython-312.pyc b/__pycache__/common_tools.cpython-312.pyc deleted file mode 100644 index 83b6bc0a5ec05ef93b94ea3e2a83b6102b98e0a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33948 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 z%0D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 Cn5`=S diff --git a/__pycache__/manage_tunnel.cpython-312.pyc b/__pycache__/manage_tunnel.cpython-312.pyc deleted file mode 100644 index 50273d04311b3c76229d191a87570b7caf8abbaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12893 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 diff --git a/__pycache__/message.cpython-312.pyc b/__pycache__/message.cpython-312.pyc deleted file mode 100644 index f8ad5926834231349236e9b29a74c5c4d03ee854..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2957 zcmbVOOKcNI7@qZW?8JFBB+iR&9w5vk5rq;!v?YL2(NYL66@t)e zN!)~|tiO)jah4XtE(elacgm7scZv(SSLy0xh%=qKJ{c~HEaE6Ho^1y3CAgL>x5JJZ zG|Ca~8Xk7epd>f!I*URM)BA}OkBehcm|JJV3z+(T_OA<}Z@GD-uL)Y2=@#7*2ek8O*1A#xv+6AUY>>y!i0G&6}!S#5|f4HgDqpIBuj%Ouvg8vpxef< zO<%LuXv-MdV6TR8!QR^p+m!fOPpo=&+7gDCM@!z#xHctKm9qkFh1h@_wD&Ru+;)a- zI_gTpN!SqI8}wV>!YoW|Wyq$aIu~FTq1tN^uG_WU)@BIet1zR$-NrO*nyX>+0`{92 zA9QyzY|}TH^8XVVsm;}`i(+jqA&OqxJ3|w@8L}y<__ZGW%tnOFZ3se2-L!2AW z>ADQwc~zIrJZXfu_P#F3ZDQV`l51xHboafkTQP?Q(v)jvb+9t1w6jtM9Pz;|5@h~4 zltQ7FlN!Bx;G*<|)kq?$m`+WdUZqzW+`XP@Ngf;52SMzfmWe)Ydd4k|{1IrZCP8bJ zZchh}M`Nm{^OB~iT4%ay3rTJvJFH*2Q4Xt0yl-066j>P)_U-MBDPv&VH7Z9Wpl&$| z_jCH_sqx<4US3i8&%}sK_)ld`8cT>8;Rivl(?1lE#JI%ArHB;Pd9k7vwW>==ou+es zB%$buRa%HudRK;=;89g{(-jj*(t4*%$ElA)4vk)trZ=ocqoP7q>2(iGI^`N2vXE1w z-wV@UDo{KoDjGdFp?1?9PsAk6#DX9zvMvZ3{ivAkK~0QGW?f`N7^O&vmOeqIYfO`g zSzEFLMH!XH5}IUIL&fctPm|(uS{#W;rZ*{+s*Y1asqSX|w5*J(LOiTVl2Q`ZMOARF zYC>32bV(DEX2W`=^2S%SQl?v%b#UUdUE?uHjY_%(bOfQk*x~8T~&xpl3U-+tpl{vb>+`_FDp6yTM>T>)A-^lTET%? zuYrRF-1-;aecN}-x7hQ2?To8{JC<>E5qB84qlmi<+_l(ww?2=%3V4qtXg6^C!s+Z( z9=8{8$YQk^xMiU;JCMgM1-unlb2YOy3+ET#ovA6{-OJ6bGp?_zmxFCU0?T|)k>6+V z`yTWAmv$Tcp$D!<)eozGo-z&(8T{!|092>n;QJr*2bKm5{=El{k2)T9R;0DXYOSREhj)$ zQ+8a$Z3b@3?q2N7>zYD%Pop`Pv>#)SKR*$I*&qbdioT4$^_Fd2vLJDoM;H}Yh_YgzdZc&2Ye diff --git a/__pycache__/start_wg.cpython-312.pyc b/__pycache__/start_wg.cpython-312.pyc deleted file mode 100644 index d7729fae8250946b79ef94dc9859d9a247a8aedd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 630 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 diff --git a/__pycache__/wp_app_config.cpython-312.pyc b/__pycache__/wp_app_config.cpython-312.pyc deleted file mode 100644 index 65b6f49c2268fcd2564373ae183ea291ac33a552..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9435 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 XU9i(d`@oLn5BAqB&hIFA5rO{~WD;yg From d6e9613157a9e65fbe9d5fa118cf68a26868eeea Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 19:26:48 +0200 Subject: [PATCH 41/61] new file .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aaaf151 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +debug.log +.venv +.idea +.vscode +__pycache__ From a62394e40ea052090b0b555e29e1044a9167eb86 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 19:28:35 +0200 Subject: [PATCH 42/61] add remove .gitignore file --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index aaaf151..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -debug.log -.venv -.idea -.vscode -__pycache__ From c56b42df3ed42dc9d5935bf7c977039eba494d20 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 19:29:00 +0200 Subject: [PATCH 43/61] add .gitignore to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aaaf151 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +debug.log +.venv +.idea +.vscode +__pycache__ From f4a51f005000b6f586cb1c45f91d8ea4ca2dcb09 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 20:25:09 +0200 Subject: [PATCH 44/61] fix message window a path errors --- common_tools.py | 302 +++++++++++++++++--------- manage_tunnel.py | 15 -- ssl_decrypt.py | 32 ++- ssl_encrypt.py | 53 ++++- start_wg.py | 3 +- wirepy.py | 536 +++++++++++++++++++++++++++++++++++------------ wp_app_config.py | 82 ++++---- 7 files changed, 724 insertions(+), 299 deletions(-) delete mode 100644 manage_tunnel.py diff --git a/common_tools.py b/common_tools.py index b87fc1a..4655ef4 100755 --- a/common_tools.py +++ b/common_tools.py @@ -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("") self.widget.unbind("") - + # Add new bindings if tooltips are enabled if self.state_var is None or self.state_var.get(): self.widget.bind("", self.show_tooltip) self.widget.bind("", 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 - - \ No newline at end of file diff --git a/manage_tunnel.py b/manage_tunnel.py deleted file mode 100644 index 7199260..0000000 --- a/manage_tunnel.py +++ /dev/null @@ -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() - diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 66c5902..0587e7f 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -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) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 9710ece..f924b26 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -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, + ] + ) diff --git a/start_wg.py b/start_wg.py index decf670..0262a49 100755 --- a/start_wg.py +++ b/start_wg.py @@ -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 diff --git a/wirepy.py b/wirepy.py index 9e7114d..0231db8 100755 --- a/wirepy.py +++ b/wirepy.py @@ -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("<>", "") 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 = 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: """ diff --git a/wp_app_config.py b/wp_app_config.py index f0b24d4..52363cb 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -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"), } - \ No newline at end of file From ca26576eab25075d7b59f2c4d2f7130e17d84f94 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 7 May 2025 22:42:08 +0200 Subject: [PATCH 45/61] test with methode for username --- common_tools.py | 30 ++++++++++++++++++++++++++++++ ssl_decrypt.py | 6 ++---- ssl_encrypt.py | 6 +++--- wirepy.py | 2 +- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/common_tools.py b/common_tools.py index 4655ef4..8f29f89 100755 --- a/common_tools.py +++ b/common_tools.py @@ -591,6 +591,36 @@ class ConfigManager: config = cls.load() return config.get(key, default) + @property + def username(self) -> str: + """ + Returns the username of the logged-in user, + even if the script is running with root privileges. + + Returns: + str: The username of the logged-in user + """ + import os + from pathlib import Path + + # Versuche zuerst SUDO_USER zu bekommen + sudo_user = os.environ.get("SUDO_USER") + if sudo_user: + return sudo_user + + # Extrahiere aus Path.home() + home_path = str(Path.home()) + if "/home/" in home_path: + return home_path.split("/home/")[1].split("/")[0] + + # Fallbacks + try: + return os.getlogin() + except: + import getpass + + return getpass.getuser() + class ThemeManager: @staticmethod diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 0587e7f..5c0d45c 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -6,11 +6,9 @@ import shutil from pathlib import Path from subprocess import check_call from wp_app_config import AppConfig +from common_tools import ConfigManager -uname: Path = Path("/tmp/.log_user") - -log_name = Path(uname).read_text(encoding="utf-8") - +log_name: str = ConfigManager.username() keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") # PKEYFILE: Path = "/usr/local/etc/ssl/pwgk.pem" diff --git a/ssl_encrypt.py b/ssl_encrypt.py index f924b26..9110d54 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -7,11 +7,11 @@ from pathlib import Path from subprocess import check_call from common_tools import LxTools from wp_app_config import AppConfig +from common_tools import ConfigManager +log_name = ConfigManager.username() -keyfile: Path = Path( - f"/home/{AppConfig.USER_FILE.read_text(encoding="utf-8")}/.config/wire_py/pbwgk.pem" -) +keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") if not keyfile.is_file(): diff --git a/wirepy.py b/wirepy.py index 0231db8..0189206 100755 --- a/wirepy.py +++ b/wirepy.py @@ -23,7 +23,7 @@ from common_tools import ( ) from wp_app_config import AppConfig, Msg -LxTools.uos() + Create.dir_and_files() Create.make_dir() Create.decrypt() From 481362b2e6b95a0ce148d736f1168ec93079d674 Mon Sep 17 00:00:00 2001 From: punix Date: Fri, 9 May 2025 16:04:43 +0200 Subject: [PATCH 46/61] add new methode of get_username() as fallback and add own file_parser for replace con_to_dict --- common_tools.py | 114 ++++++++++++++++++++++++++++++----------------- ssl_decrypt.py | 13 ++++-- ssl_encrypt.py | 7 +-- wirepy.py | 1 - wp_app_config.py | 1 + 5 files changed, 86 insertions(+), 50 deletions(-) diff --git a/common_tools.py b/common_tools.py index 8f29f89..45466b9 100755 --- a/common_tools.py +++ b/common_tools.py @@ -99,6 +99,7 @@ class Create: if file_in_path: if process.returncode == 0: print("File successfully decrypted...") + else: print(f"Error with the following code... {process.returncode}") else: @@ -244,16 +245,26 @@ class LxTools(tk.Tk): return lists_file @staticmethod - def uos() -> None: + def get_username() -> str: """ - uos = LOGIN USERNAME + Returns the username of the logged-in user, + even if the script is running with root privileges. + """ + try: + result = subprocess.run( + ["logname"], + stdout=subprocess.PIPE, + text=True, + check=True, + ) + if result.returncode != 0: + exit(1) + else: + print(result.stdout.strip()) + return result.stdout.strip() - This method displays the username of the logged-in user, - even if you are rooted in a shell - """ - log_name: str = f"{Path.home()}"[6:] - file: Path = Path.home() / "/tmp/.log_user" - Path(file).write_text(log_name, encoding="utf-8") + except subprocess.CalledProcessError: + pass @staticmethod def clean_files(TEMP_DIR: Path = None, file: Path = None) -> None: @@ -265,8 +276,12 @@ class LxTools(tk.Tk): """ if AppConfig.TEMP_DIR is not None: shutil.rmtree(AppConfig.TEMP_DIR) - if file is not None: - Path.unlink(file) + try: + if file is not None: + Path.unlink(file) + + except FileNotFoundError: + pass @staticmethod def msg_window( @@ -389,6 +404,55 @@ class Tunnel: Class of Methods for Wire-Py """ + @staticmethod + def parse_files_to_dictionary() -> Dict[str, List[str]]: + data = {} + + if not AppConfig.TEMP_DIR.exists() or not AppConfig.TEMP_DIR.is_dir(): + pass + + # Get a list of all files in the directorys + files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()] + if not files: + pass + + # Search for the string in the files + for file in files: + try: + with open(file, "r") as f: + content = f.read() + # Hier parsen wir die relevanten Zeilen aus dem Inhalt + address_line = next( + line + for line in content.splitlines() + if line.startswith("Address") + ) + dns_line = next( + line for line in content.splitlines() if line.startswith("DNS") + ) + endpoint_line = next( + line + for line in content.splitlines() + if line.startswith("Endpoint") + ) + + # Extrahiere die Werte + address = address_line.split("=")[1].strip() + dns = dns_line.split("=")[1].strip() + endpoint = endpoint_line.split("=")[1].strip() + + # Speichere im Dictionary + data[file.stem] = { + "Address": address, + "DNS": dns, + "Endpoint": endpoint, + } + except Exception: + # Ignore errors and continue to the next file + continue + + return data + @classmethod def con_to_dict(cls, file: TextIO) -> Tuple[str, str, str, Optional[str]]: """ @@ -591,36 +655,6 @@ class ConfigManager: config = cls.load() return config.get(key, default) - @property - def username(self) -> str: - """ - Returns the username of the logged-in user, - even if the script is running with root privileges. - - Returns: - str: The username of the logged-in user - """ - import os - from pathlib import Path - - # Versuche zuerst SUDO_USER zu bekommen - sudo_user = os.environ.get("SUDO_USER") - if sudo_user: - return sudo_user - - # Extrahiere aus Path.home() - home_path = str(Path.home()) - if "/home/" in home_path: - return home_path.split("/home/")[1].split("/")[0] - - # Fallbacks - try: - return os.getlogin() - except: - import getpass - - return getpass.getuser() - class ThemeManager: @staticmethod diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 5c0d45c..5808145 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -6,13 +6,18 @@ import shutil from pathlib import Path from subprocess import check_call from wp_app_config import AppConfig -from common_tools import ConfigManager +import getpass + +log_name: str = getpass.getuser() +if log_name == "root": + + from common_tools import LxTools + + log_name: str = LxTools.get_username() + print("replacement method applied") -log_name: str = ConfigManager.username() keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") -# PKEYFILE: Path = "/usr/local/etc/ssl/pwgk.pem" - if not keyfile.is_file(): check_call( diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 9110d54..69827f6 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -5,13 +5,10 @@ import os import shutil from pathlib import Path from subprocess import check_call -from common_tools import LxTools from wp_app_config import AppConfig -from common_tools import ConfigManager +from common_tools import LxTools -log_name = ConfigManager.username() - -keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") +keyfile: Path = AppConfig.PUBLICKEY if not keyfile.is_file(): diff --git a/wirepy.py b/wirepy.py index 0189206..b5efe06 100755 --- a/wirepy.py +++ b/wirepy.py @@ -23,7 +23,6 @@ from common_tools import ( ) from wp_app_config import AppConfig, Msg - Create.dir_and_files() Create.make_dir() Create.decrypt() diff --git a/wp_app_config.py b/wp_app_config.py index 52363cb..ea2f826 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -19,6 +19,7 @@ class AppConfig: CONFIG_DIR: Path = BASE_DIR / ".config/wire_py" TEMP_DIR: Path = Path("/tmp/tlecdcwg") USER_FILE: Path = Path("/tmp/.log_user") + PUBLICKEY: Path = CONFIG_DIR / "pbwgk.pem" # Configuration files SETTINGS_FILE: Path = CONFIG_DIR / "settings" From d0adaa76e4f8d26c67f4432667cf867a30303e33 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 10 May 2025 01:55:30 +0200 Subject: [PATCH 47/61] AppConfig and common_utils further developed for Zenrale configuration --- common_tools.py | 11 +++++---- settings | 11 ++++----- ssl_decrypt.py | 9 +------- wirepy.py | 7 ++++-- wp_app_config.py | 58 ++++++++++++++++++++++++++++++++---------------- 5 files changed, 56 insertions(+), 40 deletions(-) diff --git a/common_tools.py b/common_tools.py index 45466b9..5ffc99f 100755 --- a/common_tools.py +++ b/common_tools.py @@ -84,7 +84,7 @@ class Create: AppConfig.TEMP_DIR.mkdir() @staticmethod - def decrypt() -> None: + def decrypt() -> str: """ Starts SSL dencrypt """ @@ -106,7 +106,7 @@ class Create: print(_("Ready for import")) @staticmethod - def encrypt() -> None: + def encrypt() -> str: """ Starts SSL encryption """ @@ -258,10 +258,9 @@ class LxTools(tk.Tk): check=True, ) if result.returncode != 0: - exit(1) - else: - print(result.stdout.strip()) - return result.stdout.strip() + pass + + return result.stdout.strip() except subprocess.CalledProcessError: pass diff --git a/settings b/settings index c0aaccd..6ef82b9 100644 --- a/settings +++ b/settings @@ -1,8 +1,9 @@ -[UPDATES] +# Configuration on -[THEME] -light -[TOOLTIP] +# Theme +dark +# Tooltips True -[AUTOSTART ON] +# Autostart off + diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 5808145..617f805 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -6,15 +6,8 @@ import shutil from pathlib import Path from subprocess import check_call from wp_app_config import AppConfig -import getpass -log_name: str = getpass.getuser() -if log_name == "root": - - from common_tools import LxTools - - log_name: str = LxTools.get_username() - print("replacement method applied") +log_name = AppConfig.USER_FILE.read_text() keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") diff --git a/wirepy.py b/wirepy.py index b5efe06..281e5dc 100755 --- a/wirepy.py +++ b/wirepy.py @@ -2,6 +2,8 @@ """ this script is a simple GUI for managing Wireguard Tunnels """ + +import getpass import os import shutil import subprocess @@ -23,8 +25,9 @@ from common_tools import ( ) from wp_app_config import AppConfig, Msg -Create.dir_and_files() -Create.make_dir() +AppConfig.USER_FILE.write_text(getpass.getuser()) +AppConfig.ensure_directories() +AppConfig.create_default_settings() Create.decrypt() diff --git a/wp_app_config.py b/wp_app_config.py index ea2f826..4698e07 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -4,6 +4,7 @@ import gettext import locale from pathlib import Path +import subprocess from typing import Dict, Any @@ -24,7 +25,14 @@ class AppConfig: # Configuration files SETTINGS_FILE: Path = CONFIG_DIR / "settings" KEYS_FILE: Path = CONFIG_DIR / "keys" + SYSTEMD_USER_FOLDER: Path = Path.home() / ".config/systemd/user" AUTOSTART_SERVICE: Path = Path.home() / ".config/systemd/user/wg_start.service" + DEFAULT_SETTINGS: Dict[str, str] = { + "# Configuration": "on", + "# Theme": "dark", + "# Tooltips": True, + "# Autostart": "off", + } # Updates # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year @@ -79,7 +87,8 @@ class AppConfig: @classmethod def ensure_directories(cls) -> None: """Ensures that all required directories exist""" - cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True) + if not cls.CONFIG_DIR.exists(): + cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True) cls.TEMP_DIR.mkdir(parents=True, exist_ok=True) @classmethod @@ -91,29 +100,40 @@ class AppConfig: ) cls.SETTINGS_FILE.write_text(content) - @classmethod - def get_image_paths(cls) -> Dict[str, Path]: - """Returns paths to UI images""" - return { - "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", - } - @classmethod def get_autostart_content(cls) -> str: """Returns the content for the autostart service file""" + SYSTEMD_FILE: list[str] = [ + "[Unit]", + "Description=Automatic Tunnel Start", + "After=network-online.target", + "", + "[Service]", + "Type=oneshot", + "ExecStartPre=/bin/sleep 5", + "ExecStart=/usr/local/bin/start_wg.py", + "", + "[Install]", + "WantedBy=default.target", + ] + if not cls.SYSTEMD_USER_FOLDER.exists(): + cls.SYSTEMD_USER_FOLDER.mkdir(parents=True, exist_ok=True) - return """[Unit]Description=Automatic Tunnel Start -After=network-online.target + for line in SYSTEMD_FILE: + cls.AUTOSTART_SERVICE.write_text(line) -[Service] -Type=oneshot -ExecStartPre=/bin/sleep 5 -ExecStart=/usr/local/bin/start_wg.py -[Install] -WantedBy=default.target""" + process = subprocess.run( + ["systemctl", "--user", "enable", "wg_start.service"], + stdout=subprocess.PIPE, + text=True, + check=True, + ) + print(process.stdout) + if process.returncode == 0: + print("File for autostart created successfully") + print(process.stdout) + else: + print(f"Error with the following code... {process.returncode}") # here is inizialize the class for translate strrings From a903666a26ac61ffd201929ab78c11af54094d6d Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 10 May 2025 14:23:22 +0200 Subject: [PATCH 48/61] fix ssl_encrypt.py read user_log datei added again --- ssl_encrypt.py | 4 +++- wp_app_config.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 69827f6..0f07f6a 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -8,7 +8,9 @@ from subprocess import check_call from wp_app_config import AppConfig from common_tools import LxTools -keyfile: Path = AppConfig.PUBLICKEY +log_name = AppConfig.USER_FILE.read_text() + +keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") if not keyfile.is_file(): diff --git a/wp_app_config.py b/wp_app_config.py index 4698e07..1a0c28b 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -89,7 +89,10 @@ class AppConfig: """Ensures that all required directories exist""" if not cls.CONFIG_DIR.exists(): cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True) + cls.KEYS_FILE.touch() cls.TEMP_DIR.mkdir(parents=True, exist_ok=True) + if not cls.KEYS_FILE.exists(): + cls.KEYS_FILE.touch() @classmethod def create_default_settings(cls) -> None: From 6604650adffad8750898fa325f98017ee0dcb523 Mon Sep 17 00:00:00 2001 From: punix Date: Sun, 11 May 2025 18:24:57 +0200 Subject: [PATCH 49/61] ssl_decrypt.py now with output and check_call replace with subprocess.run --- .gitignore | 1 + common_tools.py | 23 +++++++++-------- ssl_decrypt.py | 64 ++++++++++++++++++++++++++++++++---------------- ssl_encrypt.py | 2 +- wirepy.py | 2 +- wp_app_config.py | 15 +++++++----- 6 files changed, 68 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index aaaf151..e0b46ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ debug.log .venv +.venv.bak .idea .vscode __pycache__ diff --git a/common_tools.py b/common_tools.py index 5ffc99f..331384f 100755 --- a/common_tools.py +++ b/common_tools.py @@ -90,20 +90,23 @@ class Create: """ process: CompletedProcess[str] = subprocess.run( ["pkexec", "/usr/local/bin/ssl_decrypt.py"], - stdout=subprocess.PIPE, + capture_output=True, text=True, - check=True, + check=False, ) - path: Path = Path.home() / ".config/wire_py/" - file_in_path: list[Path] = list(path.rglob("*.dat")) - if file_in_path: - if process.returncode == 0: - print("File successfully decrypted...") - else: - print(f"Error with the following code... {process.returncode}") + # Output from Openssl + if process.stdout: + print(process.stdout) + + # Output from Openssl Error + if process.stderr: + print(process.stderr) + + if process.returncode == 0: + print("Datei entschlüsseln wurde erfolgreich entschlossen.") else: - print(_("Ready for import")) + print(f"Fehler bei der Verarbeitung von Dateien: Code {process.returncode}") @staticmethod def encrypt() -> str: diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 617f805..6d65207 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -1,19 +1,19 @@ #!/usr/bin/python3 """ This Script decrypt Wireguard files for Wirepy users """ -import os -import shutil from pathlib import Path -from subprocess import check_call +import shutil +from subprocess import CompletedProcess +import subprocess from wp_app_config import AppConfig log_name = AppConfig.USER_FILE.read_text() keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") +path_of_crypted_tunnel: Path = Path(f"/home/{log_name}/.config/wire_py") if not keyfile.is_file(): - - check_call( + process: CompletedProcess[str] = subprocess.run( [ "openssl", "rsa", @@ -24,21 +24,27 @@ if not keyfile.is_file(): "-outform", "PEM", "-pubout", - ] + ], + capture_output=True, + text=True, + check=False, ) + print(process.stdout) + if process.returncode == 0: + print("Public key generated successfully.") + else: + print(f"Error with the following code... {process.returncode}") shutil.chown(keyfile, 1000, 1000) -AppConfig.TEMP_DIR2 = f"/home/{log_name}/.config/wire_py/" -detl: list[str] = os.listdir(AppConfig.TEMP_DIR2) -os.chdir(AppConfig.TEMP_DIR2) -detl.remove("keys") -detl.remove("settings") -if os.path.exists(f"{AppConfig.TEMP_DIR2}pbwgk.pem"): - detl.remove("pbwgk.pem") - for detunnels in detl: - tlname2 = f"{detunnels[:-4]}.conf" - extpath = f"{AppConfig.TEMP_DIR}/{tlname2}" - check_call( +if AppConfig.PUBLICKEY.exists: + + crypted__tunnel = [str(file) for file in path_of_crypted_tunnel.glob("*.dat")] + + for tunnel_path in crypted__tunnel: + + base_name = Path(tunnel_path).stem + + process: CompletedProcess[str] = subprocess.run( [ "openssl", "pkeyutl", @@ -46,9 +52,25 @@ if os.path.exists(f"{AppConfig.TEMP_DIR2}pbwgk.pem"): "-inkey", AppConfig.SYSTEM_PATHS["pkey_path"], "-in", - detunnels, + tunnel_path, # full path to the file "-out", - extpath, - ] + f"{AppConfig.TEMP_DIR}/{base_name}.conf", + ], + capture_output=True, + text=True, + check=False, ) - shutil.chown(extpath, 1000, 1000) + + print(f"Processing of the file: {tunnel_path}") + + if process.stdout: + print(process.stdout) + + # Output from Openssl Error + if process.stderr: + print("(Error):", process.stderr) + + if process.returncode == 0: + print(f"File {base_name}.dat successfully decrypted.") + else: + print(f"Error by {tunnel_path}: Code: {process.returncode}") diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 0f07f6a..306928d 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -2,8 +2,8 @@ """ This Script encrypt Wireguardfiles for Wirepy users for more Security """ import os -import shutil from pathlib import Path +import shutil from subprocess import check_call from wp_app_config import AppConfig from common_tools import LxTools diff --git a/wirepy.py b/wirepy.py index 281e5dc..457b2df 100755 --- a/wirepy.py +++ b/wirepy.py @@ -874,7 +874,7 @@ class FrameWidgets(ttk.Frame): """ checkbox for enable autostart Tunnel """ - Create.files_for_autostart() + AppConfig.get_autostart_content() if self.l_box.size() != 0: self.wg_autostart.configure(state="normal") self.lb_rename.config(state="normal") diff --git a/wp_app_config.py b/wp_app_config.py index 1a0c28b..8f34591 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -122,18 +122,21 @@ class AppConfig: if not cls.SYSTEMD_USER_FOLDER.exists(): cls.SYSTEMD_USER_FOLDER.mkdir(parents=True, exist_ok=True) - for line in SYSTEMD_FILE: - cls.AUTOSTART_SERVICE.write_text(line) + from subprocess import CompletedProcess - process = subprocess.run( + if not cls.AUTOSTART_SERVICE.is_file(): + + content = "\n".join([line for line in SYSTEMD_FILE]) + cls.AUTOSTART_SERVICE.write_text(content) + + process: CompletedProcess[str] = subprocess.run( ["systemctl", "--user", "enable", "wg_start.service"], - stdout=subprocess.PIPE, + capture_output=True, text=True, - check=True, + check=False, ) print(process.stdout) if process.returncode == 0: - print("File for autostart created successfully") print(process.stdout) else: print(f"Error with the following code... {process.returncode}") From fb0158d1cd7391d1b29f77d9826ca98170f26567 Mon Sep 17 00:00:00 2001 From: punix Date: Sun, 11 May 2025 22:00:28 +0200 Subject: [PATCH 50/61] replace all check_call with subprocess.run --- common_tools.py | 80 ++++++------------------------------- ssl_encrypt.py | 102 ++++++++++++++++++++++++----------------------- start_wg.py | 14 ++++++- wg_start.service | 1 + wirepy.py | 30 +++++++++----- 5 files changed, 98 insertions(+), 129 deletions(-) diff --git a/common_tools.py b/common_tools.py index 331384f..224df96 100755 --- a/common_tools.py +++ b/common_tools.py @@ -10,7 +10,7 @@ from typing import Optional, Dict, Any, NoReturn, TextIO, Tuple, List import zipfile from datetime import datetime from pathlib import Path -from subprocess import check_call, CompletedProcess +from subprocess import CompletedProcess from tkinter import ttk, Toplevel from wp_app_config import AppConfig, Msg import requests @@ -26,63 +26,6 @@ class Create: the tunnel from the user's home directory """ - @staticmethod - def dir_and_files() -> None: - """ - check and create folders and files if not present - """ - - pth: Path = Path.home() / ".config/wire_py" - pth.mkdir(parents=True, exist_ok=True) - sett: Path = Path.home() / ".config/wire_py/settings" - AppConfig.KEYS_FILE - - if sett.exists(): - pass - - else: - sett.touch() - sett.write_text( - "[UPDATES]\non\n[THEME]\nlight\n[TOOLTIP]\nTrue\n[AUTOSTART ON]\noff\n" - ) - - if AppConfig.KEYS_FILE.exists(): - pass - - else: - AppConfig.KEYS_FILE.touch() - - @staticmethod - def files_for_autostart() -> None: - """ - check and create a file for auto start if not present and enable the service - """ - - pth2: Path = Path.home() / ".config/systemd/user" - pth2.mkdir(parents=True, exist_ok=True) - wg_ser: Path = Path.home() / ".config/systemd/user/wg_start.service" - - if wg_ser.exists(): - pass - - else: - wg_ser.touch() - wg_ser.write_text( - "[Unit]\nDescription=Automatic Tunnel Start\nAfter=network-online.target\n\n[Service]\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 - def make_dir() -> None: - """Folder Name "tlecdewg" = Tunnel Encrypt Decrypt Wireguard""" - - if AppConfig.TEMP_DIR.exists(): - pass - else: - AppConfig.TEMP_DIR.mkdir() - @staticmethod def decrypt() -> str: """ @@ -96,17 +39,17 @@ class Create: ) # Output from Openssl - if process.stdout: - print(process.stdout) + # if process.stdout: + # print(process.stdout) # Output from Openssl Error if process.stderr: print(process.stderr) if process.returncode == 0: - print("Datei entschlüsseln wurde erfolgreich entschlossen.") + print("Files successfully decrypted...") else: - print(f"Fehler bei der Verarbeitung von Dateien: Code {process.returncode}") + print(f"Error process decrypt: Code {process.returncode}") @staticmethod def encrypt() -> str: @@ -115,15 +58,18 @@ class Create: """ process: CompletedProcess[str] = subprocess.run( ["pkexec", "/usr/local/bin/ssl_encrypt.py"], - stdout=subprocess.PIPE, + capture_output=True, text=True, - check=True, + check=False, ) - print(process.stdout) + # Output from Openssl Error + if process.stderr: + print(process.stderr) + if process.returncode == 0: - print("All Files successfully encrypted...") + print("Files successfully encrypted...") else: - print(f"Error with the following code... {process.returncode}") + print(f"Error process encrypt: Code {process.returncode}") class LxTools(tk.Tk): diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 306928d..a92b35c 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -1,12 +1,11 @@ #!/usr/bin/python3 """ This Script encrypt Wireguardfiles for Wirepy users for more Security """ -import os from pathlib import Path import shutil -from subprocess import check_call +import subprocess +from subprocess import CompletedProcess from wp_app_config import AppConfig -from common_tools import LxTools log_name = AppConfig.USER_FILE.read_text() @@ -14,7 +13,7 @@ keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") if not keyfile.is_file(): - check_call( + process: CompletedProcess[str] = subprocess.run( [ "openssl", "rsa", @@ -25,56 +24,59 @@ if not keyfile.is_file(): "-outform", "PEM", "-pubout", - ] + ], + capture_output=True, + text=True, + check=False, ) + + if process.stdout: + print(process.stdout) + + # Output from Openssl Error + if process.stderr: + print("(Error):", process.stderr) + + if process.returncode == 0: + print("Public key generated successfully.") + else: + print(f"Error generate Publickey: Code: {process.returncode}") + shutil.chown(keyfile, 1000, 1000) - if AppConfig.TEMP_DIR.exists(): - tl = LxTools.get_file_name(AppConfig.TEMP_DIR) - CPTH: str = f"{keyfile}" - CRYPTFILES: str = CPTH[:-9] + if AppConfig.TEMP_DIR.exists() and not any(AppConfig.TEMP_DIR.iterdir()): + clear_files = [str(file) for file in path_of_crypted_tunnel.glob()] - if keyfile.exists() and len(tl) != 0: - 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, - ] - ) + for config_file in clear_files: + base_name = Path(config_file).stem + process: CompletedProcess[str] = subprocess.run( + [ + "openssl", + "pkeyutl", + "-encrypt", + "-inkey", + keyfile, + "-pubin", + "-in", + config_file, + "-out", + f"{AppConfig.CONFIG_DIR}/{base_name}.dat", + ], + capture_output=True, + text=True, + check=False, + ) -else: + print(f"Processing of the file: {config_file}") - if AppConfig.TEMP_DIR.exists(): - tl: list[str] = os.listdir(f"{AppConfig.TEMP_DIR}") - CPTH: str = f"{keyfile}" - CRYPTFILES: str = CPTH[:-9] + if process.stdout: + print(process.stdout) - if keyfile.exists() and len(tl) != 0: - 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, - ] - ) + # Output from Openssl Error + if process.stderr: + print("(Error):", process.stderr) + + if process.returncode == 0: + print(f"File {base_name}.dat successfully encrypted.") + else: + print(f"Error by {config_file}: Code: {process.returncode}") diff --git a/start_wg.py b/start_wg.py index 0262a49..20ca206 100755 --- a/start_wg.py +++ b/start_wg.py @@ -4,13 +4,23 @@ """ from pathlib import Path -from subprocess import check_call +import subprocess +from subprocess import CompletedProcess path_to_file = Path(Path.home() / ".config/wire_py/settings") a_con = Path(path_to_file).read_text(encoding="utf-8").splitlines(keepends=True) a_con = a_con[7].strip() if a_con != "off": - check_call(["nmcli", "connection", "up", a_con]) + process: CompletedProcess[str] = subprocess.run( + ["nmcli", "connection", "up", a_con], + capture_output=True, + text=True, + check=False, + ) + # Output from start_wg error + if process.stderr: + print(process.stderr) # this is for the error, later on logfile + else: pass diff --git a/wg_start.service b/wg_start.service index 0352b4e..5d41844 100644 --- a/wg_start.service +++ b/wg_start.service @@ -6,5 +6,6 @@ After=network-online.target Type=oneshot ExecStartPre=/bin/sleep 5 ExecStart=/usr/local/bin/start_wg.py + [Install] WantedBy=default.target diff --git a/wirepy.py b/wirepy.py index 457b2df..1e288de 100755 --- a/wirepy.py +++ b/wirepy.py @@ -11,7 +11,7 @@ import sys import tkinter as tk import webbrowser from pathlib import Path -from subprocess import check_call +from subprocess import CompletedProcess from tkinter import TclError, filedialog, ttk from common_tools import ( @@ -645,7 +645,7 @@ class FrameWidgets(ttk.Frame): def import_sl(self) -> None: """validity check of wireguard config files""" - Create.dir_and_files() + AppConfig.ensure_directories() try: filepath = filedialog.askopenfilename( initialdir=f"{Path.home()}", @@ -696,10 +696,12 @@ class FrameWidgets(ttk.Frame): new_conf = f"{AppConfig.TEMP_DIR}/{path_split}" if self.a != "": - check_call(["nmcli", "connection", "down", self.a]) + process: CompletedProcess[str] = subprocess.run( + ["nmcli", "connection", "down", self.a] + ) self.reset_fields() - subprocess.check_output( + process: CompletedProcess[str] = subprocess.run( [ "nmcli", "connection", @@ -716,10 +718,12 @@ class FrameWidgets(ttk.Frame): shutil.copy(filepath, f"{AppConfig.TEMP_DIR}/") if self.a != "": - check_call(["nmcli", "connection", "down", self.a]) + process: CompletedProcess[str] = subprocess.run( + ["nmcli", "connection", "down", self.a] + ) self.reset_fields() - subprocess.check_output( + process: CompletedProcess[str] = subprocess.run( [ "nmcli", "connection", @@ -760,7 +764,7 @@ class FrameWidgets(ttk.Frame): self.color_label() self.stop() data = self.handle_tunnel_data(self.a) - check_call( + process: CompletedProcess[str] = subprocess.run( [ "nmcli", "con", @@ -801,7 +805,9 @@ class FrameWidgets(ttk.Frame): ) as file2: key = Tunnel.con_to_dict(file2) pre_key = key[3] - check_call(["nmcli", "connection", "delete", select_tl]) + process: CompletedProcess[str] = subprocess.run( + ["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() @@ -1145,7 +1151,9 @@ class FrameWidgets(ttk.Frame): """ if action == "stop": if self.a: - check_call(["nmcli", "connection", "down", self.a]) + process: CompletedProcess[str] = subprocess.run( + ["nmcli", "connection", "down", self.a] + ) self.update_connection_display() self.reset_fields() self.start() @@ -1153,7 +1161,9 @@ class FrameWidgets(ttk.Frame): elif action == "start": if tunnel_name or self.a: target_tunnel = tunnel_name or self.a - check_call(["nmcli", "connection", "up", target_tunnel]) + process: CompletedProcess[str] = subprocess.run( + ["nmcli", "connection", "up", target_tunnel] + ) self.update_connection_display() data = self.handle_tunnel_data(self.a) self.init_and_report(data) From 3da54642a0a4bb66a8da0670e6b03e7be80ae13e Mon Sep 17 00:00:00 2001 From: punix Date: Mon, 12 May 2025 15:11:40 +0200 Subject: [PATCH 51/61] ssl_de/encrypt new works --- common_tools.py | 1 + org.sslcrypt.policy | 1 + ssl_decrypt.py | 4 +-- ssl_encrypt.py | 67 ++++++++++++++++++++++----------------------- wirepy.py | 4 ++- 5 files changed, 40 insertions(+), 37 deletions(-) diff --git a/common_tools.py b/common_tools.py index 224df96..4b429fb 100755 --- a/common_tools.py +++ b/common_tools.py @@ -62,6 +62,7 @@ class Create: text=True, check=False, ) + # Output from Openssl Error if process.stderr: print(process.stderr) diff --git a/org.sslcrypt.policy b/org.sslcrypt.policy index 6a2c47a..d9b5100 100644 --- a/org.sslcrypt.policy +++ b/org.sslcrypt.policy @@ -25,6 +25,7 @@ License along with this library. If not, see auth_admin_keep + auth_admin_keep yes /usr/local/bin/ssl_encrypt.py diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 6d65207..03e4525 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -7,7 +7,7 @@ from subprocess import CompletedProcess import subprocess from wp_app_config import AppConfig -log_name = AppConfig.USER_FILE.read_text() +log_name = AppConfig.USER_FILE.read_text().strip() keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") path_of_crypted_tunnel: Path = Path(f"/home/{log_name}/.config/wire_py") @@ -60,7 +60,7 @@ if AppConfig.PUBLICKEY.exists: text=True, check=False, ) - + shutil.chown(f"{AppConfig.TEMP_DIR}/{base_name}.conf", 1000, 1000) print(f"Processing of the file: {tunnel_path}") if process.stdout: diff --git a/ssl_encrypt.py b/ssl_encrypt.py index a92b35c..7147140 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -1,16 +1,17 @@ #!/usr/bin/python3 """ This Script encrypt Wireguardfiles for Wirepy users for more Security """ - from pathlib import Path import shutil import subprocess from subprocess import CompletedProcess from wp_app_config import AppConfig -log_name = AppConfig.USER_FILE.read_text() +log_name = AppConfig.USER_FILE.read_text().strip() keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") +target: Path = Path(f"/home/{log_name}/.config/wire_py/") + if not keyfile.is_file(): process: CompletedProcess[str] = subprocess.run( @@ -44,39 +45,37 @@ if not keyfile.is_file(): shutil.chown(keyfile, 1000, 1000) - if AppConfig.TEMP_DIR.exists() and not any(AppConfig.TEMP_DIR.iterdir()): - clear_files = [str(file) for file in path_of_crypted_tunnel.glob()] +# any() get True when directory is not empty +if AppConfig.TEMP_DIR.exists() and any(AppConfig.TEMP_DIR.iterdir()): + clear_files = [str(file) for file in AppConfig.TEMP_DIR.glob("*.conf")] - for config_file in clear_files: - base_name = Path(config_file).stem - process: CompletedProcess[str] = subprocess.run( - [ - "openssl", - "pkeyutl", - "-encrypt", - "-inkey", - keyfile, - "-pubin", - "-in", - config_file, - "-out", - f"{AppConfig.CONFIG_DIR}/{base_name}.dat", - ], - capture_output=True, - text=True, - check=False, - ) + for config_file in clear_files: + base_name = Path(config_file).stem + process: CompletedProcess[str] = subprocess.run( + [ + "openssl", + "pkeyutl", + "-encrypt", + "-inkey", + keyfile, + "-pubin", + "-in", + config_file, + "-out", + f"{target}/{base_name}.dat", + ], + capture_output=True, + text=True, + check=False, + ) - print(f"Processing of the file: {config_file}") + print(f"Processing of the file: {config_file}") - if process.stdout: - print(process.stdout) + # Output from Openssl Error + if process.stderr: + print("(Error):", process.stderr) - # Output from Openssl Error - if process.stderr: - print("(Error):", process.stderr) - - if process.returncode == 0: - print(f"File {base_name}.dat successfully encrypted.") - else: - print(f"Error by {config_file}: Code: {process.returncode}") + if process.returncode == 0: + print(f"File {base_name}.dat successfully encrypted.") + else: + print(f"Error by {config_file}: Code: {process.returncode}") diff --git a/wirepy.py b/wirepy.py index 1e288de..618e57a 100755 --- a/wirepy.py +++ b/wirepy.py @@ -713,6 +713,7 @@ class FrameWidgets(ttk.Frame): ], text=True, ) + Create.encrypt() else: shutil.copy(filepath, f"{AppConfig.TEMP_DIR}/") @@ -735,8 +736,8 @@ class FrameWidgets(ttk.Frame): ], text=True, ) - Create.encrypt() + Create.encrypt() self.str_var.set("") self.a = Tunnel.active() self.l_box.insert(0, self.a) @@ -774,6 +775,7 @@ class FrameWidgets(ttk.Frame): "no", ] ) + elif ("PrivateKey = " in read) and ("Endpoint = " in read): pass else: From 0c4d000d9644ad71c0ed7b6ca500e701fcdd8003 Mon Sep 17 00:00:00 2001 From: punix Date: Mon, 12 May 2025 16:48:48 +0200 Subject: [PATCH 52/61] add ckeck_key_is_exist() for import --- common_tools.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ wp_app_config.py | 7 +++++++ 2 files changed, 58 insertions(+) diff --git a/common_tools.py b/common_tools.py index 4b429fb..ee823cb 100755 --- a/common_tools.py +++ b/common_tools.py @@ -81,6 +81,57 @@ class LxTools(tk.Tk): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) + @staticmethod + def ckeck_key_is_exist( + directorys: list[str], search_string: str = None + ) -> bool | None: + """ + Check if the key is exist in the file + Args: + directorys (list[str]): list of directories to search in + search_string (str): string to search for + Returns: + bool: True if the key is found, False otherwise + """ + + if search_string: + result = False + + for directory in directorys: + + in_paths = Path(directory) + + if not in_paths.exists() or not in_paths.is_dir(): + continue + + # Get a list of all files in the directorys + files = [file for file in in_paths.iterdir() if file.is_file()] + if not files: + continue + + # Search for the string in the files + for file in files: + try: + with open(file, "r", errors="ignore") as f: + for line in f: + if search_string in line: + # Set the result to True if the string is found + result = True + break + + # If the string is found, stop searching for the string in other files + if result: + break + + except Exception: + # Ignore errors and continue to the next file + continue + + else: + result = None + print(result) + return result + @staticmethod def center_window_cross_platform(window, width, height): """ diff --git a/wp_app_config.py b/wp_app_config.py index 8f34591..b9d07e5 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -57,6 +57,13 @@ class AppConfig: "pkey_path": "/usr/local/etc/ssl/pwgk.pem", } + # Lists of searches + DIRECTORYS: list[str] = [ + "/etc/netplan/", + "/etc/NetworkManager/system-connections/", + "/var/lib/NetworkManager/user-connections/", + ] + # Images and icons paths IMAGE_PATHS: Dict[str, str] = { "icon_vpn": "/usr/share/icons/lx-icons/48/wg_vpn.png", From d6c20b81f92e9a0643f82e275794c1ef0c8e9982 Mon Sep 17 00:00:00 2001 From: punix Date: Sun, 18 May 2025 12:47:52 +0200 Subject: [PATCH 53/61] part 1 load data from dictionary works --- common_tools.py | 243 +++++++++++++++++++++++--------------------- install | 7 +- match_found.py | 61 +++++++++++ org.sslcrypt.policy | 10 +- wirepy.py | 79 ++++++-------- wp_app_config.py | 7 -- 6 files changed, 231 insertions(+), 176 deletions(-) create mode 100755 match_found.py diff --git a/common_tools.py b/common_tools.py index ee823cb..e6583f5 100755 --- a/common_tools.py +++ b/common_tools.py @@ -3,14 +3,17 @@ import os import shutil import signal +import base64 +import secrets import subprocess +from subprocess import CompletedProcess, run +import re import sys import tkinter as tk from typing import Optional, Dict, Any, NoReturn, TextIO, Tuple, List import zipfile from datetime import datetime from pathlib import Path -from subprocess import CompletedProcess from tkinter import ttk, Toplevel from wp_app_config import AppConfig, Msg import requests @@ -19,7 +22,7 @@ import requests _ = AppConfig.setup_translations() -class Create: +class CryptoUtil: """ This class is for the creation of the folders and files required by Wire-Py, as well as for decryption @@ -72,6 +75,52 @@ class Create: else: print(f"Error process encrypt: Code {process.returncode}") + @staticmethod + def find_key(key: str = "") -> bool: + """ + Checks if the private key already exists in the system using an external script. + Returns True only if the full key is found exactly (no partial match). + """ + process: CompletedProcess[bool] = run( + ["pkexec", "/usr/local/bin/match_found.py", key], + capture_output=True, + text=True, + check=False, + ) + if "True" in process.stdout: + return True + elif "False" in process.stdout: + return False + print( + f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}" + ) + return False + + @staticmethod + def is_valid_base64(key: str) -> bool: + """ + Validates if the input is a valid Base64 string (WireGuard private key format). + Returns True only for non-empty strings that match the expected length. + """ + # Check for empty string + if not key or key.strip() == "": + return False + + # Regex pattern to validate Base64: [A-Za-z0-9+/]+={0,2} + base64_pattern = r"^[A-Za-z0-9+/]+={0,2}$" + if not re.match(base64_pattern, key): + return False + + try: + # Decode and check length (WireGuard private keys are 32 bytes long) + decoded = base64.b64decode(key) + if len(decoded) != 32: # 32 bytes = 256 bits + return False + except Exception: + return False + + return True + class LxTools(tk.Tk): """ @@ -81,57 +130,6 @@ class LxTools(tk.Tk): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - @staticmethod - def ckeck_key_is_exist( - directorys: list[str], search_string: str = None - ) -> bool | None: - """ - Check if the key is exist in the file - Args: - directorys (list[str]): list of directories to search in - search_string (str): string to search for - Returns: - bool: True if the key is found, False otherwise - """ - - if search_string: - result = False - - for directory in directorys: - - in_paths = Path(directory) - - if not in_paths.exists() or not in_paths.is_dir(): - continue - - # Get a list of all files in the directorys - files = [file for file in in_paths.iterdir() if file.is_file()] - if not files: - continue - - # Search for the string in the files - for file in files: - try: - with open(file, "r", errors="ignore") as f: - for line in f: - if search_string in line: - # Set the result to True if the string is found - result = True - break - - # If the string is found, stop searching for the string in other files - if result: - break - - except Exception: - # Ignore errors and continue to the next file - continue - - else: - result = None - print(result) - return result - @staticmethod def center_window_cross_platform(window, width, height): """ @@ -405,23 +403,74 @@ class Tunnel: """ @staticmethod - def parse_files_to_dictionary() -> Dict[str, List[str]]: + def parse_files_to_dictionary( + directory: Path = None, filepath: str = None + ) -> dict | str | None: data = {} - if not AppConfig.TEMP_DIR.exists() or not AppConfig.TEMP_DIR.is_dir(): - pass - - # Get a list of all files in the directorys - files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()] - if not files: - pass - - # Search for the string in the files - for file in files: + if filepath is not None: + filepath = Path(filepath) try: - with open(file, "r") as f: - content = f.read() - # Hier parsen wir die relevanten Zeilen aus dem Inhalt + content = filepath.read_text() + + # parse the content + address_line = next( + line for line in content.splitlines() if line.startswith("Address") + ) + dns_line = next( + line for line in content.splitlines() if line.startswith("DNS") + ) + endpoint_line = next( + line for line in content.splitlines() if line.startswith("Endpoint") + ) + private_key_line = next( + line + for line in content.splitlines() + if line.startswith("PrivateKey") + ) + + content = secrets.token_bytes(len(content)) + + # extract the values + address = address_line.split("=")[1].strip() + dns = dns_line.split("=")[1].strip() + endpoint = endpoint_line.split("=")[1].strip() + private_key = private_key_line.split("=")[1].strip() + + # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters. + original_stem = filepath.stem + truncated_stem = ( + original_stem[-12:] if len(original_stem) > 12 else original_stem + ) + + # save in the dictionary + data[truncated_stem] = { + "Address": address, + "DNS": dns, + "Endpoint": endpoint, + "PrivateKey": private_key, + } + + content = secrets.token_bytes(len(content)) + + except StopIteration as e: + print(f"Error: {e}") + pass + + elif directory is not None: + + if not directory.exists() or not directory.is_dir(): + print("Temp directory does not exist or is not a directory.") + return None + + # Get a list of all files in the directory + files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()] + + # Search for the string in the files + for file in files: + try: + content = file.read_text() + # parse the content address_line = next( line for line in content.splitlines() @@ -436,67 +485,27 @@ class Tunnel: if line.startswith("Endpoint") ) - # Extrahiere die Werte + # extract values address = address_line.split("=")[1].strip() dns = dns_line.split("=")[1].strip() endpoint = endpoint_line.split("=")[1].strip() - # Speichere im Dictionary + # save values to dictionary data[file.stem] = { "Address": address, "DNS": dns, "Endpoint": endpoint, } - except Exception: - # Ignore errors and continue to the next file - continue - return data + except Exception: + # Ignore errors and continue to the next file + continue - @classmethod - def con_to_dict(cls, file: TextIO) -> Tuple[str, str, str, Optional[str]]: - """ - Returns tuple of (address, dns, endpoint, pre_key) - """ - - dictlist: List[str] = [] - for lines in file.readlines(): - line_plit: List[str] = lines.split() - dictlist = dictlist + line_plit - dictlist.remove("[Interface]") - dictlist.remove("[Peer]") - for items in dictlist: - if items == "=": - dictlist.remove(items) - if items == "::/0": - dictlist.remove(items) - - # Here is the beginning (Loop) of convert List to Dictionary - for _ in dictlist: - a: List[str] = [dictlist[0], dictlist[1]] - b: List[str] = [dictlist[2], dictlist[3]] - c: List[str] = [dictlist[4], dictlist[5]] - d: List[str] = [dictlist[6], dictlist[7]] - e: List[str] = [dictlist[8], dictlist[9]] - f: List[str] = [dictlist[10], dictlist[11]] - g: List[str] = [dictlist[12], dictlist[13]] - h: List[str] = [dictlist[14], dictlist[15]] - new_list: List[List[str]] = [a, b, c, d, e, f, g, h] - final_dict: Dict[str, str] = {} - for elements in new_list: - final_dict[elements[0]] = elements[1] - - # end... result a Dictionary - - address: str = final_dict["Address"] - dns: str = final_dict["DNS"] - if "," in dns: - dns = dns[:-1] - endpoint: str = final_dict["Endpoint"] - pre_key: Optional[str] = final_dict.get("PresharedKey") - if pre_key is None: - pre_key: Optional[str] = final_dict.get("PreSharedKey") - return address, dns, endpoint, pre_key + content = secrets.token_bytes(len(content)) + if filepath is not None: + return data, truncated_stem + else: + return data @staticmethod def active() -> str: diff --git a/install b/install index 0495dde..cb4818f 100755 --- a/install +++ b/install @@ -18,6 +18,7 @@ install_file_with(){ else sudo apt install python3-tk && \ sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv match_found.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -44,6 +45,7 @@ install_arch_d(){ else sudo pacman -S --noconfirm tk python3 python-requests && \ sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv match_found.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -121,6 +123,7 @@ install(){ else sudo dnf install python3-tkinter -y sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv match_found.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -146,6 +149,7 @@ install(){ exit 0 else sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ + sudo cp -fv match_found.py /usr/local/bin/ && \ sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ @@ -181,7 +185,8 @@ install(){ remove(){ sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \ - /usr/local/bin/wp_app_config.py common_tools.py /usr/local/bin/ssl_encrypt.py /usr/local/bin/ssl_decrypt.py + /usr/local/bin/wp_app_config.py common_tools.py /usr/local/bin/ssl_encrypt.py \ + /usr/local/bin/ssl_decrypt.py /usr/local/bin/match_found.py if [ $? -ne 0 ] then exit 0 diff --git a/match_found.py b/match_found.py new file mode 100755 index 0000000..b69ea65 --- /dev/null +++ b/match_found.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 + +import argparse +from pathlib import Path + + +directorys: list[str] = [ + "/etc/netplan/", + "/etc/NetworkManager/system-connections/", + "/var/lib/NetworkManager/user-connections/", +] + + +def search_string_in_directory( + directories: list[str] = directorys, # Use the predefined list as default + search_string: str = "", # Default is empty string +) -> bool: + + if len(search_string) == 0: + return False + + result = False + for directory in directories: + in_paths = Path(directory) + if not in_paths.exists() or not in_paths.is_dir(): + continue + + files = [file for file in in_paths.iterdir() if file.is_file()] + if not files: + continue + + # Search for the string in each file + for file in files: + try: + with open(file, "r", errors="ignore") as f: + for line in f: + if search_string in line: + result = True # String found + break + if result: + break # No need to check further + except Exception: + continue # Skip files that cause errors + + # Invert the logic: return False if string is found, True otherwise + return result + + +def main() -> bool: + parser = argparse.ArgumentParser( + description="Script only for use to compare the private key in the Network configurations to avoid errors with the network manager." + ) + parser.add_argument("search_string", help="Search string") + args = parser.parse_args() + + result = search_string_in_directory(search_string=args.search_string) + print(result) + + +if __name__ == "__main__": + main() diff --git a/org.sslcrypt.policy b/org.sslcrypt.policy index d9b5100..84fff56 100644 --- a/org.sslcrypt.policy +++ b/org.sslcrypt.policy @@ -38,6 +38,14 @@ License along with this library. If not, see yes /usr/local/bin/ssl_decrypt.py - + + + + + auth_admin_keep + auth_admin_keep + yes + + /usr/local/bin/match_found.py \ No newline at end of file diff --git a/wirepy.py b/wirepy.py index 618e57a..98da4c1 100755 --- a/wirepy.py +++ b/wirepy.py @@ -17,7 +17,7 @@ from tkinter import TclError, filedialog, ttk from common_tools import ( ConfigManager, ThemeManager, - Create, + CryptoUtil, GiteaUpdate, Tunnel, Tooltip, @@ -28,7 +28,7 @@ from wp_app_config import AppConfig, Msg AppConfig.USER_FILE.write_text(getpass.getuser()) AppConfig.ensure_directories() AppConfig.create_default_settings() -Create.decrypt() +CryptoUtil.decrypt() class Wirepy(tk.Tk): @@ -242,15 +242,20 @@ class FrameWidgets(ttk.Frame): 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.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() - data = self.handle_tunnel_data(self.a) + self.handle_tunnel_data(self.a, self.tl) + self.show_data() else: self.start() @@ -586,22 +591,6 @@ class FrameWidgets(ttk.Frame): 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 @@ -652,25 +641,19 @@ class FrameWidgets(ttk.Frame): title=_("Select Wireguard config File"), filetypes=[(_("WG config files"), "*.conf")], ) - - # Überprüfe, ob der Benutzer den Dialog abgebrochen hat + # Check if a file was selected 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] + filepath = Path(filepath) + read = filepath.read_text(encoding="utf-8") if ( "PrivateKey = " in read and "PublicKey = " in read - and "Endpoint =" in read + and "Endpoint = " in read ): - with open(filepath, "r", encoding="utf-8") as file: - key = Tunnel.con_to_dict(file) + key = Tunnel.con_to_dict(read) pre_key = key[3] if len(pre_key) != 0: @@ -714,7 +697,7 @@ class FrameWidgets(ttk.Frame): text=True, ) - Create.encrypt() + CryptoUtil.encrypt() else: shutil.copy(filepath, f"{AppConfig.TEMP_DIR}/") @@ -737,7 +720,7 @@ class FrameWidgets(ttk.Frame): text=True, ) - Create.encrypt() + CryptoUtil.encrypt() self.str_var.set("") self.a = Tunnel.active() self.l_box.insert(0, self.a) @@ -764,7 +747,7 @@ class FrameWidgets(ttk.Frame): self.str_var.set(self.a) self.color_label() self.stop() - data = self.handle_tunnel_data(self.a) + self.handle_tunnel_data(self.a, self.tl) process: CompletedProcess[str] = subprocess.run( [ "nmcli", @@ -1053,7 +1036,7 @@ class FrameWidgets(ttk.Frame): theme_set5.writelines(lines5) self.autoconnect_var.set(value=new_a_connect) self.update_connection_display() - Create.encrypt() + CryptoUtil.encrypt() except IndexError: @@ -1070,18 +1053,17 @@ class FrameWidgets(ttk.Frame): 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 - """ + 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: ")}{data[0]}") + self.add.set(f" Address: {values['Address']}") self.DNS = tk.StringVar() - self.DNS.set(f" DNS: {data[1]}") + self.DNS.set(f" DNS: {values['DNS']}") self.enp = tk.StringVar() - self.enp.set(f"{_("Endpoint: ")}{data[2]}") + self.enp.set(f"Endpoint: {values['Endpoint']}") def show_data(self) -> None: """ @@ -1118,10 +1100,8 @@ class FrameWidgets(ttk.Frame): else: - data = self.handle_tunnel_data(self.a) - if data: - - self.handle_connection_state("stop") + self.handle_tunnel_data(self.a, self.tl) + self.handle_connection_state("stop") except IndexError: @@ -1167,8 +1147,7 @@ class FrameWidgets(ttk.Frame): ["nmcli", "connection", "up", target_tunnel] ) self.update_connection_display() - data = self.handle_tunnel_data(self.a) - self.init_and_report(data) + self.handle_tunnel_data(self.a, self.tl) self.show_data() self.color_label() self.stop() diff --git a/wp_app_config.py b/wp_app_config.py index b9d07e5..8f34591 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -57,13 +57,6 @@ class AppConfig: "pkey_path": "/usr/local/etc/ssl/pwgk.pem", } - # Lists of searches - DIRECTORYS: list[str] = [ - "/etc/netplan/", - "/etc/NetworkManager/system-connections/", - "/var/lib/NetworkManager/user-connections/", - ] - # Images and icons paths IMAGE_PATHS: Dict[str, str] = { "icon_vpn": "/usr/share/icons/lx-icons/48/wg_vpn.png", From 55f2119bc37707771ac275a67ad05bd5f674f158 Mon Sep 17 00:00:00 2001 From: punix Date: Mon, 19 May 2025 21:35:14 +0200 Subject: [PATCH 54/61] conversion to app and configmanager part 2 export still missing --- common_tools.py | 18 +-- start_wg.py | 11 +- wirepy.py | 391 +++++++++++++++++++++++------------------------ wp_app_config.py | 9 +- 4 files changed, 206 insertions(+), 223 deletions(-) diff --git a/common_tools.py b/common_tools.py index e6583f5..0b14624 100755 --- a/common_tools.py +++ b/common_tools.py @@ -10,7 +10,7 @@ from subprocess import CompletedProcess, run import re import sys import tkinter as tk -from typing import Optional, Dict, Any, NoReturn, TextIO, Tuple, List +from typing import Optional, Dict, Any, NoReturn, List import zipfile from datetime import datetime from pathlib import Path @@ -34,6 +34,9 @@ class CryptoUtil: """ Starts SSL dencrypt """ + crypted_tunnel = [str(file) for file in AppConfig.CONFIG_DIR.glob("*.dat")] + if crypted_tunnel == []: + return process: CompletedProcess[str] = subprocess.run( ["pkexec", "/usr/local/bin/ssl_decrypt.py"], capture_output=True, @@ -41,10 +44,6 @@ class CryptoUtil: check=False, ) - # Output from Openssl - # if process.stdout: - # print(process.stdout) - # Output from Openssl Error if process.stderr: print(process.stderr) @@ -404,7 +403,7 @@ class Tunnel: @staticmethod def parse_files_to_dictionary( - directory: Path = None, filepath: str = None + directory: Path = None, filepath: str = None, content: str = None ) -> dict | str | None: data = {} @@ -453,8 +452,7 @@ class Tunnel: content = secrets.token_bytes(len(content)) - except StopIteration as e: - print(f"Error: {e}") + except StopIteration: pass elif directory is not None: @@ -500,8 +498,8 @@ class Tunnel: except Exception: # Ignore errors and continue to the next file continue - - content = secrets.token_bytes(len(content)) + if content is not None: + content = secrets.token_bytes(len(content)) if filepath is not None: return data, truncated_stem else: diff --git a/start_wg.py b/start_wg.py index 20ca206..4842df3 100755 --- a/start_wg.py +++ b/start_wg.py @@ -3,17 +3,16 @@ This script belongs to wirepy and is for the auto start of the tunnel """ -from pathlib import Path import subprocess from subprocess import CompletedProcess +from wp_app_config import AppConfig +from common_tools import ConfigManager -path_to_file = Path(Path.home() / ".config/wire_py/settings") +ConfigManager.init(AppConfig.SETTINGS_FILE) -a_con = Path(path_to_file).read_text(encoding="utf-8").splitlines(keepends=True) -a_con = a_con[7].strip() -if a_con != "off": +if ConfigManager.get("autostart") != "off": process: CompletedProcess[str] = subprocess.run( - ["nmcli", "connection", "up", a_con], + ["nmcli", "connection", "up", ConfigManager.get("autostart")], capture_output=True, text=True, check=False, diff --git a/wirepy.py b/wirepy.py index 98da4c1..2e75505 100755 --- a/wirepy.py +++ b/wirepy.py @@ -4,7 +4,6 @@ this script is a simple GUI for managing Wireguard Tunnels """ import getpass -import os import shutil import subprocess import sys @@ -243,7 +242,6 @@ class FrameWidgets(ttk.Frame): # 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) @@ -638,137 +636,133 @@ class FrameWidgets(ttk.Frame): try: filepath = filedialog.askopenfilename( initialdir=f"{Path.home()}", - title=_("Select Wireguard config File"), - filetypes=[(_("WG config files"), "*.conf")], + title="Select Wireguard config File", + filetypes=[("WG config files", "*.conf")], ) - # Check if a file was selected - if not filepath: - return + data_import, key_name = Tunnel.parse_files_to_dictionary(filepath=filepath) - filepath = Path(filepath) - read = filepath.read_text(encoding="utf-8") - - if ( - "PrivateKey = " in read - and "PublicKey = " in read - and "Endpoint = " in read - ): - key = Tunnel.con_to_dict(read) - 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 != "": - process: CompletedProcess[str] = subprocess.run( - ["nmcli", "connection", "down", self.a] - ) - self.reset_fields() - - process: CompletedProcess[str] = subprocess.run( - [ - "nmcli", - "connection", - "import", - "type", - "wireguard", - "file", - new_conf, - ], - text=True, - ) - - CryptoUtil.encrypt() - else: - shutil.copy(filepath, f"{AppConfig.TEMP_DIR}/") - - if self.a != "": - process: CompletedProcess[str] = subprocess.run( - ["nmcli", "connection", "down", self.a] - ) - self.reset_fields() - - process: CompletedProcess[str] = subprocess.run( - [ - "nmcli", - "connection", - "import", - "type", - "wireguard", - "file", - filepath, - ], - text=True, - ) - - CryptoUtil.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() - self.handle_tunnel_data(self.a, self.tl) - process: CompletedProcess[str] = subprocess.run( - [ - "nmcli", - "con", - "mod", - self.a, - "connection.autoconnect", - "no", - ] - ) - - elif ("PrivateKey = " in read) and ("Endpoint = " in read): - pass - else: + 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["no_valid_file"], + 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: @@ -785,37 +779,30 @@ 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: - key = Tunnel.con_to_dict(file2) - pre_key = key[3] + process: CompletedProcess[str] = subprocess.run( - ["nmcli", "connection", "delete", select_tl] + ["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]) - 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) + 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 @@ -878,22 +865,18 @@ 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) - ) - if lines[7] != "off\n": - print(f"{lines[7]} starts automatically when the system starts.") + 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 = lines[7] + self.auto_con = ConfigManager.get("autostart") 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) @@ -923,31 +906,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" - ) + ConfigManager.set("autostart", "off") - tl = LxTools.get_file_name(AppConfig.TEMP_DIR) + 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: - 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" - ) + ConfigManager.set("autostart", select_tl) except IndexError: self.selected_option.set(1) @@ -1004,7 +971,7 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(self.select_tunnel[0]) # nmcli connection modify old connection.id iphone - subprocess.check_output( + process: CompletedProcess[str] = subprocess.run( [ "nmcli", "connection", @@ -1013,30 +980,28 @@ class FrameWidgets(ttk.Frame): "connection.id", self.lb_rename.get(), ], + capture_output=True, text=True, + check=False, ) - 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") + 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() - 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() - CryptoUtil.encrypt() except IndexError: @@ -1059,9 +1024,9 @@ class FrameWidgets(ttk.Frame): values = data[tunnel] # Address Label self.add = tk.StringVar() - self.add.set(f" Address: {values['Address']}") + self.add.set(f"Address: {values['Address']}") self.DNS = tk.StringVar() - self.DNS.set(f" DNS: {values['DNS']}") + self.DNS.set(f" DNS: {values['DNS']}") self.enp = tk.StringVar() self.enp.set(f"Endpoint: {values['Endpoint']}") @@ -1134,8 +1099,18 @@ class FrameWidgets(ttk.Frame): if action == "stop": if self.a: process: CompletedProcess[str] = subprocess.run( - ["nmcli", "connection", "down", self.a] + ["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() @@ -1144,8 +1119,20 @@ class FrameWidgets(ttk.Frame): if tunnel_name or self.a: target_tunnel = tunnel_name or self.a process: CompletedProcess[str] = subprocess.run( - ["nmcli", "connection", "up", target_tunnel] + ["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() diff --git a/wp_app_config.py b/wp_app_config.py index 8f34591..39e827f 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -24,7 +24,6 @@ class AppConfig: # Configuration files SETTINGS_FILE: Path = CONFIG_DIR / "settings" - KEYS_FILE: Path = CONFIG_DIR / "keys" SYSTEMD_USER_FOLDER: Path = Path.home() / ".config/systemd/user" AUTOSTART_SERVICE: Path = Path.home() / ".config/systemd/user/wg_start.service" DEFAULT_SETTINGS: Dict[str, str] = { @@ -89,10 +88,7 @@ class AppConfig: """Ensures that all required directories exist""" if not cls.CONFIG_DIR.exists(): cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True) - cls.KEYS_FILE.touch() cls.TEMP_DIR.mkdir(parents=True, exist_ok=True) - if not cls.KEYS_FILE.exists(): - cls.KEYS_FILE.touch() @classmethod def create_default_settings(cls) -> None: @@ -180,7 +176,7 @@ 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": _( + "false_signs": _( "No valid sign. These must not be used.\nBlank, Slash, Backslash and { }\n" ), "is_in_use": _("The tunnel is already in use"), @@ -188,6 +184,9 @@ class Msg: "Oh... no valid Wireguard File!\nPlease select a valid Wireguard File" ), "tl_exist": _("Tunnel already available!\nPlease use another file for import"), + "invalid_base64": _( + "Invalid base64 format!\nPlease use a Config file with valid key." + ), } TTIP: Dict[str, str] = { # Strings for Tooltips From 4cdcfadbac64266504f0acea35e5ee89cd0884a9 Mon Sep 17 00:00:00 2001 From: punix Date: Tue, 20 May 2025 12:31:30 +0200 Subject: [PATCH 55/61] class descriptions added redundancy reduced --- common_tools.py | 107 +++++++++++++++++++++++++++++++---------------- wirepy.py | 26 +++++++----- wp_app_config.py | 19 ++++++++- 3 files changed, 104 insertions(+), 48 deletions(-) diff --git a/common_tools.py b/common_tools.py index 0b14624..9eec161 100755 --- a/common_tools.py +++ b/common_tools.py @@ -6,7 +6,7 @@ import signal import base64 import secrets import subprocess -from subprocess import CompletedProcess, run +from subprocess import CompletedProcess import re import sys import tkinter as tk @@ -30,7 +30,7 @@ class CryptoUtil: """ @staticmethod - def decrypt() -> str: + def decrypt() -> None: """ Starts SSL dencrypt """ @@ -54,7 +54,7 @@ class CryptoUtil: print(f"Error process decrypt: Code {process.returncode}") @staticmethod - def encrypt() -> str: + def encrypt() -> None: """ Starts SSL encryption """ @@ -80,7 +80,7 @@ class CryptoUtil: Checks if the private key already exists in the system using an external script. Returns True only if the full key is found exactly (no partial match). """ - process: CompletedProcess[bool] = run( + process: CompletedProcess[bool] = subprocess.run( ["pkexec", "/usr/local/bin/match_found.py", key], capture_output=True, text=True, @@ -121,7 +121,7 @@ class CryptoUtil: return True -class LxTools(tk.Tk): +class LxTools: """ Class LinuxTools methods that can also be used for other apps """ @@ -183,7 +183,7 @@ class LxTools(tk.Tk): break if primary_info: - # Parse the geometry: WIDTHxHEIGHT+X+Y + # Parse the geometry: WIDTH x HEIGHT+X+Y geometry = primary_info.split("+") dimensions = geometry[0].split("x") primary_width = int(dimensions[0]) @@ -217,31 +217,6 @@ class LxTools(tk.Tk): 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]: - """ - 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 get_username() -> str: """ @@ -266,11 +241,42 @@ class LxTools(tk.Tk): @staticmethod def clean_files(TEMP_DIR: Path = None, file: Path = None) -> None: """ - method that can be added after need to delete a folder and a file when quitting. + Deletes temporary files and directories for cleanup when exiting the application. + + This method safely removes an optional directory defined by `AppConfig.TEMP_DIR` + and a single file to free up resources at the end of the program's execution. + All operations are performed securely, and errors such as `FileNotFoundError` + are ignored if the target files or directories do not exist. + Args: - :param file: default None - :param AppConfig.TEMP_DIR: default None + TEMP_DIR (Path, optional): Path to the temporary directory that should be deleted. + If `None`, the value of `AppConfig.TEMP_DIR` is used. + file (Path, optional): Path to the file that should be deleted. + If `None`, no additional file will be deleted. + + Returns: + None: The method does not return any value. + + Examples: + - Without parameters: + ```python + Tooltip.clean_files() + ``` + Deletes the temporary directory defined in the configuration (`AppConfig.TEMP_DIR`). + + - With an explicit file path: + ```python + Tooltip.clean_files(file=Path("temp/file.txt")) + ``` + Deletes the file `file.txt`, if it exists, and ignores errors. + + - Both parameters: + ```python + Tooltip.clean(TEMP_DIR=Path("/tmp/data"), file=Path("log.txt")) + ``` + Deletes the directory `/tmp/data` and the file `log.txt`, if they exist. """ + if AppConfig.TEMP_DIR is not None: shutil.rmtree(AppConfig.TEMP_DIR) try: @@ -597,8 +603,20 @@ class Tunnel: # ConfigManager with caching class ConfigManager: """ - Universal class for managing configuration files with caching. - Can be reused in different projects. + Universal class for managing configuration files with caching support. + + This class provides a general solution to load, save, and manage configuration + files across different projects. It uses a caching system to optimize access efficiency. + The `init()` method initializes the configuration file path, while `load()` and `save()` + synchronize data between the file and internal memory structures. + + Key Features: + - Caching to minimize I/O operations. + - Default values for missing or corrupted configuration files. + - Reusability across different projects and use cases. + + The class is designed for central application configuration management, working closely + with `ThemeManager` to dynamically manage themes or other settings. """ _config = None @@ -664,6 +682,23 @@ class ConfigManager: class ThemeManager: + """ + Class for central theme management and UI customization. + + This static class allows dynamic adjustment of the application's appearance. + The method `change_theme()` updates the current theme and saves + the selection in the configuration file via `ConfigManager`. + It ensures a consistent visual design across the entire project. + + Key Features: + - Central control over themes. + - Automatic saving of theme settings to the configuration file. + - Tight integration with `ConfigManager` for persistent storage of preferences. + + The class is designed to apply themes consistently throughout the application, + ensuring that changes are traceable and uniform across all parts of the project. + """ + @staticmethod def change_theme(root, theme_in_use, theme_name=None): """Change application theme centrally""" diff --git a/wirepy.py b/wirepy.py index 2e75505..930c394 100755 --- a/wirepy.py +++ b/wirepy.py @@ -244,7 +244,7 @@ class FrameWidgets(ttk.Frame): 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() @@ -475,7 +475,7 @@ class FrameWidgets(ttk.Frame): a tk.Toplevel window """ - def link_btn() -> str | None: + def link_btn() -> None: webbrowser.open("https://git.ilunix.de/punix/Wire-Py") msg_t = _( @@ -583,8 +583,7 @@ class FrameWidgets(ttk.Frame): ) 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: + if self.l_box.size() == 0: Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state) else: Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tooltip_state) @@ -908,9 +907,7 @@ class FrameWidgets(ttk.Frame): 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: + if self.l_box.size() == 0: self.wg_autostart.configure(state="disabled") if self.selected_option.get() >= 1: @@ -923,9 +920,12 @@ class FrameWidgets(ttk.Frame): def tl_rename(self) -> None: """ - method to rename a tunnel + Method to rename a tunnel. Validates input for length, + special characters, and duplicate names, + performs the renaming via `nmcli` if valid, updates + the configuration file in the directory, + and adjusts UI elements such as listboxes and labels. """ - name_of_file = LxTools.get_file_name(AppConfig.TEMP_DIR) special_characters = ["\\", "/", "{", "}", " "] if len(self.lb_rename.get()) > 12: @@ -955,7 +955,9 @@ class FrameWidgets(ttk.Frame): Msg.STR["false_signs"], ) - elif self.lb_rename.get() in name_of_file: + elif self.lb_rename.get() in [ + file.stem for file in AppConfig.CONFIG_DIR.glob("*.dat") + ]: LxTools.msg_window( AppConfig.IMAGE_PATHS["icon_info"], @@ -1019,7 +1021,9 @@ class FrameWidgets(ttk.Frame): print(e) def handle_tunnel_data(self, active=None, data=None) -> None: - + """Processes tunnel data from an active connection and updates + UI elements like labels with information about address, DNS, and endpoint. + """ tunnel = active values = data[tunnel] # Address Label diff --git a/wp_app_config.py b/wp_app_config.py index 39e827f..18fb3df 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -9,7 +9,24 @@ from typing import Dict, Any class AppConfig: - """Central configuration class for Wire-Py application""" + """Central configuration and system setup manager for the Wire-Py application. + + This class serves as a singleton-like container for all global configuration data, + including paths, UI settings, localization, versioning, and system-specific resources. + It ensures that required directories, files, and services are created and configured + before the application starts. Additionally, it provides tools for managing translations, + default settings, and autostart functionality to maintain a consistent user experience. + + Key Responsibilities: + - Centralizes all configuration values (paths, UI preferences, localization). + - Ensures required directories and files exist on startup. + - Handles translation setup via `gettext` for multilingual support. + - Manages default settings file generation. + - Configures autostart services using systemd for user-specific launch behavior. + + This class is used globally across the application to access configuration data + consistently and perform system-level setup tasks. + """ # Localization APP_NAME: str = "wirepy" From 5ac37ad9ad63282a1b91b42b3e3fbe2018a11818 Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 21 May 2025 21:29:21 +0200 Subject: [PATCH 56/61] remove USER_FILE usage in ssl_decrypt.py and ssl_encrypt.py; switch to argparse for command-line arguments --- common_tools.py | 50 ++++++++++++++++++++++++------------------------ match_found.py | 3 +-- ssl_decrypt.py | 33 +++++++++++++++++++++----------- ssl_encrypt.py | 29 ++++++++++++++++++++-------- start_wg.py | 5 ++--- wirepy.py | 49 +++++++++++++++++++++++++++++++---------------- wp_app_config.py | 7 ++----- 7 files changed, 106 insertions(+), 70 deletions(-) diff --git a/common_tools.py b/common_tools.py index 9eec161..bbd1b2d 100755 --- a/common_tools.py +++ b/common_tools.py @@ -6,7 +6,7 @@ import signal import base64 import secrets import subprocess -from subprocess import CompletedProcess +from subprocess import CompletedProcess, run import re import sys import tkinter as tk @@ -30,36 +30,36 @@ class CryptoUtil: """ @staticmethod - def decrypt() -> None: + def decrypt(user) -> None: """ Starts SSL dencrypt """ - crypted_tunnel = [str(file) for file in AppConfig.CONFIG_DIR.glob("*.dat")] - if crypted_tunnel == []: - return - process: CompletedProcess[str] = subprocess.run( - ["pkexec", "/usr/local/bin/ssl_decrypt.py"], - capture_output=True, - text=True, - check=False, - ) - - # Output from Openssl Error - if process.stderr: - print(process.stderr) - - if process.returncode == 0: - print("Files successfully decrypted...") + if len([file.stem for file in AppConfig.CONFIG_DIR.glob("*.dat")]) == 0: + pass else: - print(f"Error process decrypt: Code {process.returncode}") + process: CompletedProcess[str] = run( + ["pkexec", "/usr/local/bin/ssl_decrypt.py", "--user", user], + capture_output=True, + text=True, + check=False, + ) + + # Output from Openssl Error + if process.stderr: + print(process.stderr) + + if process.returncode == 0: + print("Files successfully decrypted...") + else: + print(f"Error process decrypt: Code {process.returncode}") @staticmethod - def encrypt() -> None: + def encrypt(user) -> None: """ Starts SSL encryption """ - process: CompletedProcess[str] = subprocess.run( - ["pkexec", "/usr/local/bin/ssl_encrypt.py"], + process: CompletedProcess[str] = run( + ["pkexec", "/usr/local/bin/ssl_encrypt.py", "--user", user], capture_output=True, text=True, check=False, @@ -80,7 +80,7 @@ class CryptoUtil: Checks if the private key already exists in the system using an external script. Returns True only if the full key is found exactly (no partial match). """ - process: CompletedProcess[bool] = subprocess.run( + process: CompletedProcess[bool] = run( ["pkexec", "/usr/local/bin/match_found.py", key], capture_output=True, text=True, @@ -224,9 +224,9 @@ class LxTools: even if the script is running with root privileges. """ try: - result = subprocess.run( + result = run( ["logname"], - stdout=subprocess.PIPE, + stdout=PIPE, text=True, check=True, ) diff --git a/match_found.py b/match_found.py index b69ea65..1a73de3 100755 --- a/match_found.py +++ b/match_found.py @@ -42,11 +42,10 @@ def search_string_in_directory( except Exception: continue # Skip files that cause errors - # Invert the logic: return False if string is found, True otherwise return result -def main() -> bool: +def main() -> None: parser = argparse.ArgumentParser( description="Script only for use to compare the private key in the Network configurations to avoid errors with the network manager." ) diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 03e4525..0439c79 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -1,19 +1,30 @@ #!/usr/bin/python3 """ This Script decrypt Wireguard files for Wirepy users """ - +import argparse from pathlib import Path +import pwd import shutil -from subprocess import CompletedProcess -import subprocess +from subprocess import CompletedProcess, run from wp_app_config import AppConfig -log_name = AppConfig.USER_FILE.read_text().strip() +parser = argparse.ArgumentParser() +parser.add_argument("--user", required=True, help="Username of the target file system") +args = parser.parse_args() -keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") -path_of_crypted_tunnel: Path = Path(f"/home/{log_name}/.config/wire_py") +try: + # Retrieve UID and GID + user_info = pwd.getpwnam(args.user) + uid = user_info.pw_uid # User ID (e.g., 1000) + gid = user_info.pw_gid # Group ID (e.g., 1000) +except KeyError: + print(f"User '{args.user}' not found.") + exit(1) + +keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem") +path_of_crypted_tunnel: Path = Path(f"/home/{args.user}/.config/wire_py") if not keyfile.is_file(): - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( [ "openssl", "rsa", @@ -34,9 +45,9 @@ if not keyfile.is_file(): print("Public key generated successfully.") else: print(f"Error with the following code... {process.returncode}") - shutil.chown(keyfile, 1000, 1000) + shutil.chown(keyfile, uid, gid) -if AppConfig.PUBLICKEY.exists: +if AppConfig.PUBLICKEY.exists(): crypted__tunnel = [str(file) for file in path_of_crypted_tunnel.glob("*.dat")] @@ -44,7 +55,7 @@ if AppConfig.PUBLICKEY.exists: base_name = Path(tunnel_path).stem - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( [ "openssl", "pkeyutl", @@ -60,7 +71,7 @@ if AppConfig.PUBLICKEY.exists: text=True, check=False, ) - shutil.chown(f"{AppConfig.TEMP_DIR}/{base_name}.conf", 1000, 1000) + shutil.chown(f"{AppConfig.TEMP_DIR}/{base_name}.conf", uid, gid) print(f"Processing of the file: {tunnel_path}") if process.stdout: diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 7147140..537572b 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -1,20 +1,33 @@ #!/usr/bin/python3 """ This Script encrypt Wireguardfiles for Wirepy users for more Security """ + +import argparse from pathlib import Path +import pwd import shutil -import subprocess -from subprocess import CompletedProcess +from subprocess import CompletedProcess, run from wp_app_config import AppConfig -log_name = AppConfig.USER_FILE.read_text().strip() +parser = argparse.ArgumentParser() +parser.add_argument("--user", required=True, help="Username of the target file system") +args = parser.parse_args() -keyfile: Path = Path(f"/home/{log_name}/.config/wire_py/pbwgk.pem") +try: + # Retrieve UID and GID + user_info = pwd.getpwnam(args.user) + uid = user_info.pw_uid # User ID (e.g., 1000) + gid = user_info.pw_gid # Group ID (e.g., 1000) +except KeyError: + print(f"User '{args.user}' not found.") + exit(1) -target: Path = Path(f"/home/{log_name}/.config/wire_py/") +keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem") + +target: Path = Path(f"/home/{args.user}/.config/wire_py/") if not keyfile.is_file(): - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( [ "openssl", "rsa", @@ -43,7 +56,7 @@ if not keyfile.is_file(): else: print(f"Error generate Publickey: Code: {process.returncode}") - shutil.chown(keyfile, 1000, 1000) + shutil.chown(keyfile, uid, gid) # any() get True when directory is not empty if AppConfig.TEMP_DIR.exists() and any(AppConfig.TEMP_DIR.iterdir()): @@ -51,7 +64,7 @@ if AppConfig.TEMP_DIR.exists() and any(AppConfig.TEMP_DIR.iterdir()): for config_file in clear_files: base_name = Path(config_file).stem - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( [ "openssl", "pkeyutl", diff --git a/start_wg.py b/start_wg.py index 4842df3..e4a755d 100755 --- a/start_wg.py +++ b/start_wg.py @@ -3,15 +3,14 @@ This script belongs to wirepy and is for the auto start of the tunnel """ -import subprocess -from subprocess import CompletedProcess +from subprocess import CompletedProcess, run from wp_app_config import AppConfig from common_tools import ConfigManager ConfigManager.init(AppConfig.SETTINGS_FILE) if ConfigManager.get("autostart") != "off": - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( ["nmcli", "connection", "up", ConfigManager.get("autostart")], capture_output=True, text=True, diff --git a/wirepy.py b/wirepy.py index 930c394..4071ad2 100755 --- a/wirepy.py +++ b/wirepy.py @@ -10,7 +10,7 @@ import sys import tkinter as tk import webbrowser from pathlib import Path -from subprocess import CompletedProcess +from subprocess import CompletedProcess, run from tkinter import TclError, filedialog, ttk from common_tools import ( @@ -24,10 +24,9 @@ from common_tools import ( ) from wp_app_config import AppConfig, Msg -AppConfig.USER_FILE.write_text(getpass.getuser()) AppConfig.ensure_directories() AppConfig.create_default_settings() -CryptoUtil.decrypt() +CryptoUtil.decrypt(getpass.getuser()) class Wirepy(tk.Tk): @@ -538,7 +537,13 @@ class FrameWidgets(ttk.Frame): self.tooltip_label.set(_("Enable Tooltips")) def tooltips_toggle(self): - """Toggles tooltips on/off and updates the menu label""" + """ + Toggles the visibility of tooltips (on/off) and updates + the corresponding menu label. Inverts the current tooltip state + (`self.tooltip_state`), saves the new value in the configuration, + and applies the change immediately. Updates the menu entry's label to + reflect the new tooltip status (e.g., "Tooltips: On" or "Tooltips: Off"). + """ # Toggle the boolean state new_bool_state = not self.tooltip_state.get() # Save the converted value in the configuration @@ -674,7 +679,7 @@ class FrameWidgets(ttk.Frame): self.tl.update(data_import) if self.a != "": - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( ["nmcli", "connection", "down", self.a], capture_output=True, text=True, @@ -688,7 +693,7 @@ class FrameWidgets(ttk.Frame): print(f"Error process decrypt: Code {process.returncode}") self.reset_fields() - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( [ "nmcli", "connection", @@ -711,7 +716,7 @@ class FrameWidgets(ttk.Frame): else: print(f"Error process decrypt: Code {process.returncode}") - CryptoUtil.encrypt() + CryptoUtil.encrypt(getpass.getuser()) LxTools.clean_files(AppConfig.TEMP_DIR, file=None) AppConfig.ensure_directories() self.str_var.set("") @@ -740,7 +745,7 @@ class FrameWidgets(ttk.Frame): self.stop() self.handle_tunnel_data(self.a, self.tl) self.show_data() - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( ["nmcli", "con", "mod", self.a, "connection.autoconnect", "no"], capture_output=True, text=True, @@ -779,7 +784,7 @@ class FrameWidgets(ttk.Frame): self.select_tunnel = self.l_box.curselection() select_tl = self.l_box.get(self.select_tunnel[0]) - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( ["nmcli", "connection", "delete", select_tl], capture_output=True, text=True, @@ -973,7 +978,7 @@ class FrameWidgets(ttk.Frame): select_tl = self.l_box.get(self.select_tunnel[0]) # nmcli connection modify old connection.id iphone - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( [ "nmcli", "connection", @@ -1036,7 +1041,13 @@ class FrameWidgets(ttk.Frame): def show_data(self) -> None: """ - shows data in the label + Displays network-related data (address, DNS, endpoint) + in the UI using ttk.Label widgets. + Creates three labels for address, DNS, and endpoint with + specific styling (color, font), positioning them in a + grid layout (`lb_frame` and `lb_frame2`). + Each label is linked to a corresponding text variable + (`self.add`, `self.DNS`, `self.enp`) for dynamic data updates. """ # Address Label self.address = ttk.Label( @@ -1059,7 +1070,13 @@ class FrameWidgets(ttk.Frame): def wg_switch(self, event=None) -> None: """ - Deals with switching the VPN connection + Manages switching between active and inactiveVPN connections. + If no tunnel is selected (`self.a == ""`), it starts a new connection + with the selected tunnel from the listbox (`l_box`). + Otherwise, it stops the current connection and updates + tunnel data using `handle_tunnel_data`. + Handles errors like `IndexError` by displaying appropriate + messages if no items are selected or the listbox is empty. """ try: if self.a == "": @@ -1102,7 +1119,7 @@ class FrameWidgets(ttk.Frame): """ if action == "stop": if self.a: - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( ["nmcli", "connection", "down", self.a], capture_output=True, text=True, @@ -1122,7 +1139,7 @@ class FrameWidgets(ttk.Frame): elif action == "start": if tunnel_name or self.a: target_tunnel = tunnel_name or self.a - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( ["nmcli", "connection", "up", target_tunnel], capture_output=True, text=True, @@ -1164,7 +1181,7 @@ class FrameWidgets(ttk.Frame): if __name__ == "__main__": _ = AppConfig.setup_translations() - LxTools.sigi(AppConfig.TEMP_DIR, AppConfig.USER_FILE) + LxTools.sigi(AppConfig.TEMP_DIR) window = Wirepy() """ the hidden files are hidden in Filedialog @@ -1177,5 +1194,5 @@ if __name__ == "__main__": window.tk.call("set", "::tk::dialog::file::showHiddenVar", "0") window.mainloop() -LxTools.clean_files(AppConfig.TEMP_DIR, AppConfig.USER_FILE) +LxTools.clean_files(AppConfig.TEMP_DIR) sys.exit(0) diff --git a/wp_app_config.py b/wp_app_config.py index 18fb3df..7719a11 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -4,7 +4,7 @@ import gettext import locale from pathlib import Path -import subprocess +from subprocess import CompletedProcess, run from typing import Dict, Any @@ -36,7 +36,6 @@ class AppConfig: 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") PUBLICKEY: Path = CONFIG_DIR / "pbwgk.pem" # Configuration files @@ -135,14 +134,12 @@ class AppConfig: if not cls.SYSTEMD_USER_FOLDER.exists(): cls.SYSTEMD_USER_FOLDER.mkdir(parents=True, exist_ok=True) - from subprocess import CompletedProcess - if not cls.AUTOSTART_SERVICE.is_file(): content = "\n".join([line for line in SYSTEMD_FILE]) cls.AUTOSTART_SERVICE.write_text(content) - process: CompletedProcess[str] = subprocess.run( + process: CompletedProcess[str] = run( ["systemctl", "--user", "enable", "wg_start.service"], capture_output=True, text=True, From 79f6fc0265bad5f55276ab64f5c197a5a61a50bf Mon Sep 17 00:00:00 2001 From: punix Date: Fri, 23 May 2025 12:36:28 +0200 Subject: [PATCH 57/61] finish logging --- common_tools.py | 48 +++++++++++++++++++++--------------- ssl_decrypt.py | 27 +++++++++------------ ssl_encrypt.py | 23 +++++------------- start_wg.py | 4 +-- wirepy.py | 63 +++++++++++++++--------------------------------- wp_app_config.py | 21 +++++++++++----- 6 files changed, 83 insertions(+), 103 deletions(-) diff --git a/common_tools.py b/common_tools.py index bbd1b2d..7650fae 100755 --- a/common_tools.py +++ b/common_tools.py @@ -15,7 +15,7 @@ import zipfile from datetime import datetime from pathlib import Path from tkinter import ttk, Toplevel -from wp_app_config import AppConfig, Msg +from wp_app_config import AppConfig, Msg, logging import requests # Translate @@ -46,12 +46,14 @@ class CryptoUtil: # Output from Openssl Error if process.stderr: - print(process.stderr) + logging.error(process.stderr, exc_info=True) if process.returncode == 0: - print("Files successfully decrypted...") + logging.info("Files successfully decrypted...", exc_info=True) else: - print(f"Error process decrypt: Code {process.returncode}") + logging.error( + f"Error process decrypt: Code {process.returncode}", exc_info=True + ) @staticmethod def encrypt(user) -> None: @@ -67,12 +69,14 @@ class CryptoUtil: # Output from Openssl Error if process.stderr: - print(process.stderr) + logging.error(process.stderr, exc_info=True) if process.returncode == 0: - print("Files successfully encrypted...") + logging.info("Files successfully encrypted...", exc_info=True) else: - print(f"Error process encrypt: Code {process.returncode}") + logging.error( + f"Error process encrypt: Code {process.returncode}", exc_info=True + ) @staticmethod def find_key(key: str = "") -> bool: @@ -90,8 +94,9 @@ class CryptoUtil: return True elif "False" in process.stdout: return False - print( - f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}" + logging.error( + f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}", + exc_info=True, ) return False @@ -310,13 +315,13 @@ class LxTools: msg.title(w_title) msg.configure(pady=15, padx=15) - # Lade das erste Bild für das Fenster + # load first image for window 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") + logging.error(f"Error on load Window Image: {e}", exc_info=True) + msg.i_window = tk.Label(msg, text="Image not found") label: tk.Label = tk.Label(msg, text=w_txt) label.grid(column=1, row=0) @@ -340,12 +345,11 @@ class LxTools: ) button.grid(column=0, columnspan=2, row=1) - # 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}") + logging.error(f"Error loading the window icon: {e}", exc_info=True) msg.columnconfigure(0, weight=1) msg.rowconfigure(0, weight=1) @@ -385,16 +389,17 @@ class LxTools: # 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}." + logging.error( + f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.", + exc_info=True, ) LxTools.clean_files(file_path, file) - print("Breakdown by user...") + logging.info("Breakdown by user...") sys.exit(exit_code) else: - print(f"Signal {signum} received and ignored.") + logging.info(f"Signal {signum} received and ignored.") LxTools.clean_files(file_path, file) - print("Process unexpectedly ended...") + logging.error("Process unexpectedly ended...") # Register signal handlers for various signals signal.signal(signal.SIGINT, signal_handler) @@ -464,7 +469,10 @@ class Tunnel: elif directory is not None: if not directory.exists() or not directory.is_dir(): - print("Temp directory does not exist or is not a directory.") + logging.error( + "Temp directory does not exist or is not a directory.", + exc_info=True, + ) return None # Get a list of all files in the directory diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 0439c79..4ce87c2 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -5,7 +5,7 @@ from pathlib import Path import pwd import shutil from subprocess import CompletedProcess, run -from wp_app_config import AppConfig +from wp_app_config import AppConfig, logging parser = argparse.ArgumentParser() parser.add_argument("--user", required=True, help="Username of the target file system") @@ -17,7 +17,7 @@ try: uid = user_info.pw_uid # User ID (e.g., 1000) gid = user_info.pw_gid # Group ID (e.g., 1000) except KeyError: - print(f"User '{args.user}' not found.") + logging.error(f"User '{args.user}' not found.", exc_info=True) exit(1) keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem") @@ -40,11 +40,13 @@ if not keyfile.is_file(): text=True, check=False, ) - print(process.stdout) + if process.returncode == 0: - print("Public key generated successfully.") + logging.info("Public key generated successfully.", exc_info=True) else: - print(f"Error with the following code... {process.returncode}") + logging.error( + f"Error with the following code... {process.returncode}", exc_info=True + ) shutil.chown(keyfile, uid, gid) if AppConfig.PUBLICKEY.exists(): @@ -72,16 +74,11 @@ if AppConfig.PUBLICKEY.exists(): check=False, ) shutil.chown(f"{AppConfig.TEMP_DIR}/{base_name}.conf", uid, gid) - print(f"Processing of the file: {tunnel_path}") - - if process.stdout: - print(process.stdout) + logging.info(f"Processing of the file: {tunnel_path}", exc_info=True) # Output from Openssl Error if process.stderr: - print("(Error):", process.stderr) - - if process.returncode == 0: - print(f"File {base_name}.dat successfully decrypted.") - else: - print(f"Error by {tunnel_path}: Code: {process.returncode}") + logging.error( + f"{process.stderr} Error by [{tunnel_path}] Code: {process.returncode}", + exc_info=True, + ) diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 537572b..191e0a2 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -2,11 +2,12 @@ """ This Script encrypt Wireguardfiles for Wirepy users for more Security """ import argparse +import logging from pathlib import Path import pwd import shutil from subprocess import CompletedProcess, run -from wp_app_config import AppConfig +from wp_app_config import AppConfig, logging parser = argparse.ArgumentParser() parser.add_argument("--user", required=True, help="Username of the target file system") @@ -18,7 +19,7 @@ try: uid = user_info.pw_uid # User ID (e.g., 1000) gid = user_info.pw_gid # Group ID (e.g., 1000) except KeyError: - print(f"User '{args.user}' not found.") + logging.error(f"User '{args.user}' not found.", exc_info=True) exit(1) keyfile: Path = Path(f"/home/{args.user}/.config/wire_py/pbwgk.pem") @@ -44,17 +45,12 @@ if not keyfile.is_file(): check=False, ) - if process.stdout: - print(process.stdout) - # Output from Openssl Error if process.stderr: - print("(Error):", process.stderr) + logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True) if process.returncode == 0: - print("Public key generated successfully.") - else: - print(f"Error generate Publickey: Code: {process.returncode}") + logging.info("Public key generated successfully.", exc_info=True) shutil.chown(keyfile, uid, gid) @@ -82,13 +78,6 @@ if AppConfig.TEMP_DIR.exists() and any(AppConfig.TEMP_DIR.iterdir()): check=False, ) - print(f"Processing of the file: {config_file}") - # Output from Openssl Error if process.stderr: - print("(Error):", process.stderr) - - if process.returncode == 0: - print(f"File {base_name}.dat successfully encrypted.") - else: - print(f"Error by {config_file}: Code: {process.returncode}") + logging.error(process.stderr, exc_info=True) diff --git a/start_wg.py b/start_wg.py index e4a755d..7583eec 100755 --- a/start_wg.py +++ b/start_wg.py @@ -4,7 +4,7 @@ """ from subprocess import CompletedProcess, run -from wp_app_config import AppConfig +from wp_app_config import AppConfig, logging from common_tools import ConfigManager ConfigManager.init(AppConfig.SETTINGS_FILE) @@ -18,7 +18,7 @@ if ConfigManager.get("autostart") != "off": ) # Output from start_wg error if process.stderr: - print(process.stderr) # this is for the error, later on logfile + logging.error(process.stderr, exc_info=True) else: pass diff --git a/wirepy.py b/wirepy.py index 4071ad2..bbacf24 100755 --- a/wirepy.py +++ b/wirepy.py @@ -5,7 +5,6 @@ this script is a simple GUI for managing Wireguard Tunnels import getpass import shutil -import subprocess import sys import tkinter as tk import webbrowser @@ -22,7 +21,7 @@ from common_tools import ( Tooltip, LxTools, ) -from wp_app_config import AppConfig, Msg +from wp_app_config import AppConfig, Msg, logging AppConfig.ensure_directories() AppConfig.create_default_settings() @@ -522,7 +521,7 @@ class FrameWidgets(ttk.Frame): # 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}") + logging.error(f"Error checking for updates: {e}", exc_info=True) # Fallback to a default message if there's an error self.update_ui_for_update("No Internet Connection!") @@ -663,7 +662,6 @@ class FrameWidgets(ttk.Frame): 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 @@ -687,10 +685,8 @@ class FrameWidgets(ttk.Frame): ) if process.stderr: - print(process.stderr) + logging.error(f"{process.stderr}: Code {process.returncode}") - else: - print(f"Error process decrypt: Code {process.returncode}") self.reset_fields() process: CompletedProcess[str] = run( @@ -709,12 +705,9 @@ class FrameWidgets(ttk.Frame): ) 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}") + logging.error( + f"{process.stderr} Code: {process.returncode}", exc_info=True + ) CryptoUtil.encrypt(getpass.getuser()) LxTools.clean_files(AppConfig.TEMP_DIR, file=None) @@ -753,7 +746,7 @@ class FrameWidgets(ttk.Frame): ) if process.stderr: - print(process.stderr) + logging.error(process.stderr, exc_info=True) if process.returncode == 0: print(f">> {import_file.stem} << autostart is disabled by default") @@ -773,8 +766,6 @@ class FrameWidgets(ttk.Frame): print("File import: abort by user...") except FileNotFoundError: print("File import: abort by user...") - except subprocess.CalledProcessError: - print("Tunnel exist!") def delete(self) -> None: """ @@ -792,12 +783,9 @@ class FrameWidgets(ttk.Frame): ) 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}") + logging.error( + f"{process.stderr} Code: {process.returncode}", exc_info=True + ) self.l_box.delete(self.select_tunnel[0]) Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat") @@ -871,9 +859,6 @@ class FrameWidgets(ttk.Frame): """ 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") @@ -992,10 +977,9 @@ class FrameWidgets(ttk.Frame): check=False, ) if process.stderr: - print(process.stderr) - - if process.returncode != 0: - print(f"Error process: Code {process.returncode}") + logging.error( + f"{process.stderr} Code: {process.returncode}", exc_info=True + ) source = Path(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat") destination = AppConfig.CONFIG_DIR / f"{self.lb_rename.get()}.dat" @@ -1019,11 +1003,8 @@ class FrameWidgets(ttk.Frame): Msg.STR["sel_list"], ) - except subprocess.CalledProcessError: - pass - except EOFError as e: - print(e) + logging.error(e, exc_info=True) def handle_tunnel_data(self, active=None, data=None) -> None: """Processes tunnel data from an active connection and updates @@ -1127,10 +1108,9 @@ class FrameWidgets(ttk.Frame): ) if process.stderr: - print(process.stderr) - - if process.returncode != 0: - print(f"Error process: Code {process.returncode}") + logging.error( + f"{process.stderr} Code: {process.returncode}", exc_info=True + ) self.update_connection_display() self.reset_fields() @@ -1147,12 +1127,9 @@ class FrameWidgets(ttk.Frame): ) if process.stderr: - print(process.stderr) - - if process.returncode == 0: - print(f"Tunnel >> {target_tunnel} << started") - else: - print(f"Error process: Code {process.returncode}") + logging.error( + f"{process.stderr} Code: {process.returncode}", exc_info=True + ) self.update_connection_display() self.handle_tunnel_data(self.a, self.tl) diff --git a/wp_app_config.py b/wp_app_config.py index 7719a11..9f88244 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 """App configuration for Wire-Py""" - +import logging import gettext import locale from pathlib import Path @@ -28,6 +28,17 @@ class AppConfig: consistently and perform system-level setup tasks. """ + # Logging + LOG_DIR = Path.home() / ".local/share/wirepy" + Path(LOG_DIR).mkdir(parents=True, exist_ok=True) + LOG_FILE_PATH = LOG_DIR / "wirepy.log" + + logging.basicConfig( + filename=f"{LOG_FILE_PATH}", + level=logging.ERROR, + format="%(asctime)s - %(levelname)s - %(message)s", + ) + # Localization APP_NAME: str = "wirepy" LOCALE_DIR: Path = Path("/usr/share/locale/") @@ -145,11 +156,9 @@ class AppConfig: text=True, check=False, ) - print(process.stdout) - if process.returncode == 0: - print(process.stdout) - else: - print(f"Error with the following code... {process.returncode}") + + if process.stderr: + logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True) # here is inizialize the class for translate strrings From 7f4fabe856d9ed76ef512fc853c3a2a267280024 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 24 May 2025 14:53:56 +0200 Subject: [PATCH 58/61] Enhanced export functionality with error handling and updated Active method implementation for improved error management --- common_tools.py | 161 +++++++++++++++++++++++------------------------ ssl_encrypt.py | 1 - wirepy.py | 15 ++--- wp_app_config.py | 5 ++ 4 files changed, 87 insertions(+), 95 deletions(-) diff --git a/common_tools.py b/common_tools.py index 7650fae..a800047 100755 --- a/common_tools.py +++ b/common_tools.py @@ -1,6 +1,6 @@ """ Classes Method and Functions for lx Apps """ -import os +import getpass import shutil import signal import base64 @@ -222,27 +222,6 @@ class LxTools: y = (screen_height - height) // 2 window.geometry(f"{width}x{height}+{x}+{y}") - @staticmethod - def get_username() -> str: - """ - Returns the username of the logged-in user, - even if the script is running with root privileges. - """ - try: - result = run( - ["logname"], - stdout=PIPE, - text=True, - check=True, - ) - if result.returncode != 0: - pass - - return result.stdout.strip() - - except subprocess.CalledProcessError: - pass - @staticmethod def clean_files(TEMP_DIR: Path = None, file: Path = None) -> None: """ @@ -520,82 +499,50 @@ class Tunnel: return data @staticmethod - def active() -> str: + def get_active() -> str: """ Shows the Active Tunnel """ - active = ( - os.popen('nmcli con show --active | grep -iPo "(.*)(wireguard)"') - .read() - .split() - ) - if not active: - active = "" - else: - active = active[0] + active = None + try: + process: CompletedProcess[str] = run( + ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show", "--active"], + capture_output=True, + text=True, + check=False, + ) - return active + active = next( + line.split(":")[0].strip() + for line in process.stdout.splitlines() + if line.endswith("wireguard") + ) + + if process.stderr and "error" in process.stderr.lower(): + logging.error(f"Error output on nmcli: {process.stderr}") + + except StopIteration: + active = None + except Exception as e: + logging.error(f"Error on nmcli: {e}") + active = None + + return active if active is not None else "" @staticmethod - def list() -> List[str]: - """ - Returns a list of Wireguard tunnel names - """ - AppConfig.TEMP_DIR: Path = Path("/tmp/tlecdcwg/") - wg_s: List[str] = os.listdir(AppConfig.TEMP_DIR) - - 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() -> bool: """ This will export the tunnels. A zipfile with the current date and time is created in the user's home directory with the correct right - Args: - AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text - AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon - AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text - AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon """ now_time: datetime = datetime.now() now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") - tl: List[str] = Tunnel.list() try: - if len(tl) != 0: - wg_tar: str = f"{Path.home()}/{now_datetime}" - shutil.copytree("/tmp/tlecdcwg/", "/tmp/wire_py", dirs_exist_ok=True) - source: Path = Path("/tmp/wire_py") - shutil.make_archive(wg_tar, "zip", source) - shutil.rmtree(source) - 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"], - ) - - else: - - LxTools.msg_window( - AppConfig.IMAGE_PATHS["icon_error"], - AppConfig.IMAGE_PATHS["icon_msg"], - Msg.STR["exp_err"], - Msg.STR["exp_try"], - ) - - else: + AppConfig.ensure_directories() + CryptoUtil.decrypt(getpass.getuser()) + if len([file.name for file in AppConfig.TEMP_DIR.glob("*.conf")]) == 0: LxTools.msg_window( AppConfig.IMAGE_PATHS["icon_info"], @@ -603,10 +550,58 @@ class Tunnel: Msg.STR["sel_tl"], Msg.STR["tl_first"], ) + return False + else: + wg_tar: str = f"{AppConfig.BASE_DIR}/{now_datetime}" + try: + shutil.make_archive(wg_tar, "zip", AppConfig.TEMP_DIR) + with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: + if 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"], + ) + else: + logging.error( + f"There was a mistake at creating the Zip file. File is empty." + ) + LxTools.msg_window( + AppConfig.IMAGE_PATHS["icon_error"], + AppConfig.IMAGE_PATHS["icon_msg"], + Msg.STR["exp_err"], + Msg.STR["exp_zip"], + ) + return False + return True + except PermissionError: + logging.error( + f"Permission denied when creating archive in {wg_tar}" + ) + return False + + except Exception as e: + logging.error(f"Export failed: {str(e)}") + LxTools.msg_window( + AppConfig.IMAGE_PATHS["icon_error"], + AppConfig.IMAGE_PATHS["icon_msg"], + Msg.STR["exp_err"], + Msg.STR["exp_try"], + ) + return False + except zipfile.BadZipFile as e: + logging.error(f"Invalid ZIP file: {e}") + return False except TypeError: pass + finally: + LxTools.clean_files(AppConfig.TEMP_DIR) + AppConfig.ensure_directories() + # ConfigManager with caching class ConfigManager: diff --git a/ssl_encrypt.py b/ssl_encrypt.py index 191e0a2..f3068ba 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -2,7 +2,6 @@ """ This Script encrypt Wireguardfiles for Wirepy users for more Security """ import argparse -import logging from pathlib import Path import pwd import shutil diff --git a/wirepy.py b/wirepy.py index bbacf24..970272d 100755 --- a/wirepy.py +++ b/wirepy.py @@ -177,7 +177,7 @@ class FrameWidgets(ttk.Frame): self.about_btn.grid(column=2, columnspan=2, row=0) self.readme = tk.Menu(self) - self.a = Tunnel.active() + self.a = Tunnel.get_active() # Label Frame 1 self.lb_frame_btn_lbox = ttk.Frame(self) @@ -292,14 +292,7 @@ class FrameWidgets(ttk.Frame): 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"], - ), + command=lambda: Tunnel.export(), padding=0, ) @@ -713,7 +706,7 @@ class FrameWidgets(ttk.Frame): LxTools.clean_files(AppConfig.TEMP_DIR, file=None) AppConfig.ensure_directories() self.str_var.set("") - self.a = Tunnel.active() + self.a = Tunnel.get_active() self.l_box.insert(0, self.a) self.wg_autostart.configure(state="normal") self.l_box.selection_clear(0, tk.END) @@ -1147,7 +1140,7 @@ class FrameWidgets(ttk.Frame): """ Updated the display after connection changes """ - self.a = Tunnel.active() + self.a = Tunnel.get_active() if not hasattr(self, "str_var"): self.str_var = tk.StringVar() self.str_var.set(self.a) diff --git a/wp_app_config.py b/wp_app_config.py index 9f88244..8b3f355 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -195,6 +195,11 @@ class Msg: "imp_err": _("Import Error"), "exp_err": _("Export Error"), "exp_try": _("Export failed! Please try again"), + "exp_zip": _( + "The error occurs because the zipfile module encountered an issue.\n" + "Please verify that you have the latest version of WirePy installed.\n" + "You can also contact the WirePy developer team to resolve this issue quickly.\n" + ), "tl_first": _("Please first import tunnel"), "sel_list": _("Please select a tunnel from the list"), "sign_len": _("The new name may contain only 12 characters"), From b764547d16cb9d8f15deb561e1e5ceb3b1339724 Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 24 May 2025 16:26:44 +0200 Subject: [PATCH 59/61] fix in export method (remove !=0 by nemelist) --- common_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common_tools.py b/common_tools.py index a800047..2b8bb18 100755 --- a/common_tools.py +++ b/common_tools.py @@ -556,7 +556,7 @@ class Tunnel: try: shutil.make_archive(wg_tar, "zip", AppConfig.TEMP_DIR) with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: - if len(zf.namelist()) != 0: + if zf.namelist(): LxTools.msg_window( AppConfig.IMAGE_PATHS["icon_info"], @@ -566,7 +566,7 @@ class Tunnel: ) else: logging.error( - f"There was a mistake at creating the Zip file. File is empty." + "There was a mistake at creating the Zip file. File is empty." ) LxTools.msg_window( AppConfig.IMAGE_PATHS["icon_error"], From 68580d0ded84a3e7318b20dcc1395bcd74e7139a Mon Sep 17 00:00:00 2001 From: punix Date: Sat, 24 May 2025 18:12:05 +0200 Subject: [PATCH 60/61] return and back to back --- common_tools.py | 84 ++++++++++++++++++------------------------------ wirepy.py | 20 +++++------- wp_app_config.py | 14 ++++---- 3 files changed, 47 insertions(+), 71 deletions(-) diff --git a/common_tools.py b/common_tools.py index 2b8bb18..f9686fa 100755 --- a/common_tools.py +++ b/common_tools.py @@ -10,7 +10,7 @@ from subprocess import CompletedProcess, run import re import sys import tkinter as tk -from typing import Optional, Dict, Any, NoReturn, List +from typing import Optional, Dict, Any, NoReturn import zipfile from datetime import datetime from pathlib import Path @@ -84,7 +84,7 @@ class CryptoUtil: Checks if the private key already exists in the system using an external script. Returns True only if the full key is found exactly (no partial match). """ - process: CompletedProcess[bool] = run( + process: CompletedProcess[str] = run( ["pkexec", "/usr/local/bin/match_found.py", key], capture_output=True, text=True, @@ -120,7 +120,8 @@ class CryptoUtil: decoded = base64.b64decode(key) if len(decoded) != 32: # 32 bytes = 256 bits return False - except Exception: + except Exception as e: + logging.error(f"Error on decode Base64: {e}", exc_info=True) return False return True @@ -158,7 +159,7 @@ class LxTools: geometry = monitor.get_geometry() scale_factor = monitor.get_scale_factor() - # Calculate center position on primary monitor + # Calculate center position on the primary monitor x = geometry.x + (geometry.width - width // scale_factor) // 2 y = geometry.y + (geometry.height - height // scale_factor) // 2 @@ -196,14 +197,14 @@ class LxTools: primary_x = int(geometry[1]) primary_y = int(geometry[2]) - # Calculate center position on primary monitor + # Calculate center position on the 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): + except (ImportError, IndexError, ValueError): pass # Final fallback: Use standard Tkinter method @@ -215,7 +216,7 @@ class LxTools: if ( screen_width > screen_height * 1.8 ): # Heuristic for detecting multiple monitors - # Assume primary monitor is on the left half + # Assume the primary monitor is on the left half screen_width = screen_width // 2 x = (screen_width - width) // 2 @@ -223,7 +224,7 @@ class LxTools: window.geometry(f"{width}x{height}+{x}+{y}") @staticmethod - def clean_files(TEMP_DIR: Path = None, file: Path = None) -> None: + def clean_files(tmp_dir: Path = AppConfig.TEMP_DIR, file: Path = None) -> None: """ Deletes temporary files and directories for cleanup when exiting the application. @@ -231,34 +232,13 @@ class LxTools: and a single file to free up resources at the end of the program's execution. All operations are performed securely, and errors such as `FileNotFoundError` are ignored if the target files or directories do not exist. - - Args: - TEMP_DIR (Path, optional): Path to the temporary directory that should be deleted. + :param tmp_dir: (Path, optional): Path to the temporary directory that should be deleted. If `None`, the value of `AppConfig.TEMP_DIR` is used. - file (Path, optional): Path to the file that should be deleted. + :param file: (Path, optional): Path to the file that should be deleted. If `None`, no additional file will be deleted. Returns: None: The method does not return any value. - - Examples: - - Without parameters: - ```python - Tooltip.clean_files() - ``` - Deletes the temporary directory defined in the configuration (`AppConfig.TEMP_DIR`). - - - With an explicit file path: - ```python - Tooltip.clean_files(file=Path("temp/file.txt")) - ``` - Deletes the file `file.txt`, if it exists, and ignores errors. - - - Both parameters: - ```python - Tooltip.clean(TEMP_DIR=Path("/tmp/data"), file=Path("log.txt")) - ``` - Deletes the directory `/tmp/data` and the file `log.txt`, if they exist. """ if AppConfig.TEMP_DIR is not None: @@ -282,8 +262,10 @@ class LxTools: """ Creates message windows - :argument AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text - :argument AppConfig.IMAGE_PATHS["icon_vpn"] = Image for Task Icon + :param image_path2: + :param image_path: + AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text + AppConfig.IMAGE_PATHS["icon_vpn"] = Image for Task Icon :argument w_title = Windows Title :argument w_txt = Text for Tk Window :argument txt2 = Text for Button two @@ -294,7 +276,7 @@ class LxTools: msg.title(w_title) msg.configure(pady=15, padx=15) - # load first image for window + # load first image for a window try: msg.img = tk.PhotoImage(file=image_path) msg.i_window = tk.Label(msg, image=msg.img) @@ -394,7 +376,7 @@ class Tunnel: @staticmethod def parse_files_to_dictionary( directory: Path = None, filepath: str = None, content: str = None - ) -> dict | str | None: + ) -> tuple[dict, str] | dict | None: data = {} if filepath is not None: @@ -530,7 +512,7 @@ class Tunnel: return active if active is not None else "" @staticmethod - def export() -> bool: + def export() -> bool | None: """ This will export the tunnels. A zipfile with the current date and time is created @@ -582,6 +564,11 @@ class Tunnel: ) return False + except zipfile.BadZipFile as e: + logging.error(f"Invalid ZIP file: {e}") + return False + except TypeError: + pass except Exception as e: logging.error(f"Export failed: {str(e)}") LxTools.msg_window( @@ -591,12 +578,6 @@ class Tunnel: Msg.STR["exp_try"], ) return False - except zipfile.BadZipFile as e: - logging.error(f"Invalid ZIP file: {e}") - return False - - except TypeError: - pass finally: LxTools.clean_files(AppConfig.TEMP_DIR) @@ -649,7 +630,7 @@ class ConfigManager: cls._config = { "updates": "on", "theme": "light", - "tooltips": "True", # Default Value as string ! + "tooltips": "True", # Default Value as string! "autostart": "off", } return cls._config @@ -714,7 +695,7 @@ class GiteaUpdate: """ Calling download requests the download URL of the running script, the taskbar image for the “Download OK” window, the taskbar image for the - “Download error” window and the variable res + “Download error” window, and the variable res """ @staticmethod @@ -771,13 +752,12 @@ class GiteaUpdate: """ Downloads new version of wirepy - Args: - urld: Download URL - res: Result filename - AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text - AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon - AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text - AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon + :param urld: Download URL + :param res: Result filename + :param image_path: AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text + :param image_path2: AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon + :param image_path3: AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text + :param image_path4: AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon """ try: to_down: str = f"wget -qP {Path.home()} {" "} {urld}" @@ -846,7 +826,7 @@ class Tooltip: self.x_offset = x_offset self.y_offset = y_offset - # Initial binding based on current state + # Initial binding based on the current state self.update_bindings() # Add trace to the state_var if provided diff --git a/wirepy.py b/wirepy.py index 970272d..ca0f411 100755 --- a/wirepy.py +++ b/wirepy.py @@ -400,7 +400,7 @@ class FrameWidgets(ttk.Frame): # Update the labels based on the result def update_ui_for_update(self, res): - """Update UI elements based on update check result""" + """Update UI elements based on an update check result""" # First, remove the update button if it exists to avoid conflicts if hasattr(self, "update_btn"): self.update_btn.grid_forget() @@ -412,13 +412,13 @@ class FrameWidgets(ttk.Frame): self.update_tooltip.set(_("Updates you have disabled")) # Clear the foreground color as requested self.update_foreground.set("") - # Set tooltip for the label + # Set the tooltip for the label Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) elif res == "No Internet Connection!": self.update_label.set(_("No Server Connection!")) self.update_foreground.set("red") - # Set tooltip for "No Server Connection" + # Set the tooltip for "No Server Connection" Tooltip( self.updates_lb, _("Could not connect to update server"), @@ -429,7 +429,7 @@ class FrameWidgets(ttk.Frame): 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 + # Set the tooltip for the label Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state) else: @@ -505,7 +505,7 @@ class FrameWidgets(ttk.Frame): AppConfig.UPDATE_URL, AppConfig.VERSION, "on" ) - # Make sure UI is updated regardless of previous state + # Make sure the UI is updated regardless of the previous state if hasattr(self, "update_btn"): self.update_btn.grid_forget() if hasattr(self, "updates_lb"): @@ -550,8 +550,8 @@ class FrameWidgets(ttk.Frame): # 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""" + def update_theme_label(self) -> None: + """Update the theme label based on the current theme""" current_theme = ConfigManager.get("theme") if current_theme == "light": self.theme_label.set(_("Dark")) @@ -751,14 +751,10 @@ class FrameWidgets(ttk.Frame): Msg.STR["imp_err"], Msg.STR["no_valid_file"], ) - except IsADirectoryError: + except (IsADirectoryError, TypeError, FileNotFoundError): 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...") def delete(self) -> None: """ diff --git a/wp_app_config.py b/wp_app_config.py index 8b3f355..e678661 100644 --- a/wp_app_config.py +++ b/wp_app_config.py @@ -76,7 +76,7 @@ class AppConfig: } # System-dependent paths - SYSTEM_PATHS: Dict[str, str] = { + SYSTEM_PATHS: Dict[str, Path] = { "ssl_decrypt": "/usr/local/bin/ssl_decrypt.py", "ssl_encrypt": "/usr/local/bin/ssl_encrypt.py", "tcl_path": "/usr/share/TK-Themes", @@ -84,7 +84,7 @@ class AppConfig: } # Images and icons paths - IMAGE_PATHS: Dict[str, str] = { + IMAGE_PATHS: Dict[str, Path] = { "icon_vpn": "/usr/share/icons/lx-icons/48/wg_vpn.png", "icon_msg": "/usr/share/icons/lx-icons/48/wg_msg.png", "icon_import": "/usr/share/icons/lx-icons/48/wg_import.png", @@ -127,9 +127,9 @@ class AppConfig: cls.SETTINGS_FILE.write_text(content) @classmethod - def get_autostart_content(cls) -> str: - """Returns the content for the autostart service file""" - SYSTEMD_FILE: list[str] = [ + def get_autostart_content(cls) -> None: + """Returns the content for an autostart service file""" + systemd_file: list[str] = [ "[Unit]", "Description=Automatic Tunnel Start", "After=network-online.target", @@ -147,7 +147,7 @@ class AppConfig: if not cls.AUTOSTART_SERVICE.is_file(): - content = "\n".join([line for line in SYSTEMD_FILE]) + content = "\n".join([line for line in systemd_file]) cls.AUTOSTART_SERVICE.write_text(content) process: CompletedProcess[str] = run( @@ -161,7 +161,7 @@ class AppConfig: logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True) -# here is inizialize the class for translate strrings +# here is initializing the class for translation strings _ = AppConfig.setup_translations() From f682858051f2f8c4f99ac27142400a099f7dc3ca Mon Sep 17 00:00:00 2001 From: punix Date: Wed, 4 Jun 2025 18:49:17 +0200 Subject: [PATCH 61/61] large update --- Changelog | 11 +- common_tools.py | 883 -------------------------------- install | 233 --------- lx-icons/128/download.png | Bin 0 -> 3715 bytes lx-icons/128/download_error.png | Bin 0 -> 2408 bytes lx-icons/128/log.png | Bin 0 -> 4566 bytes lx-icons/256/download.png | Bin 0 -> 7533 bytes lx-icons/256/download_error.png | Bin 0 -> 4662 bytes lx-icons/256/log.png | Bin 0 -> 10443 bytes lx-icons/32/download.png | Bin 0 -> 984 bytes lx-icons/32/download_error.png | Bin 0 -> 657 bytes lx-icons/32/log.png | Bin 0 -> 906 bytes lx-icons/32/wg_vpn.png | Bin 5909 -> 0 bytes lx-icons/48/download.png | Bin 0 -> 1443 bytes lx-icons/48/download_error.png | Bin 0 -> 906 bytes lx-icons/48/log.png | Bin 0 -> 1655 bytes lx-icons/64/download.png | Bin 0 -> 1799 bytes lx-icons/64/download_error.png | Bin 0 -> 1187 bytes lx-icons/64/log.png | Bin 0 -> 2015 bytes match_found.py | 3 +- settings | 9 - ssl_decrypt.py | 2 +- ssl_encrypt.py | 2 +- start_wg.py | 8 +- tunnel.py | 230 +++++++++ wg_start.service | 11 - wirepy.py | 37 +- wp_app_config.py | 17 +- 28 files changed, 274 insertions(+), 1172 deletions(-) delete mode 100755 common_tools.py delete mode 100755 install create mode 100644 lx-icons/128/download.png create mode 100644 lx-icons/128/download_error.png create mode 100644 lx-icons/128/log.png create mode 100644 lx-icons/256/download.png create mode 100644 lx-icons/256/download_error.png create mode 100644 lx-icons/256/log.png create mode 100644 lx-icons/32/download.png create mode 100644 lx-icons/32/download_error.png create mode 100644 lx-icons/32/log.png delete mode 100644 lx-icons/32/wg_vpn.png create mode 100644 lx-icons/48/download.png create mode 100644 lx-icons/48/download_error.png create mode 100644 lx-icons/48/log.png create mode 100644 lx-icons/64/download.png create mode 100644 lx-icons/64/download_error.png create mode 100644 lx-icons/64/log.png delete mode 100644 settings create mode 100644 tunnel.py delete mode 100644 wg_start.service mode change 100644 => 100755 wp_app_config.py diff --git a/Changelog b/Changelog index 8c67e92..b3414ab 100644 --- a/Changelog +++ b/Changelog @@ -7,23 +7,26 @@ My standard System: Linux Mint 22 Cinnamon - If Wire-Py already runs, prevent further start - for loops with lists replaced by List Comprehensions - ### Added -13-04-0725 +03-06-2025 + + - + ### Added +13-04-20255 - Installer update for Open Suse Tumbleweed and Leap - add symbolic link wirepy.py ### Added -09-04-0725 +09-04-2025 - Installer now with query and remove - Icons merged ### Added -07-04-0725 +07-04-2025 - Installers will support other systems again - Installer is now finished clean with wrong password diff --git a/common_tools.py b/common_tools.py deleted file mode 100755 index f9686fa..0000000 --- a/common_tools.py +++ /dev/null @@ -1,883 +0,0 @@ -""" Classes Method and Functions for lx Apps """ - -import getpass -import shutil -import signal -import base64 -import secrets -import subprocess -from subprocess import CompletedProcess, run -import re -import sys -import tkinter as tk -from typing import Optional, Dict, Any, NoReturn -import zipfile -from datetime import datetime -from pathlib import Path -from tkinter import ttk, Toplevel -from wp_app_config import AppConfig, Msg, logging -import requests - -# Translate -_ = AppConfig.setup_translations() - - -class CryptoUtil: - """ - This class is for the creation of the folders and files - required by Wire-Py, as well as for decryption - the tunnel from the user's home directory - """ - - @staticmethod - def decrypt(user) -> None: - """ - Starts SSL dencrypt - """ - if len([file.stem for file in AppConfig.CONFIG_DIR.glob("*.dat")]) == 0: - pass - else: - process: CompletedProcess[str] = run( - ["pkexec", "/usr/local/bin/ssl_decrypt.py", "--user", user], - capture_output=True, - text=True, - check=False, - ) - - # Output from Openssl Error - if process.stderr: - logging.error(process.stderr, exc_info=True) - - if process.returncode == 0: - logging.info("Files successfully decrypted...", exc_info=True) - else: - logging.error( - f"Error process decrypt: Code {process.returncode}", exc_info=True - ) - - @staticmethod - def encrypt(user) -> None: - """ - Starts SSL encryption - """ - process: CompletedProcess[str] = run( - ["pkexec", "/usr/local/bin/ssl_encrypt.py", "--user", user], - capture_output=True, - text=True, - check=False, - ) - - # Output from Openssl Error - if process.stderr: - logging.error(process.stderr, exc_info=True) - - if process.returncode == 0: - logging.info("Files successfully encrypted...", exc_info=True) - else: - logging.error( - f"Error process encrypt: Code {process.returncode}", exc_info=True - ) - - @staticmethod - def find_key(key: str = "") -> bool: - """ - Checks if the private key already exists in the system using an external script. - Returns True only if the full key is found exactly (no partial match). - """ - process: CompletedProcess[str] = run( - ["pkexec", "/usr/local/bin/match_found.py", key], - capture_output=True, - text=True, - check=False, - ) - if "True" in process.stdout: - return True - elif "False" in process.stdout: - return False - logging.error( - f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}", - exc_info=True, - ) - return False - - @staticmethod - def is_valid_base64(key: str) -> bool: - """ - Validates if the input is a valid Base64 string (WireGuard private key format). - Returns True only for non-empty strings that match the expected length. - """ - # Check for empty string - if not key or key.strip() == "": - return False - - # Regex pattern to validate Base64: [A-Za-z0-9+/]+={0,2} - base64_pattern = r"^[A-Za-z0-9+/]+={0,2}$" - if not re.match(base64_pattern, key): - return False - - try: - # Decode and check length (WireGuard private keys are 32 bytes long) - decoded = base64.b64decode(key) - if len(decoded) != 32: # 32 bytes = 256 bits - return False - except Exception as e: - logging.error(f"Error on decode Base64: {e}", exc_info=True) - return False - - return True - - -class LxTools: - """ - Class LinuxTools methods that can also be used for other apps - """ - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - - @staticmethod - 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") - 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 the 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 - ) - - # Parse the output to find the primary monitor - primary_info = None - for line in output.splitlines(): - if "primary" in line: - parts = line.split() - for part in parts: - if "x" in part and "+" in part: - primary_info = part - break - break - - if primary_info: - # Parse the geometry: WIDTH x HEIGHT+X+Y - geometry = primary_info.split("+") - dimensions = geometry[0].split("x") - primary_width = int(dimensions[0]) - primary_height = int(dimensions[1]) - primary_x = int(geometry[1]) - primary_y = int(geometry[2]) - - # Calculate center position on the 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 (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 - # Assume the 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 clean_files(tmp_dir: Path = AppConfig.TEMP_DIR, file: Path = None) -> None: - """ - Deletes temporary files and directories for cleanup when exiting the application. - - This method safely removes an optional directory defined by `AppConfig.TEMP_DIR` - and a single file to free up resources at the end of the program's execution. - All operations are performed securely, and errors such as `FileNotFoundError` - are ignored if the target files or directories do not exist. - :param tmp_dir: (Path, optional): Path to the temporary directory that should be deleted. - If `None`, the value of `AppConfig.TEMP_DIR` is used. - :param file: (Path, optional): Path to the file that should be deleted. - If `None`, no additional file will be deleted. - - Returns: - None: The method does not return any value. - """ - - if AppConfig.TEMP_DIR is not None: - shutil.rmtree(AppConfig.TEMP_DIR) - try: - if file is not None: - Path.unlink(file) - - except FileNotFoundError: - pass - - @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: - """ - Creates message windows - - :param image_path2: - :param image_path: - AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text - AppConfig.IMAGE_PATHS["icon_vpn"] = Image for Task Icon - :argument w_title = Windows Title - :argument w_txt = Text for Tk Window - :argument txt2 = Text for Button two - :argument com = function for Button command - """ - msg: tk.Toplevel = tk.Toplevel() - msg.resizable(width=False, height=False) - msg.title(w_title) - msg.configure(pady=15, padx=15) - - # load first image for a window - try: - msg.img = tk.PhotoImage(file=image_path) - msg.i_window = tk.Label(msg, image=msg.img) - except Exception as e: - logging.error(f"Error on load Window Image: {e}", exc_info=True) - msg.i_window = tk.Label(msg, text="Image not found") - - 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.grid(column=0, row=1, sticky="e", columnspan=2) - 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.grid(column=0, columnspan=2, row=1) - - try: - icon = tk.PhotoImage(file=image_path2) - msg.iconphoto(True, icon) - except Exception as e: - logging.error(f"Error loading the window icon: {e}", exc_info=True) - - msg.columnconfigure(0, weight=1) - msg.rowconfigure(0, weight=1) - msg.winfo_toplevel() - - @staticmethod - def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None: - """ - Function for cleanup after a program interruption - - :param file: Optional - File to be deleted - :param file_path: Optional - Directory to be deleted - """ - - def signal_handler(signum: int, frame: Any) -> NoReturn: - """ - Determines clear text names for signal numbers and handles signals - - Args: - signum: The signal number - frame: The current stack frame - - Returns: - 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 - ) - - 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 - logging.error( - f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.", - exc_info=True, - ) - LxTools.clean_files(file_path, file) - logging.info("Breakdown by user...") - sys.exit(exit_code) - else: - logging.info(f"Signal {signum} received and ignored.") - LxTools.clean_files(file_path, file) - logging.error("Process unexpectedly ended...") - - # Register signal handlers for various signals - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGHUP, signal_handler) - - -class Tunnel: - """ - Class of Methods for Wire-Py - """ - - @staticmethod - def parse_files_to_dictionary( - directory: Path = None, filepath: str = None, content: str = None - ) -> tuple[dict, str] | dict | None: - data = {} - - if filepath is not None: - filepath = Path(filepath) - try: - content = filepath.read_text() - - # parse the content - address_line = next( - line for line in content.splitlines() if line.startswith("Address") - ) - dns_line = next( - line for line in content.splitlines() if line.startswith("DNS") - ) - endpoint_line = next( - line for line in content.splitlines() if line.startswith("Endpoint") - ) - private_key_line = next( - line - for line in content.splitlines() - if line.startswith("PrivateKey") - ) - - content = secrets.token_bytes(len(content)) - - # extract the values - address = address_line.split("=")[1].strip() - dns = dns_line.split("=")[1].strip() - endpoint = endpoint_line.split("=")[1].strip() - private_key = private_key_line.split("=")[1].strip() - - # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters. - original_stem = filepath.stem - truncated_stem = ( - original_stem[-12:] if len(original_stem) > 12 else original_stem - ) - - # save in the dictionary - data[truncated_stem] = { - "Address": address, - "DNS": dns, - "Endpoint": endpoint, - "PrivateKey": private_key, - } - - content = secrets.token_bytes(len(content)) - - except StopIteration: - pass - - elif directory is not None: - - if not directory.exists() or not directory.is_dir(): - logging.error( - "Temp directory does not exist or is not a directory.", - exc_info=True, - ) - return None - - # Get a list of all files in the directory - files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()] - - # Search for the string in the files - for file in files: - try: - content = file.read_text() - # parse the content - address_line = next( - line - for line in content.splitlines() - if line.startswith("Address") - ) - dns_line = next( - line for line in content.splitlines() if line.startswith("DNS") - ) - endpoint_line = next( - line - for line in content.splitlines() - if line.startswith("Endpoint") - ) - - # extract values - address = address_line.split("=")[1].strip() - dns = dns_line.split("=")[1].strip() - endpoint = endpoint_line.split("=")[1].strip() - - # save values to dictionary - data[file.stem] = { - "Address": address, - "DNS": dns, - "Endpoint": endpoint, - } - - except Exception: - # Ignore errors and continue to the next file - continue - if content is not None: - content = secrets.token_bytes(len(content)) - if filepath is not None: - return data, truncated_stem - else: - return data - - @staticmethod - def get_active() -> str: - """ - Shows the Active Tunnel - """ - active = None - try: - process: CompletedProcess[str] = run( - ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show", "--active"], - capture_output=True, - text=True, - check=False, - ) - - active = next( - line.split(":")[0].strip() - for line in process.stdout.splitlines() - if line.endswith("wireguard") - ) - - if process.stderr and "error" in process.stderr.lower(): - logging.error(f"Error output on nmcli: {process.stderr}") - - except StopIteration: - active = None - except Exception as e: - logging.error(f"Error on nmcli: {e}") - active = None - - return active if active is not None else "" - - @staticmethod - def export() -> bool | None: - """ - This will export the tunnels. - A zipfile with the current date and time is created - in the user's home directory with the correct right - """ - now_time: datetime = datetime.now() - now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") - - try: - AppConfig.ensure_directories() - CryptoUtil.decrypt(getpass.getuser()) - if len([file.name for file in AppConfig.TEMP_DIR.glob("*.conf")]) == 0: - - LxTools.msg_window( - AppConfig.IMAGE_PATHS["icon_info"], - AppConfig.IMAGE_PATHS["icon_msg"], - Msg.STR["sel_tl"], - Msg.STR["tl_first"], - ) - return False - else: - wg_tar: str = f"{AppConfig.BASE_DIR}/{now_datetime}" - try: - shutil.make_archive(wg_tar, "zip", AppConfig.TEMP_DIR) - with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: - if zf.namelist(): - - LxTools.msg_window( - AppConfig.IMAGE_PATHS["icon_info"], - AppConfig.IMAGE_PATHS["icon_vpn"], - Msg.STR["exp_succ"], - Msg.STR["exp_in_home"], - ) - else: - logging.error( - "There was a mistake at creating the Zip file. File is empty." - ) - LxTools.msg_window( - AppConfig.IMAGE_PATHS["icon_error"], - AppConfig.IMAGE_PATHS["icon_msg"], - Msg.STR["exp_err"], - Msg.STR["exp_zip"], - ) - return False - return True - except PermissionError: - logging.error( - f"Permission denied when creating archive in {wg_tar}" - ) - return False - - except zipfile.BadZipFile as e: - logging.error(f"Invalid ZIP file: {e}") - return False - except TypeError: - pass - except Exception as e: - logging.error(f"Export failed: {str(e)}") - LxTools.msg_window( - AppConfig.IMAGE_PATHS["icon_error"], - AppConfig.IMAGE_PATHS["icon_msg"], - Msg.STR["exp_err"], - Msg.STR["exp_try"], - ) - return False - - finally: - LxTools.clean_files(AppConfig.TEMP_DIR) - AppConfig.ensure_directories() - - -# ConfigManager with caching -class ConfigManager: - """ - Universal class for managing configuration files with caching support. - - This class provides a general solution to load, save, and manage configuration - files across different projects. It uses a caching system to optimize access efficiency. - The `init()` method initializes the configuration file path, while `load()` and `save()` - synchronize data between the file and internal memory structures. - - Key Features: - - Caching to minimize I/O operations. - - Default values for missing or corrupted configuration files. - - Reusability across different projects and use cases. - - The class is designed for central application configuration management, working closely - with `ThemeManager` to dynamically manage themes or other settings. - """ - - _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""" - if not cls._config: - 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", - } - except (IndexError, FileNotFoundError): - # DeDefault values in case of error - cls._config = { - "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", - f"{cls._config['updates']}\n", - "# Theme\n", - f"{cls._config['theme']}\n", - "# Tooltips\n", - f"{str(cls._config['tooltips'])}\n", - "# Autostart\n", - f"{cls._config['autostart']}\n", - ] - 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""" - config = cls.load() - return config.get(key, default) - - -class ThemeManager: - """ - Class for central theme management and UI customization. - - This static class allows dynamic adjustment of the application's appearance. - The method `change_theme()` updates the current theme and saves - the selection in the configuration file via `ConfigManager`. - It ensures a consistent visual design across the entire project. - - Key Features: - - Central control over themes. - - Automatic saving of theme settings to the configuration file. - - Tight integration with `ConfigManager` for persistent storage of preferences. - - The class is designed to apply themes consistently throughout the application, - ensuring that changes are traceable and uniform across all parts of the project. - """ - - @staticmethod - def change_theme(root, theme_in_use, theme_name=None): - """Change application theme centrally""" - root.tk.call("set_theme", theme_in_use) - if theme_in_use == theme_name: - ConfigManager.set("theme", theme_in_use) - - -class GiteaUpdate: - """ - Calling download requests the download URL of the running script, - the taskbar image for the “Download OK” window, the taskbar image for the - “Download error” window, and the variable res - """ - - @staticmethod - def api_down(update_api_url: str, version: str, update_setting: str = None) -> str: - """ - Checks for updates via API - - Args: - 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: - """ - Downloads new version of wirepy - - :param urld: Download URL - :param res: Result filename - :param image_path: AppConfig.IMAGE_PATHS["icon_info"]: Image for TK window which is displayed to the left of the text - :param image_path2: AppConfig.IMAGE_PATHS["icon_vpn"]: Image for Task Icon - :param image_path3: AppConfig.IMAGE_PATHS["icon_error"]: Image for TK window which is displayed to the left of the text - :param image_path4: AppConfig.IMAGE_PATHS["icon_msg"]: Image for Task Icon - """ - try: - to_down: str = f"wget -qP {Path.home()} {" "} {urld}" - result: int = subprocess.call(to_down, shell=True) - if result == 0: - shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000) - - 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, - ) - - 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, - ) - - 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, - ) - - -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: - """Tooltip Class""" - self.widget: Any = widget - self.text: str = text - self.tooltip_window: Optional[Toplevel] = None - self.state_var = state_var - self.x_offset = x_offset - self.y_offset = y_offset - - # Initial binding based on the 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("") - self.widget.unbind("") - - # Add new bindings if tooltips are enabled - if self.state_var is None or self.state_var.get(): - self.widget.bind("", self.show_tooltip) - self.widget.bind("", 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, - ) - 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 diff --git a/install b/install deleted file mode 100755 index cb4818f..0000000 --- a/install +++ /dev/null @@ -1,233 +0,0 @@ -#!/bin/bash -NORMAL='\033[0m' -GREEN='\033[1;32m' -RED='\033[31;1;42m' -BLUE='\033[30;1;34m' - -install_file_with(){ - clear - mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \ - mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \ - systemctl --user enable wg_start.service >/dev/null 2>&1 - sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/ - if [ $? -ne 0 ] - then - systemctl --user disable wg_start.service - rm -r ~/.config/wire_py && rm -r ~/.config/systemd - exit 0 - else - sudo apt install python3-tk && \ - sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ - sudo cp -fv match_found.py /usr/local/bin/ && \ - sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ - sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ - sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ - sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy - sudo mkdir -p /usr/local/etc/ssl - if [ ! -f /usr/local/etc/ssl/pwgk.pem ] - then - sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096 - fi - fi - } - -install_arch_d(){ - clear - mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \ - mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \ - systemctl --user enable wg_start.service >/dev/null 2>&1 - sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/ - if [ $? -ne 0 ] - then - systemctl --user disable wg_start.service - rm -r ~/.config/wire_py && rm -r ~/.config/systemd - exit 0 - else - sudo pacman -S --noconfirm tk python3 python-requests && \ - sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ - sudo cp -fv match_found.py /usr/local/bin/ && \ - sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ - sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ - sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ - sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy - sudo mkdir -p /usr/local/etc/ssl - if [ ! -f /usr/local/etc/ssl/pwgk.pem ] - then - sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096 - fi - - fi - } - -install(){ - if grep -i 'debian' /etc/os-release > /dev/null 2>&1 - then - groups > /tmp/isgroup - if grep 'sudo' /tmp/isgroup - then - install_file_with - else - echo -e "$BLUE"The installer found that they are not in the group sudo."" - echo -e "with "$RED"su -"$BLUE" "they can enter the root shell in which they then"" - echo -e "enter "$GREEN""usermod -aG sudo $USER.""$BLUE"" - echo -e ""after logging in from the system, they can then run Wire-Py install again." $NORMAL" - read -n 1 -s -r -p $"Press Enter to exit" - clear - exit 0 - - fi - - elif grep -i 'mint\|ubuntu\|pop|' /etc/os-release > /dev/null 2>&1 - then - install_file_with - - elif grep -i 'arch' /etc/os-release > /dev/null 2>&1 - then - groups > /tmp/isgroup - clear - if grep 'wheel' /tmp/isgroup - then - install_arch_d - else - echo "The installer found that they are not in the group sudo." - echo "The sudoers file must be edited with" - echo -e "$RED""su -""$NORMAL" - echo -e "$GREEN"""EDITOR=nano visudo"""$NORMAL" - echo "Find the line:" - echo "## Uncomment to allow members of group wheel to execute any command" - echo "remove '#' on # %wheel ALL=(ALL) ALL and save the file" - echo -e "then enter "$GREEN"gpasswd -a $USER wheel.""$NORMAL" - echo "after logging in from the system, they can then run Wire-Py install again." - read -n 1 -s -r -p $"Press Enter to exit" - clear - exit 0 - - fi - - elif grep -i '|manjaro\|garuda\|endeavour|' /etc/os-release > /dev/null 2>&1 - then - install_arch_d - - elif grep -i 'fedora' /etc/os-release > /dev/null 2>&1 - then - clear - mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \ - mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \ - systemctl --user enable wg_start.service >/dev/null 2>&1 - sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/ - if [ $? -ne 0 ] - then - systemctl --user disable wg_start.service - rm -r ~/.config/wire_py && rm -r ~/.config/systemd - exit 0 - else - sudo dnf install python3-tkinter -y - sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ - sudo cp -fv match_found.py /usr/local/bin/ && \ - sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ - sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ - sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ - sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy - sudo mkdir -p /usr/local/etc/ssl - if [ ! -f /usr/local/etc/ssl/pwgk.pem ] - then - sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096 - fi - - fi - elif grep -i 'suse' /etc/os-release > /dev/null 2>&1 - then - clear - mkdir -p ~/.config/wire_py && touch ~/.config/wire_py/keys && cp -u settings ~/.config/wire_py/ && \ - mkdir -p ~/.config/systemd/user && cp -u wg_start.service ~/.config/systemd/user/ && \ - systemctl --user enable wg_start.service >/dev/null 2>&1 - sudo cp -f org.sslcrypt.policy /usr/share/polkit-1/actions/ - if [ $? -ne 0 ] - then - systemctl --user disable wg_start.service - rm -r ~/.config/wire_py && rm -r ~/.config/systemd - exit 0 - else - sudo cp -fv wirepy.py start_wg.py wp_app_config.py common_tools.py ssl_encrypt.py ssl_decrypt.py /usr/local/bin/ && \ - sudo cp -fv match_found.py /usr/local/bin/ && \ - sudo cp -uR lx-icons /usr/share/icons/ && sudo cp -uR TK-Themes /usr/share/ && \ - sudo cp -u languages/de/*.mo /usr/share/locale/de/LC_MESSAGES/ && \ - sudo cp -fv Wire-Py.desktop /usr/share/applications/ && \ - sudo ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy - sudo mkdir -p /usr/local/etc/ssl - if [ ! -f /usr/local/etc/ssl/pwgk.pem ] - then - sudo openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096 - fi - if grep -i 'Tumbleweed' /etc/os-release > /dev/null 2>&1 - then - sudo zypper install python313-tk - else - sudo zypper install python36-tk - fi - - fi - - else - clear - echo $"Your System could not be determined." - echo - read -n 1 -s -r -p $"Press Enter to exit" - clear - exit 0 - - fi - #clear - read -n 1 -s -r -p $"Press Enter to exit" - clear - - } - -remove(){ - sudo rm -f /usr/local/bin/wirepy /usr/local/bin/wirepy.py /usr/local/bin/start_wg.py \ - /usr/local/bin/wp_app_config.py common_tools.py /usr/local/bin/ssl_encrypt.py \ - /usr/local/bin/ssl_decrypt.py /usr/local/bin/match_found.py - if [ $? -ne 0 ] - then - exit 0 - else - systemctl --user disable wg_start.service - rm -r ~/.config/wire_py && rm -r ~/.config/systemd - sudo rm /usr/share/applications/Wire-Py.desktop - sudo rm /usr/share/locale/de/LC_MESSAGES/languages/de/wirepy.mo - sudo rm -r /usr/local/etc/ssl - which syncpy >/dev/null - if [ $? -ne 0 ] - then - sudo rm -r /usr/share/icons/lx-icons && sudo rm -r /usr/share/TK-Themes - - fi - - echo - read -p "Press Enter to exit..." - - fi - - } - -which wirepy >/dev/null -if [ $? -eq 0 ] - then - echo "Do you want to update/reinstall or uninstall wirepy?" - echo - echo "Update/reinstall: press y, uninstall press r" - echo - read -n 1 -s -r -p "Cancel with any other key..." result - case $result in - [y]* ) clear; install; exit;; - [Y]* ) clear; install; exit;; - [j]* ) clear; install; exit;; - [J]* ) clear; install; exit;; - [r]* ) clear; remove; exit;; - [R]* ) clear; remove; exit;; - esac - clear -else - install - -fi \ No newline at end of file diff --git a/lx-icons/128/download.png b/lx-icons/128/download.png new file mode 100644 index 0000000000000000000000000000000000000000..15893504ea82e2d0ba21c9570de599bffbd8a2ff GIT binary patch literal 3715 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 literal 0 HcmV?d00001 diff --git a/lx-icons/128/log.png b/lx-icons/128/log.png new file mode 100644 index 0000000000000000000000000000000000000000..06de63c271cbc80db70f077159acb32ade5a07fd GIT binary patch literal 4566 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 EFFs4@kpKVy literal 0 HcmV?d00001 diff --git a/lx-icons/256/download.png b/lx-icons/256/download.png new file mode 100644 index 0000000000000000000000000000000000000000..614bf401453725a333a3e0c272b5c93023aab1d3 GIT binary patch literal 7533 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 F{{f%F2q^#n literal 0 HcmV?d00001 diff --git a/lx-icons/32/download.png b/lx-icons/32/download.png new file mode 100644 index 0000000000000000000000000000000000000000..e209fbd27900007daeae53cf22b23f503d486d9c GIT binary patch literal 984 zcmV;}11J26P)y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2k8YK4Qtvw3tw_KUrx^X&j0b<@4&whm6er-M4wqzRb}9R0AFEPH*V^vtwxg(fPg=M$znn> zNNBo7C>X|MPKw*1zK=d!&W4@9J@F-%HD%Vssn%o*0B_&E$D_Vu^~|+YR9_@Dl1h$a zY}^WHYHY^kY#0I{MO)w)Jc`ma$H<$Q*XI;OEx`T$B_$QbIP07&&s#z6h`huW@RLXk z2Kv%3B3g3zpb;EgS&FKuY$#ig5;4bB1KlFxq*=kT|^Z2#QsS*#&V$K-V?)U)jf_r;q5{ zKG+QfwiFu&R~%w)&Vs~JAS5Hj`Nh}<*x7rjB=Hnzx<;-0IUm|S^p6WPUFG_dDnj89 zrE89{a7un+DX=L6nK5P#R)D$e{^V?B zQbHe!!X$=f7dYOGJesYiO&(a-FOO4U5*flQCj3C}ALA2@7!tYCh(0#}0000y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2k8YK3Kkn4)3)0H00GrWL_t(o!|j(%%i2H?$N#GmB8lc8fnJmta|jW%P$)cC zze#&4^wxVR{Ve?qy?FEsD5N2WYzUf1FyLE=FZCsEFGUc2q+rAcnmNww?);e9o!J3i zgD}R(|KRg=3ITvxt@c6J_0K%d0{{+(!`Z@2CX<-cFbv^&-WS((JGsBQN~_g+CrJ_j zV6j-B-EJQ@7={5|*AoNi_xtcX?+pNC9-V>|AO*-Jb!wryiNoub)nBAd-755O3MEXxo@@yw>GD$31jE z7i+$4+mK}$9LHTxfoYmY!p&xb@pya^=H+tvB19BLuq+FWM&soB?)yF*$APA4==FLh zt@~sy&@@eAjFoSRM73IdTd&uDD2j6Y`*%8>pO$5P6-9A(p;c2#>F*o>P)b7p;Vl5J z>;B?6PAe9R-wK5Sy4@}v3 rUmH%7@OdVNVUSX(l$RvwpQhhGZQH-_;dIKN00000NkvXXu0mjfoly{I literal 0 HcmV?d00001 diff --git a/lx-icons/32/log.png b/lx-icons/32/log.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc7be1ffe7c349336c654d68dddb68052f85400 GIT binary patch literal 906 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 diff --git a/lx-icons/48/download.png b/lx-icons/48/download.png new file mode 100644 index 0000000000000000000000000000000000000000..4302a7cb4688606cc66bd646455cf18ed15eeee1 GIT binary patch literal 1443 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 B8Ib@0 literal 0 HcmV?d00001 diff --git a/lx-icons/64/download.png b/lx-icons/64/download.png new file mode 100644 index 0000000000000000000000000000000000000000..cc12d8afa67fe03b6b9a0c8ecd196b5dee7a7bc8 GIT binary patch literal 1799 zcmV+i2l)7jP)y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2k8YK4XmTrY!pe?o-wQ@;} ziWo%jffz_E7`bRr3K)!Xk&;L-5JQcTD5Yr-5DX?3tZmUm!q$i;Rv#=RfD}O}wY6p2 z?shNT+w5G94+~Rz*>;yL+oj|Gv|nb<&ivo`Z|6T}fB|y&@L>w$RI011hu5vLvXa3# zm%6&Tp-HHzsi7egsjaQ0BV$DlmN24UjAN0r?*9t_R8&;Zw8dhft7Pz<=ZJ@?Tp=#9D|N4B+gz?f!~@RNm4;rFHn3H$ASq(5XzSk3=!z6E=S z6T=aJf?4@6nNx=RgU(+Z&`mc;xU_T!UMX84;YrqHJU)A7LQnr~0wj^G|A)1J;Uq65 z4|5CWN|>P;c@Z$LXdX7K-XLLK;N?ZYkspts_GXQQzb>tk7Xbq$*_e#-k^=w$3P9*Ym&hxg#@^|KOQM@m457y=YE{s|Z;nHib*^H>uAz-G7M#g)&?i-6XSR;<`tELngS z%9vj$;A3I8IR+WjQ29lpRwE^CV*Ci0pZ_ejz5AYo^*TK$YIK(qe_Xf#$`o0N%{e#} z(FI6OO^OczU9N6iYq=)jqJpAPH^~4KgmRez{zblF?_)X@(CyO0?+d^v8bQ<4{U@NQ zvk7~uci;C?m}m*aBw^TlXkW3Rc79YE_aNr? z1|SL|7{wS6z_P655}mfrF^S@SUjQN@@reNd`tm*ikI&Q3B{YbCDXdh+_ROe8FP*&< z2lgG1aJ#iV0Wvv>9|5(EwfOn9pCnwh%6|)Gfi5dveXCer z1ax$EVEOG*m>Q*0_<tk#VA3=#sceinp?P^y$* z7$$UBAzrJ&Sf%B98@jzc(n~9KN)U(47*whb~^67Fq#OLa*DRh1f47lFa3M>IhoCJS)7op}4urkJw8 zHt9=?}6(UE9VK>L{v-dkSerDZ{_QAM>+IQEMn3YeCc35`<$Rh1g5 zOOa<5ild;bG=Q^mNF_-ynNy%tN7t7eG-Vvifei$QyjkQ$2*Ex-?REF|!#szuV@Jwm zxjET5|92C#`*jdL=W$Bvq&AJl?cvrcHbW+BqfTmmrkJZ!by5cT@D7HmW4_?3Xtgj@zXIu5bU-_d$G^%3ur&pgLZ{VTlG0D z6yGV)bHj1o-|l}OfIpV!NV+HLhm)E#^&2@$vPtg8)5w$?W|F@;kC>k}J87DJB3A1i zr1PN9NfShjdB&r;OQ)0k;yjZ1!c>l?>6Nh-aA9>#L`z^8MyWQbHzYrtw40%sMZ=}( pdz$&Mevz0G^}AUt7HT-%{sWQ(Ll?YJgCzg}002ovPDHLkV1nvUDPI5p literal 0 HcmV?d00001 diff --git a/lx-icons/64/download_error.png b/lx-icons/64/download_error.png new file mode 100644 index 0000000000000000000000000000000000000000..0cd41616ee7054666cadf803288a73517fae4339 GIT binary patch literal 1187 zcmV;U1YG-xP)y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2k8YK3KTaUJ-E6600ZYqL_t(|+U=W7PwPe$hTpMcupO}ZDB?p6vI+}e1*$~0 zgdIPk`>O4}{Rv(458TbI)O{CKB|u`u1|&pOk&q|~7_tBy5@b6GiJgRay1MCwCM1wW ze3LmlXXbePJ~`)%$72I8tXM1(45(2`U)Pq;=XngcwS_<_q)|E^q1Iw}ipe+D|!(l8gF19>DHk-xK(NWt0wzs!2Iy&lG0Q>vmz5`$w29A%9yXreX zKab7LP4GNFH~?(hMko}5rfIMCt5hn8Mx$_D7osS>8-g!WKoA6oqWEf`r>7?XK&e#f zX$k(t7r|n&*n}~Z0363bqtO^%0KJGG2mqa?N_Hc}05AXy00Y1PFaQhy1Hb?<01N;F zzyL4+3;+Z0!UBUJqEe}VQVLNN5eNjnECAJN71?aI`Dn{DO$39%uKi8ZgzLIcRTT>h z3z(Rgc+(z1-vK0(N#t@l$gF9f)hYx*=sF7eUIaBw!`Rpu5{bmSpYZ1M zd1Nvf%+1Zc`7wFl0q{JJjg1YIN+syJ{$_v2aS)HkAxRQcRfQx;Z>j;lm0>ID(pFbjTZ(cV2j}PK zczk@^zq`BppsMNv$8ogFAud0QJb%92zq7NmNeDSySy>T7q0qCoMx%j~lM`5$m5xTE z-_D%r&m`bHkC={NxrPFB?i^am#)zvSiX`c5Y9soGMemb_hyZbwU zkFi*+S=bE2z|GB#_wexWo387JlarH9FJn+j2}cN_K7~N*_4-e)>z*bO2^0ziB$G*$ z%jG{$Pf!2gdET}xi}oU(5b~dJN-0MO@qAbhz9EDh0{FUKuOA*99Q*(v4dAH<@vXJ` zH60?6h#ZMTRsl@+I!$-f=;zN#gTWx_MQ|%^{{rTDvG?ezxv~HN002ovPDHLkV1i;r B_Ll$v literal 0 HcmV?d00001 diff --git a/lx-icons/64/log.png b/lx-icons/64/log.png new file mode 100644 index 0000000000000000000000000000000000000000..e1eb8dc09fc8a77a04ee7c1ddde6287d223dd816 GIT binary patch literal 2015 zcmV<52O#)~P)y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2k8YK6)z3*`D%Rt00$#UL_t(|+U1-5OViyO$6wRAip(ccB(%cknVF?%Dv<;w zOT$>EJ?IZT-a_UN(hvO$5fn%UW??@lWl2bu-NupxA1X`I=}c4WPSMm*6Ti4kxAXm> zHY{hiPu*_o4TpVp&biLGUi&-WfUmDF6%`d6IBU3PN@{9qO2oy*iP6!~C9gF$HYQXm zl~5=YrZb}UyU%7710p9UXT=>isk5_F0HU$6aoN@!8ygcjIXNZ>#WSyYp-?ES#(5!x zC@n1&fG8|1T-J5R#>Qrz3DKr&La0T)#jtM{{CnpP|(YUPZt5hlzfD%l1 zdu#gk`}_L^AoBC`1t9wS`4aEda>na#~wk`Sj`2p8=qup@E#79NgXA0r2th zAvQJ^jYebt0Q7o2w{PDjF)`8HCzVP`OG~4srpEpOba!{t+}zC8ty_QSkBW-o+O=y; zOib7}fTpG(W6K4_V%9F{OIUt3JVLF zoSd|M00x7B($Z269y~Z}_cxb9LP8iC8ltDC$MymA_V)7h=~K3E-#%{}Z*Omslapy} zZMA&>&CSik#l^9C^X7TmNF)+cQc|d@s)KX_^YAQWFJ*--_3YkoXlamupPEJUrQuC`+D&_d` z<0K^|E$V~Y+S*vZemx-}Aq%#3b8{mrD~qC{A~YHej*gDzCXq;RaB#5LcJ10VYqMn$ zKz4RETCEl#1VRXmMx*)I^uD&XmWqmsMFXg*so~tYbF5vvc2V|+4jtm}zyC(9R%0+2 zFc=INjYbRx15;B|=BCr>(ChW+bUGp1n$F0QT+M$HRvY5kkz{W@>7R;^JbGlas9?5VIrzVPRoBdi03TpFhvr=HthY z)YsRuZQC~62jJ)Dx8Uq;Q&SU(iHZ37`r1B#HEY%^D0?e}puD`C^z?Kb9UW~S0DQ~d zmXwstD|ircZrIM!dkRs0CKq;wOT!|?5$d@CNMD2{0OxR0H*Bie8N;Jm5MWG z&ais*YWo3T%HBSI{`{BozJC46l`B`we|)eT0Prn)o1dTm%j~U2qrqr25*iw6e*l7l zg3#%7ba!{pIj6q9p4{AA+}zyk4}hnqCuwPEb7pUKIvuxe-D2g5XXg+7 zMx&8?_wJ!oD(4;gwn+f%)~&Ocz3uJorLwYeL7{J(1OSkbkih->_c0g@=Dz0UW@2Ju z@bmMte*mGOq14va{*b+`tgIw6GjqYAZ<_?LY11a;a=H2E@s}@OC@3gk=gys2tCkgj zOeP~UGt->CZEI`8-QAtw;NU+406?7<8-+r_rAwC>92~TY(6=Sc^m*A!W(o=l1RzeGIwh=8 zKbOqZnzpOUrHF_K0ILaoTiTj`S1tU8vbm9w5dnyvo*t|F_O)8=tg<=y4TX~zFJ2VW z)6-Vv{N&`MxP1BYtinn74W+XmK76pMYkscM*<~-Dj){pusZ`?P;(`!@>gsCB%F0&s xZa<&m>6uz``Fu)BiWnasH None: parser = argparse.ArgumentParser( - description="Script only for use to compare the private key in the Network configurations to avoid errors with the network manager." + description="Script only for use to compare the private key in the" + "Network configurations to avoid errors with the network manager." ) parser.add_argument("search_string", help="Search string") args = parser.parse_args() diff --git a/settings b/settings deleted file mode 100644 index 6ef82b9..0000000 --- a/settings +++ /dev/null @@ -1,9 +0,0 @@ -# Configuration -on -# Theme -dark -# Tooltips -True -# Autostart -off - diff --git a/ssl_decrypt.py b/ssl_decrypt.py index 4ce87c2..c3cef08 100755 --- a/ssl_decrypt.py +++ b/ssl_decrypt.py @@ -5,7 +5,7 @@ from pathlib import Path import pwd import shutil from subprocess import CompletedProcess, run -from wp_app_config import AppConfig, logging +from shared_libs.wp_app_config import AppConfig, logging parser = argparse.ArgumentParser() parser.add_argument("--user", required=True, help="Username of the target file system") diff --git a/ssl_encrypt.py b/ssl_encrypt.py index f3068ba..0a00e7f 100755 --- a/ssl_encrypt.py +++ b/ssl_encrypt.py @@ -6,7 +6,7 @@ from pathlib import Path import pwd import shutil from subprocess import CompletedProcess, run -from wp_app_config import AppConfig, logging +from shared_libs.wp_app_config import AppConfig, logging parser = argparse.ArgumentParser() parser.add_argument("--user", required=True, help="Username of the target file system") diff --git a/start_wg.py b/start_wg.py index 7583eec..3336915 100755 --- a/start_wg.py +++ b/start_wg.py @@ -2,13 +2,13 @@ """ This script belongs to wirepy and is for the auto start of the tunnel """ - +import logging from subprocess import CompletedProcess, run -from wp_app_config import AppConfig, logging -from common_tools import ConfigManager +from shared_libs.wp_app_config import AppConfig +from shared_libs.common_tools import ConfigManager, LogConfig ConfigManager.init(AppConfig.SETTINGS_FILE) - +LogConfig.logger(ConfigManager.get("logfile")) if ConfigManager.get("autostart") != "off": process: CompletedProcess[str] = run( ["nmcli", "connection", "up", ConfigManager.get("autostart")], diff --git a/tunnel.py b/tunnel.py new file mode 100644 index 0000000..fe4cf9a --- /dev/null +++ b/tunnel.py @@ -0,0 +1,230 @@ +#!/usr/bin/python3 +import logging +import getpass +import zipfile +from datetime import datetime +from pathlib import Path +import shutil +from subprocess import run, CompletedProcess +import secrets +from shared_libs.wp_app_config import AppConfig, Msg +from shared_libs.common_tools import LxTools, CryptoUtil + +# Translate +_ = AppConfig.setup_translations() + + +class Tunnel: + """ + Class of Methods for Wire-Py + """ + + @staticmethod + def parse_files_to_dictionary( + directory: Path = None, filepath: str = None, content: str = None + ) -> tuple[dict, str] | dict | None: + data = {} + + if filepath is not None: + filepath = Path(filepath) + try: + content = filepath.read_text() + + # parse the content + address_line = next( + line for line in content.splitlines() if line.startswith("Address") + ) + dns_line = next( + line for line in content.splitlines() if line.startswith("DNS") + ) + endpoint_line = next( + line for line in content.splitlines() if line.startswith("Endpoint") + ) + private_key_line = next( + line + for line in content.splitlines() + if line.startswith("PrivateKey") + ) + + content = secrets.token_bytes(len(content)) + + # extract the values + address = address_line.split("=")[1].strip() + dns = dns_line.split("=")[1].strip() + endpoint = endpoint_line.split("=")[1].strip() + private_key = private_key_line.split("=")[1].strip() + + # Shorten the tunnel name to the maximum allowed length if it exceeds 12 characters. + original_stem = filepath.stem + truncated_stem = ( + original_stem[-12:] if len(original_stem) > 12 else original_stem + ) + + # save in the dictionary + data[truncated_stem] = { + "Address": address, + "DNS": dns, + "Endpoint": endpoint, + "PrivateKey": private_key, + } + + content = secrets.token_bytes(len(content)) + + except StopIteration: + pass + + elif directory is not None: + + if not directory.exists() or not directory.is_dir(): + logging.error( + "Temp directory does not exist or is not a directory.", + exc_info=True, + ) + return None + + # Get a list of all files in the directory + files = [file for file in AppConfig.TEMP_DIR.iterdir() if file.is_file()] + + # Search for the string in the files + for file in files: + try: + content = file.read_text() + # parse the content + address_line = next( + line + for line in content.splitlines() + if line.startswith("Address") + ) + dns_line = next( + line for line in content.splitlines() if line.startswith("DNS") + ) + endpoint_line = next( + line + for line in content.splitlines() + if line.startswith("Endpoint") + ) + + # extract values + address = address_line.split("=")[1].strip() + dns = dns_line.split("=")[1].strip() + endpoint = endpoint_line.split("=")[1].strip() + + # save values to dictionary + data[file.stem] = { + "Address": address, + "DNS": dns, + "Endpoint": endpoint, + } + + except Exception: + # Ignore errors and continue to the next file + continue + if content is not None: + content = secrets.token_bytes(len(content)) + if filepath is not None: + return data, truncated_stem + else: + return data + + @staticmethod + def get_active() -> str: + """ + Shows the Active Tunnel + """ + active = None + try: + process: CompletedProcess[str] = run( + ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show", "--active"], + capture_output=True, + text=True, + check=False, + ) + + active = next( + line.split(":")[0].strip() + for line in process.stdout.splitlines() + if line.endswith("wireguard") + ) + + if process.stderr and "error" in process.stderr.lower(): + logging.error(f"Error output on nmcli: {process.stderr}") + + except StopIteration: + active = None + except Exception as e: + logging.error(f"Error on nmcli: {e}") + active = None + + return active if active is not None else "" + + @staticmethod + def export() -> bool | None: + """ + This will export the tunnels. + A zipfile with the current date and time is created + in the user's home directory with the correct right + """ + now_time: datetime = datetime.now() + now_datetime: str = now_time.strftime("wg-exp-%m-%d-%Y-%H:%M") + + try: + AppConfig.ensure_directories() + CryptoUtil.decrypt(getpass.getuser()) + if len([file.name for file in AppConfig.TEMP_DIR.glob("*.conf")]) == 0: + + LxTools.msg_window( + AppConfig.IMAGE_PATHS["icon_info"], + AppConfig.IMAGE_PATHS["icon_msg"], + Msg.STR["sel_tl"], + Msg.STR["tl_first"], + ) + return False + else: + wg_tar: str = f"{AppConfig.BASE_DIR}/{now_datetime}" + try: + shutil.make_archive(wg_tar, "zip", AppConfig.TEMP_DIR) + with zipfile.ZipFile(f"{wg_tar}.zip", "r") as zf: + if zf.namelist(): + + LxTools.msg_window( + AppConfig.IMAGE_PATHS["icon_info"], + AppConfig.IMAGE_PATHS["icon_vpn"], + Msg.STR["exp_succ"], + Msg.STR["exp_in_home"], + ) + else: + logging.error( + "There was a mistake at creating the Zip file. File is empty." + ) + LxTools.msg_window( + AppConfig.IMAGE_PATHS["icon_error"], + AppConfig.IMAGE_PATHS["icon_msg"], + Msg.STR["exp_err"], + Msg.STR["exp_zip"], + ) + return False + return True + except PermissionError: + logging.error( + f"Permission denied when creating archive in {wg_tar}" + ) + return False + + except zipfile.BadZipFile as e: + logging.error(f"Invalid ZIP file: {e}") + return False + except TypeError: + pass + except Exception as e: + logging.error(f"Export failed: {str(e)}") + LxTools.msg_window( + AppConfig.IMAGE_PATHS["icon_error"], + AppConfig.IMAGE_PATHS["icon_msg"], + Msg.STR["exp_err"], + Msg.STR["exp_try"], + ) + return False + + finally: + LxTools.clean_files(AppConfig.TEMP_DIR) + AppConfig.ensure_directories() diff --git a/wg_start.service b/wg_start.service deleted file mode 100644 index 5d41844..0000000 --- a/wg_start.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Automatic Tunnel Start -After=network-online.target - -[Service] -Type=oneshot -ExecStartPre=/bin/sleep 5 -ExecStart=/usr/local/bin/start_wg.py - -[Install] -WantedBy=default.target diff --git a/wirepy.py b/wirepy.py index ca0f411..3edc8dc 100755 --- a/wirepy.py +++ b/wirepy.py @@ -2,7 +2,7 @@ """ this script is a simple GUI for managing Wireguard Tunnels """ - +import logging import getpass import shutil import sys @@ -11,21 +11,19 @@ import webbrowser from pathlib import Path from subprocess import CompletedProcess, run from tkinter import TclError, filedialog, ttk +from tunnel import Tunnel -from common_tools import ( +from shared_libs.gitea import GiteaUpdate +from shared_libs.common_tools import ( + LxTools, + CryptoUtil, + LogConfig, ConfigManager, ThemeManager, - CryptoUtil, - GiteaUpdate, - Tunnel, Tooltip, - LxTools, ) -from wp_app_config import AppConfig, Msg, logging -AppConfig.ensure_directories() -AppConfig.create_default_settings() -CryptoUtil.decrypt(getpass.getuser()) +from shared_libs.wp_app_config import AppConfig, Msg class Wirepy(tk.Tk): @@ -169,7 +167,11 @@ class FrameWidgets(ttk.Frame): self.settings.add_command( label=self.theme_label.get(), command=self.on_theme_toggle ) - + # Logviewer Menu + self.settings.add_command( + label="Log Viewer", + command=lambda: run(["logviewer", "--modul=wp_app_config"]), + ) # About BTN Menu / Label self.about_btn = ttk.Button( self.menu_frame, text=_("About"), style="Toolbutton", command=self.about @@ -451,12 +453,7 @@ class FrameWidgets(ttk.Frame): 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"], + f"{AppConfig.DOWNLOAD_URL}/{res}.zip", res ), ) @@ -1145,10 +1142,14 @@ class FrameWidgets(ttk.Frame): if __name__ == "__main__": - + AppConfig.ensure_directories() + AppConfig.create_default_settings() + CryptoUtil.decrypt(getpass.getuser(), AppConfig.CONFIG_DIR) _ = AppConfig.setup_translations() LxTools.sigi(AppConfig.TEMP_DIR) + window = Wirepy() + LogConfig.logger(ConfigManager.get("logfile")) """ the hidden files are hidden in Filedialog """ diff --git a/wp_app_config.py b/wp_app_config.py old mode 100644 new mode 100755 index e678661..435f9cf --- a/wp_app_config.py +++ b/wp_app_config.py @@ -29,16 +29,10 @@ class AppConfig: """ # Logging - LOG_DIR = Path.home() / ".local/share/wirepy" + LOG_DIR = Path.home() / ".local/share/lxlogs" Path(LOG_DIR).mkdir(parents=True, exist_ok=True) LOG_FILE_PATH = LOG_DIR / "wirepy.log" - logging.basicConfig( - filename=f"{LOG_FILE_PATH}", - level=logging.ERROR, - format="%(asctime)s - %(levelname)s - %(message)s", - ) - # Localization APP_NAME: str = "wirepy" LOCALE_DIR: Path = Path("/usr/share/locale/") @@ -58,6 +52,7 @@ class AppConfig: "# Theme": "dark", "# Tooltips": True, "# Autostart": "off", + "# Logfile": LOG_FILE_PATH, } # Updates @@ -69,6 +64,7 @@ class AppConfig: # UI configuration UI_CONFIG: Dict[str, Any] = { "window_title": "Wire-Py", + "window_title2": "LogViewer", "window_size": (600, 383), "font_family": "Ubuntu", "font_size": 11, @@ -94,6 +90,7 @@ class AppConfig: "icon_stop": "/usr/share/icons/lx-icons/48/wg_vpn-stop.png", "icon_info": "/usr/share/icons/lx-icons/64/info.png", "icon_error": "/usr/share/icons/lx-icons/64/error.png", + "icon_log": "/usr/share/icons/lx-icons/48/log.png", } @staticmethod @@ -160,6 +157,12 @@ class AppConfig: if process.stderr: logging.error(f"{process.stderr} Code: {process.returncode}", exc_info=True) + @classmethod + def ensure_log(cls) -> None: + """Ensures that the log file exists""" + if not cls.LOG_FILE_PATH.exists(): + cls.LOG_FILE_PATH.touch() + # here is initializing the class for translation strings _ = AppConfig.setup_translations()